Revert codestyle changes (#40)

* Revert "7f7efbb94bab8574e42d942ad82d414e007b2970"

Code style changes should probably be part of a PR
This commit is contained in:
Eliot Partridge
2019-08-30 10:53:14 -05:00
committed by GitHub
parent eefd9e9a2c
commit caf70a864f
446 changed files with 70667 additions and 70655 deletions
+45 -45
View File
@@ -1,80 +1,80 @@
import {
BASE_CHARACTER_LIMIT, ADDITIONAL_CHARACTERS_SUPPORTER1, ADDITIONAL_CHARACTERS_SUPPORTER2,
ADDITIONAL_CHARACTERS_SUPPORTER3, ADDITIONAL_CHARACTERS_PAST_SUPPORTER
BASE_CHARACTER_LIMIT, ADDITIONAL_CHARACTERS_SUPPORTER1, ADDITIONAL_CHARACTERS_SUPPORTER2,
ADDITIONAL_CHARACTERS_SUPPORTER3, ADDITIONAL_CHARACTERS_PAST_SUPPORTER
} from './constants';
import { AccountDataFlags } from './interfaces';
import { hasFlag } from './utils';
export interface AccountRoles {
roles?: string[] | undefined;
roles?: string[] | undefined;
}
export interface AccountSupporter extends AccountRoles {
supporter?: number | undefined;
supporterInvited?: boolean;
flags?: AccountDataFlags;
supporter?: number | undefined;
supporterInvited?: boolean;
flags?: AccountDataFlags;
}
export function hasRole(account: AccountRoles | undefined, role: string): boolean {
return !!(account && account.roles && account.roles.indexOf(role) !== -1);
return !!(account && account.roles && account.roles.indexOf(role) !== -1);
}
export function isAdmin(account: AccountRoles): boolean {
return hasRole(account, 'admin') || hasRole(account, 'superadmin');
return hasRole(account, 'admin') || hasRole(account, 'superadmin');
}
export function isMod(account: AccountRoles): boolean {
return hasRole(account, 'mod') || isAdmin(account);
return hasRole(account, 'mod') || isAdmin(account);
}
export function isDev(account: AccountRoles): boolean {
return hasRole(account, 'dev');
return hasRole(account, 'dev');
}
export function meetsRequirement(account: AccountSupporter, require: string | undefined): boolean {
return !require || hasRole(account, require) || meetsSupporterRequirement(account, require);
return !require || hasRole(account, require) || meetsSupporterRequirement(account, require);
}
function meetsSupporterRequirement(account: AccountSupporter, require: string): boolean {
const level = account.supporter || 0;
const modOrDev = isMod(account) || isDev(account);
const level = account.supporter || 0;
const modOrDev = isMod(account) || isDev(account);
if (require === 'inv') {
return modOrDev || level >= 1 || !!account.supporterInvited;
} else if (require === 'sup1') {
return modOrDev || level >= 1;
} else if (require === 'sup2') {
return modOrDev || level >= 2;
} else if (require === 'sup3') {
return modOrDev || level >= 3;
} else {
return false;
}
if (require === 'inv') {
return modOrDev || level >= 1 || !!account.supporterInvited;
} else if (require === 'sup1') {
return modOrDev || level >= 1;
} else if (require === 'sup2') {
return modOrDev || level >= 2;
} else if (require === 'sup3') {
return modOrDev || level >= 3;
} else {
return false;
}
}
export function getCharacterLimit(account: AccountSupporter) {
switch (account.supporter || 0) {
case 1: return BASE_CHARACTER_LIMIT + ADDITIONAL_CHARACTERS_SUPPORTER1;
case 2: return BASE_CHARACTER_LIMIT + ADDITIONAL_CHARACTERS_SUPPORTER2;
case 3: return BASE_CHARACTER_LIMIT + ADDITIONAL_CHARACTERS_SUPPORTER3;
default:
if (hasFlag(account.flags, AccountDataFlags.PastSupporter)) {
return BASE_CHARACTER_LIMIT + ADDITIONAL_CHARACTERS_PAST_SUPPORTER;
} else {
return BASE_CHARACTER_LIMIT;
}
}
switch (account.supporter || 0) {
case 1: return BASE_CHARACTER_LIMIT + ADDITIONAL_CHARACTERS_SUPPORTER1;
case 2: return BASE_CHARACTER_LIMIT + ADDITIONAL_CHARACTERS_SUPPORTER2;
case 3: return BASE_CHARACTER_LIMIT + ADDITIONAL_CHARACTERS_SUPPORTER3;
default:
if (hasFlag(account.flags, AccountDataFlags.PastSupporter)) {
return BASE_CHARACTER_LIMIT + ADDITIONAL_CHARACTERS_PAST_SUPPORTER;
} else {
return BASE_CHARACTER_LIMIT;
}
}
}
export function getSupporterInviteLimit(account: AccountSupporter) {
if (isMod(account) || isDev(account)) {
return 100;
} else {
switch (account.supporter) {
case 1: return 1;
case 2: return 5;
case 3: return 10;
default: return 0;
}
}
if (isMod(account) || isDev(account)) {
return 100;
} else {
switch (account.supporter) {
case 1: return 1;
case 2: return 5;
case 3: return 10;
default: return 0;
}
}
}
File diff suppressed because it is too large Load Diff
+346 -346
View File
@@ -3,8 +3,8 @@ import { escape, escapeRegExp, startsWith, range, uniq, compact } from 'lodash';
import { fromNow, toInt, hasFlag, compareDates, removeItem, includes } from './utils';
import { DAY } from './constants';
import {
Account, OriginInfo, Document, OriginRef, SupporterFlags, BannedMuted, AccountBase, LogEntry,
DuplicateResult, DuplicateBase, Duplicate, Auth, MergeInfo
Account, OriginInfo, Document, OriginRef, SupporterFlags, BannedMuted, AccountBase, LogEntry,
DuplicateResult, DuplicateBase, Duplicate, Auth, MergeInfo
} from './adminInterfaces';
import { hasRole } from './accountUtils';
import { filterBadWordsPartial } from './swears';
@@ -12,559 +12,559 @@ import { faPlusCircle, faClock, faMinusCircle, faCaretSquareUp, faCaretSquareDow
import { element, textNode } from '../client/htmlUtils';
interface UpdatedAt {
updatedAt: Date;
updatedAt: Date;
}
export const compareUpdatedAt = (a: UpdatedAt, b: UpdatedAt) => compareDates(a.updatedAt, b.updatedAt);
export const compareOrigins = (a: OriginInfo, b: OriginInfo) => a.ip.localeCompare(b.ip);
export const compareOriginRefs = (a: OriginRef, b: OriginRef) =>
compareDates(b.last, a.last) || compareOrigins(a.origin, b.origin);
compareDates(b.last, a.last) || compareOrigins(a.origin, b.origin);
export const compareByName = <T extends { name: string; }>(a: T, b: T) => (a.name || '').localeCompare(b.name || '');
export const getId = (item: Document) => item._id;
export const tagBad = (s: string) => `<span class='bad'>${s}</span>`;
export function compareAccounts(a: Account, b: Account) {
return compareDates(a.createdAt, b.createdAt);
return compareDates(a.createdAt, b.createdAt);
}
export function compareAuths(a: Auth, b: Auth) {
const aDeleted = a.disabled || a.banned || false;
const bDeleted = b.disabled || b.banned || false;
const aDeleted = a.disabled || a.banned || false;
const bDeleted = b.disabled || b.banned || false;
if (aDeleted && !bDeleted) {
return 1;
} else if (!aDeleted && bDeleted) {
return -1;
} else {
return compareByName(a, b);
}
if (aDeleted && !bDeleted) {
return 1;
} else if (!aDeleted && bDeleted) {
return -1;
} else {
return compareByName(a, b);
}
}
export function highlightWords(text?: string) {
text = text || '';
text = filterBadWordsPartial(text, tagBad);
return text;
text = text || '';
text = filterBadWordsPartial(text, tagBad);
return text;
}
export function getAge(birthdate: Date) {
return moment().diff(birthdate, 'years');
return moment().diff(birthdate, 'years');
}
// chat & events
function enc(text?: string): string {
return escape(text || '');
return escape(text || '');
}
function encWithHighlight(text?: string): string {
return highlightWords(enc(text || ''));
return highlightWords(enc(text || ''));
}
export function formatEventDesc(text: string): string {
return encWithHighlight(text).replace(/\[([a-z0-f]{24})\]/g, `<a tabindex onclick="goToAccount('$1')">[$1]</a>`);
return encWithHighlight(text).replace(/\[([a-z0-f]{24})\]/g, `<a tabindex onclick="goToAccount('$1')">[$1]</a>`);
}
function getMessageTag(message: string) {
if (/^\/p /.test(message)) {
return 'party';
} else if (/^\/w /.test(message)) {
return 'whisper';
} else if (/^\/s[s123] /.test(message)) {
return 'supporter';
} else if (/^\//.test(message)) {
return 'command';
} else {
return 'none';
}
if (/^\/p /.test(message)) {
return 'party';
} else if (/^\/w /.test(message)) {
return 'whisper';
} else if (/^\/s[s123] /.test(message)) {
return 'supporter';
} else if (/^\//.test(message)) {
return 'command';
} else {
return 'none';
}
}
export function replaceSwears(element: HTMLElement) {
const text = element.textContent;
const text = element.textContent;
if (text) {
const replaced = encWithHighlight(text);
if (text) {
const replaced = encWithHighlight(text);
if (text !== replaced) {
element.innerHTML = replaced;
}
}
if (text !== replaced) {
element.innerHTML = replaced;
}
}
}
function formatChatLine(l: string): HTMLElement {
// 00:00:01 [system] Timed out for swearing
// 00:00:01 [patreon] fetched patreon data
// 00:00:01 [dev][Autumn Leafs] hello world
// 00:00:01 [dev][Autumn Leafs][muted] hello world
// 00:00:01 [dev][Autumn Leafs][ignored] hello world
// 00:00:01 [dev-pl][Autumn Leafs][ignored] hello world
// 00:00:01 [57a3dc6f2f0019a161cdebf6][dev][Autumn Leafs][ignored] hello world
// 00:00:01 [1][dev][Autumn Leafs][ignored] hello world
// 00:00:01 [1:merged][dev][Autumn Leafs][ignored] hello world
// 00:00:01 [merged][dev][Autumn Leafs][ignored] hello world
// 00:00:01 [merged][dev][main][Autumn Leafs][ignored] hello world
// 00:00:01 [system] Timed out for swearing
// 00:00:01 [patreon] fetched patreon data
// 00:00:01 [dev][Autumn Leafs] hello world
// 00:00:01 [dev][Autumn Leafs][muted] hello world
// 00:00:01 [dev][Autumn Leafs][ignored] hello world
// 00:00:01 [dev-pl][Autumn Leafs][ignored] hello world
// 00:00:01 [57a3dc6f2f0019a161cdebf6][dev][Autumn Leafs][ignored] hello world
// 00:00:01 [1][dev][Autumn Leafs][ignored] hello world
// 00:00:01 [1:merged][dev][Autumn Leafs][ignored] hello world
// 00:00:01 [merged][dev][Autumn Leafs][ignored] hello world
// 00:00:01 [merged][dev][main][Autumn Leafs][ignored] hello world
/* tslint:disable:max-line-length */
const regex = /^([0-9:]+) (\[(?:merged|\d+|\d+:merged|[a-z0-9]{24})\])?\[([a-z0-9_-]+)\](?:\[([a-z0-9_-]+)\])?((?:\[.*?\])?)(?:\[(muted|ignored|ignorepub)\])?\t(.*)$/;
const m = regex.exec(l);
/* tslint:disable:max-line-length */
const regex = /^([0-9:]+) (\[(?:merged|\d+|\d+:merged|[a-z0-9]{24})\])?\[([a-z0-9_-]+)\](?:\[([a-z0-9_-]+)\])?((?:\[.*?\])?)(?:\[(muted|ignored|ignorepub)\])?\t(.*)$/;
const m = regex.exec(l);
if (m) {
const [, time, accountId, server, map, name, mutedIgnored, message] = m;
const messageTag = server === 'system' ? 'system' : getMessageTag(message);
const modTag = mutedIgnored ? ' message-muted' : '';
if (m) {
const [, time, accountId, server, map, name, mutedIgnored, message] = m;
const messageTag = server === 'system' ? 'system' : getMessageTag(message);
const modTag = mutedIgnored ? ' message-muted' : '';
return element('div', 'chatlog-line', [
element('span', 'time', [], { 'data-text': time }),
accountId ? element('span', 'account-id', [textNode(accountId)]) : undefined,
element('span', `server server-${server.replace(/-.+$/g, '')}`, [textNode(`[${server}]`)]),
map ? element('span', `map map-${map}`, [textNode(`[${map}]`)]) : undefined,
element('span', mutedIgnored ? `name ${mutedIgnored}` : `name`, [textNode(name)]),
textNode(' '),
element('span', `message message-${messageTag}${modTag}`, [textNode(message)]),
textNode(' '),
element('a', 'chat-translate', [], undefined, { click: translateChat }),
]);
} else {
return element('div', '', [textNode(highlightWords(l))]);
}
return element('div', 'chatlog-line', [
element('span', 'time', [], { 'data-text': time }),
accountId ? element('span', 'account-id', [textNode(accountId)]) : undefined,
element('span', `server server-${server.replace(/-.+$/g, '')}`, [textNode(`[${server}]`)]),
map ? element('span', `map map-${map}`, [textNode(`[${map}]`)]) : undefined,
element('span', mutedIgnored ? `name ${mutedIgnored}` : `name`, [textNode(name)]),
textNode(' '),
element('span', `message message-${messageTag}${modTag}`, [textNode(message)]),
textNode(' '),
element('a', 'chat-translate', [], undefined, { click: translateChat }),
]);
} else {
return element('div', '', [textNode(highlightWords(l))]);
}
}
function translateChat(this: HTMLElement) {
const lines: string[] = [];
let parent = this.parentElement;
const lines: string[] = [];
let parent = this.parentElement;
for (let i = 0; parent && i < 10; i++) {
lines.push(parent.querySelector('.message')!.textContent!);
parent = parent.nextElementSibling as HTMLElement;
}
for (let i = 0; parent && i < 10; i++) {
lines.push(parent.querySelector('.message')!.textContent!);
parent = parent.nextElementSibling as HTMLElement;
}
window.open(`https://translate.google.com/#auto/en/${encodeURIComponent(lines.join('\n'))}`);
window.open(`https://translate.google.com/#auto/en/${encodeURIComponent(lines.join('\n'))}`);
}
if (typeof window !== 'undefined') {
(window as any).goToAccount = (accountId: string) => {
window.dispatchEvent(new CustomEvent('go-to-account', { detail: accountId }));
};
(window as any).goToAccount = (accountId: string) => {
window.dispatchEvent(new CustomEvent('go-to-account', { detail: accountId }));
};
}
export function formatChat(chat: string): HTMLElement[] {
return (chat || '<no messages>')
.trim()
.split(/\r?\n/g)
.reverse()
.map(formatChatLine);
return (chat || '<no messages>')
.trim()
.split(/\r?\n/g)
.reverse()
.map(formatChatLine);
}
export interface ChatDate {
value: string;
label: string;
value: string;
label: string;
}
export function createChatDate(date: moment.Moment): ChatDate {
return {
value: date.toISOString(),
label: date.format('MMMM Do YYYY'),
};
return {
value: date.toISOString(),
label: date.format('MMMM Do YYYY'),
};
}
export function createDateRange(startDate: string | Date, days: number): ChatDate[] {
return range(days, 0)
.map(d => moment(startDate).subtract(d, 'days'))
.map(createChatDate);
return range(days, 0)
.map(d => moment(startDate).subtract(d, 'days'))
.map(createChatDate);
}
// filtering
export function filterAccounts(items: Account[], search: string, showOnly: string, not: boolean) {
if (search) {
items = items.filter(createFilter(search));
}
if (search) {
items = items.filter(createFilter(search));
}
const filter = createFilter2(showOnly);
const filter = createFilter2(showOnly);
if (filter) {
if (not) {
items = items.filter(i => !filter(i));
} else {
items = items.filter(filter);
}
}
if (filter) {
if (not) {
items = items.filter(i => !filter(i));
} else {
items = items.filter(filter);
}
}
return items;
return items;
}
export function createFilter(search: string): (account: Account) => boolean {
const regex = new RegExp(escapeRegExp(search), 'i');
const regex = new RegExp(escapeRegExp(search), 'i');
function test(value: string): boolean {
return !!value && regex.test(value);
}
function test(value: string): boolean {
return !!value && regex.test(value);
}
function testAuth(auth: Auth) {
return test(auth.name) || auth.provider === search || auth.url === search;
}
function testAuth(auth: Auth) {
return test(auth.name) || auth.provider === search || auth.url === search;
}
function testMerge(merge: MergeInfo) {
return merge.id === search;
}
function testMerge(merge: MergeInfo) {
return merge.id === search;
}
function filter(account: Account): boolean {
if (account._id === search)
return true;
if (test(account.name))
return true;
if (test(account.note))
return true;
if (account.roles && account.roles.some(test))
return true;
if (account.emails && account.emails.some(test))
return true;
if (account.auths && account.auths.some(testAuth))
return true;
if (account.merges && account.merges.some(testMerge))
return true;
function filter(account: Account): boolean {
if (account._id === search)
return true;
if (test(account.name))
return true;
if (test(account.note))
return true;
if (account.roles && account.roles.some(test))
return true;
if (account.emails && account.emails.some(test))
return true;
if (account.auths && account.auths.some(testAuth))
return true;
if (account.merges && account.merges.some(testMerge))
return true;
return false;
}
return false;
}
function prefixWith(prefix: string, action: (phrase: string) => (account: Account) => boolean) {
return startsWith(search, prefix) ? action(search.substr(prefix.length)) : undefined;
}
function prefixWith(prefix: string, action: (phrase: string) => (account: Account) => boolean) {
return startsWith(search, prefix) ? action(search.substr(prefix.length)) : undefined;
}
function prefixWithRegex(prefix: string, action: (regex: RegExp) => (account: Account) => boolean) {
return prefixWith(prefix, phrase => action(new RegExp(escapeRegExp(phrase), 'i')));
}
function prefixWithRegex(prefix: string, action: (regex: RegExp) => (account: Account) => boolean) {
return prefixWith(prefix, phrase => action(new RegExp(escapeRegExp(phrase), 'i')));
}
function prefixWithNumber(prefix: string, action: (count: number) => (account: Account) => boolean) {
return prefixWith(prefix, phrase => action(+phrase));
}
function prefixWithNumber(prefix: string, action: (count: number) => (account: Account) => boolean) {
return prefixWith(prefix, phrase => action(+phrase));
}
const exactMatch = (phrase: string) => (account: Account) => account.nameLower === phrase;
const isOld = (max: number) => (account: Account) => !account.lastVisit || account.lastVisit.getTime() < max;
const exactMatch = (phrase: string) => (account: Account) => account.nameLower === phrase;
const isOld = (max: number) => (account: Account) => !account.lastVisit || account.lastVisit.getTime() < max;
return prefixWithRegex('name:', regex => account => regex.test(account.name))
|| prefixWithRegex('note:', regex => account => regex.test(account.note))
|| prefixWithRegex('email:', regex => account => !!account.emails && account.emails.some(e => regex.test(e)))
|| prefixWith('role:', role => account => hasRole(account, role))
|| prefixWith('exact:', phrase => exactMatch(phrase.toLowerCase()))
|| prefixWith('disabled!', () => account => !!account.auths && account.auths.some(a => !!a.disabled))
|| prefixWith('locked!', () => account => !!account.auths && account.auths.some(a => !!a.banned))
|| prefixWithNumber('ignores:', count => account => (account.ignoresCount || 0) >= count)
|| prefixWithNumber('ponies:', count => account => account.characterCount >= count)
|| prefixWithNumber('auths:', count => account => !!account.auths && account.auths.length >= count)
|| prefixWithNumber('old:', days => isOld(fromNow(-days * DAY).getTime()))
|| prefixWithNumber('spam:', count => account => !!account.counters && account.counters.spam! >= count)
|| prefixWithNumber('swearing:', count => account => !!account.counters && account.counters.swears! >= count)
|| prefixWithNumber('timeouts:', count => account => !!account.counters && account.counters.timeouts! >= count)
|| prefixWithNumber('limits:', count => account => !!account.counters && account.counters.inviteLimit! >= count)
|| filter;
return prefixWithRegex('name:', regex => account => regex.test(account.name))
|| prefixWithRegex('note:', regex => account => regex.test(account.note))
|| prefixWithRegex('email:', regex => account => !!account.emails && account.emails.some(e => regex.test(e)))
|| prefixWith('role:', role => account => hasRole(account, role))
|| prefixWith('exact:', phrase => exactMatch(phrase.toLowerCase()))
|| prefixWith('disabled!', () => account => !!account.auths && account.auths.some(a => !!a.disabled))
|| prefixWith('locked!', () => account => !!account.auths && account.auths.some(a => !!a.banned))
|| prefixWithNumber('ignores:', count => account => (account.ignoresCount || 0) >= count)
|| prefixWithNumber('ponies:', count => account => account.characterCount >= count)
|| prefixWithNumber('auths:', count => account => !!account.auths && account.auths.length >= count)
|| prefixWithNumber('old:', days => isOld(fromNow(-days * DAY).getTime()))
|| prefixWithNumber('spam:', count => account => !!account.counters && account.counters.spam! >= count)
|| prefixWithNumber('swearing:', count => account => !!account.counters && account.counters.swears! >= count)
|| prefixWithNumber('timeouts:', count => account => !!account.counters && account.counters.timeouts! >= count)
|| prefixWithNumber('limits:', count => account => !!account.counters && account.counters.inviteLimit! >= count)
|| filter;
}
function hasAnyBan(account: Account) {
return isBanned(account) || isMuted(account) || isShadowed(account);
return isBanned(account) || isMuted(account) || isShadowed(account);
}
export function createPotentialDuplicatesFilter(getAccountsByBrowserId: (id: string) => Account[] | undefined): (account: Account) => boolean {
return i => {
const name = i.nameLower;
return i => {
const name = i.nameLower;
if (name === 'anonymous' || !i.lastBrowserId)
return false;
if (name === 'anonymous' || !i.lastBrowserId)
return false;
const accounts = getAccountsByBrowserId(i.lastBrowserId);
const accounts = getAccountsByBrowserId(i.lastBrowserId);
if (accounts !== undefined && accounts.length > 1) {
for (const a of accounts) {
if (a !== i && a.nameLower === name) {
return true;
}
}
}
if (accounts !== undefined && accounts.length > 1) {
for (const a of accounts) {
if (a !== i && a.nameLower === name) {
return true;
}
}
}
return false;
};
return false;
};
}
export function createFilter2(showOnly: string): ((account: Account) => boolean) | undefined {
const now = Date.now();
const now = Date.now();
if (showOnly === 'banned') {
return hasAnyBan;
} else if (showOnly === 'timed out') {
return i => !!((i.mute && i.mute > now) || (i.shadow && i.shadow > now) || (i.ban && i.ban > now));
} else if (showOnly === 'with flags') {
return i => !!i.flags;
} else if (showOnly === 'notes') {
return i => !!i.note;
} else if (showOnly === 'supporters') {
return i => !!(i.patreon || i.supporter || i.supporterDeclinedSince);
} else {
return undefined;
}
if (showOnly === 'banned') {
return hasAnyBan;
} else if (showOnly === 'timed out') {
return i => !!((i.mute && i.mute > now) || (i.shadow && i.shadow > now) || (i.ban && i.ban > now));
} else if (showOnly === 'with flags') {
return i => !!i.flags;
} else if (showOnly === 'notes') {
return i => !!i.note;
} else if (showOnly === 'supporters') {
return i => !!(i.patreon || i.supporter || i.supporterDeclinedSince);
} else {
return undefined;
}
}
export function getPotentialDuplicates(account: Account, getAccountsByBrowserId: (id: string) => Account[] | undefined) {
const accounts = account.lastBrowserId ? getAccountsByBrowserId(account.lastBrowserId) : undefined;
const name = account.nameLower;
const accounts = account.lastBrowserId ? getAccountsByBrowserId(account.lastBrowserId) : undefined;
const name = account.nameLower;
if (accounts !== undefined && accounts.length > 1 && name !== 'anonymous') {
return accounts.filter(a => a !== account && a.nameLower === name);
} else {
return [];
}
if (accounts !== undefined && accounts.length > 1 && name !== 'anonymous') {
return accounts.filter(a => a !== account && a.nameLower === name);
} else {
return [];
}
}
// duplicates
export function compareDuplicates(a: DuplicateBase, b: DuplicateBase): number {
if (a.note !== b.note)
return b.note - a.note;
if (a.emails !== b.emails)
return b.emails - a.emails;
if (a.name !== b.name)
return b.name - a.name;
if (a.browserId !== b.browserId)
return a.browserId ? -1 : 1;
if (a.origins !== b.origins)
return b.origins - a.origins;
if (a.ponies !== b.ponies)
return (b.ponies ? b.ponies.length : 0) - (a.ponies ? a.ponies.length : 0);
return b.lastVisit.getTime() - a.lastVisit.getTime();
if (a.note !== b.note)
return b.note - a.note;
if (a.emails !== b.emails)
return b.emails - a.emails;
if (a.name !== b.name)
return b.name - a.name;
if (a.browserId !== b.browserId)
return a.browserId ? -1 : 1;
if (a.origins !== b.origins)
return b.origins - a.origins;
if (a.ponies !== b.ponies)
return (b.ponies ? b.ponies.length : 0) - (a.ponies ? a.ponies.length : 0);
return b.lastVisit.getTime() - a.lastVisit.getTime();
}
export function emailName(email: string): string {
return email.substr(0, email.indexOf('@')).toLowerCase();
return email.substr(0, email.indexOf('@')).toLowerCase();
}
export function createEmailMatcher(emails: string[]): ((email: string) => boolean) | undefined {
if (!emails || !emails.length) {
return undefined;
} else {
const match = emails.map(emailName).map(escapeRegExp).join('|');
const regex = new RegExp(`^(?:${match})@`, 'i');
return email => regex.test(email);
}
if (!emails || !emails.length) {
return undefined;
} else {
const match = emails.map(emailName).map(escapeRegExp).join('|');
const regex = new RegExp(`^(?:${match})@`, 'i');
return email => regex.test(email);
}
}
export function createDuplicate(account: Account, base: Account): Duplicate {
const indenticalEmail = account.emails && base.emails && account.emails.some(e => base.emails!.indexOf(e) !== -1);
const indenticalEmail = account.emails && base.emails && account.emails.some(e => base.emails!.indexOf(e) !== -1);
const isMatch = createEmailMatcher(base.emails || []);
const duplicateEmails = isMatch && account.emails
&& account.emails.reduce((sum, e) => sum + (isMatch(e) ? 1 : 0), 0);
const isMatch = createEmailMatcher(base.emails || []);
const duplicateEmails = isMatch && account.emails
&& account.emails.reduce((sum, e) => sum + (isMatch(e) ? 1 : 0), 0);
const duplicateOrigins = base.originsRefs && account.originsRefs
&& account.originsRefs.reduce((sum, o) => sum + (base.originsRefs!.some(r => o.origin.ip === r.origin.ip) ? 1 : 0), 0);
const duplicateOrigins = base.originsRefs && account.originsRefs
&& account.originsRefs.reduce((sum, o) => sum + (base.originsRefs!.some(r => o.origin.ip === r.origin.ip) ? 1 : 0), 0);
const name = account.nameLower !== 'anonymous' && account.nameLower === base.nameLower;
const name = account.nameLower !== 'anonymous' && account.nameLower === base.nameLower;
const note = (account.note && account.note.indexOf(base._id) !== -1)
|| (base.note && base.note.indexOf(account._id) !== -1);
const note = (account.note && account.note.indexOf(base._id) !== -1)
|| (base.note && base.note.indexOf(account._id) !== -1);
const browserId = !!account.lastBrowserId && account.lastBrowserId === base.lastBrowserId;
const birthdate = !!(base.birthdate && account.birthdate && base.birthdate.getTime() === account.birthdate.getTime());
const browserId = !!account.lastBrowserId && account.lastBrowserId === base.lastBrowserId;
const birthdate = !!(base.birthdate && account.birthdate && base.birthdate.getTime() === account.birthdate.getTime());
return {
account,
name: name ? 1 : 0,
note: note ? 1 : 0,
indenticalEmail: !!indenticalEmail,
emails: toInt(duplicateEmails),
origins: toInt(duplicateOrigins),
lastVisit: account.lastVisit || new Date(0),
browserId,
birthdate,
perma: isPermaBanned(account) || isPermaShadowed(account),
};
return {
account,
name: name ? 1 : 0,
note: note ? 1 : 0,
indenticalEmail: !!indenticalEmail,
emails: toInt(duplicateEmails),
origins: toInt(duplicateOrigins),
lastVisit: account.lastVisit || new Date(0),
browserId,
birthdate,
perma: isPermaBanned(account) || isPermaShadowed(account),
};
}
export function createDuplicateResult(account: Account, base: Account): DuplicateResult {
return { ...createDuplicate(account, base), account: account._id };
return { ...createDuplicate(account, base), account: account._id };
}
export function pushOrdered<T>(items: T[], item: T, compare: (a: T, b: T) => number) {
for (let i = 0; i < items.length; i++) {
if (compare(items[i], item) >= 0) {
items.splice(i, 0, item);
return;
}
}
for (let i = 0; i < items.length; i++) {
if (compare(items[i], item) >= 0) {
items.splice(i, 0, item);
return;
}
}
items.push(item);
items.push(item);
}
export function duplicatesCollector(duplicates: string[]) {
const set = new Set();
const set = new Set();
return (item: string) => {
if (set.has(item)) {
duplicates.push(item);
} else {
set.add(item);
}
};
return (item: string) => {
if (set.has(item)) {
duplicates.push(item);
} else {
set.add(item);
}
};
}
export function patreonSupporterLevel(account: AccountBase<any>) {
return account.patreon! & 0xf;
return account.patreon! & 0xf;
}
export function supporterLevel(account: AccountBase<any>) {
const flags = account.supporter!;
const ignore = hasFlag(flags, SupporterFlags.IgnorePatreon);
const patreonSupporter = patreonSupporterLevel(account);
const flagsSupporter = flags & 0xf;
return Math.max(ignore ? 0 : patreonSupporter, flagsSupporter);
const flags = account.supporter!;
const ignore = hasFlag(flags, SupporterFlags.IgnorePatreon);
const patreonSupporter = patreonSupporterLevel(account);
const flagsSupporter = flags & 0xf;
return Math.max(ignore ? 0 : patreonSupporter, flagsSupporter);
}
export function isPastSupporter(account: AccountBase<any>) {
const flags = account.supporter!;
return (hasFlag(flags, SupporterFlags.PastSupporter) || hasFlag(flags, SupporterFlags.ForcePastSupporter)) &&
!hasFlag(flags, SupporterFlags.IgnorePastSupporter);
const flags = account.supporter!;
return (hasFlag(flags, SupporterFlags.PastSupporter) || hasFlag(flags, SupporterFlags.ForcePastSupporter)) &&
!hasFlag(flags, SupporterFlags.IgnorePastSupporter);
}
const fieldToAction: { [key: string]: string | undefined; } = {
mute: 'Muted',
shadow: 'Shadowed',
ban: 'Banned',
mute: 'Muted',
shadow: 'Shadowed',
ban: 'Banned',
};
export function banMessage(field: string, value: number) {
const action = fieldToAction[field] || 'Did';
const action = fieldToAction[field] || 'Did';
if (value === 0) {
return `Un${action.toLowerCase()}`;
} else if (value === -1) {
return action;
} else {
return `${action} for (${moment.duration(value - Date.now()).humanize()})`;
}
if (value === 0) {
return `Un${action.toLowerCase()}`;
} else if (value === -1) {
return action;
} else {
return `${action} for (${moment.duration(value - Date.now()).humanize()})`;
}
}
export function isActive(value: number | undefined): boolean {
return !!value && (value === -1 || value > Date.now());
return !!value && (value === -1 || value > Date.now());
}
export function isPerma(value: number | undefined): boolean {
return value === -1;
return value === -1;
}
export function isTemporarilyActive(value: number | undefined): boolean {
return !!value && value > Date.now();
return !!value && value > Date.now();
}
export function isMuted(account: BannedMuted): boolean {
return isActive(account.mute);
return isActive(account.mute);
}
export function isShadowed(account: BannedMuted): boolean {
return isActive(account.shadow);
return isActive(account.shadow);
}
export function isBanned(account: BannedMuted): boolean {
return isActive(account.ban);
return isActive(account.ban);
}
export function isPermaShadowed(account: BannedMuted): boolean {
return isPerma(account.shadow);
return isPerma(account.shadow);
}
export function isPermaBanned(account: BannedMuted): boolean {
return isPerma(account.ban);
return isPerma(account.ban);
}
export function isTemporarilyBanned(account: BannedMuted): boolean {
return isTemporarilyActive(account.ban);
return isTemporarilyActive(account.ban);
}
export interface SupporterChange {
message: string;
date: Date;
icon: any;
class: string;
message: string;
date: Date;
icon: any;
class: string;
}
export function createSupporterChanges(entries: LogEntry[]): SupporterChange[] {
const changes = entries.map(l => ({
message: l.message,
level: +((/\d+/.exec(l.message) || ['0'])[0]),
added: /added/i.test(l.message),
date: new Date(l.date),
icon: /added/i.test(l.message) ? faPlusCircle : (/decline/i.test(l.message) ? faClock : faMinusCircle),
class: /added/i.test(l.message) ? 'text-success' : (/decline/i.test(l.message) ? 'text-warning' : 'text-danger'),
}));
const changes = entries.map(l => ({
message: l.message,
level: +((/\d+/.exec(l.message) || ['0'])[0]),
added: /added/i.test(l.message),
date: new Date(l.date),
icon: /added/i.test(l.message) ? faPlusCircle : (/decline/i.test(l.message) ? faClock : faMinusCircle),
class: /added/i.test(l.message) ? 'text-success' : (/decline/i.test(l.message) ? 'text-warning' : 'text-danger'),
}));
for (let i = 1; i < changes.length; i++) {
const prev = changes[i - 1];
const current = changes[i];
for (let i = 1; i < changes.length; i++) {
const prev = changes[i - 1];
const current = changes[i];
if (current.date.getMonth() !== prev.date.getMonth()) {
current.class += ' border-left border-success pl-2';
}
if (current.date.getMonth() !== prev.date.getMonth()) {
current.class += ' border-left border-success pl-2';
}
if (current.added && prev.added) {
if (current.level > prev.level) {
current.icon = faCaretSquareUp;
current.class = 'text-info';
} else if (current.level < prev.level) {
current.icon = faCaretSquareDown;
current.class = 'text-info';
}
}
}
if (current.added && prev.added) {
if (current.level > prev.level) {
current.icon = faCaretSquareUp;
current.class = 'text-info';
} else if (current.level < prev.level) {
current.icon = faCaretSquareDown;
current.class = 'text-info';
}
}
}
return changes;
return changes;
}
export function getIdsFromNote(note: string | undefined) {
return note ? uniq(note.match(/[0-9a-f]{24}/g)) : [];
return note ? uniq(note.match(/[0-9a-f]{24}/g)) : [];
}
export function addToMap<T>(map: Map<string, T[]>, key: string, item: T) {
const items = map.get(key);
const items = map.get(key);
if (items) {
items.push(item);
} else {
map.set(key, [item]);
}
if (items) {
items.push(item);
} else {
map.set(key, [item]);
}
}
export function removeFromMap<T>(map: Map<string, T[]>, key: string, item: T) {
const items = map.get(key);
const items = map.get(key);
if (items) {
removeItem(items, item);
if (items) {
removeItem(items, item);
if (items.length === 0) {
map.delete(key);
}
}
if (items.length === 0) {
map.delete(key);
}
}
}
export function parsePonies(ponies: string, filterIds?: string[]) {
return compact(ponies
.split(/\n\r?/g)
.map(x => /\[system\] removed pony \[([a-f0-9]{24})\] "(.+)" (\S+)/.exec(x)))
.map(([_, id, name, info]) => ({ id, name, info }))
.filter(({ id }) => !filterIds || includes(filterIds, id));
return compact(ponies
.split(/\n\r?/g)
.map(x => /\[system\] removed pony \[([a-f0-9]{24})\] "(.+)" (\S+)/.exec(x)))
.map(([_, id, name, info]) => ({ id, name, info }))
.filter(({ id }) => !filterIds || includes(filterIds, id));
}
export function createIdStore() {
const idsMap = new Map<string, string>();
const idsMap = new Map<string, string>();
return (id: string) => {
const result = idsMap.get(id);
return (id: string) => {
const result = idsMap.get(id);
if (result) {
return result;
} else {
idsMap.set(id, id);
return id;
}
};
if (result) {
return result;
} else {
idsMap.set(id, id);
return id;
}
};
}
export function getTranslationUrl(text: string) {
return `https://translate.google.com/#view=home&op=translate&sl=auto&tl=en&text=${encodeURIComponent(text)}`;
// return `https://translate.google.com/#auto/en/${encodeURIComponent(text)}`;
return `https://translate.google.com/#view=home&op=translate&sl=auto&tl=en&text=${encodeURIComponent(text)}`;
// return `https://translate.google.com/#auto/en/${encodeURIComponent(text)}`;
}
+91 -91
View File
@@ -5,131 +5,131 @@ import { includes } from './utils';
import { WHITE } from './colors';
const enum AnimationPhase {
Starting,
Playing,
Ending,
Starting,
Playing,
Ending,
}
export interface SpriteAnimation {
loop: boolean;
start: number;
middle: number;
end: number;
fps: number;
palette: Uint32Array;
frames: Sprite[];
flipFrames?: Sprite[];
loop: boolean;
start: number;
middle: number;
end: number;
fps: number;
palette: Uint32Array;
frames: Sprite[];
flipFrames?: Sprite[];
}
export interface AnimationPlayer {
nextAnimation: SpriteAnimation | undefined;
currentAnimation: SpriteAnimation | undefined;
time: number;
frame: number;
phase: AnimationPhase;
dirty: boolean;
palette: Palette;
nextAnimation: SpriteAnimation | undefined;
currentAnimation: SpriteAnimation | undefined;
time: number;
frame: number;
phase: AnimationPhase;
dirty: boolean;
palette: Palette;
}
export function createAnimationPlayer(palette: Palette): AnimationPlayer {
return {
nextAnimation: undefined,
currentAnimation: undefined,
time: 0,
frame: 0,
phase: AnimationPhase.Starting,
dirty: true,
palette,
};
return {
nextAnimation: undefined,
currentAnimation: undefined,
time: 0,
frame: 0,
phase: AnimationPhase.Starting,
dirty: true,
palette,
};
}
export function isAnimationPlaying(player: AnimationPlayer) {
return player.currentAnimation !== undefined;
return player.currentAnimation !== undefined;
}
export function playOneOfAnimations(player: AnimationPlayer, animations: SpriteAnimation[]) {
if (player.phase === AnimationPhase.Ending || !includes(animations, player.currentAnimation)) {
playAnimation(player, sample(animations));
}
if (player.phase === AnimationPhase.Ending || !includes(animations, player.currentAnimation)) {
playAnimation(player, sample(animations));
}
}
export function playAnimation(player: AnimationPlayer, animation: SpriteAnimation | undefined) {
if (player.currentAnimation !== animation) {
if (player.currentAnimation) {
if (player.nextAnimation !== animation || player.phase !== AnimationPhase.Ending) {
player.nextAnimation = animation;
player.time = (player.frame + 1) / player.currentAnimation.fps;
player.phase = AnimationPhase.Ending;
}
} else {
player.currentAnimation = animation;
player.time = 0;
player.phase = AnimationPhase.Starting;
}
player.dirty = true;
} else if (player.phase === AnimationPhase.Ending) {
player.nextAnimation = animation;
player.dirty = true;
}
if (player.currentAnimation !== animation) {
if (player.currentAnimation) {
if (player.nextAnimation !== animation || player.phase !== AnimationPhase.Ending) {
player.nextAnimation = animation;
player.time = (player.frame + 1) / player.currentAnimation.fps;
player.phase = AnimationPhase.Ending;
}
} else {
player.currentAnimation = animation;
player.time = 0;
player.phase = AnimationPhase.Starting;
}
player.dirty = true;
} else if (player.phase === AnimationPhase.Ending) {
player.nextAnimation = animation;
player.dirty = true;
}
}
export function updateAnimation(player: AnimationPlayer, delta: number) {
if (player.currentAnimation !== undefined) {
player.time += delta;
const { start, middle, end, fps, loop } = player.currentAnimation;
if (player.currentAnimation !== undefined) {
player.time += delta;
const { start, middle, end, fps, loop } = player.currentAnimation;
let extraFrame = Math.floor(player.time * fps);
let extraFrame = Math.floor(player.time * fps);
if (player.phase === AnimationPhase.Starting && extraFrame > start) {
player.phase = loop ? AnimationPhase.Playing : AnimationPhase.Ending;
player.dirty = true;
}
if (player.phase === AnimationPhase.Starting && extraFrame > start) {
player.phase = loop ? AnimationPhase.Playing : AnimationPhase.Ending;
player.dirty = true;
}
if (player.phase === AnimationPhase.Playing) {
extraFrame = start + ((extraFrame - start) % middle);
}
if (player.phase === AnimationPhase.Playing) {
extraFrame = start + ((extraFrame - start) % middle);
}
if (player.phase === AnimationPhase.Ending && extraFrame > (start + middle + end)) {
player.currentAnimation = undefined;
player.dirty = true;
if (player.phase === AnimationPhase.Ending && extraFrame > (start + middle + end)) {
player.currentAnimation = undefined;
player.dirty = true;
if (player.nextAnimation !== undefined) {
const nextAnimation = player.nextAnimation;
player.nextAnimation = undefined;
playAnimation(player, nextAnimation);
}
}
if (player.nextAnimation !== undefined) {
const nextAnimation = player.nextAnimation;
player.nextAnimation = undefined;
playAnimation(player, nextAnimation);
}
}
if (player.frame !== extraFrame) {
player.frame = extraFrame;
player.dirty = true;
}
}
if (player.frame !== extraFrame) {
player.frame = extraFrame;
player.dirty = true;
}
}
}
export function drawAnimation(
batch: PaletteSpriteBatch, player: AnimationPlayer, x: number, y: number, color = WHITE, flip = false, maxY = 0
batch: PaletteSpriteBatch, player: AnimationPlayer, x: number, y: number, color = WHITE, flip = false, maxY = 0
) {
const animation = player.currentAnimation;
const animation = player.currentAnimation;
if (animation !== undefined) {
const frames = (flip && animation.flipFrames) ? animation.flipFrames : animation.frames;
if (animation !== undefined) {
const frames = (flip && animation.flipFrames) ? animation.flipFrames : animation.frames;
if (player.frame < frames.length) {
const frame = frames[player.frame];
if (player.frame < frames.length) {
const frame = frames[player.frame];
if (DEVELOPMENT && !frame) {
throw new Error('Undefined frame in sprite animation');
}
if (DEVELOPMENT && !frame) {
throw new Error('Undefined frame in sprite animation');
}
if (!frame) // TEMP
return;
if (!frame) // TEMP
return;
if (maxY === 0) {
batch.drawSprite(frame, color, player.palette, x, y);
} else {
drawSpriteCropped(batch, frame, color, player.palette, x, y, maxY);
}
}
}
if (maxY === 0) {
batch.drawSprite(frame, color, player.palette, x, y);
} else {
drawSpriteCropped(batch, frame, color, player.palette, x, y, maxY);
}
}
}
}
+111 -111
View File
@@ -1,183 +1,183 @@
export interface Animation {
fps: number;
loop: boolean;
frames: any[];
fps: number;
loop: boolean;
frames: any[];
}
export interface AnimatorTransition<T extends Animation> {
state: AnimatorState<T>;
exitAfter?: number;
enterTime?: number;
keepTime?: boolean;
onlyDirectTo?: AnimatorState<T>;
state: AnimatorState<T>;
exitAfter?: number;
enterTime?: number;
keepTime?: boolean;
onlyDirectTo?: AnimatorState<T>;
}
export interface AnimatorState<T extends Animation = Animation> {
name: string;
animation: T;
variants: { [key: string]: T; };
from: AnimatorTransition<T>[];
name: string;
animation: T;
variants: { [key: string]: T; };
from: AnimatorTransition<T>[];
}
export function animatorState<T extends Animation>(
name: string, animation: T, variants: { [key: string]: T; } = {}
name: string, animation: T, variants: { [key: string]: T; } = {}
): AnimatorState<T> {
return { name, animation, variants, from: [] };
return { name, animation, variants, from: [] };
}
export function animatorTransition<T extends Animation>(
from: AnimatorState<T>, to: AnimatorState<T>, options: Partial<AnimatorTransition<T>> = {}
from: AnimatorState<T>, to: AnimatorState<T>, options: Partial<AnimatorTransition<T>> = {}
) {
to.from.push({ state: from, ...options });
to.from.push({ state: from, ...options });
}
export const anyState = animatorState<any>('any', { fps: 1, loop: false, frames: [] });
export interface Animator<T extends Animation> {
state: AnimatorState<T> | undefined;
target: AnimatorState<T> | undefined;
next: AnimatorTransition<T> | undefined;
time: number;
variant: string;
state: AnimatorState<T> | undefined;
target: AnimatorState<T> | undefined;
next: AnimatorTransition<T> | undefined;
time: number;
variant: string;
}
export function createAnimator<T extends Animation>(): Animator<T> {
return {
time: 0,
variant: '',
state: undefined,
target: undefined,
next: undefined,
};
return {
time: 0,
variant: '',
state: undefined,
target: undefined,
next: undefined,
};
}
export function getAnimation<T extends Animation>(animator: Animator<T>) {
return animator.state && getAnimationForState(animator.state, animator.variant);
return animator.state && getAnimationForState(animator.state, animator.variant);
}
export function getAnimationFrame<T extends Animation>(animator: Animator<T>) {
const animation = getAnimation(animator);
return animation ? Math.floor(animator.time * animation.fps) % animation.frames.length : 0;
const animation = getAnimation(animator);
return animation ? Math.floor(animator.time * animation.fps) % animation.frames.length : 0;
}
export function resetAnimatorState<T extends Animation>(animator: Animator<T>) {
animator.state = undefined;
animator.target = undefined;
animator.next = undefined;
animator.state = undefined;
animator.target = undefined;
animator.next = undefined;
}
export function setAnimatorState<T extends Animation>(animator: Animator<T>, state: AnimatorState<T>) {
if (animator.target !== state) {
if (animator.state !== state) {
if (animator.state === undefined) {
animator.state = state;
} else {
animator.target = state;
}
} else {
animator.target = undefined;
}
if (animator.target !== state) {
if (animator.state !== state) {
if (animator.state === undefined) {
animator.state = state;
} else {
animator.target = state;
}
} else {
animator.target = undefined;
}
animator.next = undefined;
}
animator.next = undefined;
}
}
export function updateAnimator<T extends Animation>(animator: Animator<T>, delta: number) {
const time = animator.time;
animator.time += delta;
const time = animator.time;
animator.time += delta;
if (animator.target !== undefined && animator.state !== undefined && animator.state !== animator.target) {
const animation = getAnimationForState(animator.state, animator.variant);
const animationLength = animation.frames.length / animation.fps;
const frameBefore = Math.floor(time / animationLength);
const frameAfter = Math.floor(animator.time / animationLength);
const frameTimeAfter = (animator.time % animationLength) / animationLength;
if (animator.target !== undefined && animator.state !== undefined && animator.state !== animator.target) {
const animation = getAnimationForState(animator.state, animator.variant);
const animationLength = animation.frames.length / animation.fps;
const frameBefore = Math.floor(time / animationLength);
const frameAfter = Math.floor(animator.time / animationLength);
const frameTimeAfter = (animator.time % animationLength) / animationLength;
let animationEnded = frameBefore !== frameAfter;
let switched = false;
let animationEnded = frameBefore !== frameAfter;
let switched = false;
do {
switched = false;
const transition = animator.next = animator.next || findTransition(animator.state, animator.target);
do {
switched = false;
const transition = animator.next = animator.next || findTransition(animator.state, animator.target);
if (transition !== undefined) {
const exitAfter = transition.exitAfter === undefined ? 1 : transition.exitAfter;
if (transition !== undefined) {
const exitAfter = transition.exitAfter === undefined ? 1 : transition.exitAfter;
if (frameTimeAfter >= exitAfter || animationEnded) {
if (!transition.keepTime) {
animator.time = transition.enterTime || 0;
} else {
animator.time = animator.time % animationLength;
}
if (frameTimeAfter >= exitAfter || animationEnded) {
if (!transition.keepTime) {
animator.time = transition.enterTime || 0;
} else {
animator.time = animator.time % animationLength;
}
setCurrentState(animator, transition.state);
setCurrentState(animator, transition.state);
switched = true;
animationEnded = false;
}
}
} while (switched && animator.target);
}
switched = true;
animationEnded = false;
}
}
} while (switched && animator.target);
}
}
function setCurrentState<T extends Animation>(animator: Animator<T>, state: AnimatorState<T>) {
animator.next = undefined;
animator.state = state;
animator.next = undefined;
animator.state = state;
if (state === animator.target) {
animator.target = undefined;
}
if (state === animator.target) {
animator.target = undefined;
}
}
function getAnimationForState<T extends Animation>(state: AnimatorState<T>, variant: string) {
return state.variants[variant] || state.animation;
return state.variants[variant] || state.animation;
}
function findTransition<T extends Animation>(
current: AnimatorState<T>, target: AnimatorState<T>
current: AnimatorState<T>, target: AnimatorState<T>
): AnimatorTransition<T> | undefined {
return findTransMinMax(current, target, 0, 1)
|| findTrans(anyState, target, target, 0, 0, [])
|| findTransMinMax(current, target, 2, 10);
return findTransMinMax(current, target, 0, 1)
|| findTrans(anyState, target, target, 0, 0, [])
|| findTransMinMax(current, target, 2, 10);
}
function findTransMinMax<T extends Animation>(
current: AnimatorState<T>, target: AnimatorState<T>, min: number, max: number
current: AnimatorState<T>, target: AnimatorState<T>, min: number, max: number
): AnimatorTransition<T> | undefined {
for (let i = min; i <= max; i++) {
const trans = findTrans(current, target, target, 0, i, [current]);
for (let i = min; i <= max; i++) {
const trans = findTrans(current, target, target, 0, i, [current]);
if (trans !== undefined) {
return trans;
}
}
if (trans !== undefined) {
return trans;
}
}
return undefined;
return undefined;
}
function findTrans<T extends Animation>(
current: AnimatorState<T>, target: AnimatorState<T>, finalTarget: AnimatorState<T>,
depth: number, maxDepth: number, done: AnimatorState<T>[]
current: AnimatorState<T>, target: AnimatorState<T>, finalTarget: AnimatorState<T>,
depth: number, maxDepth: number, done: AnimatorState<T>[]
): AnimatorTransition<T> | undefined {
if (done.indexOf(target) === -1) {
done.push(target);
if (done.indexOf(target) === -1) {
done.push(target);
for (const from of target.from) {
if (from.state === current && (from.onlyDirectTo === undefined || from.onlyDirectTo === finalTarget)) {
return { ...from, state: target };
}
}
for (const from of target.from) {
if (from.state === current && (from.onlyDirectTo === undefined || from.onlyDirectTo === finalTarget)) {
return { ...from, state: target };
}
}
if (depth < maxDepth) {
for (const from of target.from) {
const trans = findTrans(current, from.state, finalTarget, depth + 1, maxDepth, done);
if (depth < maxDepth) {
for (const from of target.from) {
const trans = findTrans(current, from.state, finalTarget, depth + 1, maxDepth, done);
if (trans !== undefined) {
return trans;
}
}
}
}
if (trans !== undefined) {
return trans;
}
}
}
}
return undefined;
return undefined;
}
+14 -14
View File
@@ -1,20 +1,20 @@
import { BinaryWriter, getWriterBuffer, createBinaryWriter, resizeWriter } from 'ag-sockets/dist/browser';
export function writeBinary(write: (writer: BinaryWriter) => void): Uint8Array {
const writer = createBinaryWriter();
const writer = createBinaryWriter();
do {
try {
write(writer);
break;
} catch (e) {
if (e instanceof RangeError || /DataView/.test(e.message)) {
resizeWriter(writer);
} else {
throw e;
}
}
} while (true);
do {
try {
write(writer);
break;
} catch (e) {
if (e instanceof RangeError || /DataView/.test(e.message)) {
resizeWriter(writer);
} else {
throw e;
}
}
} while (true);
return getWriterBuffer(writer);
return getWriterBuffer(writer);
}
+78 -78
View File
@@ -2,113 +2,113 @@ export type WriteBits = (value: number, bits: number) => void;
export type ReadBits = (bits: number) => number;
export function numberToBitCount(value: number) {
value = value >>> 0;
value = value >>> 0;
for (let mask = 0xffffffff >>> 0, bits = 0; mask; mask = (mask << 1) >>> 0, bits++) {
if ((value & mask) === 0) {
return bits;
}
}
for (let mask = 0xffffffff >>> 0, bits = 0; mask; mask = (mask << 1) >>> 0, bits++) {
if ((value & mask) === 0) {
return bits;
}
}
return 32;
return 32;
}
export function countBits(value: number) {
value = value >>> 0;
value = value >>> 0;
let bits = 0;
let bits = 0;
while (value) {
bits += value & 1;
value = value >>> 1;
}
while (value) {
bits += value & 1;
value = value >>> 1;
}
return bits;
return bits;
}
export function bitWriter(writes: (writer: WriteBits) => void): Uint8Array {
let buffer = new Uint8Array(16);
let length = 0;
let byte = 0;
let byteBits = 0;
let buffer = new Uint8Array(16);
let length = 0;
let byte = 0;
let byteBits = 0;
function writeByte(value: number) {
if (buffer.length <= length) {
const newBuffer = new Uint8Array(buffer.length * 2);
newBuffer.set(buffer);
buffer = newBuffer;
}
function writeByte(value: number) {
if (buffer.length <= length) {
const newBuffer = new Uint8Array(buffer.length * 2);
newBuffer.set(buffer);
buffer = newBuffer;
}
buffer[length] = value;
length++;
}
buffer[length] = value;
length++;
}
writes((value, bits) => {
if (bits < 0 || bits > 32) {
throw new Error('Invalid bit count');
}
writes((value, bits) => {
if (bits < 0 || bits > 32) {
throw new Error('Invalid bit count');
}
while (bits) {
const revByteBits = 8 - byteBits;
const writeBits = revByteBits < bits ? revByteBits : bits;
const write = (value >> (bits - writeBits)) & (0xff >> (8 - writeBits));
byte |= write << (revByteBits - writeBits);
byteBits += writeBits;
bits -= writeBits;
while (bits) {
const revByteBits = 8 - byteBits;
const writeBits = revByteBits < bits ? revByteBits : bits;
const write = (value >> (bits - writeBits)) & (0xff >> (8 - writeBits));
byte |= write << (revByteBits - writeBits);
byteBits += writeBits;
bits -= writeBits;
if (byteBits === 8) {
writeByte(byte);
byte = 0;
byteBits = 0;
}
}
});
if (byteBits === 8) {
writeByte(byte);
byte = 0;
byteBits = 0;
}
}
});
if (byteBits) {
writeByte(byte);
byteBits = 0;
byte = 0;
}
if (byteBits) {
writeByte(byte);
byteBits = 0;
byte = 0;
}
return buffer.subarray(0, length);
return buffer.subarray(0, length);
}
export function bitReader(buffer: Uint8Array): ReadBits {
let offset = 0;
let offset = 0;
return bitReaderCustom(() => {
if (buffer.length <= offset) {
throw new Error('Reading past end');
}
return bitReaderCustom(() => {
if (buffer.length <= offset) {
throw new Error('Reading past end');
}
return buffer[offset++];
});
return buffer[offset++];
});
}
export function bitReaderCustom(readByte: () => number): ReadBits {
let byte = 0;
let byteBits = 0;
let byte = 0;
let byteBits = 0;
return bits => {
if (bits < 0 || bits > 32) {
throw new Error('Invalid bit count');
}
return bits => {
if (bits < 0 || bits > 32) {
throw new Error('Invalid bit count');
}
let result = 0;
let result = 0;
while (bits) {
if (!byteBits) {
byte = readByte();
byteBits = 8;
}
while (bits) {
if (!byteBits) {
byte = readByte();
byteBits = 8;
}
const readBits = byteBits < bits ? byteBits : bits;
const read = (byte >> (byteBits - readBits)) & (0xff >> (8 - readBits));
result = (result << readBits) | read;
bits -= readBits;
byteBits -= readBits;
}
const readBits = byteBits < bits ? byteBits : bits;
const read = (byte >> (byteBits - readBits)) & (0xff >> (8 - readBits));
result = (result << readBits) | read;
bits -= readBits;
byteBits -= readBits;
}
return result >>> 0;
};
return result >>> 0;
};
}
+68 -68
View File
@@ -8,123 +8,123 @@ const cameraPadding = 0.3;
export const characterHeight = 25;
export function createCamera(): Camera {
return {
x: 0,
y: 0,
w: 100,
h: 100,
offset: 0,
shift: 0,
shiftTarget: 0,
shiftRatio: 0,
actualY: 0,
};
return {
x: 0,
y: 0,
w: 100,
h: 100,
offset: 0,
shift: 0,
shiftTarget: 0,
shiftRatio: 0,
actualY: 0,
};
}
export function setupCamera(camera: Camera, x: number, y: number, width: number, height: number, map: Size) {
camera.w = clamp(width, CAMERA_WIDTH_MIN, CAMERA_WIDTH_MAX);
camera.h = clamp(height, CAMERA_HEIGHT_MIN, CAMERA_HEIGHT_MAX);
camera.x = clamp(x, 0, toScreenX(map.width) - camera.w);
camera.y = clamp(y, 0, toScreenY(map.height) - camera.h);
camera.w = clamp(width, CAMERA_WIDTH_MIN, CAMERA_WIDTH_MAX);
camera.h = clamp(height, CAMERA_HEIGHT_MIN, CAMERA_HEIGHT_MAX);
camera.x = clamp(x, 0, toScreenX(map.width) - camera.w);
camera.y = clamp(y, 0, toScreenY(map.height) - camera.h);
}
export function updateCamera(camera: Camera, player: Point, map: Size) {
const cameraWith = camera.w;
const cameraHeight = camera.h;
const cameraHeightShifted = Math.ceil(camera.h - camera.offset);
const cameraWith = camera.w;
const cameraHeight = camera.h;
const cameraHeightShifted = Math.ceil(camera.h - camera.offset);
const playerX = toScreenX(player.x);
const playerY = toScreenY(player.y);
const playerX = toScreenX(player.x);
const playerY = toScreenY(player.y);
const mapWidth = toScreenX(map.width);
const mapHeight = toScreenY(map.height);
const mapWidth = toScreenX(map.width);
const mapHeight = toScreenY(map.height);
const minX = Math.min(0, (mapWidth - cameraWith) / 2);
const minY = Math.min(0, (mapHeight - cameraHeight) / 2);
const minYShifted = Math.min(0, (mapHeight - cameraHeightShifted) / 2);
const maxX = Math.max(mapWidth - cameraWith, minX);
const maxY = Math.max(mapHeight - cameraHeight, minY);
const maxYShifted = Math.max(mapHeight - cameraHeightShifted, minY);
const minX = Math.min(0, (mapWidth - cameraWith) / 2);
const minY = Math.min(0, (mapHeight - cameraHeight) / 2);
const minYShifted = Math.min(0, (mapHeight - cameraHeightShifted) / 2);
const maxX = Math.max(mapWidth - cameraWith, minX);
const maxY = Math.max(mapHeight - cameraHeight, minY);
const maxYShifted = Math.max(mapHeight - cameraHeightShifted, minY);
const hSpace = Math.floor(cameraWith * cameraPadding);
const vSpace = Math.floor(cameraHeight * cameraPadding);
const vSpaceShifted = Math.floor(cameraHeightShifted * cameraPadding);
const hSpace = Math.floor(cameraWith * cameraPadding);
const vSpace = Math.floor(cameraHeight * cameraPadding);
const vSpaceShifted = Math.floor(cameraHeightShifted * cameraPadding);
const hPad = (cameraWith - hSpace) / 2;
const vPad = (cameraHeight - vSpace) / 2;
const vPadShifted = (cameraHeightShifted - vSpaceShifted) / 2;
const hPad = (cameraWith - hSpace) / 2;
const vPad = (cameraHeight - vSpace) / 2;
const vPadShifted = (cameraHeightShifted - vSpaceShifted) / 2;
const minCamX = clamp(playerX - (hSpace + hPad), minX, maxX);
const maxCamX = clamp(playerX - hPad, minX, maxX);
const minCamY = clamp(playerY - (vSpace + vPad) - characterHeight, minY, maxY);
const maxCamY = clamp(playerY - vPad - characterHeight, minY, maxY);
const minCamYShifted = clamp(playerY - (vSpaceShifted + vPadShifted) - characterHeight, minYShifted, maxYShifted);
const maxCamYShifted = clamp(playerY - vPadShifted - characterHeight, minYShifted, maxYShifted);
const minCamX = clamp(playerX - (hSpace + hPad), minX, maxX);
const maxCamX = clamp(playerX - hPad, minX, maxX);
const minCamY = clamp(playerY - (vSpace + vPad) - characterHeight, minY, maxY);
const maxCamY = clamp(playerY - vPad - characterHeight, minY, maxY);
const minCamYShifted = clamp(playerY - (vSpaceShifted + vPadShifted) - characterHeight, minYShifted, maxYShifted);
const maxCamYShifted = clamp(playerY - vPadShifted - characterHeight, minYShifted, maxYShifted);
camera.x = Math.floor(clamp(camera.x, minCamX, maxCamX));
camera.y = Math.floor(clamp(camera.y, minCamY, maxCamY));
camera.shiftTarget = Math.floor(clamp(camera.shiftTarget, minCamYShifted, maxCamYShifted));
camera.actualY = calculateCameraY(camera);
camera.x = Math.floor(clamp(camera.x, minCamX, maxCamX));
camera.y = Math.floor(clamp(camera.y, minCamY, maxCamY));
camera.shiftTarget = Math.floor(clamp(camera.shiftTarget, minCamYShifted, maxCamYShifted));
camera.actualY = calculateCameraY(camera);
}
export function centerCameraOn(camera: Camera, point: Point) {
camera.x = Math.floor(toScreenX(point.x) - camera.w / 2);
camera.y = Math.floor((toScreenY(point.y) - camera.h / 2) - characterHeight);
camera.shiftTarget = Math.floor((toScreenY(point.y) - Math.ceil(camera.h - camera.offset) / 2) - characterHeight);
camera.x = Math.floor(toScreenX(point.x) - camera.w / 2);
camera.y = Math.floor((toScreenY(point.y) - camera.h / 2) - characterHeight);
camera.shiftTarget = Math.floor((toScreenY(point.y) - Math.ceil(camera.h - camera.offset) / 2) - characterHeight);
}
export function calculateCameraY(camera: Camera) {
return Math.round(lerp(camera.y, camera.shiftTarget - camera.offset, camera.shiftRatio));
return Math.round(lerp(camera.y, camera.shiftTarget - camera.offset, camera.shiftRatio));
}
export function isWorldPointVisible(camera: Camera, point: Point): boolean {
return pointInRect(toScreenX(point.x), toScreenY(point.y), camera);
return pointInRect(toScreenX(point.x), toScreenY(point.y), camera);
}
export function isWorldPointWithPaddingVisible(camera: Camera, point: Point, padding: number): boolean {
return pointInXYWH(
toScreenX(point.x), toScreenY(point.y),
camera.x - padding, camera.actualY - padding, camera.w + 2 * padding, camera.h + 2 * padding);
return pointInXYWH(
toScreenX(point.x), toScreenY(point.y),
camera.x - padding, camera.actualY - padding, camera.w + 2 * padding, camera.h + 2 * padding);
}
export function isAreaVisible(camera: Camera, x: number, y: number, w: number, h: number): boolean {
return intersect(camera.x, camera.actualY, camera.w, camera.h, x, y, w, h);
return intersect(camera.x, camera.actualY, camera.w, camera.h, x, y, w, h);
}
export function isRectVisible(camera: Camera, rect: Rect): boolean {
return intersect(camera.x, camera.actualY, camera.w, camera.h, rect.x, rect.y, rect.w, rect.h);
return intersect(camera.x, camera.actualY, camera.w, camera.h, rect.x, rect.y, rect.w, rect.h);
}
export function isBoundsVisible(camera: Camera, bounds: Rect | undefined, x: number, y: number): boolean {
return bounds !== undefined &&
isAreaVisible(camera, toScreenX(x) + bounds.x, toScreenY(y) + bounds.y, bounds.w, bounds.h);
return bounds !== undefined &&
isAreaVisible(camera, toScreenX(x) + bounds.x, toScreenY(y) + bounds.y, bounds.w, bounds.h);
}
export function isEntityVisible(camera: Camera, entity: Entity): boolean {
return isBoundsVisible(camera, entity.bounds, entity.x, entity.y);
return isBoundsVisible(camera, entity.bounds, entity.x, entity.y);
}
function isChatBaloonAboveScreenTop(camera: Camera, entity: Entity) {
return getChatBallonXY(entity, camera).y <= -5;
return getChatBallonXY(entity, camera).y <= -5;
}
export function isChatVisible(camera: Camera, entity: Entity): boolean {
return isBoundsVisible(camera, entity.bounds, entity.x, entity.y)
&& !isChatBaloonAboveScreenTop(camera, entity);
return isBoundsVisible(camera, entity.bounds, entity.x, entity.y)
&& !isChatBaloonAboveScreenTop(camera, entity);
}
export function screenToWorld(camera: Camera, point: Point): Point {
return {
x: toWorldX(point.x + camera.x),
y: toWorldY(point.y + camera.actualY),
};
return {
x: toWorldX(point.x + camera.x),
y: toWorldY(point.y + camera.actualY),
};
}
export function worldToScreen(camera: Camera, point: Point): Point {
return {
x: Math.floor(toScreenX(point.x) - camera.x),
y: Math.floor(toScreenY(point.y) - camera.actualY),
};
return {
x: Math.floor(toScreenX(point.x) - camera.x),
y: Math.floor(toScreenY(point.y) - camera.actualY),
};
}
// export function mapDepth(camera: Camera, y: number): number {
+233 -233
View File
@@ -9,298 +9,298 @@ let isCollidingCount = 0;
let isCollidingObjectCount = 0;
export function getCollisionStats() {
const stats = { isCollidingCount, isCollidingObjectCount };
isCollidingCount = 0;
isCollidingObjectCount = 0;
return stats;
const stats = { isCollidingCount, isCollidingObjectCount };
isCollidingCount = 0;
isCollidingObjectCount = 0;
return stats;
}
export function isOutsideMap<T>(x: number, y: number, map: IMap<T>): boolean {
return x < 0 || y < 0 || x >= map.width || y >= map.height;
return x < 0 || y < 0 || x >= map.width || y >= map.height;
}
export function canCollideWith(entity: Entity): boolean {
return (entity.flags & EntityFlags.CanCollideWith) !== 0;
return (entity.flags & EntityFlags.CanCollideWith) !== 0;
}
export function isStaticCollision<T>(entity: Entity, map: IMap<T>, forceOnGround = false) {
if (DEVELOPMENT && entity.type !== PONY_TYPE) {
console.error(`isStaticCollision: non-pony entity`);
}
if (DEVELOPMENT && entity.type !== PONY_TYPE) {
console.error(`isStaticCollision: non-pony entity`);
}
const flying = !forceOnGround && isInTheAir(entity);
return isPonyColliding(entity.x, entity.y, map as any, flying);
const flying = !forceOnGround && isInTheAir(entity);
return isPonyColliding(entity.x, entity.y, map as any, flying);
}
export function fixCollision<T>(entity: Entity, map: IMap<T>) {
if (DEVELOPMENT && entity.type !== PONY_TYPE) {
console.error(`fixCollision: non-pony entity`);
}
if (DEVELOPMENT && entity.type !== PONY_TYPE) {
console.error(`fixCollision: non-pony entity`);
}
const flying = isInTheAir(entity);
const flying = isInTheAir(entity);
for (let x = -1; x <= 1; x++) {
for (let y = -1; y <= 1; y++) {
const tx = entity.x + x;
const ty = entity.y + y;
for (let x = -1; x <= 1; x++) {
for (let y = -1; y <= 1; y++) {
const tx = entity.x + x;
const ty = entity.y + y;
if (!isPonyColliding(tx, ty, map as any, flying)) {
entity.x += x;
entity.y += y;
return true;
}
}
}
if (!isPonyColliding(tx, ty, map as any, flying)) {
entity.x += x;
entity.y += y;
return true;
}
}
}
return false;
return false;
}
function isPonyColliding<T extends Region | undefined>(x: number, y: number, map: IMap<T>, flying: boolean): boolean {
if (isOutsideMap(x, y, map)) {
return true;
}
if (isOutsideMap(x, y, map)) {
return true;
}
const region = getRegionGlobal(map, x, y);
const region = getRegionGlobal(map, x, y);
if (region === undefined) {
return true;
}
if (region === undefined) {
return true;
}
const rx = clamp(Math.floor((x - region.x * REGION_SIZE) * tileWidth), 0, REGION_WIDTH);
const ry = clamp(Math.floor((y - region.y * REGION_SIZE) * tileHeight), 0, REGION_HEIGHT);
const pixel = region.collider[rx + ry * REGION_WIDTH];
const mask = flying ? 2 : 1;
const rx = clamp(Math.floor((x - region.x * REGION_SIZE) * tileWidth), 0, REGION_WIDTH);
const ry = clamp(Math.floor((y - region.y * REGION_SIZE) * tileHeight), 0, REGION_HEIGHT);
const pixel = region.collider[rx + ry * REGION_WIDTH];
const mask = flying ? 2 : 1;
return (pixel & mask) !== 0;
return (pixel & mask) !== 0;
}
function isColliding(x: number, y: number, mask: number, map: IMap<Region | undefined>) {
if (x < 0 || x >= (map.width * tileWidth) || y < 0 || y >= (map.height * tileHeight)) {
return true;
} else {
const regionX = (x / REGION_WIDTH) | 0;
const regionY = (y / REGION_HEIGHT) | 0;
const region = map.regions[regionX + regionY * map.regionsX];
if (x < 0 || x >= (map.width * tileWidth) || y < 0 || y >= (map.height * tileHeight)) {
return true;
} else {
const regionX = (x / REGION_WIDTH) | 0;
const regionY = (y / REGION_HEIGHT) | 0;
const region = map.regions[regionX + regionY * map.regionsX];
if (region === undefined) {
return true;
} else {
const insideX = (x % REGION_WIDTH) | 0;
const insideY = (y % REGION_HEIGHT) | 0;
return (region.collider[insideX + insideY * REGION_WIDTH] & mask) !== 0;
}
}
if (region === undefined) {
return true;
} else {
const insideX = (x % REGION_WIDTH) | 0;
const insideY = (y % REGION_HEIGHT) | 0;
return (region.collider[insideX + insideY * REGION_WIDTH] & mask) !== 0;
}
}
}
export function updatePosition(entity: Entity, delta: number, map: IMap<Region | undefined>) {
const ex = entity.x;
const ey = entity.y;
const speed = (!isFlying(entity) && isInWaterAt(map, ex, ey)) ? 0.5 : 1.0;
const destX = ex + entity.vx * speed * delta;
const destY = ey + entity.vy * speed * delta;
const ex = entity.x;
const ey = entity.y;
const speed = (!isFlying(entity) && isInWaterAt(map, ex, ey)) ? 0.5 : 1.0;
const destX = ex + entity.vx * speed * delta;
const destY = ey + entity.vy * speed * delta;
if ((entity.flags & EntityFlags.CanCollide) === 0) {
entity.x = destX;
entity.y = destY;
return;
}
if ((entity.flags & EntityFlags.CanCollide) === 0) {
entity.x = destX;
entity.y = destY;
return;
}
if (DEVELOPMENT && entity.type !== PONY_TYPE) {
console.error(`updatePosition: non-pony entity`);
}
if (DEVELOPMENT && entity.type !== PONY_TYPE) {
console.error(`updatePosition: non-pony entity`);
}
const flying = isInTheAir(entity);
const mask = flying ? 2 : 1;
const flying = isInTheAir(entity);
const mask = flying ? 2 : 1;
const srcX = ex * tileWidth;
const srcY = ey * tileHeight;
let dstX = destX * tileWidth;
let dstY = destY * tileHeight;
const srcX = ex * tileWidth;
const srcY = ey * tileHeight;
let dstX = destX * tileWidth;
let dstY = destY * tileHeight;
const x0 = Math.floor(srcX) | 0;
const y0 = Math.floor(srcY) | 0;
const x1 = Math.floor(dstX) | 0;
const y1 = Math.floor(dstY) | 0;
const x0 = Math.floor(srcX) | 0;
const y0 = Math.floor(srcY) | 0;
const x1 = Math.floor(dstX) | 0;
const y1 = Math.floor(dstY) | 0;
const minX = Math.min(x0, x1) | 0;
const maxX = Math.max(x0, x1) | 0;
const minY = Math.min(y0, y1) | 0;
const maxY = Math.max(y0, y1) | 0;
const minX = Math.min(x0, x1) | 0;
const maxX = Math.max(x0, x1) | 0;
const minY = Math.min(y0, y1) | 0;
const maxY = Math.max(y0, y1) | 0;
let x = x0 | 0;
let y = y0 | 0;
let x = x0 | 0;
let y = y0 | 0;
let actualX = x | 0;
let actualY = y | 0;
let actualX = x | 0;
let actualY = y | 0;
if (isColliding(actualX, actualY, mask, map)) {
if (!isOutsideMap(destX, destY, map)) {
entity.x = destX;
entity.y = destY;
}
if (isColliding(actualX, actualY, mask, map)) {
if (!isOutsideMap(destX, destY, map)) {
entity.x = destX;
entity.y = destY;
}
return;
}
return;
}
const a = (dstY - srcY) / (dstX - srcX);
const b = srcY - a * srcX;
const useGt = srcY < dstY;
const a = (dstY - srcY) / (dstX - srcX);
const b = srcY - a * srcX;
const useGt = srcY < dstY;
let stepXT = 0 | 0, stepYT = 0 | 0;
let stepXF = 0 | 0, stepYF = 0 | 0;
let ox = 0, oy = 0;
let stepXT = 0 | 0, stepYT = 0 | 0;
let stepXF = 0 | 0, stepYF = 0 | 0;
let ox = 0, oy = 0;
const shiftRight = srcX <= dstX;
const shiftLeft = srcX >= dstX;
const shiftUp = srcY >= dstY;
const shiftDown = srcY <= dstY;
const horizontalOrVertical = srcX === dstX || srcY === dstY;
const shiftRight = srcX <= dstX;
const shiftLeft = srcX >= dstX;
const shiftUp = srcY >= dstY;
const shiftDown = srcY <= dstY;
const horizontalOrVertical = srcX === dstX || srcY === dstY;
if (srcX < dstX) {
if (srcY < dstY) {
ox = 1;
oy = 1;
stepYT = 1 | 0;
stepXF = 1 | 0;
} else {
ox = 1;
stepYT = -1 | 0;
stepXF = 1 | 0;
}
} else if (srcX > dstX) {
if (srcY < dstY) {
oy = 1;
stepYT = 1 | 0;
stepXF = -1 | 0;
} else {
stepYT = -1 | 0;
stepXF = -1 | 0;
}
} else {
if (srcY < dstY) {
stepYF = stepYT = 1 | 0;
} else {
stepYF = stepYT = -1 | 0;
}
}
if (srcX < dstX) {
if (srcY < dstY) {
ox = 1;
oy = 1;
stepYT = 1 | 0;
stepXF = 1 | 0;
} else {
ox = 1;
stepYT = -1 | 0;
stepXF = 1 | 0;
}
} else if (srcX > dstX) {
if (srcY < dstY) {
oy = 1;
stepYT = 1 | 0;
stepXF = -1 | 0;
} else {
stepYT = -1 | 0;
stepXF = -1 | 0;
}
} else {
if (srcY < dstY) {
stepYF = stepYT = 1 | 0;
} else {
stepYF = stepYT = -1 | 0;
}
}
let steps = 1000;
let steps = 1000;
for (; steps; steps--) {
const fx = a * (x + ox) + b;
const fy = y + oy;
for (; steps; steps--) {
const fx = a * (x + ox) + b;
const fy = y + oy;
let tx = 0 | 0;
let ty = 0 | 0;
let tx = 0 | 0;
let ty = 0 | 0;
if (useGt ? (fx > fy) : (fx < fy)) {
tx = (tx + stepXT) | 0;
ty = (ty + stepYT) | 0;
} else {
tx = (tx + stepXF) | 0;
ty = (ty + stepYF) | 0;
}
if (useGt ? (fx > fy) : (fx < fy)) {
tx = (tx + stepXT) | 0;
ty = (ty + stepYT) | 0;
} else {
tx = (tx + stepXF) | 0;
ty = (ty + stepYF) | 0;
}
x = (x + tx) | 0;
y = (y + ty) | 0;
x = (x + tx) | 0;
y = (y + ty) | 0;
if (x < minX || x > maxX || y < minY || y > maxY) {
break;
}
if (x < minX || x > maxX || y < minY || y > maxY) {
break;
}
let actualNX = (actualX + tx) | 0;
let actualNY = (actualY + ty) | 0;
let collides = isColliding(actualNX, actualNY, mask, map);
let canMove = false;
let actualNX = (actualX + tx) | 0;
let actualNY = (actualY + ty) | 0;
let collides = isColliding(actualNX, actualNY, mask, map);
let canMove = false;
if (collides) {
if (tx !== 0) {
let canShiftUp = false;
let canShiftDown = false;
if (collides) {
if (tx !== 0) {
let canShiftUp = false;
let canShiftDown = false;
if (
shiftUp && (canShiftUp = !isColliding(actualX, actualY - 1, mask, map)) &&
!isColliding(actualNX, actualY - 1, mask, map)
) {
actualNX = actualX;
actualNY -= 1;
dstY -= 1;
collides = false;
} else if (
shiftDown && (canShiftDown = !isColliding(actualX, actualY + 1, mask, map)) &&
!isColliding(actualNX, actualY + 1, mask, map)
) {
actualNX = actualX;
actualNY += 1;
dstY += 1;
collides = false;
} else if (shiftUp && canShiftUp && !isColliding(actualNX, actualY - 2, mask, map)) {
actualNX = actualX;
actualNY -= 1;
dstY -= 1;
collides = false;
} else if (shiftDown && canShiftDown && !isColliding(actualNX, actualY + 2, mask, map)) {
actualNX = actualX;
actualNY += 1;
dstY += 1;
collides = false;
}
if (
shiftUp && (canShiftUp = !isColliding(actualX, actualY - 1, mask, map)) &&
!isColliding(actualNX, actualY - 1, mask, map)
) {
actualNX = actualX;
actualNY -= 1;
dstY -= 1;
collides = false;
} else if (
shiftDown && (canShiftDown = !isColliding(actualX, actualY + 1, mask, map)) &&
!isColliding(actualNX, actualY + 1, mask, map)
) {
actualNX = actualX;
actualNY += 1;
dstY += 1;
collides = false;
} else if (shiftUp && canShiftUp && !isColliding(actualNX, actualY - 2, mask, map)) {
actualNX = actualX;
actualNY -= 1;
dstY -= 1;
collides = false;
} else if (shiftDown && canShiftDown && !isColliding(actualNX, actualY + 2, mask, map)) {
actualNX = actualX;
actualNY += 1;
dstY += 1;
collides = false;
}
canMove = canShiftUp || canShiftDown;
} else {
let canShiftLeft = false;
let canShiftRight = false;
canMove = canShiftUp || canShiftDown;
} else {
let canShiftLeft = false;
let canShiftRight = false;
if (
shiftLeft && (canShiftLeft = !isColliding(actualX - 1, actualY, mask, map)) &&
!isColliding(actualX - 1, actualNY, mask, map)
) {
actualNX -= 1;
actualNY = actualY;
dstX -= 1;
collides = false;
} else if (
shiftRight && (canShiftRight = !isColliding(actualX + 1, actualY, mask, map)) &&
!isColliding(actualX + 1, actualNY, mask, map)
) {
actualNX += 1;
actualNY = actualY;
dstX += 1;
collides = false;
} else if (shiftLeft && canShiftLeft && !isColliding(actualX - 2, actualNY, mask, map)) {
actualNX -= 1;
actualNY = actualY;
dstX -= 1;
collides = false;
} else if (shiftRight && canShiftRight && !isColliding(actualX + 2, actualNY, mask, map)) {
actualNX += 1;
actualNY = actualY;
dstX += 1;
collides = false;
}
if (
shiftLeft && (canShiftLeft = !isColliding(actualX - 1, actualY, mask, map)) &&
!isColliding(actualX - 1, actualNY, mask, map)
) {
actualNX -= 1;
actualNY = actualY;
dstX -= 1;
collides = false;
} else if (
shiftRight && (canShiftRight = !isColliding(actualX + 1, actualY, mask, map)) &&
!isColliding(actualX + 1, actualNY, mask, map)
) {
actualNX += 1;
actualNY = actualY;
dstX += 1;
collides = false;
} else if (shiftLeft && canShiftLeft && !isColliding(actualX - 2, actualNY, mask, map)) {
actualNX -= 1;
actualNY = actualY;
dstX -= 1;
collides = false;
} else if (shiftRight && canShiftRight && !isColliding(actualX + 2, actualNY, mask, map)) {
actualNX += 1;
actualNY = actualY;
dstX += 1;
collides = false;
}
canMove = canShiftLeft || canShiftRight;
}
}
canMove = canShiftLeft || canShiftRight;
}
}
if (!collides) {
actualX = actualNX;
actualY = actualNY;
} else if (!canMove || horizontalOrVertical) {
break;
}
}
if (!collides) {
actualX = actualNX;
actualY = actualNY;
} else if (!canMove || horizontalOrVertical) {
break;
}
}
const epsilon = 1 / 1024;
const left = Math.min(x0, actualX);
const right = Math.max(x0 + 1, actualX + 1) - epsilon;
const top = Math.min(y0, actualY);
const bottom = Math.max(y0 + 1, actualY + 1) - epsilon;
const epsilon = 1 / 1024;
const left = Math.min(x0, actualX);
const right = Math.max(x0 + 1, actualX + 1) - epsilon;
const top = Math.min(y0, actualY);
const bottom = Math.max(y0 + 1, actualY + 1) - epsilon;
entity.x = toWorldX(clamp(dstX, left, right));
entity.y = toWorldY(clamp(dstY, top, bottom));
entity.x = toWorldX(clamp(dstX, left, right));
entity.y = toWorldY(clamp(dstY, top, bottom));
if (DEVELOPMENT && steps <= 0) {
console.error('Overflow collision steps');
}
if (DEVELOPMENT && steps <= 0) {
console.error('Overflow collision steps');
}
}
+357 -357
View File
@@ -2,239 +2,239 @@ import { isString } from 'lodash';
import { clamp } from './utils';
export const colorNames: { [key: string]: string | undefined } = {
aliceblue: 'f0f8ff',
antiquewhite: 'faebd7',
aqua: '00ffff',
aquamarine: '7fffd4',
azure: 'f0ffff',
beige: 'f5f5dc',
bisque: 'ffe4c4',
black: '000000',
blanchedalmond: 'ffebcd',
blue: '0000ff',
blueviolet: '8a2be2',
brown: 'a52a2a',
burlywood: 'deb887',
cadetblue: '5f9ea0',
chartreuse: '7fff00',
chocolate: 'd2691e',
coral: 'ff7f50',
cornflowerblue: '6495ed',
cornsilk: 'fff8dc',
crimson: 'dc143c',
cyan: '00ffff',
darkblue: '00008b',
darkcyan: '008b8b',
darkgoldenrod: 'b8860b',
darkgray: 'a9a9a9',
darkgreen: '006400',
darkkhaki: 'bdb76b',
darkmagenta: '8b008b',
darkolivegreen: '556b2f',
darkorange: 'ff8c00',
darkorchid: '9932cc',
darkred: '8b0000',
darksalmon: 'e9967a',
darkseagreen: '8fbc8f',
darkslateblue: '483d8b',
darkslategray: '2f4f4f',
darkturquoise: '00ced1',
darkviolet: '9400d3',
deeppink: 'ff1493',
deepskyblue: '00bfff',
dimgray: '696969',
dodgerblue: '1e90ff',
feldspar: 'd19275',
firebrick: 'b22222',
floralwhite: 'fffaf0',
forestgreen: '228b22',
fuchsia: 'ff00ff',
gainsboro: 'dcdcdc',
ghostwhite: 'f8f8ff',
gold: 'ffd700',
goldenrod: 'daa520',
gray: '808080',
green: '008000',
greenyellow: 'adff2f',
honeydew: 'f0fff0',
hotpink: 'ff69b4',
indianred: 'cd5c5c',
indigo: '4b0082',
ivory: 'fffff0',
khaki: 'f0e68c',
lavender: 'e6e6fa',
lavenderblush: 'fff0f5',
lawngreen: '7cfc00',
lemonchiffon: 'fffacd',
lightblue: 'add8e6',
lightcoral: 'f08080',
lightcyan: 'e0ffff',
lightgoldenrodyellow: 'fafad2',
lightgrey: 'd3d3d3',
lightgreen: '90ee90',
lightpink: 'ffb6c1',
lightsalmon: 'ffa07a',
lightseagreen: '20b2aa',
lightskyblue: '87cefa',
lightslateblue: '8470ff',
lightslategray: '778899',
lightsteelblue: 'b0c4de',
lightyellow: 'ffffe0',
lime: '00ff00',
limegreen: '32cd32',
linen: 'faf0e6',
magenta: 'ff00ff',
maroon: '800000',
mediumaquamarine: '66cdaa',
mediumblue: '0000cd',
mediumorchid: 'ba55d3',
mediumpurple: '9370d8',
mediumseagreen: '3cb371',
mediumslateblue: '7b68ee',
mediumspringgreen: '00fa9a',
mediumturquoise: '48d1cc',
mediumvioletred: 'c71585',
midnightblue: '191970',
mintcream: 'f5fffa',
mistyrose: 'ffe4e1',
moccasin: 'ffe4b5',
navajowhite: 'ffdead',
navy: '000080',
oldlace: 'fdf5e6',
olive: '808000',
olivedrab: '6b8e23',
orange: 'ffa500',
orangered: 'ff4500',
orchid: 'da70d6',
palegoldenrod: 'eee8aa',
palegreen: '98fb98',
paleturquoise: 'afeeee',
palevioletred: 'd87093',
papayawhip: 'ffefd5',
peachpuff: 'ffdab9',
peru: 'cd853f',
pink: 'ffc0cb',
plum: 'dda0dd',
powderblue: 'b0e0e6',
purple: '800080',
red: 'ff0000',
rosybrown: 'bc8f8f',
royalblue: '4169e1',
saddlebrown: '8b4513',
salmon: 'fa8072',
sandybrown: 'f4a460',
seagreen: '2e8b57',
seashell: 'fff5ee',
sienna: 'a0522d',
silver: 'c0c0c0',
skyblue: '87ceeb',
slateblue: '6a5acd',
slategray: '708090',
snow: 'fffafa',
springgreen: '00ff7f',
steelblue: '4682b4',
tan: 'd2b48c',
teal: '008080',
thistle: 'd8bfd8',
tomato: 'ff6347',
turquoise: '40e0d0',
violet: 'ee82ee',
violetred: 'd02090',
wheat: 'f5deb3',
white: 'ffffff',
whitesmoke: 'f5f5f5',
yellow: 'ffff00',
yellowgreen: '9acd32'
aliceblue: 'f0f8ff',
antiquewhite: 'faebd7',
aqua: '00ffff',
aquamarine: '7fffd4',
azure: 'f0ffff',
beige: 'f5f5dc',
bisque: 'ffe4c4',
black: '000000',
blanchedalmond: 'ffebcd',
blue: '0000ff',
blueviolet: '8a2be2',
brown: 'a52a2a',
burlywood: 'deb887',
cadetblue: '5f9ea0',
chartreuse: '7fff00',
chocolate: 'd2691e',
coral: 'ff7f50',
cornflowerblue: '6495ed',
cornsilk: 'fff8dc',
crimson: 'dc143c',
cyan: '00ffff',
darkblue: '00008b',
darkcyan: '008b8b',
darkgoldenrod: 'b8860b',
darkgray: 'a9a9a9',
darkgreen: '006400',
darkkhaki: 'bdb76b',
darkmagenta: '8b008b',
darkolivegreen: '556b2f',
darkorange: 'ff8c00',
darkorchid: '9932cc',
darkred: '8b0000',
darksalmon: 'e9967a',
darkseagreen: '8fbc8f',
darkslateblue: '483d8b',
darkslategray: '2f4f4f',
darkturquoise: '00ced1',
darkviolet: '9400d3',
deeppink: 'ff1493',
deepskyblue: '00bfff',
dimgray: '696969',
dodgerblue: '1e90ff',
feldspar: 'd19275',
firebrick: 'b22222',
floralwhite: 'fffaf0',
forestgreen: '228b22',
fuchsia: 'ff00ff',
gainsboro: 'dcdcdc',
ghostwhite: 'f8f8ff',
gold: 'ffd700',
goldenrod: 'daa520',
gray: '808080',
green: '008000',
greenyellow: 'adff2f',
honeydew: 'f0fff0',
hotpink: 'ff69b4',
indianred: 'cd5c5c',
indigo: '4b0082',
ivory: 'fffff0',
khaki: 'f0e68c',
lavender: 'e6e6fa',
lavenderblush: 'fff0f5',
lawngreen: '7cfc00',
lemonchiffon: 'fffacd',
lightblue: 'add8e6',
lightcoral: 'f08080',
lightcyan: 'e0ffff',
lightgoldenrodyellow: 'fafad2',
lightgrey: 'd3d3d3',
lightgreen: '90ee90',
lightpink: 'ffb6c1',
lightsalmon: 'ffa07a',
lightseagreen: '20b2aa',
lightskyblue: '87cefa',
lightslateblue: '8470ff',
lightslategray: '778899',
lightsteelblue: 'b0c4de',
lightyellow: 'ffffe0',
lime: '00ff00',
limegreen: '32cd32',
linen: 'faf0e6',
magenta: 'ff00ff',
maroon: '800000',
mediumaquamarine: '66cdaa',
mediumblue: '0000cd',
mediumorchid: 'ba55d3',
mediumpurple: '9370d8',
mediumseagreen: '3cb371',
mediumslateblue: '7b68ee',
mediumspringgreen: '00fa9a',
mediumturquoise: '48d1cc',
mediumvioletred: 'c71585',
midnightblue: '191970',
mintcream: 'f5fffa',
mistyrose: 'ffe4e1',
moccasin: 'ffe4b5',
navajowhite: 'ffdead',
navy: '000080',
oldlace: 'fdf5e6',
olive: '808000',
olivedrab: '6b8e23',
orange: 'ffa500',
orangered: 'ff4500',
orchid: 'da70d6',
palegoldenrod: 'eee8aa',
palegreen: '98fb98',
paleturquoise: 'afeeee',
palevioletred: 'd87093',
papayawhip: 'ffefd5',
peachpuff: 'ffdab9',
peru: 'cd853f',
pink: 'ffc0cb',
plum: 'dda0dd',
powderblue: 'b0e0e6',
purple: '800080',
red: 'ff0000',
rosybrown: 'bc8f8f',
royalblue: '4169e1',
saddlebrown: '8b4513',
salmon: 'fa8072',
sandybrown: 'f4a460',
seagreen: '2e8b57',
seashell: 'fff5ee',
sienna: 'a0522d',
silver: 'c0c0c0',
skyblue: '87ceeb',
slateblue: '6a5acd',
slategray: '708090',
snow: 'fffafa',
springgreen: '00ff7f',
steelblue: '4682b4',
tan: 'd2b48c',
teal: '008080',
thistle: 'd8bfd8',
tomato: 'ff6347',
turquoise: '40e0d0',
violet: 'ee82ee',
violetred: 'd02090',
wheat: 'f5deb3',
white: 'ffffff',
whitesmoke: 'f5f5f5',
yellow: 'ffff00',
yellowgreen: '9acd32'
};
const TRANSPARENT = 0x00000000 >>> 0;
const BLACK = 0x000000ff >>> 0;
export interface HSVA {
h: number;
s: number;
v: number;
a: number;
h: number;
s: number;
v: number;
a: number;
}
export interface RGB {
r: number;
g: number;
b: number;
r: number;
g: number;
b: number;
}
export interface RGBA extends RGB {
a: number;
a: number;
}
export function getR(color: number) {
return (color >> 24) & 0xff;
return (color >> 24) & 0xff;
}
export function getG(color: number) {
return (color >> 16) & 0xff;
return (color >> 16) & 0xff;
}
export function getB(color: number) {
return (color >> 8) & 0xff;
return (color >> 8) & 0xff;
}
export function getAlpha(color: number) {
return color & 0xff;
return color & 0xff;
}
export function withAlpha(color: number, alpha: number) {
return (color & 0xffffff00) | (alpha & 0xff);
return (color & 0xffffff00) | (alpha & 0xff);
}
export function withAlphaFloat(color: number, alpha: number) {
return (color & 0xffffff00) | ((alpha * 255) & 0xff);
return (color & 0xffffff00) | ((alpha * 255) & 0xff);
}
// to
export function colorToRGBA(color: number): RGBA {
return {
r: getR(color),
g: getG(color),
b: getB(color),
a: getAlpha(color),
};
return {
r: getR(color),
g: getG(color),
b: getB(color),
a: getAlpha(color),
};
}
export function colorToHSVA(color: number, h?: number): HSVA {
return rgb2hsv(getR(color), getG(color), getB(color), getAlpha(color) / 255, h);
return rgb2hsv(getR(color), getG(color), getB(color), getAlpha(color) / 255, h);
}
export function colorToCSS(color: number): string {
const alpha = getAlpha(color);
const alpha = getAlpha(color);
if (alpha === 0xff) {
return `#${colorToHexRGB(color)}`;
} else {
return `rgba(${getR(color)},${getG(color)},${getB(color)},${alpha / 255})`;
}
if (alpha === 0xff) {
return `#${colorToHexRGB(color)}`;
} else {
return `rgba(${getR(color)},${getG(color)},${getB(color)},${alpha / 255})`;
}
}
function toHex(value: number, length: number): string {
return value.toString(16).padStart(length, '0');
return value.toString(16).padStart(length, '0');
}
export function colorToHexRGB(color: number) {
return toHex(color >>> 8, 6);
return toHex(color >>> 8, 6);
}
export function colorToFloatArray(color: number): Float32Array {
const result = new Float32Array(4);
colorToExistingFloatArray(result, color);
return result;
const result = new Float32Array(4);
colorToExistingFloatArray(result, color);
return result;
}
export function colorToExistingFloatArray(array: Float32Array, color: number) {
array[0] = getR(color) / 255;
array[1] = getG(color) / 255;
array[2] = getB(color) / 255;
array[3] = getAlpha(color) / 255;
array[0] = getR(color) / 255;
array[1] = getG(color) / 255;
array[2] = getB(color) / 255;
array[3] = getAlpha(color) / 255;
}
const int8 = new Int8Array(4);
@@ -242,254 +242,254 @@ const int32 = new Int32Array(int8.buffer, 0, 1);
const float32 = new Float32Array(int8.buffer, 0, 1);
export function colorToFloat(color: number): number {
const int = (getAlpha(color) << 24) | (getB(color) << 16) | (getG(color) << 8) | getR(color);
int32[0] = int & 0xfeffffff;
return float32[0];
const int = (getAlpha(color) << 24) | (getB(color) << 16) | (getG(color) << 8) | getR(color);
int32[0] = int & 0xfeffffff;
return float32[0];
}
export function colorToFloatAlpha(color: number, alpha: number /* 0-1 */): number {
const int = (((getAlpha(color) * alpha) & 0xff) << 24) | (getB(color) << 16) | (getG(color) << 8) | getR(color);
int32[0] = int & 0xfeffffff;
return float32[0];
const int = (((getAlpha(color) * alpha) & 0xff) << 24) | (getB(color) << 16) | (getG(color) << 8) | getR(color);
int32[0] = int & 0xfeffffff;
return float32[0];
}
// from
export function colorFromRGBA(r: number, g: number, b: number, a: number /* 0-255 */) {
return ((r << 24) | (g << 16) | (b << 8) | a) >>> 0;
return ((r << 24) | (g << 16) | (b << 8) | a) >>> 0;
}
export function colorFromHSVA(h: number, s: number, v: number, a: number /* 0-1 */) {
const { r, g, b } = hsv2rgb(h, s, v);
return colorFromRGBA(r, g, b, a * 255);
const { r, g, b } = hsv2rgb(h, s, v);
return colorFromRGBA(r, g, b, a * 255);
}
export function colorFromHSVAObject({ h, s, v, a }: HSVA) {
return colorFromHSVA(h, s, v, a);
return colorFromHSVA(h, s, v, a);
}
// parse
export function parseColorFast(str: string): number {
if (!isString(str))
return TRANSPARENT;
if (!isString(str))
return TRANSPARENT;
const int = parseInt(str, 16);
const int = parseInt(str, 16);
if (str.length !== 6 || isNaN(int) || int < 0) {
return parseColorWithAlpha(str, 1);
} else {
return (((int << 8) | 0xff) >>> 0);
}
if (str.length !== 6 || isNaN(int) || int < 0) {
return parseColorWithAlpha(str, 1);
} else {
return (((int << 8) | 0xff) >>> 0);
}
}
export function parseColor(str: string): number {
if (!isString(str))
return TRANSPARENT;
if (!isString(str))
return TRANSPARENT;
str = str.trim().toLowerCase();
str = str.trim().toLowerCase();
if (str === '' || str === 'none' || str === 'transparent')
return TRANSPARENT;
if (str === '' || str === 'none' || str === 'transparent')
return TRANSPARENT;
str = colorNames[str] || str;
str = colorNames[str] || str;
const m = /(\d+)[ ,]+(\d+)[ ,]+(\d+)(?:[ ,]+(\d*\.?\d+))?/.exec(str);
const m = /(\d+)[ ,]+(\d+)[ ,]+(\d+)(?:[ ,]+(\d*\.?\d+))?/.exec(str);
if (m) {
return colorFromRGBA(
parseInt(m[1], 10),
parseInt(m[2], 10),
parseInt(m[3], 10),
m[4] ? parseFloat(m[4]) * 255 : 255);
}
if (m) {
return colorFromRGBA(
parseInt(m[1], 10),
parseInt(m[2], 10),
parseInt(m[3], 10),
m[4] ? parseFloat(m[4]) * 255 : 255);
}
const n = /[0-9a-f]+/i.exec(str);
const n = /[0-9a-f]+/i.exec(str);
if (n) {
const s = n[0];
if (n) {
const s = n[0];
if (s.length === 3) {
return colorFromRGBA(
parseInt(s.charAt(0), 16) * 0x11,
parseInt(s.charAt(1), 16) * 0x11,
parseInt(s.charAt(2), 16) * 0x11, 255);
} else {
return colorFromRGBA(
parseInt(s.substr(0, 2), 16),
parseInt(s.substr(2, 2), 16),
parseInt(s.substr(4, 2), 16),
s.length >= 8 ? parseInt(s.substr(6, 2), 16) : 255);
}
}
if (s.length === 3) {
return colorFromRGBA(
parseInt(s.charAt(0), 16) * 0x11,
parseInt(s.charAt(1), 16) * 0x11,
parseInt(s.charAt(2), 16) * 0x11, 255);
} else {
return colorFromRGBA(
parseInt(s.substr(0, 2), 16),
parseInt(s.substr(2, 2), 16),
parseInt(s.substr(4, 2), 16),
s.length >= 8 ? parseInt(s.substr(6, 2), 16) : 255);
}
}
return BLACK;
return BLACK;
}
export function parseColorWithAlpha(str: string, alpha: number /* 0-1 */): number {
return ((parseColor(str) & 0xffffff00) | ((alpha * 255) & 0xff)) >>> 0;
return ((parseColor(str) & 0xffffff00) | ((alpha * 255) & 0xff)) >>> 0;
}
// utils
export function toGrayscale(color: number) {
const c = Math.round(clamp(getR(color) * 0.2126 + getG(color) * 0.7152 + getB(color) * 0.0722, 0, 255)) | 0;
const a = getAlpha(color);
return colorFromRGBA(c, c, c, a);
const c = Math.round(clamp(getR(color) * 0.2126 + getG(color) * 0.7152 + getB(color) * 0.0722, 0, 255)) | 0;
const a = getAlpha(color);
return colorFromRGBA(c, c, c, a);
}
export function makeTransparent(color: number, factor: number /* 0-1 */): number {
return ((color & 0xffffff00) | ((getAlpha(color) * factor) & 0xff)) >>> 0;
return ((color & 0xffffff00) | ((getAlpha(color) * factor) & 0xff)) >>> 0;
}
export function multiplyColor(color: number, factor: number /* 0-1 */): number {
return colorFromRGBA(
clamp(getR(color) * factor, 0, 255),
clamp(getG(color) * factor, 0, 255),
clamp(getB(color) * factor, 0, 255),
getAlpha(color)
);
return colorFromRGBA(
clamp(getR(color) * factor, 0, 255),
clamp(getG(color) * factor, 0, 255),
clamp(getB(color) * factor, 0, 255),
getAlpha(color)
);
}
export function lerpColors(a: number, b: number, factor: number): number {
const f = factor;
const t = 1 - factor;
const f = factor;
const t = 1 - factor;
return colorFromRGBA(
getR(a) * t + getR(b) * f,
getG(a) * t + getG(b) * f,
getB(a) * t + getB(b) * f,
getAlpha(a) * t + getAlpha(b) * f
);
return colorFromRGBA(
getR(a) * t + getR(b) * f,
getG(a) * t + getG(b) * f,
getB(a) * t + getB(b) * f,
getAlpha(a) * t + getAlpha(b) * f
);
}
/// r, g, b = <0, 255>, a = <0, 1>
export function rgb2hsv(r: number, g: number, b: number, a: number /* 0-1 */, h = 0): HSVA {
r = r / 255;
g = g / 255;
b = b / 255;
h = h / 360;
r = r / 255;
g = g / 255;
b = b / 255;
h = h / 360;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const v = max;
const d = max - min;
const s = max === 0 ? 0 : d / max;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const v = max;
const d = max - min;
const s = max === 0 ? 0 : d / max;
if (max !== min) {
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
if (max !== min) {
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
return { h: h * 360, s, v, a };
return { h: h * 360, s, v, a };
}
/// h = <0, 360>; s, v = <0, 1>
export function hsv2rgb(h: number, s: number, v: number): RGB {
h = Math.max(0, Math.min(360, h === 360 ? 0 : h));
s = Math.max(0, Math.min(1, s));
v = Math.max(0, Math.min(1, v));
h = Math.max(0, Math.min(360, h === 360 ? 0 : h));
s = Math.max(0, Math.min(1, s));
v = Math.max(0, Math.min(1, v));
let r = v;
let g = v;
let b = v;
let r = v;
let g = v;
let b = v;
if (s !== 0) {
h /= 60;
if (s !== 0) {
h /= 60;
const i = Math.floor(h);
const f = h - i;
const p = v * (1 - s);
const q = v * (1 - s * f);
const t = v * (1 - s * (1 - f));
const i = Math.floor(h);
const f = h - i;
const p = v * (1 - s);
const q = v * (1 - s * f);
const t = v * (1 - s * (1 - f));
switch (i) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
default:
r = v;
g = p;
b = q;
}
}
switch (i) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
default:
r = v;
g = p;
b = q;
}
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255),
};
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255),
};
}
export function h2rgb(h: number): RGB {
h /= 60;
let r = 0, g = 0, b = 0;
const i = Math.floor(h);
const f = h - i;
const q = (1 - f);
const t = (1 - (1 - f));
h /= 60;
let r = 0, g = 0, b = 0;
const i = Math.floor(h);
const f = h - i;
const q = (1 - f);
const t = (1 - (1 - f));
switch (i) {
case 0:
r = 1;
g = t;
break;
case 1:
r = q;
g = 1;
break;
case 2:
g = 1;
b = t;
break;
case 3:
g = q;
b = 1;
break;
case 4:
r = t;
b = 1;
break;
default:
r = 1;
b = q;
}
switch (i) {
case 0:
r = 1;
g = t;
break;
case 1:
r = q;
g = 1;
break;
case 2:
g = 1;
b = t;
break;
case 3:
g = q;
b = 1;
break;
case 4:
r = t;
b = 1;
break;
default:
r = 1;
b = q;
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255)
};
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255)
};
}
+81 -81
View File
@@ -65,109 +65,109 @@ export const ENTITY_ITEM_BG = '#dc76bc';
export const MAGIC_ALPHA = 150;
export function updateActionColor(color: string) {
if (DEVELOPMENT) {
ACTION_EXPRESSION_BG = color;
}
if (DEVELOPMENT) {
ACTION_EXPRESSION_BG = color;
}
}
// utils
export function getMessageColor(type: MessageType): number {
switch (type) {
case MessageType.Chat: return WHITE;
case MessageType.System: return SYSTEM_COLOR;
case MessageType.Admin: return ADMIN_COLOR;
case MessageType.Mod: return MOD_COLOR;
case MessageType.Party: return PARTY_COLOR;
case MessageType.Thinking: return THINKING_COLOR;
case MessageType.PartyThinking: return PARTY_THINKING_COLOR;
case MessageType.Supporter1: return SUPPORTER1_COLOR;
case MessageType.Supporter2: return SUPPORTER2_COLOR;
case MessageType.Supporter3: return SUPPORTER3_COLOR;
case MessageType.Whisper:
case MessageType.WhisperTo:
return WHISPER_COLOR;
case MessageType.Announcement:
case MessageType.PartyAnnouncement:
case MessageType.WhisperAnnouncement:
case MessageType.WhisperToAnnouncement:
return ANNOUNCEMENT_COLOR;
case MessageType.Dismiss: return TRANSPARENT;
default:
return invalidEnumReturn(type, WHITE);
}
switch (type) {
case MessageType.Chat: return WHITE;
case MessageType.System: return SYSTEM_COLOR;
case MessageType.Admin: return ADMIN_COLOR;
case MessageType.Mod: return MOD_COLOR;
case MessageType.Party: return PARTY_COLOR;
case MessageType.Thinking: return THINKING_COLOR;
case MessageType.PartyThinking: return PARTY_THINKING_COLOR;
case MessageType.Supporter1: return SUPPORTER1_COLOR;
case MessageType.Supporter2: return SUPPORTER2_COLOR;
case MessageType.Supporter3: return SUPPORTER3_COLOR;
case MessageType.Whisper:
case MessageType.WhisperTo:
return WHISPER_COLOR;
case MessageType.Announcement:
case MessageType.PartyAnnouncement:
case MessageType.WhisperAnnouncement:
case MessageType.WhisperToAnnouncement:
return ANNOUNCEMENT_COLOR;
case MessageType.Dismiss: return TRANSPARENT;
default:
return invalidEnumReturn(type, WHITE);
}
}
export function fillToOutline(color: string | undefined): string | undefined {
return color ? colorToHexRGB(fillToOutlineColor(parseColorFast(color))) : undefined;
return color ? colorToHexRGB(fillToOutlineColor(parseColorFast(color))) : undefined;
}
export function fillToOutlineWithDarken(color: string | undefined): string | undefined {
return color ? colorToHexRGB(darkenForOutline(fillToOutlineColor(parseColorFast(color)))) : undefined;
return color ? colorToHexRGB(darkenForOutline(fillToOutlineColor(parseColorFast(color)))) : undefined;
}
export function fillToOutlineColor(color: number): number {
const { h, s, v, a } = colorToHSVA(color);
return colorFromHSVA(h, Math.min(s * 1.3, 1), v * 0.7, a);
const { h, s, v, a } = colorToHSVA(color);
return colorFromHSVA(h, Math.min(s * 1.3, 1), v * 0.7, a);
}
const LIGHT_BLUSH = 0xff89aeff;
const DARK_BLUSH = 0xc90040ff;
export function blushColor(coat: number): number {
const { h, s, v } = colorToHSVA(coat);
const { h, s, v } = colorToHSVA(coat);
if (
(h < 15 && s > 0.2 && s < 0.7 && v > 0.85) ||
(h > 15 && h < 50 && s > 0.2 && v > 0.85) ||
(h > 280 && s > 0.2 && s < 0.7 && v > 0.85)
) {
return DARK_BLUSH;
} else {
return LIGHT_BLUSH;
}
if (
(h < 15 && s > 0.2 && s < 0.7 && v > 0.85) ||
(h > 15 && h < 50 && s > 0.2 && v > 0.85) ||
(h > 280 && s > 0.2 && s < 0.7 && v > 0.85)
) {
return DARK_BLUSH;
} else {
return LIGHT_BLUSH;
}
}
export function getTileColor(tile: TileType, season: Season) {
switch (tile) {
case TileType.Dirt:
case TileType.ElevatedDirt:
if (season === Season.Autumn) {
return 0xedd29eff;
} else if (season === Season.Winter) {
return 0xd9c2a1ff;
} else {
return 0xf5d99bff;
}
case TileType.Water:
case TileType.WalkableWater:
case TileType.Boat:
return 0x6dbdecff;
case TileType.Grass:
if (season === Season.Autumn) {
return 0xddcf71ff;
} else if (season === Season.Winter) {
return 0xe1ebf8ff;
} else {
return 0x7cc991ff;
}
case TileType.Ice:
case TileType.WalkableIce:
return 0xc1dcecff;
case TileType.SnowOnIce:
return 0xe4eefbff;
case TileType.Wood:
return 0xd7ac7eff;
case TileType.Stone:
return 0x9da6abff;
case TileType.Stone2:
return 0xa0a691ff;
case TileType.None:
case TileType.WallH:
case TileType.WallV:
return BLACK;
default:
invalidEnum(tile);
return BLACK;
}
switch (tile) {
case TileType.Dirt:
case TileType.ElevatedDirt:
if (season === Season.Autumn) {
return 0xedd29eff;
} else if (season === Season.Winter) {
return 0xd9c2a1ff;
} else {
return 0xf5d99bff;
}
case TileType.Water:
case TileType.WalkableWater:
case TileType.Boat:
return 0x6dbdecff;
case TileType.Grass:
if (season === Season.Autumn) {
return 0xddcf71ff;
} else if (season === Season.Winter) {
return 0xe1ebf8ff;
} else {
return 0x7cc991ff;
}
case TileType.Ice:
case TileType.WalkableIce:
return 0xc1dcecff;
case TileType.SnowOnIce:
return 0xe4eefbff;
case TileType.Wood:
return 0xd7ac7eff;
case TileType.Stone:
return 0x9da6abff;
case TileType.Stone2:
return 0xa0a691ff;
case TileType.None:
case TileType.WallH:
case TileType.WallV:
return BLACK;
default:
invalidEnum(tile);
return BLACK;
}
}
+115 -115
View File
@@ -3,155 +3,155 @@ import { bitWriter, bitReader } from './bitUtils';
import { REGION_SIZE } from './constants';
function getBitsForNumber(value: number) {
let bits = 0;
let max = value - 1;
let bits = 0;
let max = value - 1;
while (max > 0) {
bits++;
max >>= 1;
}
while (max > 0) {
bits++;
max >>= 1;
}
return bits;
return bits;
}
export function compressTiles(tiles: Uint8Array): Uint8Array {
const types: number[] = [];
const types: number[] = [];
for (let i = 0; i < tiles.length; i++) {
const tile = tiles[i];
for (let i = 0; i < tiles.length; i++) {
const tile = tiles[i];
if (types.indexOf(tile) === -1) {
types.push(tile);
}
}
if (types.indexOf(tile) === -1) {
types.push(tile);
}
}
const bitsPerTile = getBitsForNumber(types.length);
const bitsPerRun = 4;
const bitsPerTile = getBitsForNumber(types.length);
const bitsPerRun = 4;
return bitWriter(write => {
write(types.length, 8);
return bitWriter(write => {
write(types.length, 8);
for (const type of types) {
write(type, 8);
}
for (const type of types) {
write(type, 8);
}
if (types.length > 1) {
for (let i = 0; i < tiles.length; i++) {
const value = tiles[i];
let count = 1;
if (types.length > 1) {
for (let i = 0; i < tiles.length; i++) {
const value = tiles[i];
let count = 1;
if (i === (tiles.length - 1)) {
write(count | 0b1000, bitsPerRun);
write(types.indexOf(value), bitsPerTile);
} else {
i++;
if (i === (tiles.length - 1)) {
write(count | 0b1000, bitsPerRun);
write(types.indexOf(value), bitsPerTile);
} else {
i++;
if (value === tiles[i]) {
while (i < tiles.length && count < 0b111 && tiles[i] === value) {
i++;
count++;
}
if (value === tiles[i]) {
while (i < tiles.length && count < 0b111 && tiles[i] === value) {
i++;
count++;
}
i--;
i--;
write(count, bitsPerRun);
write(types.indexOf(value), bitsPerTile);
} else {
let last = tiles[i];
let last2 = last;
let pushLast = true;
const values = [value];
count++;
write(count, bitsPerRun);
write(types.indexOf(value), bitsPerTile);
} else {
let last = tiles[i];
let last2 = last;
let pushLast = true;
const values = [value];
count++;
for (i++; i < tiles.length; i++) {
last2 = tiles[i];
for (i++; i < tiles.length; i++) {
last2 = tiles[i];
if (last2 === last) {
i -= 2;
count--;
pushLast = false;
break;
} else if (count === 0b111) {
i -= 1;
break;
} else {
values.push(last);
count++;
last = last2;
}
}
if (last2 === last) {
i -= 2;
count--;
pushLast = false;
break;
} else if (count === 0b111) {
i -= 1;
break;
} else {
values.push(last);
count++;
last = last2;
}
}
write(count | 0b1000, bitsPerRun);
write(count | 0b1000, bitsPerRun);
for (const v of values) {
write(types.indexOf(v), bitsPerTile);
}
for (const v of values) {
write(types.indexOf(v), bitsPerTile);
}
if (pushLast) {
write(types.indexOf(last), bitsPerTile);
}
}
}
}
}
});
if (pushLast) {
write(types.indexOf(last), bitsPerTile);
}
}
}
}
}
});
}
export function decompressTiles(data: Uint8Array): Uint8Array {
const size = REGION_SIZE * REGION_SIZE;
const result = new Uint8Array(size);
const read = bitReader(data);
const typesCount = read(8);
const types: number[] = [];
const size = REGION_SIZE * REGION_SIZE;
const result = new Uint8Array(size);
const read = bitReader(data);
const typesCount = read(8);
const types: number[] = [];
for (let i = 0; i < typesCount; i++) {
types.push(read(8));
}
for (let i = 0; i < typesCount; i++) {
types.push(read(8));
}
if (types.length === 1) {
result.fill(types[0]);
} else {
const bitsPerTile = getBitsForNumber(typesCount);
const bitsPerRun = 4;
if (types.length === 1) {
result.fill(types[0]);
} else {
const bitsPerTile = getBitsForNumber(typesCount);
const bitsPerRun = 4;
for (let i = 0; i < size;) {
const value = read(bitsPerRun);
for (let i = 0; i < size;) {
const value = read(bitsPerRun);
if ((value & 0b1000) === 0) {
const count = value;
const entry = read(bitsPerTile);
if ((value & 0b1000) === 0) {
const count = value;
const entry = read(bitsPerTile);
for (let j = 0; j < count; j++) {
result[i] = types[entry];
i++;
}
} else {
const count = value & 0b0111;
for (let j = 0; j < count; j++) {
result[i] = types[entry];
i++;
}
} else {
const count = value & 0b0111;
for (let j = 0; j < count; j++) {
result[i] = types[read(bitsPerTile)];
i++;
}
}
}
}
for (let j = 0; j < count; j++) {
result[i] = types[read(bitsPerTile)];
i++;
}
}
}
}
return result;
return result;
}
export function deserializeTiles(tiles: string) {
const decodedTiles = toByteArray(tiles);
const result: number[] = [];
const decodedTiles = toByteArray(tiles);
const result: number[] = [];
for (let i = 0; i < decodedTiles.length; i += 2) {
let count = decodedTiles[i];
const tile = decodedTiles[i + 1];
for (let i = 0; i < decodedTiles.length; i += 2) {
let count = decodedTiles[i];
const tile = decodedTiles[i + 1];
while (count > 0) {
result.push(tile);
count--;
}
}
while (count > 0) {
result.push(tile);
count--;
}
}
return result;
return result;
}
+368 -368
View File
@@ -9,176 +9,176 @@ import { getColorCount } from '../client/spriteUtils';
import * as sprites from '../generated/sprites';
import { parseColorFast, colorToHexRGB } from './color';
import {
SLEEVED_ACCESSORIES, frontHooves, mergedFacialHair, mergedBackAccessories, mergedManes,
mergedBackManes, mergedExtraAccessories, mergedHeadAccessories
SLEEVED_ACCESSORIES, frontHooves, mergedFacialHair, mergedBackAccessories, mergedManes,
mergedBackManes, mergedExtraAccessories, mergedHeadAccessories
} from '../client/ponyUtils';
import { CM_SIZE } from './constants';
export const VERSION = 3;
interface FieldDefinition<T> {
name: keyof PonyInfo;
default?: T;
omit?: (info: PonyInfoBase<any, SpriteSet<any>>) => boolean;
dontSave?: boolean;
name: keyof PonyInfo;
default?: T;
omit?: (info: PonyInfoBase<any, SpriteSet<any>>) => boolean;
dontSave?: boolean;
}
interface SetDefinition extends FieldDefinition<PrecompressedSet> {
preserveOnZero?: boolean;
sets: ColorExtraSet[];
minColors?: number;
// defaultLockFills?: boolean[];
// defaultLockOutlines?: boolean[];
preserveOnZero?: boolean;
sets: ColorExtraSet[];
minColors?: number;
// defaultLockFills?: boolean[];
// defaultLockOutlines?: boolean[];
}
export interface PrecompressedSet {
type: number;
pattern: number;
colors: number;
fillLocks: number;
fills: number[];
outlineLocks: number;
outlines: number[];
type: number;
pattern: number;
colors: number;
fillLocks: number;
fills: number[];
outlineLocks: number;
outlines: number[];
}
export interface Precompressed {
version: number;
colors: number[];
setFields: (PrecompressedSet | undefined)[];
colorFields: number[];
numberFields: number[];
booleanFields: boolean[];
cm: number[];
version: number;
colors: number[];
setFields: (PrecompressedSet | undefined)[];
colorFields: number[];
numberFields: number[];
booleanFields: boolean[];
cm: number[];
}
const identity = <T>(x: T) => x;
const not = <T>(x: T) => !x;
function emptyOrUnlocked<T>(set: SpriteSet<T> | undefined): boolean {
return !set || !set.type || !set.lockFills || set.lockFills.every(x => !x);
return !set || !set.type || !set.lockFills || set.lockFills.every(x => !x);
}
function emptyOrZeroLocked<T>(set: SpriteSet<T> | undefined, customOutlines: boolean): boolean {
return !set || (
set.type === 0 && set.pattern === 0 && set.lockFills !== undefined && set.lockFills[0] === true &&
(!customOutlines || (set.lockOutlines !== undefined && set.lockOutlines[0] === true)));
return !set || (
set.type === 0 && set.pattern === 0 && set.lockFills !== undefined && set.lockFills[0] === true &&
(!customOutlines || (set.lockOutlines !== undefined && set.lockOutlines[0] === true)));
}
function empty<T>(set: SpriteSet<T> | undefined): boolean {
return !set || !set.type;
return !set || !set.type;
}
function omitMane(info: PonyInfoNumber) {
return empty(info.mane) && emptyOrUnlocked(info.backMane)
&& emptyOrUnlocked(info.tail) && emptyOrUnlocked(info.facialHair);
return empty(info.mane) && emptyOrUnlocked(info.backMane)
&& emptyOrUnlocked(info.tail) && emptyOrUnlocked(info.facialHair);
}
function omitHead(info: PonyInfoNumber): boolean {
return emptyOrZeroLocked(info.head, !!info.customOutlines);
return emptyOrZeroLocked(info.head, !!info.customOutlines);
}
function omitSleeves(info: PonyInfoNumber) {
return !info.chestAccessory || !includes(SLEEVED_ACCESSORIES, toInt(info.chestAccessory.type));
return !info.chestAccessory || !includes(SLEEVED_ACCESSORIES, toInt(info.chestAccessory.type));
}
function omitFrontHooves(info: PonyInfoNumber) {
return empty(info.frontHooves) && emptyOrUnlocked(info.backHooves);
return empty(info.frontHooves) && emptyOrUnlocked(info.backHooves);
}
function readTimes(read: ReadBits, count: number, bitsPerItem: number): number[] {
const result: number[] = [];
const result: number[] = [];
for (let i = 0; i < count; i++) {
result[i] = read(bitsPerItem);
}
for (let i = 0; i < count; i++) {
result[i] = read(bitsPerItem);
}
return result;
return result;
}
// NOTE: do not reorder or remove
const setFields: SetDefinition[] = [
{ name: 'extraAccessory', sets: mergedExtraAccessories!, preserveOnZero: true },
{ name: 'nose', sets: sprites.noses[0]!, preserveOnZero: true },
{ name: 'ears', sets: sprites.ears!, preserveOnZero: true },
{ name: 'mane', sets: mergedManes!, preserveOnZero: true, minColors: 1, omit: omitMane },
{ name: 'backMane', sets: mergedBackManes! },
{ name: 'tail', sets: sprites.tails[0]! },
{ name: 'horn', sets: sprites.horns! },
{ name: 'wings', sets: sprites.wings[0]! },
{ name: 'frontHooves', sets: frontHooves[1]!, preserveOnZero: true, minColors: 1, omit: omitFrontHooves },
{ name: 'backHooves', sets: sprites.backLegHooves[1]! },
{ name: 'facialHair', sets: mergedFacialHair! },
{ name: 'headAccessory', sets: mergedHeadAccessories },
{ name: 'earAccessory', sets: sprites.earAccessories! },
{ name: 'faceAccessory', sets: sprites.faceAccessories! },
{ name: 'neckAccessory', sets: sprites.neckAccessories[1]! },
{ name: 'frontLegAccessory', sets: sprites.frontLegAccessories[1]! },
{ name: 'backLegAccessory', sets: sprites.backLegAccessories[1]!, omit: info => !!info.lockBackLegAccessory },
{ name: 'backAccessory', sets: mergedBackAccessories! },
{ name: 'waistAccessory', sets: sprites.waistAccessories[1]! },
{ name: 'chestAccessory', sets: sprites.chestAccessories[1]! },
{ name: 'sleeveAccessory', sets: sprites.frontLegSleeves[1]!, preserveOnZero: true, omit: omitSleeves },
{ name: 'head', sets: sprites.head0[1]!, preserveOnZero: true, omit: omitHead },
{
name: 'frontLegAccessoryRight',
sets: sprites.frontLegAccessories[1]!,
omit: info => !info.unlockFrontLegAccessory,
},
{
name: 'backLegAccessoryRight',
sets: sprites.backLegAccessories[1]!,
omit: info => !info.unlockBackLegAccessory || !!info.lockBackLegAccessory,
},
{ name: 'extraAccessory', sets: mergedExtraAccessories!, preserveOnZero: true },
{ name: 'nose', sets: sprites.noses[0]!, preserveOnZero: true },
{ name: 'ears', sets: sprites.ears!, preserveOnZero: true },
{ name: 'mane', sets: mergedManes!, preserveOnZero: true, minColors: 1, omit: omitMane },
{ name: 'backMane', sets: mergedBackManes! },
{ name: 'tail', sets: sprites.tails[0]! },
{ name: 'horn', sets: sprites.horns! },
{ name: 'wings', sets: sprites.wings[0]! },
{ name: 'frontHooves', sets: frontHooves[1]!, preserveOnZero: true, minColors: 1, omit: omitFrontHooves },
{ name: 'backHooves', sets: sprites.backLegHooves[1]! },
{ name: 'facialHair', sets: mergedFacialHair! },
{ name: 'headAccessory', sets: mergedHeadAccessories },
{ name: 'earAccessory', sets: sprites.earAccessories! },
{ name: 'faceAccessory', sets: sprites.faceAccessories! },
{ name: 'neckAccessory', sets: sprites.neckAccessories[1]! },
{ name: 'frontLegAccessory', sets: sprites.frontLegAccessories[1]! },
{ name: 'backLegAccessory', sets: sprites.backLegAccessories[1]!, omit: info => !!info.lockBackLegAccessory },
{ name: 'backAccessory', sets: mergedBackAccessories! },
{ name: 'waistAccessory', sets: sprites.waistAccessories[1]! },
{ name: 'chestAccessory', sets: sprites.chestAccessories[1]! },
{ name: 'sleeveAccessory', sets: sprites.frontLegSleeves[1]!, preserveOnZero: true, omit: omitSleeves },
{ name: 'head', sets: sprites.head0[1]!, preserveOnZero: true, omit: omitHead },
{
name: 'frontLegAccessoryRight',
sets: sprites.frontLegAccessories[1]!,
omit: info => !info.unlockFrontLegAccessory,
},
{
name: 'backLegAccessoryRight',
sets: sprites.backLegAccessories[1]!,
omit: info => !info.unlockBackLegAccessory || !!info.lockBackLegAccessory,
},
];
const booleanFields: FieldDefinition<boolean>[] = [
{ name: 'customOutlines' },
{ name: 'lockEyes' },
{ name: 'lockEyeColor' },
{ name: 'lockCoatOutline', omit: info => !info.customOutlines },
{
name: 'lockBackLegAccessory', omit: info =>
empty(info.frontLegAccessory) && empty(info.backLegAccessory) &&
empty(info.frontLegAccessoryRight) && empty(info.backLegAccessoryRight)
},
{ name: 'eyeshadow' },
{ name: 'cmFlip', omit: info => info.cm === undefined || info.cm.every(not) },
{ name: 'unlockEyeWhites' },
{ name: 'freeOutlines' },
{ name: 'unlockFrontLegAccessory' },
{ name: 'unlockBackLegAccessory', omit: info => !!info.lockBackLegAccessory },
{ name: 'unlockEyelashColor' },
{ name: 'darkenLockedOutlines', omit: info => !info.freeOutlines },
{ name: 'customOutlines' },
{ name: 'lockEyes' },
{ name: 'lockEyeColor' },
{ name: 'lockCoatOutline', omit: info => !info.customOutlines },
{
name: 'lockBackLegAccessory', omit: info =>
empty(info.frontLegAccessory) && empty(info.backLegAccessory) &&
empty(info.frontLegAccessoryRight) && empty(info.backLegAccessoryRight)
},
{ name: 'eyeshadow' },
{ name: 'cmFlip', omit: info => info.cm === undefined || info.cm.every(not) },
{ name: 'unlockEyeWhites' },
{ name: 'freeOutlines' },
{ name: 'unlockFrontLegAccessory' },
{ name: 'unlockBackLegAccessory', omit: info => !!info.lockBackLegAccessory },
{ name: 'unlockEyelashColor' },
{ name: 'darkenLockedOutlines', omit: info => !info.freeOutlines },
];
const numberFields: FieldDefinition<number>[] = [
{ name: 'eyelashes' },
{ name: 'eyeOpennessRight' },
{ name: 'eyeOpennessLeft', omit: info => !!info.lockEyes },
{ name: 'fangs' },
{ name: 'muzzle' },
{ name: 'freckles', dontSave: true }, // TODO: remove
{ name: 'eyelashes' },
{ name: 'eyeOpennessRight' },
{ name: 'eyeOpennessLeft', omit: info => !!info.lockEyes },
{ name: 'fangs' },
{ name: 'muzzle' },
{ name: 'freckles', dontSave: true }, // TODO: remove
];
const colorFields: FieldDefinition<number>[] = [
{ name: 'coatFill' },
{ name: 'coatOutline', omit: info => !info.customOutlines || !!info.lockCoatOutline },
{ name: 'eyeColorRight' },
{ name: 'eyeColorLeft', omit: info => !!info.lockEyeColor },
{ name: 'eyeWhites', default: WHITE },
{ name: 'eyeshadowColor', omit: info => !info.eyeshadow },
{ name: 'frecklesColor', omit: info => !info.freckles, dontSave: true }, // TODO: remove
{ name: 'eyeWhitesLeft', default: WHITE, omit: info => !info.unlockEyeWhites },
{ name: 'eyelashColor' },
{ name: 'eyelashColorLeft', omit: info => !info.unlockEyelashColor },
{ name: 'magicColor', default: WHITE },
{ name: 'coatFill' },
{ name: 'coatOutline', omit: info => !info.customOutlines || !!info.lockCoatOutline },
{ name: 'eyeColorRight' },
{ name: 'eyeColorLeft', omit: info => !!info.lockEyeColor },
{ name: 'eyeWhites', default: WHITE },
{ name: 'eyeshadowColor', omit: info => !info.eyeshadow },
{ name: 'frecklesColor', omit: info => !info.freckles, dontSave: true }, // TODO: remove
{ name: 'eyeWhitesLeft', default: WHITE, omit: info => !info.unlockEyeWhites },
{ name: 'eyelashColor' },
{ name: 'eyelashColorLeft', omit: info => !info.unlockEyelashColor },
{ name: 'magicColor', default: WHITE },
];
const omittableFields: FieldDefinition<any>[] = [
...setFields,
...booleanFields,
...numberFields,
...colorFields,
...setFields,
...booleanFields,
...numberFields,
...colorFields,
].filter(f => !!f.omit);
const VERSION_BITS = 6; // max 63
@@ -192,106 +192,106 @@ const NUMBERS_BITS = 6; // max 63
/* istanbul ignore next */
if (DEVELOPMENT) {
(function () {
function verifyFields(obj: any, lengthBits: number, defs: FieldDefinition<any>[], verify: (field: any) => boolean) {
const missing = Object.keys(obj)
.filter(key => verify(obj[key]))
.filter(key => defs.every(d => d.name !== key));
(function () {
function verifyFields(obj: any, lengthBits: number, defs: FieldDefinition<any>[], verify: (field: any) => boolean) {
const missing = Object.keys(obj)
.filter(key => verify(obj[key]))
.filter(key => defs.every(d => d.name !== key));
const unnecessary = defs
.filter(({ name }) => !verify(obj[name]));
const unnecessary = defs
.filter(({ name }) => !verify(obj[name]));
if (missing.length || unnecessary.length) {
throw new Error(`Incorrect fields (${missing} / ${unnecessary})`);
}
if (missing.length || unnecessary.length) {
throw new Error(`Incorrect fields (${missing} / ${unnecessary})`);
}
if (lengthBits < countBits(defs.length)) {
throw new Error(`Incorrect field length bits (${lengthBits}/${countBits(defs.length)})`);
}
}
if (lengthBits < countBits(defs.length)) {
throw new Error(`Incorrect field length bits (${lengthBits}/${countBits(defs.length)})`);
}
}
const defaultPony = createBasePony();
verifyFields(defaultPony, SET_FIELDS_LENGTH_BITS, setFields, f => f.type !== undefined);
verifyFields(defaultPony, COLOR_FIELDS_LENGTH_BITS, colorFields, isString);
verifyFields(defaultPony, NUMBER_FIELDS_LENGTH_BITS, numberFields, isNumber);
verifyFields(defaultPony, BOOLEAN_FIELDS_LENGTH_BITS, booleanFields, isBoolean);
const defaultPony = createBasePony();
verifyFields(defaultPony, SET_FIELDS_LENGTH_BITS, setFields, f => f.type !== undefined);
verifyFields(defaultPony, COLOR_FIELDS_LENGTH_BITS, colorFields, isString);
verifyFields(defaultPony, NUMBER_FIELDS_LENGTH_BITS, numberFields, isNumber);
verifyFields(defaultPony, BOOLEAN_FIELDS_LENGTH_BITS, booleanFields, isBoolean);
if (setFields.some(f => !f.sets)) {
throw new Error(`Undefined set in set field (${setFields.find(f => !f.sets)!.name})`);
}
})();
if (setFields.some(f => !f.sets)) {
throw new Error(`Undefined set in set field (${setFields.find(f => !f.sets)!.name})`);
}
})();
}
function trimRight<T>(items: T[]) {
const index = findLastIndex(items, x => !!x);
return (index !== (items.length - 1)) ? items.slice(0, index + 1) : items;
const index = findLastIndex(items, x => !!x);
return (index !== (items.length - 1)) ? items.slice(0, index + 1) : items;
}
export function precompressCM<T>(cm: (T | undefined)[] | undefined, addColor: (color: T | undefined) => number): number[] {
const result: number[] = [];
const result: number[] = [];
if (cm) {
let length = CM_SIZE * CM_SIZE;
if (cm) {
let length = CM_SIZE * CM_SIZE;
while (length > 0 && !cm[length - 1]) {
length--;
}
while (length > 0 && !cm[length - 1]) {
length--;
}
for (let i = 0; i < length; i++) {
result.push(addColor(cm[i]));
}
}
for (let i = 0; i < length; i++) {
result.push(addColor(cm[i]));
}
}
return result;
return result;
}
// lock sets
export function compressLockSet(set: boolean[] | undefined, count: number): number {
const locks = set && set.slice ? set.slice(0, count) : [];
return locks.reduce((result, l, i) => result | (l ? (1 << i) : 0), 0);
const locks = set && set.slice ? set.slice(0, count) : [];
return locks.reduce((result, l, i) => result | (l ? (1 << i) : 0), 0);
}
export function decompressLockSet(set: number, count: number, defaultValues: boolean[]): boolean[] {
const result: boolean[] = [];
const result: boolean[] = [];
for (let i = 0; i < MAX_COLORS; i++) {
result[i] = i < count ? !!(set & (1 << i)) : defaultValues[i];
}
for (let i = 0; i < MAX_COLORS; i++) {
result[i] = i < count ? !!(set & (1 << i)) : defaultValues[i];
}
return result;
return result;
}
// colors
export function precompressColorSet<T>(
set: (T | undefined)[] | undefined, count: number, locks: number, defaultColor: T, addColor: (color: T) => number
set: (T | undefined)[] | undefined, count: number, locks: number, defaultColor: T, addColor: (color: T) => number
): number[] {
const result: number[] = [];
const result: number[] = [];
if (set) {
for (let i = 0; i < count; i++) {
if ((locks & (1 << i)) === 0) {
const color = set[i];
result.push(!color || color === defaultColor ? 0 : addColor(color));
}
}
}
if (set) {
for (let i = 0; i < count; i++) {
if ((locks & (1 << i)) === 0) {
const color = set[i];
result.push(!color || color === defaultColor ? 0 : addColor(color));
}
}
}
return result;
return result;
}
export function postdecompressColorSet<T>(
colors: number[], count: number, locks: number, colorList: number[], parseColor: (color: number) => T
colors: number[], count: number, locks: number, colorList: number[], parseColor: (color: number) => T
): T[] {
const result: T[] = [];
const result: T[] = [];
for (let i = 0, j = 0; i < count; i++) {
const locked = (locks & (1 << i)) !== 0;
result.push(parseColor((locked ? 0 : colorList[colors[j++] - 1]) || BLACK));
}
for (let i = 0, j = 0; i < count; i++) {
const locked = (locks & (1 << i)) !== 0;
result.push(parseColor((locked ? 0 : colorList[colors[j++] - 1]) || BLACK));
}
return result;
return result;
}
// set
@@ -301,70 +301,70 @@ const ALL_UNLOCKED = array(MAX_COLORS, false);
const ALL_LOCKED = array(MAX_COLORS, true);
export function precompressSet<T>(
set: SpriteSet<T> | undefined, def: SetDefinition, customOutlines: boolean, defaultColor: T, addColor: (color: T) => number
set: SpriteSet<T> | undefined, def: SetDefinition, customOutlines: boolean, defaultColor: T, addColor: (color: T) => number
): PrecompressedSet | undefined {
if (!set)
return undefined;
if (!set)
return undefined;
const type = clamp(toInt(set.type), 0, def.sets.length - 1);
const type = clamp(toInt(set.type), 0, def.sets.length - 1);
if (type === 0 && !def.preserveOnZero)
return undefined;
if (type === 0 && !def.preserveOnZero)
return undefined;
const patterns = at(def.sets, type);
const pattern = clamp(toInt(set.pattern), 0, patterns ? patterns.length - 1 : 0);
const sprite = att(patterns, pattern);
const colors = Math.max(getColorCount(sprite), def.minColors || 0);
const patterns = at(def.sets, type);
const pattern = clamp(toInt(set.pattern), 0, patterns ? patterns.length - 1 : 0);
const sprite = att(patterns, pattern);
const colors = Math.max(getColorCount(sprite), def.minColors || 0);
/* istanbul ignore next */
if (type === 0 && pattern === 0 && colors === 0)
return undefined;
/* istanbul ignore next */
if (type === 0 && pattern === 0 && colors === 0)
return undefined;
const fillLocks = compressLockSet(set.lockFills, colors);
const fills = precompressColorSet(set.fills, colors, fillLocks, defaultColor, addColor);
const outlineLocks = customOutlines ? compressLockSet(set.lockOutlines, colors) : 0;
const outlines = customOutlines ? precompressColorSet(set.outlines, colors, outlineLocks, defaultColor, addColor) : [];
const fillLocks = compressLockSet(set.lockFills, colors);
const fills = precompressColorSet(set.fills, colors, fillLocks, defaultColor, addColor);
const outlineLocks = customOutlines ? compressLockSet(set.lockOutlines, colors) : 0;
const outlines = customOutlines ? precompressColorSet(set.outlines, colors, outlineLocks, defaultColor, addColor) : [];
return { type, pattern, colors, fillLocks, fills, outlineLocks, outlines };
return { type, pattern, colors, fillLocks, fills, outlineLocks, outlines };
}
export function postdecompressSet<T>(
set: PrecompressedSet, _def: SetDefinition, customOutlines: boolean, colorList: number[], parseColor: (color: number) => T
set: PrecompressedSet, _def: SetDefinition, customOutlines: boolean, colorList: number[], parseColor: (color: number) => T
): SpriteSet<T> | undefined {
return {
type: set.type,
pattern: set.pattern,
lockFills: decompressLockSet(set.fillLocks, set.colors, /*def.defaultLockFills ||*/ ALL_UNLOCKED),
fills: postdecompressColorSet(set.fills, set.colors, set.fillLocks, colorList, parseColor),
lockOutlines: customOutlines ?
decompressLockSet(set.outlineLocks, set.colors, /*def.defaultLockOutlines ||*/ ALL_LOCKED) :
ALL_LOCKED,
outlines: customOutlines ? postdecompressColorSet(set.outlines, set.colors, set.outlineLocks, colorList, parseColor) : [],
};
return {
type: set.type,
pattern: set.pattern,
lockFills: decompressLockSet(set.fillLocks, set.colors, /*def.defaultLockFills ||*/ ALL_UNLOCKED),
fills: postdecompressColorSet(set.fills, set.colors, set.fillLocks, colorList, parseColor),
lockOutlines: customOutlines ?
decompressLockSet(set.outlineLocks, set.colors, /*def.defaultLockOutlines ||*/ ALL_LOCKED) :
ALL_LOCKED,
outlines: customOutlines ? postdecompressColorSet(set.outlines, set.colors, set.outlineLocks, colorList, parseColor) : [],
};
}
// helpers
function precompressFields<TDef extends FieldDefinition<TResult>, TValue, TResult>(
data: any, defs: TDef[], defaultValue: TResult, encode: (value: TValue | undefined, def: TDef) => TResult
data: any, defs: TDef[], defaultValue: TResult, encode: (value: TValue | undefined, def: TDef) => TResult
): TResult[] {
return trimRight(defs.map(def => {
if (def.dontSave || (def.omit && def.omit(data))) {
return defaultValue;
} else {
return encode(data[def.name], def);
}
}));
return trimRight(defs.map(def => {
if (def.dontSave || (def.omit && def.omit(data))) {
return defaultValue;
} else {
return encode(data[def.name], def);
}
}));
}
function postdecompressFields<TDef extends FieldDefinition<TValue>, TValue, TResult>(
result: any, defs: TDef[], values: (TValue | undefined)[], defaultValue: TValue, decode: (value: TValue, def: TDef) => TResult
result: any, defs: TDef[], values: (TValue | undefined)[], defaultValue: TValue, decode: (value: TValue, def: TDef) => TResult
) {
for (let i = 0; i < defs.length; i++) {
const def = defs[i];
const value = i >= values.length ? undefined : values[i];
result[def.name] = decode(value === undefined ? defaultValue : value, def);
}
for (let i = 0; i < defs.length; i++) {
const def = defs[i];
const value = i >= values.length ? undefined : values[i];
result[def.name] = decode(value === undefined ? defaultValue : value, def);
}
}
// pony
@@ -372,105 +372,105 @@ function postdecompressFields<TDef extends FieldDefinition<TValue>, TValue, TRes
type Info<T> = PonyInfoBase<T, SpriteSet<T>>;
export function precompressPony<T>(info: Info<T>, defaultColor: T, parseColor: (color: T) => number): Precompressed {
const colors: number[] = [];
const customOutlines = !!info.customOutlines;
const addColor = (color: T | undefined) => {
const c = color === undefined ? 0 : parseColor(color);
return c === 0 ? 0 : pushUniq(colors, c);
};
const colors: number[] = [];
const customOutlines = !!info.customOutlines;
const addColor = (color: T | undefined) => {
const c = color === undefined ? 0 : parseColor(color);
return c === 0 ? 0 : pushUniq(colors, c);
};
return {
version: VERSION,
colors,
booleanFields: precompressFields(info, booleanFields, false as boolean, x => !!x),
numberFields: precompressFields(info, numberFields, 0, toInt),
colorFields: precompressFields(info, colorFields, 0,
(x: T | undefined, def) => (x === undefined || parseColor(x) === (def.default || BLACK)) ? 0 : addColor(x)),
setFields: precompressFields(info, setFields, undefined,
(x: SpriteSet<T> | undefined, def: SetDefinition) => precompressSet(x, def, customOutlines, defaultColor, addColor)),
cm: precompressCM(info.cm, addColor),
};
return {
version: VERSION,
colors,
booleanFields: precompressFields(info, booleanFields, false as boolean, x => !!x),
numberFields: precompressFields(info, numberFields, 0, toInt),
colorFields: precompressFields(info, colorFields, 0,
(x: T | undefined, def) => (x === undefined || parseColor(x) === (def.default || BLACK)) ? 0 : addColor(x)),
setFields: precompressFields(info, setFields, undefined,
(x: SpriteSet<T> | undefined, def: SetDefinition) => precompressSet(x, def, customOutlines, defaultColor, addColor)),
cm: precompressCM(info.cm, addColor),
};
}
const frecklesToPattern = [0, 1, 1, 2, 2, 2, 1];
const frecklesToColor: number[][] = [[], [1], [1, 2], [2], [1], [1, 2], [2]];
function fixVersion<T>(result: Info<T>, data: Precompressed, parseColor: (color: number) => T) {
if (data.version < 3) {
result.head = {
type: 0,
pattern: frecklesToPattern[result.freckles || 0] || 0,
fills: [result.coatFill],
outlines: [result.coatOutline],
lockFills: [true, true, true, true, true, true],
lockOutlines: [true, true, true, true, true, true],
};
if (data.version < 3) {
result.head = {
type: 0,
pattern: frecklesToPattern[result.freckles || 0] || 0,
fills: [result.coatFill],
outlines: [result.coatOutline],
lockFills: [true, true, true, true, true, true],
lockOutlines: [true, true, true, true, true, true],
};
frecklesToColor[result.freckles || 0].forEach(index => {
result.head!.fills![index] = result.frecklesColor || parseColor(BLACK);
result.head!.lockFills![index] = false;
});
}
frecklesToColor[result.freckles || 0].forEach(index => {
result.head!.fills![index] = result.frecklesColor || parseColor(BLACK);
result.head!.lockFills![index] = false;
});
}
}
export function createPostDecompressPony() {
return new Function('postdecompressSet', 'setFields', 'ommitableFields', 'fixVersion', [
'function identity(x) { return x; }',
'function getColor(colors, i) { return (i >= 0 && i < colors.length) ? colors[i] : 0; }',
'function getCM(cm, colors) {',
' var result = [];',
' for(var i = 0; i < cm.length; i++) { result.push(getColor(colors, cm[i] - 1) || 0); }',
' return result;',
'}',
...omittableFields.map((def, i) => `var omit_${def.name} = ommitableFields[${i}].omit;`),
'return function (data) {',
' var dataColors = data.colors;',
' var bools = data.booleanFields;',
' var numbers = data.numberFields;',
' var colors = data.colorFields;',
' var sets = data.setFields;',
' var result = {};',
...booleanFields.map((def, i) => ` result.${def.name} = bools.length > ${i} ? bools[${i}] : false;`),
...numberFields.map((def, i) => ` result.${def.name} = numbers.length > ${i} ? numbers[${i}] : 0;`),
...colorFields.map((def, i) => ` result.${def.name} = colors.length > ${i} ? ` +
`getColor(dataColors, colors[${i}] - 1) || ${def.default || BLACK} : ${def.default || BLACK};`),
' var customOutlines = !!result.customOutlines;',
...setFields.map((def, i) => ` result.${def.name} = sets.length > ${i} && sets[${i}] !== undefined ? ` +
`postdecompressSet(sets[${i}], setFields[${i}], customOutlines, data.colors, identity) : undefined;`),
` result.cm = data.cm.length ? getCM(data.cm, dataColors) : undefined;`,
...omittableFields.map(def => ` if (omit_${def.name}(result)) result.${def.name} = undefined;`),
' fixVersion(result, data, identity);',
' return result;',
'};',
].join('\n'));
return new Function('postdecompressSet', 'setFields', 'ommitableFields', 'fixVersion', [
'function identity(x) { return x; }',
'function getColor(colors, i) { return (i >= 0 && i < colors.length) ? colors[i] : 0; }',
'function getCM(cm, colors) {',
' var result = [];',
' for(var i = 0; i < cm.length; i++) { result.push(getColor(colors, cm[i] - 1) || 0); }',
' return result;',
'}',
...omittableFields.map((def, i) => `var omit_${def.name} = ommitableFields[${i}].omit;`),
'return function (data) {',
' var dataColors = data.colors;',
' var bools = data.booleanFields;',
' var numbers = data.numberFields;',
' var colors = data.colorFields;',
' var sets = data.setFields;',
' var result = {};',
...booleanFields.map((def, i) => ` result.${def.name} = bools.length > ${i} ? bools[${i}] : false;`),
...numberFields.map((def, i) => ` result.${def.name} = numbers.length > ${i} ? numbers[${i}] : 0;`),
...colorFields.map((def, i) => ` result.${def.name} = colors.length > ${i} ? ` +
`getColor(dataColors, colors[${i}] - 1) || ${def.default || BLACK} : ${def.default || BLACK};`),
' var customOutlines = !!result.customOutlines;',
...setFields.map((def, i) => ` result.${def.name} = sets.length > ${i} && sets[${i}] !== undefined ? ` +
`postdecompressSet(sets[${i}], setFields[${i}], customOutlines, data.colors, identity) : undefined;`),
` result.cm = data.cm.length ? getCM(data.cm, dataColors) : undefined;`,
...omittableFields.map(def => ` if (omit_${def.name}(result)) result.${def.name} = undefined;`),
' fixVersion(result, data, identity);',
' return result;',
'};',
].join('\n'));
}
export const fastPostdecompressPony = createPostDecompressPony()(
postdecompressSet, setFields, omittableFields, fixVersion);
postdecompressSet, setFields, omittableFields, fixVersion);
export function postdecompressPony<T>(data: Precompressed, parseColor: (color: number) => T): Info<T> {
// NOTE: when updating also update createPostDecompressPony()
// NOTE: when updating also update createPostDecompressPony()
const result: Info<T> = {} as any;
postdecompressFields(result, booleanFields, data.booleanFields, false as boolean, identity);
postdecompressFields(result, numberFields, data.numberFields, 0 as number, identity);
postdecompressFields(result, colorFields, data.colorFields, 0 as number,
(x, def) => parseColor(data.colors[x - 1] || def.default || BLACK));
const customOutlines = !!result.customOutlines;
postdecompressFields(result, setFields, data.setFields, undefined,
(x, def) => x === undefined ? undefined : postdecompressSet(x, def, customOutlines, data.colors, parseColor));
const result: Info<T> = {} as any;
postdecompressFields(result, booleanFields, data.booleanFields, false as boolean, identity);
postdecompressFields(result, numberFields, data.numberFields, 0 as number, identity);
postdecompressFields(result, colorFields, data.colorFields, 0 as number,
(x, def) => parseColor(data.colors[x - 1] || def.default || BLACK));
const customOutlines = !!result.customOutlines;
postdecompressFields(result, setFields, data.setFields, undefined,
(x, def) => x === undefined ? undefined : postdecompressSet(x, def, customOutlines, data.colors, parseColor));
result.cm = data.cm.length ? data.cm.map(x => parseColor(data.colors[x - 1] || TRANSPARENT)) : undefined;
result.cm = data.cm.length ? data.cm.map(x => parseColor(data.colors[x - 1] || TRANSPARENT)) : undefined;
omittableFields.forEach(def => {
if (def.omit && def.omit(result)) {
result[def.name] = undefined;
}
});
omittableFields.forEach(def => {
if (def.omit && def.omit(result)) {
result[def.name] = undefined;
}
});
fixVersion(result, data, parseColor);
fixVersion(result, data, parseColor);
return result;
return result;
}
// set
@@ -480,69 +480,69 @@ const PATTERN_BITS = 4; // max 15
const COLORS_BITS = 3; // max 7
export function writeSet(write: WriteBits, colorBits: number, customOutlines: boolean, set: PrecompressedSet | undefined) {
write(set ? 1 : 0, 1);
write(set ? 1 : 0, 1);
if (set) {
write(set.type, TYPE_BITS);
write(set.pattern, PATTERN_BITS);
write(set.colors - 1, COLORS_BITS);
write(set.fillLocks, set.colors);
set.fills.forEach(c => write(c, colorBits));
if (set) {
write(set.type, TYPE_BITS);
write(set.pattern, PATTERN_BITS);
write(set.colors - 1, COLORS_BITS);
write(set.fillLocks, set.colors);
set.fills.forEach(c => write(c, colorBits));
if (customOutlines) {
write(set.outlineLocks, set.colors);
set.outlines.forEach(c => write(c, colorBits));
}
}
if (customOutlines) {
write(set.outlineLocks, set.colors);
set.outlines.forEach(c => write(c, colorBits));
}
}
}
export function readSet(read: ReadBits, colorBits: number, customOutlines: boolean): PrecompressedSet | undefined {
const has = read(1);
const has = read(1);
if (has) {
const type = read(TYPE_BITS);
const pattern = read(PATTERN_BITS);
const colors = read(COLORS_BITS) + 1;
const fillLocks = read(colors);
const fills = readTimes(read, colors - countBits(fillLocks), colorBits);
const outlineLocks = customOutlines ? read(colors) : 0;
const outlines = customOutlines ? readTimes(read, colors - countBits(outlineLocks), colorBits) : [];
return { type, pattern, colors, fillLocks, fills, outlineLocks, outlines };
} else {
return undefined;
}
if (has) {
const type = read(TYPE_BITS);
const pattern = read(PATTERN_BITS);
const colors = read(COLORS_BITS) + 1;
const fillLocks = read(colors);
const fills = readTimes(read, colors - countBits(fillLocks), colorBits);
const outlineLocks = customOutlines ? read(colors) : 0;
const outlines = customOutlines ? readTimes(read, colors - countBits(outlineLocks), colorBits) : [];
return { type, pattern, colors, fillLocks, fills, outlineLocks, outlines };
} else {
return undefined;
}
}
// helpers
function writeFields<T>(write: WriteBits, lengthBits: number, fields: T[], writeField: (value: T) => void) {
write(fields.length, lengthBits);
fields.forEach(writeField);
write(fields.length, lengthBits);
fields.forEach(writeField);
}
function readFields<T>(read: ReadBits, lengthBits: number, readField: (read: ReadBits) => T): T[] {
const length = read(lengthBits);
const result: T[] = [];
const length = read(lengthBits);
const result: T[] = [];
for (let i = 0; i < length; i++) {
result.push(readField(read));
}
for (let i = 0; i < length; i++) {
result.push(readField(read));
}
return result;
return result;
}
// pony
export function writePony(write: WriteBits, data: Precompressed) {
const colorBits = Math.max(numberToBitCount(data.colors.length), 1);
const customOutlines = !!data.booleanFields[0];
write(data.version, VERSION_BITS);
writeFields(write, COLORS_LENGTH_BITS, data.colors, x => write(x >> 8, 24));
writeFields(write, BOOLEAN_FIELDS_LENGTH_BITS, data.booleanFields, x => write(x ? 1 : 0, 1));
writeFields(write, NUMBER_FIELDS_LENGTH_BITS, data.numberFields, x => write(x, NUMBERS_BITS));
writeFields(write, COLOR_FIELDS_LENGTH_BITS, data.colorFields, x => write(x, colorBits));
writeFields(write, SET_FIELDS_LENGTH_BITS, data.setFields, x => writeSet(write, colorBits, customOutlines, x));
writeFields(write, CM_LENGTH_BITS, data.cm, x => write(x, colorBits));
const colorBits = Math.max(numberToBitCount(data.colors.length), 1);
const customOutlines = !!data.booleanFields[0];
write(data.version, VERSION_BITS);
writeFields(write, COLORS_LENGTH_BITS, data.colors, x => write(x >> 8, 24));
writeFields(write, BOOLEAN_FIELDS_LENGTH_BITS, data.booleanFields, x => write(x ? 1 : 0, 1));
writeFields(write, NUMBER_FIELDS_LENGTH_BITS, data.numberFields, x => write(x, NUMBERS_BITS));
writeFields(write, COLOR_FIELDS_LENGTH_BITS, data.colorFields, x => write(x, colorBits));
writeFields(write, SET_FIELDS_LENGTH_BITS, data.setFields, x => writeSet(write, colorBits, customOutlines, x));
writeFields(write, CM_LENGTH_BITS, data.cm, x => write(x, colorBits));
}
const readColorValue = (read: ReadBits) => ((read(24) << 8) | 0xff) >>> 0;
@@ -551,74 +551,74 @@ const readBits = (bits: number) => (read: ReadBits) => read(bits);
const readNumber = readBits(NUMBERS_BITS);
export function readPony(read: ReadBits): Precompressed {
const version = read(VERSION_BITS);
const colors = readFields(read, COLORS_LENGTH_BITS, readColorValue);
const colorBits = Math.max(numberToBitCount(colors.length), 1);
const readColor = readBits(colorBits);
const booleanFields = readFields(read, BOOLEAN_FIELDS_LENGTH_BITS, readBoolean);
const customOutlines = !!booleanFields[0];
const numberFields = readFields(read, NUMBER_FIELDS_LENGTH_BITS, readNumber);
const colorFields = readFields(read, COLOR_FIELDS_LENGTH_BITS, readColor);
const setFields = readFields(read, SET_FIELDS_LENGTH_BITS, read => readSet(read, colorBits, customOutlines));
const cm = readFields(read, CM_LENGTH_BITS, readColor);
return { version, colors, booleanFields, numberFields, colorFields, setFields, cm };
const version = read(VERSION_BITS);
const colors = readFields(read, COLORS_LENGTH_BITS, readColorValue);
const colorBits = Math.max(numberToBitCount(colors.length), 1);
const readColor = readBits(colorBits);
const booleanFields = readFields(read, BOOLEAN_FIELDS_LENGTH_BITS, readBoolean);
const customOutlines = !!booleanFields[0];
const numberFields = readFields(read, NUMBER_FIELDS_LENGTH_BITS, readNumber);
const colorFields = readFields(read, COLOR_FIELDS_LENGTH_BITS, readColor);
const setFields = readFields(read, SET_FIELDS_LENGTH_BITS, read => readSet(read, colorBits, customOutlines));
const cm = readFields(read, CM_LENGTH_BITS, readColor);
return { version, colors, booleanFields, numberFields, colorFields, setFields, cm };
}
function writePonyToString(data: Precompressed): string {
return fromByteArray(bitWriter(write => writePony(write, data)));
return fromByteArray(bitWriter(write => writePony(write, data)));
}
function readPonyFromBuffer(info: Uint8Array): Precompressed {
return readPony(bitReader(info));
return readPony(bitReader(info));
}
function readPonyFromString(info: string): Precompressed {
return info ? readPonyFromBuffer(toByteArray(info)) : {
version: VERSION,
colors: [],
booleanFields: [],
numberFields: [],
colorFields: [],
setFields: [],
cm: [],
};
return info ? readPonyFromBuffer(toByteArray(info)) : {
version: VERSION,
colors: [],
booleanFields: [],
numberFields: [],
colorFields: [],
setFields: [],
cm: [],
};
}
// compress
export function compressPony(info: PonyInfoNumber): string {
return writePonyToString(precompressPony(info, BLACK, identity));
return writePonyToString(precompressPony(info, BLACK, identity));
}
export function decompressPony(info: string | Uint8Array): PonyInfoNumber {
const data = typeof info === 'string' ? readPonyFromString(info) : readPonyFromBuffer(info);
const pony = fastPostdecompressPony(data); // postdecompressPony(data, identity);
return syncLockedPonyInfoNumber(pony);
const data = typeof info === 'string' ? readPonyFromString(info) : readPonyFromBuffer(info);
const pony = fastPostdecompressPony(data); // postdecompressPony(data, identity);
return syncLockedPonyInfoNumber(pony);
}
// compress (string)
function parseColorFastSafe(color: string): number {
return color ? parseColorFast(color) : TRANSPARENT;
return color ? parseColorFast(color) : TRANSPARENT;
}
function colorToString(color: number): string {
return color ? colorToHexRGB(color) : '';
return color ? colorToHexRGB(color) : '';
}
export function compressPonyString(info: PonyInfo): string {
return writePonyToString(precompressPony(info, '000000', parseColorFastSafe));
return writePonyToString(precompressPony(info, '000000', parseColorFastSafe));
}
export function decompressPonyString(info: string, editable = false): PonyInfo {
const data = readPonyFromString(info);
const pony = postdecompressPony(data, colorToString);
const result = editable ? merge(createBasePony(), pony) : pony;
return syncLockedPonyInfo(result);
const data = readPonyFromString(info);
const pony = postdecompressPony(data, colorToString);
const result = editable ? merge(createBasePony(), pony) : pony;
return syncLockedPonyInfo(result);
}
// decode
export function decodePonyInfo(info: string | Uint8Array, paletteManager: PaletteManager): PalettePonyInfo {
return toPaletteNumber(decompressPony(info), paletteManager);
return toPaletteNumber(decompressPony(info), paletteManager);
}
+51 -51
View File
@@ -42,7 +42,7 @@ export const MAX_CHATLOG_RANGE = 11;
export const MIN_CHATLOG_RANGE = 2;
export function isChatlogRangeUnlimited(range: number | undefined) {
return !range || range < MIN_CHATLOG_RANGE || range >= MAX_CHATLOG_RANGE;
return !range || range < MIN_CHATLOG_RANGE || range >= MAX_CHATLOG_RANGE;
}
export const WATER_FPS = 6;
@@ -108,30 +108,30 @@ export const MIN_ADULT_AGE = 18;
export const REQUEST_DATE_OF_BIRTH = true;
export const TIMEOUTS = [
{ value: MINUTE * 5, label: '5 minutes' },
{ value: MINUTE * 10, label: '10 minutes' },
{ value: MINUTE * 30, label: '30 minutes' },
{ value: HOUR * 1, label: '1 hour' },
{ value: HOUR * 5, label: '5 hours' },
{ value: HOUR * 10, label: '10 hours' },
{ value: HOUR * 24, label: '24 hours' },
{ value: DAY * 2, label: '2 days' },
{ value: DAY * 5, label: '5 days' },
{ value: MINUTE * 5, label: '5 minutes' },
{ value: MINUTE * 10, label: '10 minutes' },
{ value: MINUTE * 30, label: '30 minutes' },
{ value: HOUR * 1, label: '1 hour' },
{ value: HOUR * 5, label: '5 hours' },
{ value: HOUR * 10, label: '10 hours' },
{ value: HOUR * 24, label: '24 hours' },
{ value: DAY * 2, label: '2 days' },
{ value: DAY * 5, label: '5 days' },
];
export const MONTH_NAMES_EN = [
'January',
'February ',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
'January',
'February ',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
export const OFFLINE_PONY = 'DAKVlZUvLy82QIxomgCfgAYAGIAoQGEBwAEERFEUEA==';
@@ -143,48 +143,48 @@ export const rewardLevel2 = '2411886';
export const rewardLevel3 = '2411888';
const SUPPORTER_REWARDS_COMMON = [
`In-game supporter tag`,
`Supporter chat color`,
`In-game supporter tag`,
`Supporter chat color`,
];
const SUPPORTER_REWARDS_MORE = [
`Access to patreon posts`,
`Early access to new and experimental features`,
`Access to patreon posts`,
`Early access to new and experimental features`,
];
export const SUPPORTER_REWARDS = [
[],
[
...SUPPORTER_REWARDS_COMMON,
`${ADDITIONAL_CHARACTERS_SUPPORTER1} additional slots for saving ponies`,
],
[
...SUPPORTER_REWARDS_COMMON,
...SUPPORTER_REWARDS_MORE,
`${ADDITIONAL_CHARACTERS_SUPPORTER2} additional slots for saving ponies`,
],
[
...SUPPORTER_REWARDS_COMMON,
...SUPPORTER_REWARDS_MORE,
`${ADDITIONAL_CHARACTERS_SUPPORTER3} additional slots for saving ponies`,
],
[],
[
...SUPPORTER_REWARDS_COMMON,
`${ADDITIONAL_CHARACTERS_SUPPORTER1} additional slots for saving ponies`,
],
[
...SUPPORTER_REWARDS_COMMON,
...SUPPORTER_REWARDS_MORE,
`${ADDITIONAL_CHARACTERS_SUPPORTER2} additional slots for saving ponies`,
],
[
...SUPPORTER_REWARDS_COMMON,
...SUPPORTER_REWARDS_MORE,
`${ADDITIONAL_CHARACTERS_SUPPORTER3} additional slots for saving ponies`,
],
];
export const SUPPORTER_REWARDS_LIST = [
...SUPPORTER_REWARDS_COMMON,
...SUPPORTER_REWARDS_MORE,
`Additional slots for saving ponies`,
...SUPPORTER_REWARDS_COMMON,
...SUPPORTER_REWARDS_MORE,
`Additional slots for saving ponies`,
];
export const PAST_SUPPORTER_REWARDS = [
`${ADDITIONAL_CHARACTERS_PAST_SUPPORTER} additional slots for saving ponies`,
`${ADDITIONAL_CHARACTERS_PAST_SUPPORTER} additional slots for saving ponies`,
];
export const GENERAL_RULES = [
`Be kind to others`,
`Don't spam`,
`Don't use multiple accounts`,
`Don't modify the game with hacks or scripts`,
`Don't encourage behaviour violating the rules`,
`Violation of the rules may result in temporary or permanent ban`,
`Be kind to others`,
`Don't spam`,
`Don't use multiple accounts`,
`Don't modify the game with hacks or scripts`,
`Don't encourage behaviour violating the rules`,
`Violation of the rules may result in temporary or permanent ban`,
];
+249 -249
View File
@@ -1,251 +1,251 @@
export const countryCodeToName: { [key: string]: string | undefined; } = {
AD: `Andorra`,
AE: `United Arab Emirates (the)`,
AF: `Afghanistan`,
AG: `Antigua and Barbuda`,
AI: `Anguilla`,
AL: `Albania`,
AM: `Armenia`,
AO: `Angola`,
AQ: `Antarctica`,
AR: `Argentina`,
AS: `American Samoa`,
AT: `Austria`,
AU: `Australia`,
AW: `Aruba`,
AX: `Åland Islands`,
AZ: `Azerbaijan`,
BA: `Bosnia and Herzegovina`,
BB: `Barbados`,
BD: `Bangladesh`,
BE: `Belgium`,
BF: `Burkina Faso`,
BG: `Bulgaria`,
BH: `Bahrain`,
BI: `Burundi`,
BJ: `Benin`,
BL: `Saint Barthélemy`,
BM: `Bermuda`,
BN: `Brunei Darussalam`,
BO: `Bolivia (Plurinational State of)`,
BQ: `Bonaire, Sint Eustatius and Saba`,
BR: `Brazil`,
BS: `Bahamas (the)`,
BT: `Bhutan`,
BV: `Bouvet Island`,
BW: `Botswana`,
BY: `Belarus`,
BZ: `Belize`,
CA: `Canada`,
CC: `Cocos (Keeling) Islands (the)`,
CD: `Congo (the Democratic Republic of the)`,
CF: `Central African Republic (the)`,
CG: `Congo (the)`,
CH: `Switzerland`,
CI: `Côte d'Ivoire`,
CK: `Cook Islands (the)`,
CL: `Chile`,
CM: `Cameroon`,
CN: `China`,
CO: `Colombia`,
CR: `Costa Rica`,
CU: `Cuba`,
CV: `Cabo Verde`,
CW: `Curaçao`,
CX: `Christmas Island`,
CY: `Cyprus`,
CZ: `Czechia`,
DE: `Germany`,
DJ: `Djibouti`,
DK: `Denmark`,
DM: `Dominica`,
DO: `Dominican Republic (the)`,
DZ: `Algeria`,
EC: `Ecuador`,
EE: `Estonia`,
EG: `Egypt`,
EH: `Western Sahara*`,
ER: `Eritrea`,
ES: `Spain`,
ET: `Ethiopia`,
FI: `Finland`,
FJ: `Fiji`,
FK: `Falkland Islands (the) [Malvinas]`,
FM: `Micronesia (Federated States of)`,
FO: `Faroe Islands (the)`,
FR: `France`,
GA: `Gabon`,
GB: `United Kingdom of Great Britain and Northern Ireland (the)`,
GD: `Grenada`,
GE: `Georgia`,
GF: `French Guiana`,
GG: `Guernsey`,
GH: `Ghana`,
GI: `Gibraltar`,
GL: `Greenland`,
GM: `Gambia (the)`,
GN: `Guinea`,
GP: `Guadeloupe`,
GQ: `Equatorial Guinea`,
GR: `Greece`,
GS: `South Georgia and the South Sandwich Islands`,
GT: `Guatemala`,
GU: `Guam`,
GW: `Guinea-Bissau`,
GY: `Guyana`,
HK: `Hong Kong`,
HM: `Heard Island and McDonald Islands`,
HN: `Honduras`,
HR: `Croatia`,
HT: `Haiti`,
HU: `Hungary`,
ID: `Indonesia`,
IE: `Ireland`,
IL: `Israel`,
IM: `Isle of Man`,
IN: `India`,
IO: `British Indian Ocean Territory (the)`,
IQ: `Iraq`,
IR: `Iran (Islamic Republic of)`,
IS: `Iceland`,
IT: `Italy`,
JE: `Jersey`,
JM: `Jamaica`,
JO: `Jordan`,
JP: `Japan`,
KE: `Kenya`,
KG: `Kyrgyzstan`,
KH: `Cambodia`,
KI: `Kiribati`,
KM: `Comoros (the)`,
KN: `Saint Kitts and Nevis`,
KP: `Korea (the Democratic People's Republic of)`,
KR: `Korea (the Republic of)`,
KW: `Kuwait`,
KY: `Cayman Islands (the)`,
KZ: `Kazakhstan`,
LA: `Lao People's Democratic Republic (the)`,
LB: `Lebanon`,
LC: `Saint Lucia`,
LI: `Liechtenstein`,
LK: `Sri Lanka`,
LR: `Liberia`,
LS: `Lesotho`,
LT: `Lithuania`,
LU: `Luxembourg`,
LV: `Latvia`,
LY: `Libya`,
MA: `Morocco`,
MC: `Monaco`,
MD: `Moldova (the Republic of)`,
ME: `Montenegro`,
MF: `Saint Martin (French part)`,
MG: `Madagascar`,
MH: `Marshall Islands (the)`,
MK: `Macedonia (the former Yugoslav Republic of)`,
ML: `Mali`,
MM: `Myanmar`,
MN: `Mongolia`,
MO: `Macao`,
MP: `Northern Mariana Islands (the)`,
MQ: `Martinique`,
MR: `Mauritania`,
MS: `Montserrat`,
MT: `Malta`,
MU: `Mauritius`,
MV: `Maldives`,
MW: `Malawi`,
MX: `Mexico`,
MY: `Malaysia`,
MZ: `Mozambique`,
NA: `Namibia`,
NC: `New Caledonia`,
NE: `Niger (the)`,
NF: `Norfolk Island`,
NG: `Nigeria`,
NI: `Nicaragua`,
NL: `Netherlands (the)`,
NO: `Norway`,
NP: `Nepal`,
NR: `Nauru`,
NU: `Niue`,
NZ: `New Zealand`,
OM: `Oman`,
PA: `Panama`,
PE: `Peru`,
PF: `French Polynesia`,
PG: `Papua New Guinea`,
PH: `Philippines (the)`,
PK: `Pakistan`,
PL: `Poland`,
PM: `Saint Pierre and Miquelon`,
PN: `Pitcairn`,
PR: `Puerto Rico`,
PS: `Palestine, State of`,
PT: `Portugal`,
PW: `Palau`,
PY: `Paraguay`,
QA: `Qatar`,
RE: `Réunion`,
RO: `Romania`,
RS: `Serbia`,
RU: `Russian Federation (the)`,
RW: `Rwanda`,
SA: `Saudi Arabia`,
SB: `Solomon Islands`,
SC: `Seychelles`,
SD: `Sudan (the)`,
SE: `Sweden`,
SG: `Singapore`,
SH: `Saint Helena, Ascension and Tristan da Cunha`,
SI: `Slovenia`,
SJ: `Svalbard and Jan Mayen`,
SK: `Slovakia`,
SL: `Sierra Leone`,
SM: `San Marino`,
SN: `Senegal`,
SO: `Somalia`,
SR: `Suriname`,
SS: `South Sudan`,
ST: `Sao Tome and Principe`,
SV: `El Salvador`,
SX: `Sint Maarten (Dutch part)`,
SY: `Syrian Arab Republic`,
SZ: `Swaziland`,
TC: `Turks and Caicos Islands (the)`,
TD: `Chad`,
TF: `French Southern Territories (the)`,
TG: `Togo`,
TH: `Thailand`,
TJ: `Tajikistan`,
TK: `Tokelau`,
TL: `Timor-Leste`,
TM: `Turkmenistan`,
TN: `Tunisia`,
TO: `Tonga`,
TR: `Turkey`,
TT: `Trinidad and Tobago`,
TV: `Tuvalu`,
TW: `Taiwan (Province of China)`,
TZ: `Tanzania, United Republic of`,
UA: `Ukraine`,
UG: `Uganda`,
UM: `United States Minor Outlying Islands (the)`,
US: `United States of America (the)`,
UY: `Uruguay`,
UZ: `Uzbekistan`,
VA: `Holy See (the)`,
VC: `Saint Vincent and the Grenadines`,
VE: `Venezuela (Bolivarian Republic of)`,
VG: `Virgin Islands (British)`,
VI: `Virgin Islands (U.S.)`,
VN: `Viet Nam`,
VU: `Vanuatu`,
WF: `Wallis and Futuna`,
WS: `Samoa`,
YE: `Yemen`,
YT: `Mayotte`,
ZA: `South Africa`,
ZM: `Zambia`,
ZW: `Zimbabwe`,
AD: `Andorra`,
AE: `United Arab Emirates (the)`,
AF: `Afghanistan`,
AG: `Antigua and Barbuda`,
AI: `Anguilla`,
AL: `Albania`,
AM: `Armenia`,
AO: `Angola`,
AQ: `Antarctica`,
AR: `Argentina`,
AS: `American Samoa`,
AT: `Austria`,
AU: `Australia`,
AW: `Aruba`,
AX: `Åland Islands`,
AZ: `Azerbaijan`,
BA: `Bosnia and Herzegovina`,
BB: `Barbados`,
BD: `Bangladesh`,
BE: `Belgium`,
BF: `Burkina Faso`,
BG: `Bulgaria`,
BH: `Bahrain`,
BI: `Burundi`,
BJ: `Benin`,
BL: `Saint Barthélemy`,
BM: `Bermuda`,
BN: `Brunei Darussalam`,
BO: `Bolivia (Plurinational State of)`,
BQ: `Bonaire, Sint Eustatius and Saba`,
BR: `Brazil`,
BS: `Bahamas (the)`,
BT: `Bhutan`,
BV: `Bouvet Island`,
BW: `Botswana`,
BY: `Belarus`,
BZ: `Belize`,
CA: `Canada`,
CC: `Cocos (Keeling) Islands (the)`,
CD: `Congo (the Democratic Republic of the)`,
CF: `Central African Republic (the)`,
CG: `Congo (the)`,
CH: `Switzerland`,
CI: `Côte d'Ivoire`,
CK: `Cook Islands (the)`,
CL: `Chile`,
CM: `Cameroon`,
CN: `China`,
CO: `Colombia`,
CR: `Costa Rica`,
CU: `Cuba`,
CV: `Cabo Verde`,
CW: `Curaçao`,
CX: `Christmas Island`,
CY: `Cyprus`,
CZ: `Czechia`,
DE: `Germany`,
DJ: `Djibouti`,
DK: `Denmark`,
DM: `Dominica`,
DO: `Dominican Republic (the)`,
DZ: `Algeria`,
EC: `Ecuador`,
EE: `Estonia`,
EG: `Egypt`,
EH: `Western Sahara*`,
ER: `Eritrea`,
ES: `Spain`,
ET: `Ethiopia`,
FI: `Finland`,
FJ: `Fiji`,
FK: `Falkland Islands (the) [Malvinas]`,
FM: `Micronesia (Federated States of)`,
FO: `Faroe Islands (the)`,
FR: `France`,
GA: `Gabon`,
GB: `United Kingdom of Great Britain and Northern Ireland (the)`,
GD: `Grenada`,
GE: `Georgia`,
GF: `French Guiana`,
GG: `Guernsey`,
GH: `Ghana`,
GI: `Gibraltar`,
GL: `Greenland`,
GM: `Gambia (the)`,
GN: `Guinea`,
GP: `Guadeloupe`,
GQ: `Equatorial Guinea`,
GR: `Greece`,
GS: `South Georgia and the South Sandwich Islands`,
GT: `Guatemala`,
GU: `Guam`,
GW: `Guinea-Bissau`,
GY: `Guyana`,
HK: `Hong Kong`,
HM: `Heard Island and McDonald Islands`,
HN: `Honduras`,
HR: `Croatia`,
HT: `Haiti`,
HU: `Hungary`,
ID: `Indonesia`,
IE: `Ireland`,
IL: `Israel`,
IM: `Isle of Man`,
IN: `India`,
IO: `British Indian Ocean Territory (the)`,
IQ: `Iraq`,
IR: `Iran (Islamic Republic of)`,
IS: `Iceland`,
IT: `Italy`,
JE: `Jersey`,
JM: `Jamaica`,
JO: `Jordan`,
JP: `Japan`,
KE: `Kenya`,
KG: `Kyrgyzstan`,
KH: `Cambodia`,
KI: `Kiribati`,
KM: `Comoros (the)`,
KN: `Saint Kitts and Nevis`,
KP: `Korea (the Democratic People's Republic of)`,
KR: `Korea (the Republic of)`,
KW: `Kuwait`,
KY: `Cayman Islands (the)`,
KZ: `Kazakhstan`,
LA: `Lao People's Democratic Republic (the)`,
LB: `Lebanon`,
LC: `Saint Lucia`,
LI: `Liechtenstein`,
LK: `Sri Lanka`,
LR: `Liberia`,
LS: `Lesotho`,
LT: `Lithuania`,
LU: `Luxembourg`,
LV: `Latvia`,
LY: `Libya`,
MA: `Morocco`,
MC: `Monaco`,
MD: `Moldova (the Republic of)`,
ME: `Montenegro`,
MF: `Saint Martin (French part)`,
MG: `Madagascar`,
MH: `Marshall Islands (the)`,
MK: `Macedonia (the former Yugoslav Republic of)`,
ML: `Mali`,
MM: `Myanmar`,
MN: `Mongolia`,
MO: `Macao`,
MP: `Northern Mariana Islands (the)`,
MQ: `Martinique`,
MR: `Mauritania`,
MS: `Montserrat`,
MT: `Malta`,
MU: `Mauritius`,
MV: `Maldives`,
MW: `Malawi`,
MX: `Mexico`,
MY: `Malaysia`,
MZ: `Mozambique`,
NA: `Namibia`,
NC: `New Caledonia`,
NE: `Niger (the)`,
NF: `Norfolk Island`,
NG: `Nigeria`,
NI: `Nicaragua`,
NL: `Netherlands (the)`,
NO: `Norway`,
NP: `Nepal`,
NR: `Nauru`,
NU: `Niue`,
NZ: `New Zealand`,
OM: `Oman`,
PA: `Panama`,
PE: `Peru`,
PF: `French Polynesia`,
PG: `Papua New Guinea`,
PH: `Philippines (the)`,
PK: `Pakistan`,
PL: `Poland`,
PM: `Saint Pierre and Miquelon`,
PN: `Pitcairn`,
PR: `Puerto Rico`,
PS: `Palestine, State of`,
PT: `Portugal`,
PW: `Palau`,
PY: `Paraguay`,
QA: `Qatar`,
RE: `Réunion`,
RO: `Romania`,
RS: `Serbia`,
RU: `Russian Federation (the)`,
RW: `Rwanda`,
SA: `Saudi Arabia`,
SB: `Solomon Islands`,
SC: `Seychelles`,
SD: `Sudan (the)`,
SE: `Sweden`,
SG: `Singapore`,
SH: `Saint Helena, Ascension and Tristan da Cunha`,
SI: `Slovenia`,
SJ: `Svalbard and Jan Mayen`,
SK: `Slovakia`,
SL: `Sierra Leone`,
SM: `San Marino`,
SN: `Senegal`,
SO: `Somalia`,
SR: `Suriname`,
SS: `South Sudan`,
ST: `Sao Tome and Principe`,
SV: `El Salvador`,
SX: `Sint Maarten (Dutch part)`,
SY: `Syrian Arab Republic`,
SZ: `Swaziland`,
TC: `Turks and Caicos Islands (the)`,
TD: `Chad`,
TF: `French Southern Territories (the)`,
TG: `Togo`,
TH: `Thailand`,
TJ: `Tajikistan`,
TK: `Tokelau`,
TL: `Timor-Leste`,
TM: `Turkmenistan`,
TN: `Tunisia`,
TO: `Tonga`,
TR: `Turkey`,
TT: `Trinidad and Tobago`,
TV: `Tuvalu`,
TW: `Taiwan (Province of China)`,
TZ: `Tanzania, United Republic of`,
UA: `Ukraine`,
UG: `Uganda`,
UM: `United States Minor Outlying Islands (the)`,
US: `United States of America (the)`,
UY: `Uruguay`,
UZ: `Uzbekistan`,
VA: `Holy See (the)`,
VC: `Saint Vincent and the Grenadines`,
VE: `Venezuela (Bolivarian Republic of)`,
VG: `Virgin Islands (British)`,
VI: `Virgin Islands (U.S.)`,
VN: `Viet Nam`,
VU: `Vanuatu`,
WF: `Wallis and Futuna`,
WS: `Samoa`,
YE: `Yemen`,
YT: `Mayotte`,
ZA: `South Africa`,
ZM: `Zambia`,
ZW: `Zimbabwe`,
};
+43 -43
View File
@@ -3,47 +3,47 @@ import { MessageType } from './interfaces';
export const sampleMessages: { name: string; message: string; id?: number; type?: MessageType; }[] = [];
if (DEVELOPMENT) {
sampleMessages.push(
{ name: 'Soubi', message: 'Me lo hubieras dicho al menos.', type: MessageType.Party },
{ name: 'Doggy', message: 'Mira un menor' },
{ name: 'carry *br*', message: 'menos frama vai...nunca te falei isso' },
{ name: 'Doggy', message: 'A uste le gustan menores' },
{ name: 'Doggy', message: 'Pero no soy menor de edad' },
{ name: 'springtrap girl(:3)', message: 'pelo menos desincalho' },
{ name: 'Foxy (Menina)', message: 'que vida em a metade do povo sabe menos nois ;-;' },
{ name: 'Mangle (br)', message: 'mais pelo menos meus pais ta aqui em casa', type: MessageType.Party },
{ name: 'Experiment-z30 (ESP)', message: 'Con una menos en la clase' },
{ name: '🌙 Ň✞ĦŦΜΔŘ€ ŞΔŇŞ 🌙', message: 'aceita q doi menos' },
{ name: '⭐✨柊|Lihan|柊✨⭐', message: 'quanta frescura no rabo mano, aceita que doi menos' },
{ name: '🌙 Ň✞ĦŦΜΔŘ€ ŞΔŇŞ 🌙', message: 'aceita q doi menos' },
{ name: '⭐✨柊|Lihan|柊✨⭐', message: 'quanta frescura no rabo mano, aceita que doi menos' },
{ name: 'Ilenos Pijama', message: 'Muito menos participar' },
{ name: 'luz(br)', message: 'pelo menos fan nao vai comer todos os lanches' },
{ name: '=tord=', message: 'quien saque menos' },
{ name: 'ladybug', message: 'mais ou menos .-.' },
{ name: 'springtrap girl(:3)', message: 'pelo menos desincalho' },
{ name: 'Foxy (Menina)', message: 'que vida em a metade do povo sabe menos nois ;-;' },
{ name: 'Experiment-z30 (ESP)', message: 'Con una menos en la clase' },
{ name: 'Ilenos Pijama', message: 'Muito menos participar' },
{ name: 'Mangle (br)', message: 'mais pelo menos meus pais ta aqui em casa', type: MessageType.Party },
{ name: 'luz(br)', message: 'pelo menos fan nao vai comer todos os lanches' },
{
message: '/help - show help\n/roll [[min-]max] - randomize a number\n/s - say\n/p - party chat\n/t - thinking baloon',
name: '', type: MessageType.System,
},
{ name: 'Molley', message: 'Some admin message here', type: MessageType.Admin },
{ name: 'Dolleyert', message: 'Some moderator message here', type: MessageType.Mod },
{ name: '', message: 'The server will restart soon', type: MessageType.Announcement },
{ name: 'Molley', message: '🎲 rolled 5 of 100', type: MessageType.Announcement },
{ name: 'Molley', message: 'Some thinki👻n👻g 🍎 mes<b>aaa</b>sage', type: MessageType.Thinking },
{ name: 'Molley', message: 'Some party thinking message', type: MessageType.PartyThinking },
{ name: 'Molley', message: 'Some supporter 🙂 message 1', type: MessageType.Supporter1 },
{ name: 'Molley', message: 'Some supporter 🙂 message 2', type: MessageType.Supporter2, id: 1 },
{ name: 'Molley', message: 'Some supporter 🙂 message 3', type: MessageType.Supporter3, id: 2 },
{ name: 'Molley', message: 'Some whisper message', type: MessageType.Whisper, id: 2 },
{ name: 'WWWWWWWWWWWWWWWWWWWW', message: 'WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW' },
{ name: 'WWWWWWWWWWWWWWWWWWWW', message: 'WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW' },
{ name: 'tord ⚧☿♁⚨⚩⚦⚢⚣⚤', message: 'quien saque menos ⚧☿♁⚨⚩⚦⚢⚣⚤' },
{ name: 'more symbols', message: '♈♉♊♋♌♍♎♏♐♑♒♓⛎♈♉♊♋♌♍♎♏♐♑♒♓⛎♈♉♊♋♌♍♎♏♐♑♒♓⛎' },
);
sampleMessages.push(
{ name: 'Soubi', message: 'Me lo hubieras dicho al menos.', type: MessageType.Party },
{ name: 'Doggy', message: 'Mira un menor' },
{ name: 'carry *br*', message: 'menos frama vai...nunca te falei isso' },
{ name: 'Doggy', message: 'A uste le gustan menores' },
{ name: 'Doggy', message: 'Pero no soy menor de edad' },
{ name: 'springtrap girl(:3)', message: 'pelo menos desincalho' },
{ name: 'Foxy (Menina)', message: 'que vida em a metade do povo sabe menos nois ;-;' },
{ name: 'Mangle (br)', message: 'mais pelo menos meus pais ta aqui em casa', type: MessageType.Party },
{ name: 'Experiment-z30 (ESP)', message: 'Con una menos en la clase' },
{ name: '🌙 Ň✞ĦŦΜΔŘ€ ŞΔŇŞ 🌙', message: 'aceita q doi menos' },
{ name: '⭐✨柊|Lihan|柊✨⭐', message: 'quanta frescura no rabo mano, aceita que doi menos' },
{ name: '🌙 Ň✞ĦŦΜΔŘ€ ŞΔŇŞ 🌙', message: 'aceita q doi menos' },
{ name: '⭐✨柊|Lihan|柊✨⭐', message: 'quanta frescura no rabo mano, aceita que doi menos' },
{ name: 'Ilenos Pijama', message: 'Muito menos participar' },
{ name: 'luz(br)', message: 'pelo menos fan nao vai comer todos os lanches' },
{ name: '=tord=', message: 'quien saque menos' },
{ name: 'ladybug', message: 'mais ou menos .-.' },
{ name: 'springtrap girl(:3)', message: 'pelo menos desincalho' },
{ name: 'Foxy (Menina)', message: 'que vida em a metade do povo sabe menos nois ;-;' },
{ name: 'Experiment-z30 (ESP)', message: 'Con una menos en la clase' },
{ name: 'Ilenos Pijama', message: 'Muito menos participar' },
{ name: 'Mangle (br)', message: 'mais pelo menos meus pais ta aqui em casa', type: MessageType.Party },
{ name: 'luz(br)', message: 'pelo menos fan nao vai comer todos os lanches' },
{
message: '/help - show help\n/roll [[min-]max] - randomize a number\n/s - say\n/p - party chat\n/t - thinking baloon',
name: '', type: MessageType.System,
},
{ name: 'Molley', message: 'Some admin message here', type: MessageType.Admin },
{ name: 'Dolleyert', message: 'Some moderator message here', type: MessageType.Mod },
{ name: '', message: 'The server will restart soon', type: MessageType.Announcement },
{ name: 'Molley', message: '🎲 rolled 5 of 100', type: MessageType.Announcement },
{ name: 'Molley', message: 'Some thinki👻n👻g 🍎 mes<b>aaa</b>sage', type: MessageType.Thinking },
{ name: 'Molley', message: 'Some party thinking message', type: MessageType.PartyThinking },
{ name: 'Molley', message: 'Some supporter 🙂 message 1', type: MessageType.Supporter1 },
{ name: 'Molley', message: 'Some supporter 🙂 message 2', type: MessageType.Supporter2, id: 1 },
{ name: 'Molley', message: 'Some supporter 🙂 message 3', type: MessageType.Supporter3, id: 2 },
{ name: 'Molley', message: 'Some whisper message', type: MessageType.Whisper, id: 2 },
{ name: 'WWWWWWWWWWWWWWWWWWWW', message: 'WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW' },
{ name: 'WWWWWWWWWWWWWWWWWWWW', message: 'WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW' },
{ name: 'tord ⚧☿♁⚨⚩⚦⚢⚣⚤', message: 'quien saque menos ⚧☿♁⚨⚩⚦⚢⚣⚤' },
{ name: 'more symbols', message: '♈♉♊♋♌♍♎♏♐♑♒♓⛎♈♉♊♋♌♍♎♏♐♑♒♓⛎♈♉♊♋♌♍♎♏♐♑♒♓⛎' },
);
}
+16 -16
View File
@@ -4,31 +4,31 @@ import { hasFlag } from '../utils';
export const EMPTY_EXPRESSION = 0x1fffffff;
export function encodeExpression(expression: Expression | undefined): number {
if (!expression)
return EMPTY_EXPRESSION;
if (!expression)
return EMPTY_EXPRESSION;
const { extra, rightIris, leftIris, right, left, muzzle } = expression;
const { extra, rightIris, leftIris, right, left, muzzle } = expression;
// bits: 5 | 4 | 4 | 5 | 5 | 5 = 28/32
return ((extra << 23) | (rightIris << 19) | (leftIris << 15) | (right << 10) | (left << 5) | muzzle) >>> 0;
// bits: 5 | 4 | 4 | 5 | 5 | 5 = 28/32
return ((extra << 23) | (rightIris << 19) | (leftIris << 15) | (right << 10) | (left << 5) | muzzle) >>> 0;
}
export function decodeExpression(value: number): Expression | undefined {
value = value >>> 0;
value = value >>> 0;
if (value === EMPTY_EXPRESSION)
return undefined;
if (value === EMPTY_EXPRESSION)
return undefined;
const muzzle = value & 0x1f;
const left = (value >> 5) & 0x1f;
const right = (value >> 10) & 0x1f;
const leftIris = (value >> 15) & 0xf;
const rightIris = (value >> 19) & 0xf;
const extra = (value >> 23) & 0x1f;
const muzzle = value & 0x1f;
const left = (value >> 5) & 0x1f;
const right = (value >> 10) & 0x1f;
const leftIris = (value >> 15) & 0xf;
const rightIris = (value >> 19) & 0xf;
const extra = (value >> 23) & 0x1f;
return { muzzle, left, right, leftIris, rightIris, extra };
return { muzzle, left, right, leftIris, rightIris, extra };
}
export function isCancellableExpression(expression: Expression) {
return hasFlag(expression.extra, ExpressionExtra.Zzz);
return hasFlag(expression.extra, ExpressionExtra.Zzz);
}
+97 -97
View File
@@ -1,152 +1,152 @@
import {
BinaryWriter, BinaryReader, writeInt16, readInt16, createBinaryReader, readUint16, readLength,
readUint32, readUint8, readObject, readUint8Array
BinaryWriter, BinaryReader, writeInt16, readInt16, createBinaryReader, readUint16, readLength,
readUint32, readUint8, readObject, readUint8Array
} from 'ag-sockets/dist/browser';
import { decodeString } from 'ag-sockets/dist/utf8';
import { DecodedUpdate, DecodedRegionUpdate, TileUpdate, UpdateFlags } from '../interfaces';
import { tileWidth, tileHeight, MAX_VELOCITY } from '../constants';
export function writeVelocity(writer: BinaryWriter, value: number) {
if (value >= MAX_VELOCITY || value <= -MAX_VELOCITY) {
throw new Error(`Exceeded max velocity (${value})`);
}
if (value >= MAX_VELOCITY || value <= -MAX_VELOCITY) {
throw new Error(`Exceeded max velocity (${value})`);
}
writeInt16(writer, (value * 0x8000) / MAX_VELOCITY);
writeInt16(writer, (value * 0x8000) / MAX_VELOCITY);
}
export function readVelocity(reader: BinaryReader) {
return (readInt16(reader) * MAX_VELOCITY) / 0x8000;
return (readInt16(reader) * MAX_VELOCITY) / 0x8000;
}
export function writeCoordX(writer: BinaryWriter, value: number) {
writeInt16(writer, (value * tileWidth) | 0);
writeInt16(writer, (value * tileWidth) | 0);
}
export function writeCoordY(writer: BinaryWriter, value: number) {
writeInt16(writer, (value * tileHeight) | 0);
writeInt16(writer, (value * tileHeight) | 0);
}
export function readCoordX(reader: BinaryReader) {
return readInt16(reader) / tileWidth;
return readInt16(reader) / tileWidth;
}
export function readCoordY(reader: BinaryReader) {
return readInt16(reader) / tileHeight;
return readInt16(reader) / tileHeight;
}
export function emptyUpdate(id: number): DecodedUpdate {
return {
id,
x: undefined,
y: undefined,
vx: 0,
vy: 0,
state: undefined,
expression: undefined,
type: undefined,
options: undefined,
crc: undefined,
name: undefined,
filterName: false,
info: undefined,
action: undefined,
switchRegion: false,
playerState: undefined,
};
return {
id,
x: undefined,
y: undefined,
vx: 0,
vy: 0,
state: undefined,
expression: undefined,
type: undefined,
options: undefined,
crc: undefined,
name: undefined,
filterName: false,
info: undefined,
action: undefined,
switchRegion: false,
playerState: undefined,
};
}
export function decodeUpdate(data: Uint8Array): DecodedRegionUpdate {
const reader = createBinaryReader(data);
const x = readUint16(reader);
const y = readUint16(reader);
const updates: DecodedUpdate[] = [];
let update: DecodedUpdate | undefined;
const reader = createBinaryReader(data);
const x = readUint16(reader);
const y = readUint16(reader);
const updates: DecodedUpdate[] = [];
let update: DecodedUpdate | undefined;
while (update = readOneUpdate(reader)) {
updates.push(update);
}
while (update = readOneUpdate(reader)) {
updates.push(update);
}
const removesLength = readLength(reader);
const removes: number[] = [];
const removesLength = readLength(reader);
const removes: number[] = [];
for (let i = 0; i < removesLength; i++) {
removes.push(readUint32(reader));
}
for (let i = 0; i < removesLength; i++) {
removes.push(readUint32(reader));
}
const tilesLength = readLength(reader);
const tiles: TileUpdate[] = [];
const tilesLength = readLength(reader);
const tiles: TileUpdate[] = [];
for (let i = 0; i < tilesLength; i++) {
tiles.push({
x: readUint8(reader),
y: readUint8(reader),
type: readUint8(reader),
});
}
for (let i = 0; i < tilesLength; i++) {
tiles.push({
x: readUint8(reader),
y: readUint8(reader),
type: readUint8(reader),
});
}
const tileData = readUint8Array(reader);
const tileData = readUint8Array(reader);
return { x, y, updates, removes, tiles, tileData };
return { x, y, updates, removes, tiles, tileData };
}
export function readOneUpdate(reader: BinaryReader): DecodedUpdate | undefined {
if (reader.offset >= reader.view.byteLength)
return undefined;
if (reader.offset >= reader.view.byteLength)
return undefined;
const flags = readUint16(reader);
const flags = readUint16(reader);
if (flags === 0) {
return undefined;
}
if (flags === 0) {
return undefined;
}
const id = readUint32(reader);
const update = emptyUpdate(id);
const id = readUint32(reader);
const update = emptyUpdate(id);
update.switchRegion = (flags & UpdateFlags.SwitchRegion) !== 0;
update.switchRegion = (flags & UpdateFlags.SwitchRegion) !== 0;
if ((flags & UpdateFlags.Position) !== 0) {
update.x = readCoordX(reader);
update.y = readCoordY(reader);
}
if ((flags & UpdateFlags.Position) !== 0) {
update.x = readCoordX(reader);
update.y = readCoordY(reader);
}
if ((flags & UpdateFlags.Velocity) !== 0) {
update.vx = readVelocity(reader);
update.vy = readVelocity(reader);
}
if ((flags & UpdateFlags.Velocity) !== 0) {
update.vx = readVelocity(reader);
update.vy = readVelocity(reader);
}
if ((flags & UpdateFlags.State) !== 0) {
update.state = readUint8(reader);
}
if ((flags & UpdateFlags.State) !== 0) {
update.state = readUint8(reader);
}
if ((flags & UpdateFlags.Expression) !== 0) {
update.expression = readUint32(reader);
}
if ((flags & UpdateFlags.Expression) !== 0) {
update.expression = readUint32(reader);
}
if ((flags & UpdateFlags.Type) !== 0) {
update.type = readUint16(reader);
}
if ((flags & UpdateFlags.Type) !== 0) {
update.type = readUint16(reader);
}
if ((flags & UpdateFlags.Options) !== 0) {
update.options = readObject(reader);
}
if ((flags & UpdateFlags.Options) !== 0) {
update.options = readObject(reader);
}
if ((flags & UpdateFlags.Info) !== 0) {
update.crc = readUint16(reader);
update.info = readUint8Array(reader)!;
}
if ((flags & UpdateFlags.Info) !== 0) {
update.crc = readUint16(reader);
update.info = readUint8Array(reader)!;
}
if ((flags & UpdateFlags.Action) !== 0) {
update.action = readUint8(reader);
}
if ((flags & UpdateFlags.Action) !== 0) {
update.action = readUint8(reader);
}
if ((flags & UpdateFlags.Name) !== 0) {
update.name = decodeString(readUint8Array(reader)) || undefined;
update.filterName = (flags & UpdateFlags.NameBad) !== 0;
}
if ((flags & UpdateFlags.Name) !== 0) {
update.name = decodeString(readUint8Array(reader)) || undefined;
update.filterName = (flags & UpdateFlags.NameBad) !== 0;
}
if ((flags & UpdateFlags.PlayerState) !== 0) {
update.playerState = readUint8(reader);
}
if ((flags & UpdateFlags.PlayerState) !== 0) {
update.playerState = readUint8(reader);
}
return update;
return update;
}
+101 -101
View File
@@ -1,5 +1,5 @@
import {
BinaryWriter, writeUint32, writeUint16, writeUint8, writeObject, writeUint8Array, writeLength
BinaryWriter, writeUint32, writeUint16, writeUint8, writeObject, writeUint8Array, writeLength
} from 'ag-sockets/dist/browser';
import { UpdateFlags, EntityPlayerState, Action } from '../interfaces';
import { writeBinary } from '../binaryUtils';
@@ -11,157 +11,157 @@ import { getPlayerState } from '../../server/playerUtils';
import { logger } from '../../server/logger';
function getOptionsOrUndefined(entity: ServerEntity) {
return (entity.options !== undefined && Object.keys(entity.options).length > 0) ? entity.options : undefined;
return (entity.options !== undefined && Object.keys(entity.options).length > 0) ? entity.options : undefined;
}
export function writeOneUpdate(
writer: BinaryWriter, entity: ServerEntity, flags: UpdateFlags, x: number, y: number, vx: number, vy: number,
options: any, action: Action, playerState: EntityPlayerState
writer: BinaryWriter, entity: ServerEntity, flags: UpdateFlags, x: number, y: number, vx: number, vy: number,
options: any, action: Action, playerState: EntityPlayerState
) {
if (DEVELOPMENT && flags === 0) {
logger.error(`Writing empty update`);
}
if (DEVELOPMENT && flags === 0) {
logger.error(`Writing empty update`);
}
if ((flags & UpdateFlags.Position) !== 0) {
flags |= UpdateFlags.State;
if ((flags & UpdateFlags.Position) !== 0) {
flags |= UpdateFlags.State;
if (vx || vy) {
flags |= UpdateFlags.Velocity;
}
}
if (vx || vy) {
flags |= UpdateFlags.Velocity;
}
}
if ((flags & UpdateFlags.Name) !== 0 && entity.nameBad === true) {
flags |= UpdateFlags.NameBad;
}
if ((flags & UpdateFlags.Name) !== 0 && entity.nameBad === true) {
flags |= UpdateFlags.NameBad;
}
writeUint16(writer, flags);
writeUint32(writer, entity.id);
writeUint16(writer, flags);
writeUint32(writer, entity.id);
if ((flags & UpdateFlags.Position) !== 0) {
writeCoordX(writer, x);
writeCoordY(writer, y);
}
if ((flags & UpdateFlags.Position) !== 0) {
writeCoordX(writer, x);
writeCoordY(writer, y);
}
if ((flags & UpdateFlags.Velocity) !== 0) {
writeVelocity(writer, vx);
writeVelocity(writer, vy);
}
if ((flags & UpdateFlags.Velocity) !== 0) {
writeVelocity(writer, vx);
writeVelocity(writer, vy);
}
if ((flags & UpdateFlags.State) !== 0) {
writeUint8(writer, entity.state);
}
if ((flags & UpdateFlags.State) !== 0) {
writeUint8(writer, entity.state);
}
if ((flags & UpdateFlags.Expression) !== 0) {
writeUint32(writer, entity.options!.expr!);
}
if ((flags & UpdateFlags.Expression) !== 0) {
writeUint32(writer, entity.options!.expr!);
}
if ((flags & UpdateFlags.Type) !== 0) {
writeUint16(writer, entity.type);
}
if ((flags & UpdateFlags.Type) !== 0) {
writeUint16(writer, entity.type);
}
if ((flags & UpdateFlags.Options) !== 0) {
writeObject(writer, options);
}
if ((flags & UpdateFlags.Options) !== 0) {
writeObject(writer, options);
}
if ((flags & UpdateFlags.Info) !== 0) {
writeUint16(writer, entity.crc!);
writeUint8Array(writer, entity.encryptedInfoSafe!);
}
if ((flags & UpdateFlags.Info) !== 0) {
writeUint16(writer, entity.crc!);
writeUint8Array(writer, entity.encryptedInfoSafe!);
}
if ((flags & UpdateFlags.Action) !== 0) {
writeUint8(writer, action!);
}
if ((flags & UpdateFlags.Action) !== 0) {
writeUint8(writer, action!);
}
if ((flags & UpdateFlags.Name) !== 0) {
writeUint8Array(writer, entity.encodedName!);
}
if ((flags & UpdateFlags.Name) !== 0) {
writeUint8Array(writer, entity.encodedName!);
}
if ((flags & UpdateFlags.PlayerState) !== 0) {
writeUint8(writer, playerState!);
}
if ((flags & UpdateFlags.PlayerState) !== 0) {
writeUint8(writer, playerState!);
}
}
export function writeOneEntity(writer: BinaryWriter, entity: ServerEntity, client: IClient) {
const { x, y, vx, vy } = entity;
// TODO: const expression = !!entity.options && !!entity.options.expr; // instead of in options
const options = getOptionsOrUndefined(entity);
const playerState = getPlayerState(client, entity);
const { x, y, vx, vy } = entity;
// TODO: const expression = !!entity.options && !!entity.options.expr; // instead of in options
const options = getOptionsOrUndefined(entity);
const playerState = getPlayerState(client, entity);
let flags = UpdateFlags.Position | UpdateFlags.State | UpdateFlags.Type;
let flags = UpdateFlags.Position | UpdateFlags.State | UpdateFlags.Type;
if (entity.encryptedInfoSafe !== undefined) {
flags |= UpdateFlags.Info;
}
if (entity.encryptedInfoSafe !== undefined) {
flags |= UpdateFlags.Info;
}
if (entity.encodedName !== undefined) {
flags |= UpdateFlags.Name;
}
if (entity.encodedName !== undefined) {
flags |= UpdateFlags.Name;
}
if (playerState !== 0) {
flags |= UpdateFlags.PlayerState;
}
if (playerState !== 0) {
flags |= UpdateFlags.PlayerState;
}
if (options !== undefined) {
flags |= UpdateFlags.Options;
}
if (options !== undefined) {
flags |= UpdateFlags.Options;
}
writeOneUpdate(writer, entity, flags, x, y, vx, vy, options, Action.None, playerState);
writeOneUpdate(writer, entity, flags, x, y, vx, vy, options, Action.None, playerState);
}
export function writeUpdate(writer: BinaryWriter, region: ServerRegion) {
const { x, y, entityUpdates, entityRemoves, tileUpdates } = region;
const { x, y, entityUpdates, entityRemoves, tileUpdates } = region;
writeUint16(writer, x);
writeUint16(writer, y);
writeUint16(writer, x);
writeUint16(writer, y);
for (const { entity, flags, x, y, vx, vy, options, action, playerState } of entityUpdates) {
writeOneUpdate(writer, entity, flags, x, y, vx, vy, options, action, playerState);
}
for (const { entity, flags, x, y, vx, vy, options, action, playerState } of entityUpdates) {
writeOneUpdate(writer, entity, flags, x, y, vx, vy, options, action, playerState);
}
writeUint16(writer, 0); // end marker
writeUint16(writer, 0); // end marker
writeLength(writer, entityRemoves.length);
writeLength(writer, entityRemoves.length);
for (const remove of entityRemoves) {
writeUint32(writer, remove);
}
for (const remove of entityRemoves) {
writeUint32(writer, remove);
}
writeLength(writer, tileUpdates.length);
writeLength(writer, tileUpdates.length);
for (const { x, y, type: tile } of tileUpdates) {
writeUint8(writer, x);
writeUint8(writer, y);
writeUint8(writer, tile);
}
for (const { x, y, type: tile } of tileUpdates) {
writeUint8(writer, x);
writeUint8(writer, y);
writeUint8(writer, tile);
}
writeUint8Array(writer, null); // tile data
writeUint8Array(writer, null); // tile data
}
export function writeRegion(writer: BinaryWriter, region: ServerRegion, client: IClient) {
const { x, y, entities } = region;
const { x, y, entities } = region;
writeUint16(writer, x);
writeUint16(writer, y);
writeUint16(writer, x);
writeUint16(writer, y);
for (const entity of entities) {
if (!isEntityShadowed(entity) || entity === client.pony) {
writeOneEntity(writer, entity, client);
}
}
for (const entity of entities) {
if (!isEntityShadowed(entity) || entity === client.pony) {
writeOneEntity(writer, entity, client);
}
}
writeUint16(writer, 0); // end marker
writeUint16(writer, 0); // end marker
writeLength(writer, 0); // removes
writeLength(writer, 0); // tile updates
writeUint8Array(writer, getRegionTiles(region)); // tile data
writeLength(writer, 0); // removes
writeLength(writer, 0); // tile updates
writeUint8Array(writer, getRegionTiles(region)); // tile data
}
// For testing
export function encodeUpdateSimple(region: ServerRegion) {
return writeBinary(writer => writeUpdate(writer, region));
return writeBinary(writer => writeUpdate(writer, region));
}
// For testing
export function encodeRegionSimple(region: ServerRegion, client: IClient) {
return writeBinary(writer => writeRegion(writer, region, client));
return writeBinary(writer => writeRegion(writer, region, client));
}
+1207 -1207
View File
File diff suppressed because it is too large Load Diff
+76 -76
View File
@@ -1,6 +1,6 @@
import { sort } from 'timsort';
import {
Entity, EntityState, BodyAnimation, Point, EntityFlags, IMap, Pony, Says, EntityPlayerState, WorldMap
Entity, EntityState, BodyAnimation, Point, EntityFlags, IMap, Pony, Says, EntityPlayerState, WorldMap
} from './interfaces';
import { hasFlag, distance, pushUniq, setFlag } from './utils';
import { stand, sit, lie, fly, flyBug, swim } from '../client/ponyAnimations';
@@ -13,91 +13,91 @@ import { PONY_TYPE } from './constants';
import { isStaticCollision } from './collision';
export function releaseEntity(entity: Entity) {
if (isPony(entity)) {
releasePony(entity);
}
if (isPony(entity)) {
releasePony(entity);
}
if (entity.palettes !== undefined) {
for (const palette of entity.palettes) {
releasePalette(palette);
}
}
if (entity.palettes !== undefined) {
for (const palette of entity.palettes) {
releasePalette(palette);
}
}
}
export function addChatBubble(map: WorldMap, entity: Entity, says: Says) {
entity.says = says;
pushUniq(map.entitiesWithChat, entity);
entity.says = says;
pushUniq(map.entitiesWithChat, entity);
}
export function updateEntityVelocity(map: WorldMap, entity: Entity, vx: number, vy: number) {
const wasMoving = isMoving(entity);
entity.vx = vx;
entity.vy = vy;
const isMovingNow = isMoving(entity);
addOrRemoveFromEntityList(map.entitiesMoving, entity, wasMoving, isMovingNow);
const wasMoving = isMoving(entity);
entity.vx = vx;
entity.vy = vy;
const isMovingNow = isMoving(entity);
addOrRemoveFromEntityList(map.entitiesMoving, entity, wasMoving, isMovingNow);
}
export function compareEntities(a: Entity, b: Entity) {
return (toScreenY(a.y) - toScreenY(b.y))
|| (a.order - b.order)
|| (b.id - a.id)
|| (toScreenX(a.x) - toScreenX(b.x))
|| (toScreenY(a.z) - toScreenY(b.z)
);
return (toScreenY(a.y) - toScreenY(b.y))
|| (a.order - b.order)
|| (b.id - a.id)
|| (toScreenX(a.x) - toScreenX(b.x))
|| (toScreenY(a.z) - toScreenY(b.z)
);
}
export function sortEntities(entities: Entity[]) {
sort(entities, compareEntities);
sort(entities, compareEntities);
}
export function closestEntity(point: Point, entities: Entity[]): Entity | undefined {
return entities.reduce((best, entity) => distance(point, entity) < distance(point, best) ? entity : best, entities[0]);
return entities.reduce((best, entity) => distance(point, entity) < distance(point, best) ? entity : best, entities[0]);
}
export function getBoopRect(entity: Entity) {
const right = hasFlag(entity.state, EntityState.FacingRight);
const sitting = isPonySitting(entity);
return rect(entity.x + (right ? 0.6 : -0.9) * (sitting ? 0.6 : 1), entity.y - 0.2, 0.3, 0.4);
const right = hasFlag(entity.state, EntityState.FacingRight);
const sitting = isPonySitting(entity);
return rect(entity.x + (right ? 0.6 : -0.9) * (sitting ? 0.6 : 1), entity.y - 0.2, 0.3, 0.4);
}
export function isMoving(entity: Entity) {
return entity.vx !== 0 || entity.vy !== 0;
return entity.vx !== 0 || entity.vy !== 0;
}
export function isDrawable(entity: Entity) {
return entity.type === PONY_TYPE || entity.draw !== undefined;
return entity.type === PONY_TYPE || entity.draw !== undefined;
}
export function canLand<T>(entity: Entity, map: IMap<T>) {
return !isStaticCollision(entity, map, true);
return !isStaticCollision(entity, map, true);
}
export function canStand<T>(entity: Entity, map: IMap<T>) {
return !isPonyStanding(entity) && isPonyLandedOrCanLand(entity, map);
return !isPonyStanding(entity) && isPonyLandedOrCanLand(entity, map);
}
export function canSit<T>(entity: Entity, map: IMap<T>) {
return !isPonySitting(entity) && isPonyLandedOrCanLand(entity, map) && !isMoving(entity);
return !isPonySitting(entity) && isPonyLandedOrCanLand(entity, map) && !isMoving(entity);
}
export function canLie<T>(entity: Entity, map: IMap<T>) {
return !isPonyLying(entity) && isPonyLandedOrCanLand(entity, map) && !isMoving(entity);
return !isPonyLying(entity) && isPonyLandedOrCanLand(entity, map) && !isMoving(entity);
}
export function entityInRange(entity: Entity, player: Entity) {
return (!entity.interactRange || distance(player, entity) < entity.interactRange);
return (!entity.interactRange || distance(player, entity) < entity.interactRange);
}
export function getInteractBounds(pony: Pony) {
const boundsWidth = 1;
const boundsHeight = 1;
const boundsOffset = 0.5 + (isPonySitting(pony) ? -0.3 : (isPonyLying(pony) ? -0.2 : 0));
const boundsWidth = 1;
const boundsHeight = 1;
const boundsOffset = 0.5 + (isPonySitting(pony) ? -0.3 : (isPonyLying(pony) ? -0.2 : 0));
return rect(
toScreenX(isFacingRight(pony) ? (pony.x + boundsOffset) : (pony.x - boundsOffset - boundsWidth)),
toScreenY(pony.y - boundsHeight / 2),
toScreenX(boundsWidth),
toScreenY(boundsHeight));
return rect(
toScreenX(isFacingRight(pony) ? (pony.x + boundsOffset) : (pony.x - boundsOffset - boundsWidth)),
toScreenY(pony.y - boundsHeight / 2),
toScreenX(boundsWidth),
toScreenY(boundsHeight));
}
export const SIT_ON_BOUNDS_WIDTH = 1.2;
@@ -105,121 +105,121 @@ export const SIT_ON_BOUNDS_HEIGHT = 0.5;
export const SIT_ON_BOUNDS_OFFSET = 0.4;
export function getSitOnBounds(pony: Pony) {
const width = SIT_ON_BOUNDS_WIDTH;
const height = SIT_ON_BOUNDS_HEIGHT;
const offset = isFacingRight(pony) ? -SIT_ON_BOUNDS_OFFSET : (SIT_ON_BOUNDS_OFFSET - SIT_ON_BOUNDS_WIDTH);
return rect(toScreenX(pony.x + offset), toScreenY(pony.y - SIT_ON_BOUNDS_HEIGHT / 2), toScreenX(width), toScreenY(height));
const width = SIT_ON_BOUNDS_WIDTH;
const height = SIT_ON_BOUNDS_HEIGHT;
const offset = isFacingRight(pony) ? -SIT_ON_BOUNDS_OFFSET : (SIT_ON_BOUNDS_OFFSET - SIT_ON_BOUNDS_WIDTH);
return rect(toScreenX(pony.x + offset), toScreenY(pony.y - SIT_ON_BOUNDS_HEIGHT / 2), toScreenX(width), toScreenY(height));
}
// pony state
export function isIdleAnimation(animation: BodyAnimation) {
return animation === stand || animation === sit || animation === lie || animation === fly ||
animation === flyBug || animation === swim;
return animation === stand || animation === sit || animation === lie || animation === fly ||
animation === flyBug || animation === swim;
}
export function isIdle(pony: Pony) {
return !isMoving(pony) && isIdleAnimation(pony.ponyState.animation);
return !isMoving(pony) && isIdleAnimation(pony.ponyState.animation);
}
export function canBoop(pony: Pony) {
return isIdle(pony);
return isIdle(pony);
}
export function canBoop2(entity: Entity) {
return !isMoving(entity) && (isPonyStanding(entity) || isPonySitting(entity) || isPonyLying(entity) || isPonyFlying(entity));
return !isMoving(entity) && (isPonyStanding(entity) || isPonySitting(entity) || isPonyLying(entity) || isPonyFlying(entity));
}
// entity player state
export function isHidden(entity: Entity) {
return (entity.playerState & EntityPlayerState.Hidden) !== 0;
return (entity.playerState & EntityPlayerState.Hidden) !== 0;
}
export function isIgnored(entity: Entity) {
return (entity.playerState & EntityPlayerState.Ignored) !== 0;
return (entity.playerState & EntityPlayerState.Ignored) !== 0;
}
export function isFriend(entity: Entity) {
return (entity.playerState & EntityPlayerState.Friend) !== 0;
return (entity.playerState & EntityPlayerState.Friend) !== 0;
}
export function isInTheAir(entity: Entity) {
return isFlying(entity) && (entity.inTheAirDelay === undefined || entity.inTheAirDelay <= 0);
return isFlying(entity) && (entity.inTheAirDelay === undefined || entity.inTheAirDelay <= 0);
}
// entity state
export function isFlying(entity: Entity) {
return (entity.state & EntityState.Flying) !== 0;
return (entity.state & EntityState.Flying) !== 0;
}
export function isFacingRight(entity: Entity) {
return (entity.state & EntityState.FacingRight) !== 0;
return (entity.state & EntityState.FacingRight) !== 0;
}
export function hasHeadTurned(entity: Entity) {
return (entity.state & EntityState.HeadTurned) !== 0;
return (entity.state & EntityState.HeadTurned) !== 0;
}
export function isHeadFacingRight(entity: Entity) {
const headTurned = hasHeadTurned(entity);
const facingRight = isFacingRight(entity);
return facingRight ? !headTurned : headTurned;
const headTurned = hasHeadTurned(entity);
const facingRight = isFacingRight(entity);
return facingRight ? !headTurned : headTurned;
}
export function getPonyState(state: EntityState): EntityState {
return state & EntityState.PonyStateMask;
return state & EntityState.PonyStateMask;
}
export function setPonyState(state: EntityState, set: EntityState) {
state = (state & ~EntityState.PonyStateMask) | set;
state = setFlag(state, EntityState.Flying, set === EntityState.PonyFlying);
return state;
state = (state & ~EntityState.PonyStateMask) | set;
state = setFlag(state, EntityState.Flying, set === EntityState.PonyFlying);
return state;
}
export function isSittingState(state: EntityState) {
return getPonyState(state) === EntityState.PonySitting;
return getPonyState(state) === EntityState.PonySitting;
}
export function isLyingState(state: EntityState) {
return getPonyState(state) === EntityState.PonyLying;
return getPonyState(state) === EntityState.PonyLying;
}
export function isPonyWalking(entity: Entity) {
return getPonyState(entity.state) === EntityState.PonyWalking;
return getPonyState(entity.state) === EntityState.PonyWalking;
}
export function isPonyTrotting(entity: Entity) {
return getPonyState(entity.state) === EntityState.PonyTrotting;
return getPonyState(entity.state) === EntityState.PonyTrotting;
}
export function isPonySitting(entity: Entity) {
return getPonyState(entity.state) === EntityState.PonySitting;
return getPonyState(entity.state) === EntityState.PonySitting;
}
export function isPonyStanding(entity: Entity) {
return getPonyState(entity.state) === EntityState.PonyStanding;
return getPonyState(entity.state) === EntityState.PonyStanding;
}
export function isPonyLying(entity: Entity) {
return getPonyState(entity.state) === EntityState.PonyLying;
return getPonyState(entity.state) === EntityState.PonyLying;
}
export function isPonyFlying(entity: Entity) {
return getPonyState(entity.state) === EntityState.PonyFlying;
return getPonyState(entity.state) === EntityState.PonyFlying;
}
export function isPonyLandedOrCanLand<T>(entity: Entity, map: IMap<T>) {
return !isPonyFlying(entity) || canLand(entity, map);
return !isPonyFlying(entity) || canLand(entity, map);
}
// entity flags
export function isDecal(entity: Entity) {
return (entity.flags & EntityFlags.Decal) !== 0;
return (entity.flags & EntityFlags.Decal) !== 0;
}
export function isCritter(entity: Entity) {
return (entity.flags & EntityFlags.Critter) !== 0;
return (entity.flags & EntityFlags.Critter) !== 0;
}
+219 -219
View File
@@ -7,26 +7,26 @@ const prefix = (items: string[], fix: string) => items.map(x => fix + x);
const suffix = (items: string[], fix: string) => items.map(x => x + fix);
export const THREE_LETTER_WORDS = [
'ace', 'act', 'ama', 'amp', 'amo', 'amu', 'amy', 'ana', 'ane', 'and', 'ant', 'any', 'ape', 'app', 'apo',
'apt', 'ava', 'ave', 'avo', 'awe', 'awn', 'awp', 'axe',
'boa', 'bob', 'bod', 'bog', 'bon', 'boo', 'bop', 'bot', 'boy', 'bub', 'bud', 'bug', 'bup', 'but', 'bun', 'buy',
'dad', 'doe', 'dog', 'dot', 'doy', 'dna', 'dub', 'dud', 'due', 'dun', 'dug', 'duo', 'dup', 'dva', 'dvd',
'eco', 'ecu', 'eme', 'emu', 'emo', 'eon', 'end', 'eng', 'eva', 'eve', 'exe', 'exp',
'gnu', 'goa', 'god', 'gog', 'gon', 'goo', 'got', 'gud', 'gut', 'gun', 'guv', 'guy',
'nnn', 'nog', 'non', 'noo', 'nop', 'not', 'nun', 'nut', 'nub',
'oca', 'omo', 'one', 'ooo', 'oot', 'ope', 'opt', 'oud', 'out', 'ova', 'owe', 'own', 'oxo', 'oxe', 'omg',
'pay', 'pnp', 'pod', 'pon', 'poo', 'pop', 'pot', 'pov', 'ppp', 'pub', 'pud', 'pug', 'pup', 'pun', 'put', 'pvp',
'qqq', 'que', 'qua',
'tnt', 'ton', 'top', 'tod', 'toe', 'tog', 'too', 'toy', 'tub', 'tug', 'tun', 'twa', 'two',
'uuu', 'una', 'und', 'uno', 'ump', 'upo', 'uva',
'voe', 'voy', 'vpn', 'vug', 'vvv',
'yay', 'yob', 'yod', 'yon', 'you', 'yup',
'ace', 'act', 'ama', 'amp', 'amo', 'amu', 'amy', 'ana', 'ane', 'and', 'ant', 'any', 'ape', 'app', 'apo',
'apt', 'ava', 'ave', 'avo', 'awe', 'awn', 'awp', 'axe',
'boa', 'bob', 'bod', 'bog', 'bon', 'boo', 'bop', 'bot', 'boy', 'bub', 'bud', 'bug', 'bup', 'but', 'bun', 'buy',
'dad', 'doe', 'dog', 'dot', 'doy', 'dna', 'dub', 'dud', 'due', 'dun', 'dug', 'duo', 'dup', 'dva', 'dvd',
'eco', 'ecu', 'eme', 'emu', 'emo', 'eon', 'end', 'eng', 'eva', 'eve', 'exe', 'exp',
'gnu', 'goa', 'god', 'gog', 'gon', 'goo', 'got', 'gud', 'gut', 'gun', 'guv', 'guy',
'nnn', 'nog', 'non', 'noo', 'nop', 'not', 'nun', 'nut', 'nub',
'oca', 'omo', 'one', 'ooo', 'oot', 'ope', 'opt', 'oud', 'out', 'ova', 'owe', 'own', 'oxo', 'oxe', 'omg',
'pay', 'pnp', 'pod', 'pon', 'poo', 'pop', 'pot', 'pov', 'ppp', 'pub', 'pud', 'pug', 'pup', 'pun', 'put', 'pvp',
'qqq', 'que', 'qua',
'tnt', 'ton', 'top', 'tod', 'toe', 'tog', 'too', 'toy', 'tub', 'tug', 'tun', 'twa', 'two',
'uuu', 'una', 'und', 'uno', 'ump', 'upo', 'uva',
'voe', 'voy', 'vpn', 'vug', 'vvv',
'yay', 'yob', 'yod', 'yon', 'you', 'yup',
];
export const TWO_LETTER_WORDS = [
'ox', 'ex', 'by', 'my', 'up', 'of', 'if', 'me', 'ow', 'am', 'we', 'uh', 'um', 'be', 'em', 'bi', 'oh',
'go', 'eh', 'ah', 'ye', 'ya', 'he', 'hi', 'ho', 'ha', 'yo', 'us', 'on', 'id', 'an', 'do', 'no',
'as', 'at', 'it', 'is', 'or', 'so', 'to', 'pc',
'ox', 'ex', 'by', 'my', 'up', 'of', 'if', 'me', 'ow', 'am', 'we', 'uh', 'um', 'be', 'em', 'bi', 'oh',
'go', 'eh', 'ah', 'ye', 'ya', 'he', 'hi', 'ho', 'ha', 'yo', 'us', 'on', 'id', 'an', 'do', 'no',
'as', 'at', 'it', 'is', 'or', 'so', 'to', 'pc',
];
const threeLetterWords = new RegExp(`^(${THREE_LETTER_WORDS.join('|')})$`);
@@ -39,120 +39,120 @@ const smilesLeft = ['(', '[', '{', '<', 'C', 'c'];
const flatBoth = ['|', 'i', 'l'];
const concernedBoth = ['/', '\\', 's', 'S', '?'];
const muzzlesBoth = [
[Muzzle.Scrunch, 't', 'T', 'I'],
[Muzzle.Blep, 'P', 'p', 'd'],
[Muzzle.FlatBlep, 'b'],
[Muzzle.Flat, ...flatBoth],
[Muzzle.Concerned, ...concernedBoth],
[Muzzle.ConcernedOpen, '0', 'v'],
[Muzzle.ConcernedOpen2, 'O'],
[Muzzle.Oh, 'o'],
[Muzzle.Kiss, '*', 'x', 'X'],
[Muzzle.NeutralPant, 'L'],
[Muzzle.SmilePant, 'Q'],
[Muzzle.FrownOpen, 'V'],
[Muzzle.NeutralOpen2, 'u', 'n'],
[Muzzle.NeutralOpen3, 'U'],
[Muzzle.NeutralTeeth, ...double(flatBoth)],
[Muzzle.ConcernedTeeth, ...double(concernedBoth)],
[Muzzle.Scrunch, 't', 'T', 'I'],
[Muzzle.Blep, 'P', 'p', 'd'],
[Muzzle.FlatBlep, 'b'],
[Muzzle.Flat, ...flatBoth],
[Muzzle.Concerned, ...concernedBoth],
[Muzzle.ConcernedOpen, '0', 'v'],
[Muzzle.ConcernedOpen2, 'O'],
[Muzzle.Oh, 'o'],
[Muzzle.Kiss, '*', 'x', 'X'],
[Muzzle.NeutralPant, 'L'],
[Muzzle.SmilePant, 'Q'],
[Muzzle.FrownOpen, 'V'],
[Muzzle.NeutralOpen2, 'u', 'n'],
[Muzzle.NeutralOpen3, 'U'],
[Muzzle.NeutralTeeth, ...double(flatBoth)],
[Muzzle.ConcernedTeeth, ...double(concernedBoth)],
];
export const muzzlesRight = createMap<Muzzle>([
...muzzlesBoth,
[Muzzle.Smile, '3', ...smilesRight],
[Muzzle.Frown, ...smilesLeft],
[Muzzle.SmileOpen, 'D'],
[Muzzle.SmileOpen2, 'DD'],
[Muzzle.SmileOpen3, 'DDD'],
[Muzzle.SmileTeeth, ...double(smilesRight)],
[Muzzle.FrownTeeth, ...double(smilesLeft)],
...muzzlesBoth,
[Muzzle.Smile, '3', ...smilesRight],
[Muzzle.Frown, ...smilesLeft],
[Muzzle.SmileOpen, 'D'],
[Muzzle.SmileOpen2, 'DD'],
[Muzzle.SmileOpen3, 'DDD'],
[Muzzle.SmileTeeth, ...double(smilesRight)],
[Muzzle.FrownTeeth, ...double(smilesLeft)],
]);
export const muzzlesLeft = createMap<Muzzle>([
...muzzlesBoth,
[Muzzle.Smile, ...smilesLeft],
[Muzzle.Frown, ...smilesRight],
[Muzzle.ConcernedOpen2, 'D'],
[Muzzle.ConcernedOpen3, 'DD', 'DDD'],
[Muzzle.SmileTeeth, ...double(smilesLeft)],
[Muzzle.FrownTeeth, ...double(smilesRight)],
...muzzlesBoth,
[Muzzle.Smile, ...smilesLeft],
[Muzzle.Frown, ...smilesRight],
[Muzzle.ConcernedOpen2, 'D'],
[Muzzle.ConcernedOpen3, 'DD', 'DDD'],
[Muzzle.SmileTeeth, ...double(smilesLeft)],
[Muzzle.FrownTeeth, ...double(smilesRight)],
]);
const neutralEyes = [';', ':', '=', '%', '8'];
const verticalEyesBoth = [
[Eye.Neutral, ...neutralEyes],
[Eye.X, 'X', 'x'],
[Eye.Neutral3, 'B'],
[Eye.Lines, '|'],
[Eye.Neutral, ...neutralEyes],
[Eye.X, 'X', 'x'],
[Eye.Neutral3, 'B'],
[Eye.Lines, '|'],
];
export const verticalEyesRight = createMap<Eye>([
...verticalEyesBoth,
[Eye.Angry, ...prefix(neutralEyes, '>')],
[Eye.Angry2, '>B'],
[Eye.Sad, ...prefix(neutralEyes, '<')],
[Eye.Sad2, '<B'],
[Eye.Frown, ...prefix(neutralEyes, '|')],
[Eye.Frown2, '|B'],
...verticalEyesBoth,
[Eye.Angry, ...prefix(neutralEyes, '>')],
[Eye.Angry2, '>B'],
[Eye.Sad, ...prefix(neutralEyes, '<')],
[Eye.Sad2, '<B'],
[Eye.Frown, ...prefix(neutralEyes, '|')],
[Eye.Frown2, '|B'],
]);
export const verticalEyesLeft = createMap<Eye>([
...verticalEyesBoth,
[Eye.Angry, ...suffix(neutralEyes, '<')],
[Eye.Sad, ...suffix(neutralEyes, '>')],
[Eye.Frown, ...suffix(neutralEyes, '|')],
...verticalEyesBoth,
[Eye.Angry, ...suffix(neutralEyes, '<')],
[Eye.Sad, ...suffix(neutralEyes, '>')],
[Eye.Frown, ...suffix(neutralEyes, '|')],
]);
// horizontal -_-
export const horizontalMuzzles = createMap<Muzzle>([
[Muzzle.Smile, 'c', 'C', 'v', 'V', 'u', 'U', 'w', 'W', '👃'],
[Muzzle.SmilePant, 'Q', 'P'],
[Muzzle.Frown, 'n', 'm', '^'],
[Muzzle.Neutral, '-', '//'],
[Muzzle.NeutralPant, 'q', 'p'],
[Muzzle.Flat, '_'],
[Muzzle.Kiss, '.', ',', '*', 'x', 'X', '3'],
[Muzzle.Concerned, '~'],
[Muzzle.ConcernedOpen, 'o'],
[Muzzle.ConcernedOpen2, 'A', 'O', '0'],
[Muzzle.Smile, 'c', 'C', 'v', 'V', 'u', 'U', 'w', 'W', '👃'],
[Muzzle.SmilePant, 'Q', 'P'],
[Muzzle.Frown, 'n', 'm', '^'],
[Muzzle.Neutral, '-', '//'],
[Muzzle.NeutralPant, 'q', 'p'],
[Muzzle.Flat, '_'],
[Muzzle.Kiss, '.', ',', '*', 'x', 'X', '3'],
[Muzzle.Concerned, '~'],
[Muzzle.ConcernedOpen, 'o'],
[Muzzle.ConcernedOpen2, 'A', 'O', '0'],
]);
const horizontalEyes = [
[Eye.Neutral, `'`, '.', '0', '°', 'o', 'O', 'e', 'g', '9', '6', 'd', 'b'],
[Eye.Neutral4, '='],
[Eye.Closed, '-', 'v', 'V', 'u', 'U', 'y', 'Y'],
[Eye.ClosedHappy, 'n'],
[Eye.ClosedHappy2, '^'],
[Eye.Sad, 'q', 'Q', 'p', 'P', ';', ':', ','],
[Eye.Peaceful, 't', 'T'],
[Eye.Frown, 'ô', 'Ô', 'õ', 'Õ', 'ō', 'Ō', 'ŏ', 'Ŏ'],
[Eye.Frown2, 'a'],
[Eye.Neutral, `'`, '.', '0', '°', 'o', 'O', 'e', 'g', '9', '6', 'd', 'b'],
[Eye.Neutral4, '='],
[Eye.Closed, '-', 'v', 'V', 'u', 'U', 'y', 'Y'],
[Eye.ClosedHappy, 'n'],
[Eye.ClosedHappy2, '^'],
[Eye.Sad, 'q', 'Q', 'p', 'P', ';', ':', ','],
[Eye.Peaceful, 't', 'T'],
[Eye.Frown, 'ô', 'Ô', 'õ', 'Õ', 'ō', 'Ō', 'ŏ', 'Ŏ'],
[Eye.Frown2, 'a'],
];
export const horizontalEyesLeft = createMap<Eye>([
...horizontalEyes,
[Eye.Neutral2, '>'],
[Eye.X, '<'],
[Eye.Sad, 'ò', 'Ò'],
[Eye.Angry, 'ó', 'Ó'],
...horizontalEyes,
[Eye.Neutral2, '>'],
[Eye.X, '<'],
[Eye.Sad, 'ò', 'Ò'],
[Eye.Angry, 'ó', 'Ó'],
]);
export const horizontalEyesRight = createMap<Eye>([
...horizontalEyes,
[Eye.Neutral2, '<'],
[Eye.X, '>'],
[Eye.Sad, 'ó', 'Ó'],
[Eye.Angry, 'ò', 'Ò'],
...horizontalEyes,
[Eye.Neutral2, '<'],
[Eye.X, '>'],
[Eye.Sad, 'ó', 'Ó'],
[Eye.Angry, 'ò', 'Ò'],
]);
const horizontalIrises = createMap<Iris>([
[Iris.Up, '9'],
[Iris.UpLeft, 'e'],
[Iris.UpRight, 'g'],
[Iris.Right, '<', 'd'],
[Iris.Left, '>', 'b'],
[Iris.Up, '9'],
[Iris.UpLeft, 'e'],
[Iris.UpRight, 'g'],
[Iris.Right, '<', 'd'],
[Iris.Left, '>', 'b'],
]);
const muzzleToEye: Eye[] = [];
@@ -166,7 +166,7 @@ neutralToSmile[Muzzle.ConcernedOpen] = Muzzle.SmileOpen2;
neutralToSmile[Muzzle.ConcernedOpen2] = Muzzle.SmileOpen3;
function any(obj: object) {
return `(${Object.keys(obj).map(escapeRegExp).join('|')})`;
return `(${Object.keys(obj).map(escapeRegExp).join('|')})`;
}
const bigEyes = /[O0ÒÓÔÕŌŎQ]/;
@@ -178,175 +178,175 @@ const verticalLeftRegex = new RegExp(`^${any(muzzlesLeft)}-?${tears}${any(vertic
const horizontalRegex = new RegExp(`^${any(horizontalEyesRight)}(//)?${any(horizontalMuzzles)}(//)?${any(horizontalEyesLeft)}$`);
function matchVertical(
text: string, regex: RegExp, flip: boolean, muzzleMap: Dict<Muzzle>, eyesMap: Dict<Eye>
text: string, regex: RegExp, flip: boolean, muzzleMap: Dict<Muzzle>, eyesMap: Dict<Eye>
): Expression | undefined {
if (/^([|]{2,}|BS|8x|x8|x-?x|\d+)$/i.test(text))
return undefined;
if (/^([|]{2,}|BS|8x|x8|x-?x|\d+)$/i.test(text))
return undefined;
const match = regex.exec(text);
const match = regex.exec(text);
if (!match)
return undefined;
if (!match)
return undefined;
const eyesStr = flip ? match[3] : match[1];
const muzzleStr = flip ? match[1] : match[3];
const muzzle = muzzleMap[muzzleStr];
const veye = eyesMap[eyesStr];
const eye = veye === Eye.Neutral && !/[OV]/.test(muzzleStr) ? (muzzleToEye[muzzle] || veye) : veye;
const blink = /;/.test(eyesStr);
const tear = blink && muzzleToEye[muzzle] === Eye.Sad;
const left = tear ? (/[<>]/.test(eyesStr) ? eye : Eye.Sad2) : (blink && flip ? Eye.Closed : eye);
const right = tear ? (/[<>]/.test(eyesStr) ? eye : Eye.Sad2) : (blink && !flip ? Eye.Closed : eye);
const shocked = /8/.test(eyesStr);
const rightIris = shocked ? Iris.Shocked : Iris.Forward;
const leftIris = shocked ? Iris.Shocked : (/%/.test(eyesStr) ? Iris.Up : Iris.Forward);
const extra = (tearsRegex.test(match[2]) || tear) ? ExpressionExtra.Tears : ExpressionExtra.None;
const eyesStr = flip ? match[3] : match[1];
const muzzleStr = flip ? match[1] : match[3];
const muzzle = muzzleMap[muzzleStr];
const veye = eyesMap[eyesStr];
const eye = veye === Eye.Neutral && !/[OV]/.test(muzzleStr) ? (muzzleToEye[muzzle] || veye) : veye;
const blink = /;/.test(eyesStr);
const tear = blink && muzzleToEye[muzzle] === Eye.Sad;
const left = tear ? (/[<>]/.test(eyesStr) ? eye : Eye.Sad2) : (blink && flip ? Eye.Closed : eye);
const right = tear ? (/[<>]/.test(eyesStr) ? eye : Eye.Sad2) : (blink && !flip ? Eye.Closed : eye);
const shocked = /8/.test(eyesStr);
const rightIris = shocked ? Iris.Shocked : Iris.Forward;
const leftIris = shocked ? Iris.Shocked : (/%/.test(eyesStr) ? Iris.Up : Iris.Forward);
const extra = (tearsRegex.test(match[2]) || tear) ? ExpressionExtra.Tears : ExpressionExtra.None;
return { right, left, muzzle, rightIris, leftIris, extra };
return { right, left, muzzle, rightIris, leftIris, extra };
}
function matchHorizontal(text: string): Expression | undefined {
if (/\.\.|--|vv|uu|qq|pp|nn|^\d+$/i.test(text)) {
return undefined;
}
if (/\.\.|--|vv|uu|qq|pp|nn|^\d+$/i.test(text)) {
return undefined;
}
if (/[a-zA-Z][a-z][a-z]|[A-Z]{3}/.test(text)) {
const clear = text.replace(/[^a-z]/ig, '').toLowerCase();
if (/[a-zA-Z][a-z][a-z]|[A-Z]{3}/.test(text)) {
const clear = text.replace(/[^a-z]/ig, '').toLowerCase();
if (clear.length === 3 && threeLetterWords.test(clear)) {
return undefined;
}
}
if (clear.length === 3 && threeLetterWords.test(clear)) {
return undefined;
}
}
if (/[a-z][a-z][.,*-]/i.test(text)) {
const clear = text.replace(/[^a-z]/ig, '').toLowerCase();
if (/[a-z][a-z][.,*-]/i.test(text)) {
const clear = text.replace(/[^a-z]/ig, '').toLowerCase();
if (clear.length === 2 && twoLetterWords.test(clear)) {
return undefined;
}
}
if (clear.length === 2 && twoLetterWords.test(clear)) {
return undefined;
}
}
const match = horizontalRegex.exec(text);
const match = horizontalRegex.exec(text);
if (!match) {
return undefined;
}
if (!match) {
return undefined;
}
const [, rightStr, rightBlush, muzzleStr, leftBlush, leftStr] = match;
const [, rightStr, rightBlush, muzzleStr, leftBlush, leftStr] = match;
if ((rightBlush || leftBlush) && rightBlush !== leftBlush) {
return undefined;
}
if ((rightBlush || leftBlush) && rightBlush !== leftBlush) {
return undefined;
}
const leftEye = horizontalEyesLeft[leftStr];
const rightEye = horizontalEyesRight[rightStr];
const muzzle = horizontalMuzzles[muzzleStr];
const same = rightStr === leftStr;
const lookingToSide = same && /[<>]/.test(rightStr);
const shocked = bigEyes.test(leftStr) && bigEyes.test(rightStr) && rightStr !== '0' && leftStr !== '0';
const lookingDown = (same && rightStr === '6') || (rightStr === 'b' && leftStr === 'd');
const unamused = !lookingDown && same && rightStr === '-' && /[.,_]/.test(muzzleStr);
const left = (lookingToSide || (leftStr === 'o' && bigEyes.test(rightStr))) ? Eye.Neutral2 : leftEye;
const right = (lookingToSide || (rightStr === 'o' && bigEyes.test(leftStr))) ? Eye.Neutral2 : rightEye;
const blush = /[/][/]/.test(muzzleStr) || (rightBlush && rightBlush === leftBlush);
const cry = cryingEye.test(leftStr) || cryingEye.test(rightStr);
const leftEye = horizontalEyesLeft[leftStr];
const rightEye = horizontalEyesRight[rightStr];
const muzzle = horizontalMuzzles[muzzleStr];
const same = rightStr === leftStr;
const lookingToSide = same && /[<>]/.test(rightStr);
const shocked = bigEyes.test(leftStr) && bigEyes.test(rightStr) && rightStr !== '0' && leftStr !== '0';
const lookingDown = (same && rightStr === '6') || (rightStr === 'b' && leftStr === 'd');
const unamused = !lookingDown && same && rightStr === '-' && /[.,_]/.test(muzzleStr);
const left = (lookingToSide || (leftStr === 'o' && bigEyes.test(rightStr))) ? Eye.Neutral2 : leftEye;
const right = (lookingToSide || (rightStr === 'o' && bigEyes.test(leftStr))) ? Eye.Neutral2 : rightEye;
const blush = /[/][/]/.test(muzzleStr) || (rightBlush && rightBlush === leftBlush);
const cry = cryingEye.test(leftStr) || cryingEye.test(rightStr);
return {
left: unamused ? Eye.Frown2 : left,
right: unamused ? Eye.Frown2 : right,
muzzle: same && (leftEye === Eye.ClosedHappy || leftEye === Eye.ClosedHappy2) ? (neutralToSmile[muzzle] || muzzle) : muzzle,
rightIris: lookingDown ? Iris.Down : (shocked ? Iris.Shocked : (horizontalIrises[rightStr] || Iris.Forward)),
leftIris: lookingDown ? Iris.Down : (shocked ? Iris.Shocked : (horizontalIrises[leftStr] || Iris.Forward)),
extra: (blush ? ExpressionExtra.Blush : ExpressionExtra.None) | (cry ? ExpressionExtra.Cry : ExpressionExtra.None),
};
return {
left: unamused ? Eye.Frown2 : left,
right: unamused ? Eye.Frown2 : right,
muzzle: same && (leftEye === Eye.ClosedHappy || leftEye === Eye.ClosedHappy2) ? (neutralToSmile[muzzle] || muzzle) : muzzle,
rightIris: lookingDown ? Iris.Down : (shocked ? Iris.Shocked : (horizontalIrises[rightStr] || Iris.Forward)),
leftIris: lookingDown ? Iris.Down : (shocked ? Iris.Shocked : (horizontalIrises[leftStr] || Iris.Forward)),
extra: (blush ? ExpressionExtra.Blush : ExpressionExtra.None) | (cry ? ExpressionExtra.Cry : ExpressionExtra.None),
};
}
export function expression(
right: Eye, left: Eye, muzzle: Muzzle, rightIris = Iris.Forward, leftIris = Iris.Forward, extra = ExpressionExtra.None
right: Eye, left: Eye, muzzle: Muzzle, rightIris = Iris.Forward, leftIris = Iris.Forward, extra = ExpressionExtra.None
): Expression {
return { right, left, muzzle, rightIris, leftIris, extra };
return { right, left, muzzle, rightIris, leftIris, extra };
}
const constants = createPlainMap<() => Expression | undefined>({
'^^': () => expression(Eye.ClosedHappy2, Eye.ClosedHappy2, Muzzle.Smile),
'))': () => expression(Eye.Neutral, Eye.Neutral, Muzzle.Smile),
'((': () => expression(Eye.Neutral, Eye.Neutral, Muzzle.Frown),
'>>': () => expression(Eye.Neutral2, Eye.Neutral2, Muzzle.Flat, Iris.Left, Iris.Left),
'<<': () => expression(Eye.Neutral2, Eye.Neutral2, Muzzle.Flat, Iris.Right, Iris.Right),
'🙂': () => expression(Eye.Neutral, Eye.Neutral, Muzzle.Smile),
'😵': () => expression(Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Up, Iris.Forward),
'😐': () => expression(Eye.Neutral, Eye.Neutral, Muzzle.Flat),
'😑': () => expression(Eye.Lines, Eye.Lines, Muzzle.Flat),
'😆': () => expression(Eye.X, Eye.X, Muzzle.SmileOpen),
'😟': () => expression(Eye.Sad, Eye.Sad, Muzzle.Neutral),
'😠': () => expression(Eye.Angry, Eye.Angry, Muzzle.Smile),
'🤔': () => expression(Eye.Neutral, Eye.Frown2, Muzzle.Kiss),
'😈': () => expression(Eye.Angry, Eye.Angry, Muzzle.Smile, Iris.Up, Iris.Forward),
'👿': () => expression(Eye.Angry, Eye.Angry, Muzzle.SmileTeeth),
'^^': () => expression(Eye.ClosedHappy2, Eye.ClosedHappy2, Muzzle.Smile),
'))': () => expression(Eye.Neutral, Eye.Neutral, Muzzle.Smile),
'((': () => expression(Eye.Neutral, Eye.Neutral, Muzzle.Frown),
'>>': () => expression(Eye.Neutral2, Eye.Neutral2, Muzzle.Flat, Iris.Left, Iris.Left),
'<<': () => expression(Eye.Neutral2, Eye.Neutral2, Muzzle.Flat, Iris.Right, Iris.Right),
'🙂': () => expression(Eye.Neutral, Eye.Neutral, Muzzle.Smile),
'😵': () => expression(Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Up, Iris.Forward),
'😐': () => expression(Eye.Neutral, Eye.Neutral, Muzzle.Flat),
'😑': () => expression(Eye.Lines, Eye.Lines, Muzzle.Flat),
'😆': () => expression(Eye.X, Eye.X, Muzzle.SmileOpen),
'😟': () => expression(Eye.Sad, Eye.Sad, Muzzle.Neutral),
'😠': () => expression(Eye.Angry, Eye.Angry, Muzzle.Smile),
'🤔': () => expression(Eye.Neutral, Eye.Frown2, Muzzle.Kiss),
'😈': () => expression(Eye.Angry, Eye.Angry, Muzzle.Smile, Iris.Up, Iris.Forward),
'👿': () => expression(Eye.Angry, Eye.Angry, Muzzle.SmileTeeth),
});
function matchOther(text: string): Expression | undefined {
if (/^A{5,}\.*$/.test(text)) {
return expression(Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen3, Iris.Shocked, Iris.Shocked);
} else if (/^a{5,}\.*$/.test(text)) {
return expression(Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen3);
} else if (/^z{3,}\.*$/i.test(text)) {
return expression(Eye.Closed, Eye.Closed, Muzzle.Neutral);
} else {
return constants[text] && constants[text]();
}
if (/^A{5,}\.*$/.test(text)) {
return expression(Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen3, Iris.Shocked, Iris.Shocked);
} else if (/^a{5,}\.*$/.test(text)) {
return expression(Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen3);
} else if (/^z{3,}\.*$/i.test(text)) {
return expression(Eye.Closed, Eye.Closed, Muzzle.Neutral);
} else {
return constants[text] && constants[text]();
}
}
export function matchExpression(text: string): Expression | undefined {
if (/тот/ui.test(text)) {
return undefined;
}
if (/тот/ui.test(text)) {
return undefined;
}
text = replaceRussian(text)
.replace(/D{4,}/, 'DDD')
.replace(/\\/g, '/')
.replace(/\/{3,}/g, '//');
text = replaceRussian(text)
.replace(/D{4,}/, 'DDD')
.replace(/\\/g, '/')
.replace(/\/{3,}/g, '//');
return matchVertical(text, verticalRightRegex, false, muzzlesRight, verticalEyesRight)
|| matchVertical(text, verticalLeftRegex, true, muzzlesLeft, verticalEyesLeft)
|| matchHorizontal(text)
|| matchOther(text);
return matchVertical(text, verticalRightRegex, false, muzzlesRight, verticalEyesRight)
|| matchVertical(text, verticalLeftRegex, true, muzzlesLeft, verticalEyesLeft)
|| matchHorizontal(text)
|| matchOther(text);
}
export function parseExpression(text: string): Expression | undefined {
const emoteMatch = /(?:^| )(\S+)\s*$/.exec(text);
const emote = emoteMatch && emoteMatch[1].trim();
return emote ? matchExpression(emote) : undefined;
const emoteMatch = /(?:^| )(\S+)\s*$/.exec(text);
const emote = emoteMatch && emoteMatch[1].trim();
return emote ? matchExpression(emote) : undefined;
}
function createMap<T>(values: any[][]): Dict<T> {
return values.reduce((obj: Dict<T>, [exp, ...values]) => (values.forEach(v => obj[v] = exp), obj), Object.create(null));
return values.reduce((obj: Dict<T>, [exp, ...values]) => (values.forEach(v => obj[v] = exp), obj), Object.create(null));
}
const charMap = createPlainMap<string>({
'З': '3', 'з': '3', 'Э': '3', 'э': '3',
'А': 'A', 'а': 'a', 'Д': 'A', 'д': 'A',
'В': 'B', 'в': 'B',
'Г': 'L',
'М': 'M', 'м': 'M',
'О': 'O', 'о': 'o',
'П': 'n', 'п': 'n',
'Р': 'P', 'р': 'p',
'С': 'C', 'с': 'c',
'Т': 'T', 'т': 'T',
'Х': 'X', 'х': 'x',
'Ш': 'W', 'ш': 'w',
'Ь': 'b', 'ь': 'b',
'е': 'e',
'у': 'y', 'У': 'Y',
'З': '3', 'з': '3', 'Э': '3', 'э': '3',
'А': 'A', 'а': 'a', 'Д': 'A', 'д': 'A',
'В': 'B', 'в': 'B',
'Г': 'L',
'М': 'M', 'м': 'M',
'О': 'O', 'о': 'o',
'П': 'n', 'п': 'n',
'Р': 'P', 'р': 'p',
'С': 'C', 'с': 'c',
'Т': 'T', 'т': 'T',
'Х': 'X', 'х': 'x',
'Ш': 'W', 'ш': 'w',
'Ь': 'b', 'ь': 'b',
'е': 'e',
'у': 'y', 'У': 'Y',
});
const charRegex = new RegExp(`[${Object.keys(charMap).join('')}]`, 'g');
function mapChar(x: string) {
return charMap[x];
return charMap[x];
}
function replaceRussian(text: string): string {
return text.replace(charRegex, mapChar);
return text.replace(charRegex, mapChar);
}
+288 -288
View File
@@ -2,294 +2,294 @@ import { Eye, Muzzle, Iris, ExpressionExtra } from './interfaces';
import { THREE_LETTER_WORDS, TWO_LETTER_WORDS } from './expressionUtils';
type Result = undefined
| [Eye, Eye, Muzzle]
| [Eye, Eye, Muzzle, Iris, Iris]
| [Eye, Eye, Muzzle, Iris, Iris, ExpressionExtra];
| [Eye, Eye, Muzzle]
| [Eye, Eye, Muzzle, Iris, Iris]
| [Eye, Eye, Muzzle, Iris, Iris, ExpressionExtra];
export const expressions: [string, Result][] = [
// invalid
['', undefined],
['a', undefined],
['123', undefined],
[':::', undefined],
['XDK', undefined],
['fooXD', undefined],
[':) hey', undefined],
['тот', undefined],
// in text
[' :) ', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['hi :)', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
// horizontal (right)
[':-)', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
[':)', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['=)', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
[':]', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
[':>', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
[':}', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
[':3', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['rus :з', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['rus :э', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
[':(', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
[':[', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
[':C', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
[':c', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
['rus :С', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
['rus :с', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
[':<', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
[':{', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
[':I', [Eye.Neutral, Eye.Neutral, Muzzle.Scrunch]],
[':t', [Eye.Neutral, Eye.Neutral, Muzzle.Scrunch]],
[':T', [Eye.Neutral, Eye.Neutral, Muzzle.Scrunch]],
['rus :Т', [Eye.Neutral, Eye.Neutral, Muzzle.Scrunch]],
['rus :т', [Eye.Neutral, Eye.Neutral, Muzzle.Scrunch]],
[':P', [Eye.Neutral, Eye.Neutral, Muzzle.Blep]],
[':p', [Eye.Neutral, Eye.Neutral, Muzzle.Blep]],
[':d', [Eye.Neutral, Eye.Neutral, Muzzle.Blep]],
[':b', [Eye.Neutral, Eye.Neutral, Muzzle.FlatBlep]],
['rus :Р', [Eye.Neutral, Eye.Neutral, Muzzle.Blep]],
['rus :р', [Eye.Neutral, Eye.Neutral, Muzzle.Blep]],
[':D', [Eye.Neutral, Eye.Neutral, Muzzle.SmileOpen]],
[':DD', [Eye.Neutral, Eye.Neutral, Muzzle.SmileOpen2]],
[':DDD', [Eye.Neutral, Eye.Neutral, Muzzle.SmileOpen3]],
[':DDDDD', [Eye.Neutral, Eye.Neutral, Muzzle.SmileOpen3]],
[':O', [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen2]],
[':0', [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen]],
[':o', [Eye.Neutral, Eye.Neutral, Muzzle.Oh]],
[':|', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
[':l', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
[':i', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
[':v', [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen]],
[':V', [Eye.Neutral, Eye.Neutral, Muzzle.FrownOpen]],
[':u', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralOpen2]],
[':n', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralOpen2]],
[':U', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralOpen3]],
[':*', [Eye.Neutral, Eye.Neutral, Muzzle.Kiss]],
[':x', [Eye.Neutral, Eye.Neutral, Muzzle.Kiss]],
[':X', [Eye.Neutral, Eye.Neutral, Muzzle.Kiss]],
[':/', [Eye.Neutral, Eye.Neutral, Muzzle.Concerned]],
[':\\', [Eye.Neutral, Eye.Neutral, Muzzle.Concerned]],
[':S', [Eye.Neutral, Eye.Neutral, Muzzle.Concerned]],
[':s', [Eye.Neutral, Eye.Neutral, Muzzle.Concerned]],
[':?', [Eye.Neutral, Eye.Neutral, Muzzle.Concerned]],
['>:(', [Eye.Angry, Eye.Angry, Muzzle.Frown]],
['>:<', [Eye.Angry, Eye.Angry, Muzzle.Frown]],
['<:>', [Eye.Sad, Eye.Sad, Muzzle.Smile]],
['XD', [Eye.X, Eye.X, Muzzle.SmileOpen]],
['xD', [Eye.X, Eye.X, Muzzle.SmileOpen]],
[':L', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralPant]],
[':Q', [Eye.Neutral, Eye.Neutral, Muzzle.SmilePant]],
['B)', [Eye.Neutral3, Eye.Neutral3, Muzzle.Smile]],
['8)', [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Shocked, Iris.Shocked]],
['>8)', [Eye.Angry, Eye.Angry, Muzzle.Smile, Iris.Shocked, Iris.Shocked]],
['<:)', [Eye.Sad, Eye.Sad, Muzzle.Smile]],
['>B)', [Eye.Angry2, Eye.Angry2, Muzzle.Smile]],
['<B)', [Eye.Sad2, Eye.Sad2, Muzzle.Smile]],
['|:)', [Eye.Frown, Eye.Frown, Muzzle.Smile]],
['|B)', [Eye.Frown2, Eye.Frown2, Muzzle.Smile]],
['|)', [Eye.Lines, Eye.Lines, Muzzle.Smile]],
[':))', [Eye.Neutral, Eye.Neutral, Muzzle.SmileTeeth]],
[':]]', [Eye.Neutral, Eye.Neutral, Muzzle.SmileTeeth]],
[':||', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralTeeth]],
[':((', [Eye.Neutral, Eye.Neutral, Muzzle.FrownTeeth]],
[':[[', [Eye.Neutral, Eye.Neutral, Muzzle.FrownTeeth]],
['://', [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedTeeth]],
[':SS', [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedTeeth]],
[';)', [Eye.Closed, Eye.Neutral, Muzzle.Smile]],
[';(', [Eye.Sad2, Eye.Sad2, Muzzle.Frown, Iris.Forward, Iris.Forward, ExpressionExtra.Tears]],
['>;(', [Eye.Angry, Eye.Angry, Muzzle.Frown, Iris.Forward, Iris.Forward, ExpressionExtra.Tears]],
['%)', [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Forward, Iris.Up]],
[`c':`, [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Forward, Iris.Forward, ExpressionExtra.Tears]],
[`:')`, [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Forward, Iris.Forward, ExpressionExtra.Tears]],
[`:'(`, [Eye.Sad, Eye.Sad, Muzzle.Frown, Iris.Forward, Iris.Forward, ExpressionExtra.Tears]],
[`=,)`, [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Forward, Iris.Forward, ExpressionExtra.Tears]],
['=`)', [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Forward, Iris.Forward, ExpressionExtra.Tears]],
// TODO: :@ :y :'9
// horizontal (left)
['(-:', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['|:', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
['(:', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['[:', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['c:', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['C:', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['rus с:', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['rus С:', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['):', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
['D:', [Eye.Sad, Eye.Sad, Muzzle.ConcernedOpen2]],
['DD:', [Eye.Sad, Eye.Sad, Muzzle.ConcernedOpen3]],
['DDDDD:', [Eye.Sad, Eye.Sad, Muzzle.ConcernedOpen3]],
['D:<', [Eye.Angry, Eye.Angry, Muzzle.ConcernedOpen2]],
['D8<', [Eye.Angry, Eye.Angry, Muzzle.ConcernedOpen2, Iris.Shocked, Iris.Shocked]],
['):<', [Eye.Angry, Eye.Angry, Muzzle.Frown]],
['v:', [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen]],
['/:', [Eye.Neutral, Eye.Neutral, Muzzle.Concerned]],
['(:>', [Eye.Sad, Eye.Sad, Muzzle.Smile]],
['(:|', [Eye.Frown, Eye.Frown, Muzzle.Smile]],
['(|', [Eye.Lines, Eye.Lines, Muzzle.Smile]],
['((:', [Eye.Neutral, Eye.Neutral, Muzzle.SmileTeeth]],
['[[:', [Eye.Neutral, Eye.Neutral, Muzzle.SmileTeeth]],
['||:', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralTeeth]],
[')):', [Eye.Neutral, Eye.Neutral, Muzzle.FrownTeeth]],
['(;', [Eye.Neutral, Eye.Closed, Muzzle.Smile]],
// horizontal (invalid)
['||', undefined],
['|||', undefined],
['>||', undefined],
['>xD', undefined],
['(X<', undefined],
['x-x', undefined],
// vertical
['-_-', [Eye.Frown2, Eye.Frown2, Muzzle.Flat]],
['-.-', [Eye.Frown2, Eye.Frown2, Muzzle.Kiss]],
['-,-', [Eye.Frown2, Eye.Frown2, Muzzle.Kiss]],
['^_^', [Eye.ClosedHappy2, Eye.ClosedHappy2, Muzzle.Flat]],
['-_^', [Eye.Closed, Eye.ClosedHappy2, Muzzle.Flat]],
['o_O', [Eye.Neutral2, Eye.Neutral, Muzzle.Flat]],
['0_o', [Eye.Neutral, Eye.Neutral2, Muzzle.Flat]],
['o_o', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
['o,o', [Eye.Neutral, Eye.Neutral, Muzzle.Kiss]],
['ono', [Eye.Neutral, Eye.Neutral, Muzzle.Frown]],
['O_O', [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.Shocked, Iris.Shocked]],
['OoO', [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen, Iris.Shocked, Iris.Shocked]],
['0_0', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
['°_°', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
['0.0', [Eye.Neutral, Eye.Neutral, Muzzle.Kiss]],
['._.', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
[',_,', [Eye.Sad, Eye.Sad, Muzzle.Flat]],
['v_V', [Eye.Closed, Eye.Closed, Muzzle.Flat]],
['u_U', [Eye.Closed, Eye.Closed, Muzzle.Flat]],
['n_n', [Eye.ClosedHappy, Eye.ClosedHappy, Muzzle.Flat]],
['>_<', [Eye.X, Eye.X, Muzzle.Flat, Iris.Left, Iris.Right]],
['>c<', [Eye.X, Eye.X, Muzzle.Smile, Iris.Left, Iris.Right]],
[`'c'`, [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['-C-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
['rus -с-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
['-v-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
['-V-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
['-U-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
['-u-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
['-w-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
['-W-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
[`-👃-`, [Eye.Closed, Eye.Closed, Muzzle.Smile]],
[`'_'`, [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
[`-*-`, [Eye.Closed, Eye.Closed, Muzzle.Kiss]],
[`-x-`, [Eye.Closed, Eye.Closed, Muzzle.Kiss]],
[`-X-`, [Eye.Closed, Eye.Closed, Muzzle.Kiss]],
[`>x<`, [Eye.X, Eye.X, Muzzle.Kiss, Iris.Left, Iris.Right]],
[`-o-`, [Eye.Closed, Eye.Closed, Muzzle.ConcernedOpen]],
[`-O-`, [Eye.Closed, Eye.Closed, Muzzle.ConcernedOpen2]],
[`-0-`, [Eye.Closed, Eye.Closed, Muzzle.ConcernedOpen2]],
[`^o^`, [Eye.ClosedHappy2, Eye.ClosedHappy2, Muzzle.SmileOpen2]],
[`^O^`, [Eye.ClosedHappy2, Eye.ClosedHappy2, Muzzle.SmileOpen3]],
[`-n-`, [Eye.Closed, Eye.Closed, Muzzle.Frown]],
[`-m-`, [Eye.Closed, Eye.Closed, Muzzle.Frown]],
[`-^-`, [Eye.Closed, Eye.Closed, Muzzle.Frown]],
[`-~-`, [Eye.Closed, Eye.Closed, Muzzle.Concerned]],
[`-3-`, [Eye.Closed, Eye.Closed, Muzzle.Kiss]],
[`rus -з-`, [Eye.Closed, Eye.Closed, Muzzle.Kiss]],
[`rus -э-`, [Eye.Closed, Eye.Closed, Muzzle.Kiss]],
[`-q-`, [Eye.Closed, Eye.Closed, Muzzle.NeutralPant]],
[`-p-`, [Eye.Closed, Eye.Closed, Muzzle.NeutralPant]],
[`-P-`, [Eye.Closed, Eye.Closed, Muzzle.SmilePant]],
[`-Q-`, [Eye.Closed, Eye.Closed, Muzzle.SmilePant]],
[`-A-`, [Eye.Closed, Eye.Closed, Muzzle.ConcernedOpen2]],
[`q-q`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`p-p`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`p-q`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`rus р-р`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`;-;`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`:-:`, [Eye.Sad, Eye.Sad, Muzzle.Neutral]],
[`P-P`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`rus Р-Р`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`t-t`, [Eye.Peaceful, Eye.Peaceful, Muzzle.Neutral]],
[`Т_Т`, [Eye.Peaceful, Eye.Peaceful, Muzzle.Flat, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`rus Т_т`, [Eye.Peaceful, Eye.Peaceful, Muzzle.Flat, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`Q-Q`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Shocked, Iris.Shocked, ExpressionExtra.Cry]],
[`y-y`, [Eye.Closed, Eye.Closed, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`Y-Y`, [Eye.Closed, Eye.Closed, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`у-у`, [Eye.Closed, Eye.Closed, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`У-У`, [Eye.Closed, Eye.Closed, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`ò_ó`, [Eye.Angry, Eye.Angry, Muzzle.Flat]],
[`ó_ò`, [Eye.Sad, Eye.Sad, Muzzle.Flat]],
[`ô_ô`, [Eye.Frown, Eye.Frown, Muzzle.Flat]],
[`õ_õ`, [Eye.Frown, Eye.Frown, Muzzle.Flat]],
[`ō_ō`, [Eye.Frown, Eye.Frown, Muzzle.Flat]],
[`ŏ_ŏ`, [Eye.Frown, Eye.Frown, Muzzle.Flat]],
[`Ò_Ó`, [Eye.Angry, Eye.Angry, Muzzle.Flat, Iris.Shocked, Iris.Shocked]],
[`Ô_Ô`, [Eye.Frown, Eye.Frown, Muzzle.Flat, Iris.Shocked, Iris.Shocked]],
[`Õ_Õ`, [Eye.Frown, Eye.Frown, Muzzle.Flat, Iris.Shocked, Iris.Shocked]],
[`Ō_Ō`, [Eye.Frown, Eye.Frown, Muzzle.Flat, Iris.Shocked, Iris.Shocked]],
[`Ŏ_Ŏ`, [Eye.Frown, Eye.Frown, Muzzle.Flat, Iris.Shocked, Iris.Shocked]],
[`=_=`, [Eye.Neutral4, Eye.Neutral4, Muzzle.Flat]],
[`a_a`, [Eye.Frown2, Eye.Frown2, Muzzle.Flat]],
[`e_e`, [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.UpLeft, Iris.UpLeft]],
[`е_е`, [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.UpLeft, Iris.UpLeft]],
[`g_g`, [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.UpRight, Iris.UpRight]],
[`9_9`, [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.Up, Iris.Up]],
[`6_9`, [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.Forward, Iris.Up]],
['>_>', [Eye.Neutral2, Eye.Neutral2, Muzzle.Flat, Iris.Left, Iris.Left]],
['<_<', [Eye.Neutral2, Eye.Neutral2, Muzzle.Flat, Iris.Right, Iris.Right]],
['<_>', [Eye.Neutral2, Eye.Neutral2, Muzzle.Flat, Iris.Right, Iris.Left]],
['d_d', [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.Right, Iris.Right]],
['b_b', [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.Left, Iris.Left]],
['twO', [Eye.Peaceful, Eye.Neutral, Muzzle.Smile]],
['o//o', [Eye.Neutral, Eye.Neutral, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Blush]],
['o/////o', [Eye.Neutral, Eye.Neutral, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Blush]],
['o\\\\o', [Eye.Neutral, Eye.Neutral, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Blush]],
['o\\\\\\o', [Eye.Neutral, Eye.Neutral, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Blush]],
['>//<', [Eye.X, Eye.X, Muzzle.Neutral, Iris.Left, Iris.Right, ExpressionExtra.Blush]],
['-//v//-', [Eye.Closed, Eye.Closed, Muzzle.Smile, Iris.Forward, Iris.Forward, ExpressionExtra.Blush]],
['-///v///-', [Eye.Closed, Eye.Closed, Muzzle.Smile, Iris.Forward, Iris.Forward, ExpressionExtra.Blush]],
[';//v//;', [Eye.Sad, Eye.Sad, Muzzle.Smile, Iris.Forward, Iris.Forward, ExpressionExtra.Blush | ExpressionExtra.Cry]],
[`6_6`, [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.Down, Iris.Down]],
['6.6', [Eye.Neutral, Eye.Neutral, Muzzle.Kiss, Iris.Down, Iris.Down]],
['bcd', [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Down, Iris.Down]],
// TODO: o-o' o-o' ~_~ @_@ o=o oyo *_* (amazed) -_-/ -_-\ D_D
// vertical (short)
['^^', [Eye.ClosedHappy2, Eye.ClosedHappy2, Muzzle.Smile]],
['))', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['((', [Eye.Neutral, Eye.Neutral, Muzzle.Frown]],
['<<', [Eye.Neutral2, Eye.Neutral2, Muzzle.Flat, Iris.Right, Iris.Right]],
['>>', [Eye.Neutral2, Eye.Neutral2, Muzzle.Flat, Iris.Left, Iris.Left]],
// vertical (invalid)
...[
'---', '...', '000', 'QQQ', 'One', 'Up.', 'UP.',
...THREE_LETTER_WORDS,
...THREE_LETTER_WORDS.map(x => x.toUpperCase()),
...TWO_LETTER_WORDS.map(x => x + '.'),
...TWO_LETTER_WORDS.map(x => x + ','),
...TWO_LETTER_WORDS.map(x => x + '-'),
...TWO_LETTER_WORDS.map(x => x + '*'),
].map(x => [x, undefined] as [string, any]),
['BS', undefined],
['x8', undefined],
['8x', undefined],
['xx', undefined],
['-//c-', undefined],
['-c//-', undefined],
['030', undefined],
['80', undefined],
// other
[`aaaaa`, [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen3]],
[`AAAAAA`, [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen3, Iris.Shocked, Iris.Shocked]],
[`aaaaa...`, [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen3]],
[`zzz`, [Eye.Closed, Eye.Closed, Muzzle.Neutral]],
[`ZZZZZ`, [Eye.Closed, Eye.Closed, Muzzle.Neutral]],
[`zzz...`, [Eye.Closed, Eye.Closed, Muzzle.Neutral]],
// emoji
['🙂', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['😵', [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Up, Iris.Forward]],
['😐', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
['😑', [Eye.Lines, Eye.Lines, Muzzle.Flat]],
['😆', [Eye.X, Eye.X, Muzzle.SmileOpen]],
['😟', [Eye.Sad, Eye.Sad, Muzzle.Neutral]],
['😠', [Eye.Angry, Eye.Angry, Muzzle.Smile]],
['🤔', [Eye.Neutral, Eye.Frown2, Muzzle.Kiss]],
['😈', [Eye.Angry, Eye.Angry, Muzzle.Smile, Iris.Up, Iris.Forward]],
['👿', [Eye.Angry, Eye.Angry, Muzzle.SmileTeeth]],
// unsafe faces
[':L', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralPant]],
[':Q', [Eye.Neutral, Eye.Neutral, Muzzle.SmilePant]],
// safe replacements
[':u', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralOpen2]],
[':DD', [Eye.Neutral, Eye.Neutral, Muzzle.SmileOpen2]],
// invalid
['', undefined],
['a', undefined],
['123', undefined],
[':::', undefined],
['XDK', undefined],
['fooXD', undefined],
[':) hey', undefined],
['тот', undefined],
// in text
[' :) ', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['hi :)', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
// horizontal (right)
[':-)', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
[':)', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['=)', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
[':]', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
[':>', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
[':}', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
[':3', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['rus :з', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['rus :э', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
[':(', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
[':[', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
[':C', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
[':c', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
['rus :С', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
['rus :с', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
[':<', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
[':{', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
[':I', [Eye.Neutral, Eye.Neutral, Muzzle.Scrunch]],
[':t', [Eye.Neutral, Eye.Neutral, Muzzle.Scrunch]],
[':T', [Eye.Neutral, Eye.Neutral, Muzzle.Scrunch]],
['rus :Т', [Eye.Neutral, Eye.Neutral, Muzzle.Scrunch]],
['rus :т', [Eye.Neutral, Eye.Neutral, Muzzle.Scrunch]],
[':P', [Eye.Neutral, Eye.Neutral, Muzzle.Blep]],
[':p', [Eye.Neutral, Eye.Neutral, Muzzle.Blep]],
[':d', [Eye.Neutral, Eye.Neutral, Muzzle.Blep]],
[':b', [Eye.Neutral, Eye.Neutral, Muzzle.FlatBlep]],
['rus :Р', [Eye.Neutral, Eye.Neutral, Muzzle.Blep]],
['rus :р', [Eye.Neutral, Eye.Neutral, Muzzle.Blep]],
[':D', [Eye.Neutral, Eye.Neutral, Muzzle.SmileOpen]],
[':DD', [Eye.Neutral, Eye.Neutral, Muzzle.SmileOpen2]],
[':DDD', [Eye.Neutral, Eye.Neutral, Muzzle.SmileOpen3]],
[':DDDDD', [Eye.Neutral, Eye.Neutral, Muzzle.SmileOpen3]],
[':O', [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen2]],
[':0', [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen]],
[':o', [Eye.Neutral, Eye.Neutral, Muzzle.Oh]],
[':|', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
[':l', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
[':i', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
[':v', [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen]],
[':V', [Eye.Neutral, Eye.Neutral, Muzzle.FrownOpen]],
[':u', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralOpen2]],
[':n', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralOpen2]],
[':U', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralOpen3]],
[':*', [Eye.Neutral, Eye.Neutral, Muzzle.Kiss]],
[':x', [Eye.Neutral, Eye.Neutral, Muzzle.Kiss]],
[':X', [Eye.Neutral, Eye.Neutral, Muzzle.Kiss]],
[':/', [Eye.Neutral, Eye.Neutral, Muzzle.Concerned]],
[':\\', [Eye.Neutral, Eye.Neutral, Muzzle.Concerned]],
[':S', [Eye.Neutral, Eye.Neutral, Muzzle.Concerned]],
[':s', [Eye.Neutral, Eye.Neutral, Muzzle.Concerned]],
[':?', [Eye.Neutral, Eye.Neutral, Muzzle.Concerned]],
['>:(', [Eye.Angry, Eye.Angry, Muzzle.Frown]],
['>:<', [Eye.Angry, Eye.Angry, Muzzle.Frown]],
['<:>', [Eye.Sad, Eye.Sad, Muzzle.Smile]],
['XD', [Eye.X, Eye.X, Muzzle.SmileOpen]],
['xD', [Eye.X, Eye.X, Muzzle.SmileOpen]],
[':L', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralPant]],
[':Q', [Eye.Neutral, Eye.Neutral, Muzzle.SmilePant]],
['B)', [Eye.Neutral3, Eye.Neutral3, Muzzle.Smile]],
['8)', [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Shocked, Iris.Shocked]],
['>8)', [Eye.Angry, Eye.Angry, Muzzle.Smile, Iris.Shocked, Iris.Shocked]],
['<:)', [Eye.Sad, Eye.Sad, Muzzle.Smile]],
['>B)', [Eye.Angry2, Eye.Angry2, Muzzle.Smile]],
['<B)', [Eye.Sad2, Eye.Sad2, Muzzle.Smile]],
['|:)', [Eye.Frown, Eye.Frown, Muzzle.Smile]],
['|B)', [Eye.Frown2, Eye.Frown2, Muzzle.Smile]],
['|)', [Eye.Lines, Eye.Lines, Muzzle.Smile]],
[':))', [Eye.Neutral, Eye.Neutral, Muzzle.SmileTeeth]],
[':]]', [Eye.Neutral, Eye.Neutral, Muzzle.SmileTeeth]],
[':||', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralTeeth]],
[':((', [Eye.Neutral, Eye.Neutral, Muzzle.FrownTeeth]],
[':[[', [Eye.Neutral, Eye.Neutral, Muzzle.FrownTeeth]],
['://', [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedTeeth]],
[':SS', [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedTeeth]],
[';)', [Eye.Closed, Eye.Neutral, Muzzle.Smile]],
[';(', [Eye.Sad2, Eye.Sad2, Muzzle.Frown, Iris.Forward, Iris.Forward, ExpressionExtra.Tears]],
['>;(', [Eye.Angry, Eye.Angry, Muzzle.Frown, Iris.Forward, Iris.Forward, ExpressionExtra.Tears]],
['%)', [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Forward, Iris.Up]],
[`c':`, [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Forward, Iris.Forward, ExpressionExtra.Tears]],
[`:')`, [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Forward, Iris.Forward, ExpressionExtra.Tears]],
[`:'(`, [Eye.Sad, Eye.Sad, Muzzle.Frown, Iris.Forward, Iris.Forward, ExpressionExtra.Tears]],
[`=,)`, [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Forward, Iris.Forward, ExpressionExtra.Tears]],
['=`)', [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Forward, Iris.Forward, ExpressionExtra.Tears]],
// TODO: :@ :y :'9
// horizontal (left)
['(-:', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['|:', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
['(:', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['[:', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['c:', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['C:', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['rus с:', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['rus С:', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['):', [Eye.Sad, Eye.Sad, Muzzle.Frown]],
['D:', [Eye.Sad, Eye.Sad, Muzzle.ConcernedOpen2]],
['DD:', [Eye.Sad, Eye.Sad, Muzzle.ConcernedOpen3]],
['DDDDD:', [Eye.Sad, Eye.Sad, Muzzle.ConcernedOpen3]],
['D:<', [Eye.Angry, Eye.Angry, Muzzle.ConcernedOpen2]],
['D8<', [Eye.Angry, Eye.Angry, Muzzle.ConcernedOpen2, Iris.Shocked, Iris.Shocked]],
['):<', [Eye.Angry, Eye.Angry, Muzzle.Frown]],
['v:', [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen]],
['/:', [Eye.Neutral, Eye.Neutral, Muzzle.Concerned]],
['(:>', [Eye.Sad, Eye.Sad, Muzzle.Smile]],
['(:|', [Eye.Frown, Eye.Frown, Muzzle.Smile]],
['(|', [Eye.Lines, Eye.Lines, Muzzle.Smile]],
['((:', [Eye.Neutral, Eye.Neutral, Muzzle.SmileTeeth]],
['[[:', [Eye.Neutral, Eye.Neutral, Muzzle.SmileTeeth]],
['||:', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralTeeth]],
[')):', [Eye.Neutral, Eye.Neutral, Muzzle.FrownTeeth]],
['(;', [Eye.Neutral, Eye.Closed, Muzzle.Smile]],
// horizontal (invalid)
['||', undefined],
['|||', undefined],
['>||', undefined],
['>xD', undefined],
['(X<', undefined],
['x-x', undefined],
// vertical
['-_-', [Eye.Frown2, Eye.Frown2, Muzzle.Flat]],
['-.-', [Eye.Frown2, Eye.Frown2, Muzzle.Kiss]],
['-,-', [Eye.Frown2, Eye.Frown2, Muzzle.Kiss]],
['^_^', [Eye.ClosedHappy2, Eye.ClosedHappy2, Muzzle.Flat]],
['-_^', [Eye.Closed, Eye.ClosedHappy2, Muzzle.Flat]],
['o_O', [Eye.Neutral2, Eye.Neutral, Muzzle.Flat]],
['0_o', [Eye.Neutral, Eye.Neutral2, Muzzle.Flat]],
['o_o', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
['o,o', [Eye.Neutral, Eye.Neutral, Muzzle.Kiss]],
['ono', [Eye.Neutral, Eye.Neutral, Muzzle.Frown]],
['O_O', [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.Shocked, Iris.Shocked]],
['OoO', [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen, Iris.Shocked, Iris.Shocked]],
['0_0', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
['°_°', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
['0.0', [Eye.Neutral, Eye.Neutral, Muzzle.Kiss]],
['._.', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
[',_,', [Eye.Sad, Eye.Sad, Muzzle.Flat]],
['v_V', [Eye.Closed, Eye.Closed, Muzzle.Flat]],
['u_U', [Eye.Closed, Eye.Closed, Muzzle.Flat]],
['n_n', [Eye.ClosedHappy, Eye.ClosedHappy, Muzzle.Flat]],
['>_<', [Eye.X, Eye.X, Muzzle.Flat, Iris.Left, Iris.Right]],
['>c<', [Eye.X, Eye.X, Muzzle.Smile, Iris.Left, Iris.Right]],
[`'c'`, [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['-C-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
['rus -с-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
['-v-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
['-V-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
['-U-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
['-u-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
['-w-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
['-W-', [Eye.Closed, Eye.Closed, Muzzle.Smile]],
[`-👃-`, [Eye.Closed, Eye.Closed, Muzzle.Smile]],
[`'_'`, [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
[`-*-`, [Eye.Closed, Eye.Closed, Muzzle.Kiss]],
[`-x-`, [Eye.Closed, Eye.Closed, Muzzle.Kiss]],
[`-X-`, [Eye.Closed, Eye.Closed, Muzzle.Kiss]],
[`>x<`, [Eye.X, Eye.X, Muzzle.Kiss, Iris.Left, Iris.Right]],
[`-o-`, [Eye.Closed, Eye.Closed, Muzzle.ConcernedOpen]],
[`-O-`, [Eye.Closed, Eye.Closed, Muzzle.ConcernedOpen2]],
[`-0-`, [Eye.Closed, Eye.Closed, Muzzle.ConcernedOpen2]],
[`^o^`, [Eye.ClosedHappy2, Eye.ClosedHappy2, Muzzle.SmileOpen2]],
[`^O^`, [Eye.ClosedHappy2, Eye.ClosedHappy2, Muzzle.SmileOpen3]],
[`-n-`, [Eye.Closed, Eye.Closed, Muzzle.Frown]],
[`-m-`, [Eye.Closed, Eye.Closed, Muzzle.Frown]],
[`-^-`, [Eye.Closed, Eye.Closed, Muzzle.Frown]],
[`-~-`, [Eye.Closed, Eye.Closed, Muzzle.Concerned]],
[`-3-`, [Eye.Closed, Eye.Closed, Muzzle.Kiss]],
[`rus -з-`, [Eye.Closed, Eye.Closed, Muzzle.Kiss]],
[`rus -э-`, [Eye.Closed, Eye.Closed, Muzzle.Kiss]],
[`-q-`, [Eye.Closed, Eye.Closed, Muzzle.NeutralPant]],
[`-p-`, [Eye.Closed, Eye.Closed, Muzzle.NeutralPant]],
[`-P-`, [Eye.Closed, Eye.Closed, Muzzle.SmilePant]],
[`-Q-`, [Eye.Closed, Eye.Closed, Muzzle.SmilePant]],
[`-A-`, [Eye.Closed, Eye.Closed, Muzzle.ConcernedOpen2]],
[`q-q`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`p-p`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`p-q`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`rus р-р`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`;-;`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`:-:`, [Eye.Sad, Eye.Sad, Muzzle.Neutral]],
[`P-P`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`rus Р-Р`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`t-t`, [Eye.Peaceful, Eye.Peaceful, Muzzle.Neutral]],
[`Т_Т`, [Eye.Peaceful, Eye.Peaceful, Muzzle.Flat, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`rus Т_т`, [Eye.Peaceful, Eye.Peaceful, Muzzle.Flat, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`Q-Q`, [Eye.Sad, Eye.Sad, Muzzle.Neutral, Iris.Shocked, Iris.Shocked, ExpressionExtra.Cry]],
[`y-y`, [Eye.Closed, Eye.Closed, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`Y-Y`, [Eye.Closed, Eye.Closed, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`у-у`, [Eye.Closed, Eye.Closed, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`У-У`, [Eye.Closed, Eye.Closed, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Cry]],
[`ò_ó`, [Eye.Angry, Eye.Angry, Muzzle.Flat]],
[`ó_ò`, [Eye.Sad, Eye.Sad, Muzzle.Flat]],
[`ô_ô`, [Eye.Frown, Eye.Frown, Muzzle.Flat]],
[`õ_õ`, [Eye.Frown, Eye.Frown, Muzzle.Flat]],
[`ō_ō`, [Eye.Frown, Eye.Frown, Muzzle.Flat]],
[`ŏ_ŏ`, [Eye.Frown, Eye.Frown, Muzzle.Flat]],
[`Ò_Ó`, [Eye.Angry, Eye.Angry, Muzzle.Flat, Iris.Shocked, Iris.Shocked]],
[`Ô_Ô`, [Eye.Frown, Eye.Frown, Muzzle.Flat, Iris.Shocked, Iris.Shocked]],
[`Õ_Õ`, [Eye.Frown, Eye.Frown, Muzzle.Flat, Iris.Shocked, Iris.Shocked]],
[`Ō_Ō`, [Eye.Frown, Eye.Frown, Muzzle.Flat, Iris.Shocked, Iris.Shocked]],
[`Ŏ_Ŏ`, [Eye.Frown, Eye.Frown, Muzzle.Flat, Iris.Shocked, Iris.Shocked]],
[`=_=`, [Eye.Neutral4, Eye.Neutral4, Muzzle.Flat]],
[`a_a`, [Eye.Frown2, Eye.Frown2, Muzzle.Flat]],
[`e_e`, [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.UpLeft, Iris.UpLeft]],
[`е_е`, [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.UpLeft, Iris.UpLeft]],
[`g_g`, [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.UpRight, Iris.UpRight]],
[`9_9`, [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.Up, Iris.Up]],
[`6_9`, [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.Forward, Iris.Up]],
['>_>', [Eye.Neutral2, Eye.Neutral2, Muzzle.Flat, Iris.Left, Iris.Left]],
['<_<', [Eye.Neutral2, Eye.Neutral2, Muzzle.Flat, Iris.Right, Iris.Right]],
['<_>', [Eye.Neutral2, Eye.Neutral2, Muzzle.Flat, Iris.Right, Iris.Left]],
['d_d', [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.Right, Iris.Right]],
['b_b', [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.Left, Iris.Left]],
['twO', [Eye.Peaceful, Eye.Neutral, Muzzle.Smile]],
['o//o', [Eye.Neutral, Eye.Neutral, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Blush]],
['o/////o', [Eye.Neutral, Eye.Neutral, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Blush]],
['o\\\\o', [Eye.Neutral, Eye.Neutral, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Blush]],
['o\\\\\\o', [Eye.Neutral, Eye.Neutral, Muzzle.Neutral, Iris.Forward, Iris.Forward, ExpressionExtra.Blush]],
['>//<', [Eye.X, Eye.X, Muzzle.Neutral, Iris.Left, Iris.Right, ExpressionExtra.Blush]],
['-//v//-', [Eye.Closed, Eye.Closed, Muzzle.Smile, Iris.Forward, Iris.Forward, ExpressionExtra.Blush]],
['-///v///-', [Eye.Closed, Eye.Closed, Muzzle.Smile, Iris.Forward, Iris.Forward, ExpressionExtra.Blush]],
[';//v//;', [Eye.Sad, Eye.Sad, Muzzle.Smile, Iris.Forward, Iris.Forward, ExpressionExtra.Blush | ExpressionExtra.Cry]],
[`6_6`, [Eye.Neutral, Eye.Neutral, Muzzle.Flat, Iris.Down, Iris.Down]],
['6.6', [Eye.Neutral, Eye.Neutral, Muzzle.Kiss, Iris.Down, Iris.Down]],
['bcd', [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Down, Iris.Down]],
// TODO: o-o' o-o' ~_~ @_@ o=o oyo *_* (amazed) -_-/ -_-\ D_D
// vertical (short)
['^^', [Eye.ClosedHappy2, Eye.ClosedHappy2, Muzzle.Smile]],
['))', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['((', [Eye.Neutral, Eye.Neutral, Muzzle.Frown]],
['<<', [Eye.Neutral2, Eye.Neutral2, Muzzle.Flat, Iris.Right, Iris.Right]],
['>>', [Eye.Neutral2, Eye.Neutral2, Muzzle.Flat, Iris.Left, Iris.Left]],
// vertical (invalid)
...[
'---', '...', '000', 'QQQ', 'One', 'Up.', 'UP.',
...THREE_LETTER_WORDS,
...THREE_LETTER_WORDS.map(x => x.toUpperCase()),
...TWO_LETTER_WORDS.map(x => x + '.'),
...TWO_LETTER_WORDS.map(x => x + ','),
...TWO_LETTER_WORDS.map(x => x + '-'),
...TWO_LETTER_WORDS.map(x => x + '*'),
].map(x => [x, undefined] as [string, any]),
['BS', undefined],
['x8', undefined],
['8x', undefined],
['xx', undefined],
['-//c-', undefined],
['-c//-', undefined],
['030', undefined],
['80', undefined],
// other
[`aaaaa`, [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen3]],
[`AAAAAA`, [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen3, Iris.Shocked, Iris.Shocked]],
[`aaaaa...`, [Eye.Neutral, Eye.Neutral, Muzzle.ConcernedOpen3]],
[`zzz`, [Eye.Closed, Eye.Closed, Muzzle.Neutral]],
[`ZZZZZ`, [Eye.Closed, Eye.Closed, Muzzle.Neutral]],
[`zzz...`, [Eye.Closed, Eye.Closed, Muzzle.Neutral]],
// emoji
['🙂', [Eye.Neutral, Eye.Neutral, Muzzle.Smile]],
['😵', [Eye.Neutral, Eye.Neutral, Muzzle.Smile, Iris.Up, Iris.Forward]],
['😐', [Eye.Neutral, Eye.Neutral, Muzzle.Flat]],
['😑', [Eye.Lines, Eye.Lines, Muzzle.Flat]],
['😆', [Eye.X, Eye.X, Muzzle.SmileOpen]],
['😟', [Eye.Sad, Eye.Sad, Muzzle.Neutral]],
['😠', [Eye.Angry, Eye.Angry, Muzzle.Smile]],
['🤔', [Eye.Neutral, Eye.Frown2, Muzzle.Kiss]],
['😈', [Eye.Angry, Eye.Angry, Muzzle.Smile, Iris.Up, Iris.Forward]],
['👿', [Eye.Angry, Eye.Angry, Muzzle.SmileTeeth]],
// unsafe faces
[':L', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralPant]],
[':Q', [Eye.Neutral, Eye.Neutral, Muzzle.SmilePant]],
// safe replacements
[':u', [Eye.Neutral, Eye.Neutral, Muzzle.NeutralOpen2]],
[':DD', [Eye.Neutral, Eye.Neutral, Muzzle.SmileOpen2]],
];
+136 -136
View File
@@ -8,157 +8,157 @@ export const ipRegexText = '(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})';
export const ipExceptionRegex = /\d\.\d\.\d\.\d/ui;
export const urlExceptionRegex = /^(battle|paint|f(im|an)fiction)\.net$/ui;
export const urlRegexTexts = [
'https?:?//\\S+',
'\\bwww\\.[^. ]\\S+',
'\\S+[^. ]\\. *(c[o0]m|net)\\b',
'\\S+[^. ] *\\.(c[o0]m|net)\\b',
'(^| )[a-z][a-z0-9]{2,}[.,][a-z]{2,3}(/[a-z0-9_?=+-]+)+\\b',
'https?:?//\\S+',
'\\bwww\\.[^. ]\\S+',
'\\S+[^. ]\\. *(c[o0]m|net)\\b',
'\\S+[^. ] *\\.(c[o0]m|net)\\b',
'(^| )[a-z][a-z0-9]{2,}[.,][a-z]{2,3}(/[a-z0-9_?=+-]+)+\\b',
];
export function trimRepeatedLetters(test: string): string {
if (test.length > MAX_REPEATS && (/^.?(.)\1+$/u.test(test) || /^.?(..)\1+$/u.test(test))) {
return test.substr(0, MAX_REPEATS) + '…';
} else {
return test;
}
if (test.length > MAX_REPEATS && (/^.?(.)\1+$/u.test(test) || /^.?(..)\1+$/u.test(test))) {
return test.substr(0, MAX_REPEATS) + '…';
} else {
return test;
}
}
function createCharacterMap(data: string[][]): { [key: string]: string; } {
const mappings = data.map(([to, from]) => from.split(/ /g).map(x => [x, to]));
return fromPairs(flatten(mappings));
const mappings = data.map(([to, from]) => from.split(/ /g).map(x => [x, to]));
return fromPairs(flatten(mappings));
}
const characters = createCharacterMap([
[`'`, 'Ъ ъ Ь ь'],
['a', 'á ă ắ ặ ằ ẳ ẵ ǎ â ấ ậ ầ ẩ ẫ ä ǟ ȧ ǡ ạ ȁ à ả ȃ ā ą ᶏ ẚ å ǻ ḁ ⱥ ã ɐ ₐ А а @ α'],
['A', 'Á Ă Ắ Ặ Ằ Ẳ Ẵ Ǎ Â Ấ Ậ Ầ Ẩ Ẫ Ä Ǟ Ȧ Ǡ Ạ Ȁ À Ả Ȃ Ā Ą Å Ǻ Ḁ Ⱥ Ã Ɐ ᴀ'],
['aa', 'ꜳ'],
['AA', 'Ꜳ'],
['ae', 'æ ǽ ǣ ᴂ'],
['AE', 'Æ Ǽ Ǣ ᴁ'],
['ao', 'ꜵ'],
['AO', 'Ꜵ'],
['au', 'ꜷ'],
['AU', 'Ꜷ'],
['av', 'ꜹ ꜻ'],
['AV', 'Ꜹ Ꜻ'],
['ay', 'ꜽ'],
['AY', 'Ꜽ'],
['b', 'ḃ ḅ ɓ ḇ ᵬ ᶀ ƀ ƃ б'],
['B', 'Ḃ Ḅ Ɓ Ḇ Ƀ Ƃ ʙ ᴃ Б'],
['c', 'ć č ç ḉ ĉ ɕ ċ ƈ ȼ ↄ ꜿ'],
['C', 'Ć Č Ç Ḉ Ĉ Ċ Ƈ Ȼ Ꜿ ᴄ'],
['ch', 'ч'],
['CH', 'Ч'],
['d', 'ď ḑ ḓ ȡ ḋ ḍ ɗ ᶑ ḏ ᵭ ᶁ đ ɖ ƌ ꝺ д'],
['D', 'Ď Ḑ Ḓ Ḋ Ḍ Ɗ Ḏ Dz Dž Đ Ƌ Ꝺ ᴅ Д'],
['dz', 'dz dž'],
['DZ', 'DZ DŽ'],
['e', 'é ĕ ě ȩ ḝ ê ế ệ ề ể ễ ḙ ë ė ẹ ȅ è ẻ ȇ ē ḗ ḕ ⱸ ę ᶒ ɇ ẽ ḛ ɛ ᶓ ɘ ǝ ₑ е э ε'],
['E', 'É Ĕ Ě Ȩ Ḝ Ê Ế Ệ Ề Ể Ễ Ḙ Ë Ė Ẹ Ȅ È Ẻ Ȇ Ē Ḗ Ḕ Ę Ɇ Ẽ Ḛ Ɛ Ǝ ᴇ ⱻ Е Э'],
['et', 'ꝫ'],
['ET', 'Ꝫ'],
['f', 'ḟ ƒ ᵮ ᶂ ꝼ ф'],
['F', 'Ḟ Ƒ Ꝼ ꜰ Ф'],
['ff', 'ff'],
['ffi', 'ffi'],
['ffl', 'ffl'],
['fi', 'fi'],
['fl', 'fl'],
['g', 'ǵ ğ ǧ ģ ĝ ġ ɠ ḡ ᶃ ǥ ᵹ ɡ ᵷ г'],
['G', 'Ǵ Ğ Ǧ Ģ Ĝ Ġ Ɠ Ḡ Ǥ Ᵹ ɢ ʛ Г'],
['h', 'ḫ ȟ ḩ ĥ ⱨ ḧ ḣ ḥ ɦ ẖ ħ ɥ ʮ ʯ х'],
['H', 'Ḫ Ȟ Ḩ Ĥ Ⱨ Ḧ Ḣ Ḥ Ħ ʜ Х'],
['hv', 'ƕ'],
['i', 'ı í ĭ ǐ î ï ḯ ị ȉ ì ỉ ȋ ī į ᶖ ɨ ĩ ḭ ᴉ ᵢ й ы и ι'],
['I', 'Í Ĭ Ǐ Î Ï Ḯ İ Ị Ȉ Ì Ỉ Ȋ Ī Į Ɨ Ĩ Ḭ ɪ Й Ы И'],
['ij', 'ij'],
['IJ', 'IJ'],
['is', 'ꝭ'],
['IS', 'Ꝭ'],
['j', 'ȷ ɟ ʄ ǰ ĵ ʝ ɉ ⱼ'],
['J', 'Ĵ Ɉ ᴊ'],
['k', 'ḱ ǩ ķ ⱪ ꝃ ḳ ƙ ḵ ᶄ ꝁ ꝅ ʞ к'],
['K', 'Ḱ Ǩ Ķ Ⱪ Ꝃ Ḳ Ƙ Ḵ Ꝁ Ꝅ ᴋ К'],
['l', 'ĺ ƚ ɬ ľ ļ ḽ ȴ ḷ ḹ ⱡ ꝉ ḻ ŀ ɫ ᶅ ɭ ł ꞁ л'],
['L', 'Ĺ Ƚ Ľ Ļ Ḽ Ḷ Ḹ Ⱡ Ꝉ Ḻ Ŀ Ɫ Lj Ł Ꞁ ʟ ᴌ Л'],
['lj', 'lj'],
['LJ', 'LJ'],
['m', 'ḿ ṁ ṃ ɱ ᵯ ᶆ ɯ ɰ м'],
['M', 'Ḿ Ṁ Ṃ Ɱ Ɯ ᴍ М'],
['n', 'ń ň ņ ṋ ȵ ṅ ṇ ǹ ɲ ṉ ƞ ᵰ ᶇ ɳ ñ н η'],
['N', 'Ń Ň Ņ Ṋ Ṅ Ṇ Ǹ Ɲ Ṉ Ƞ Nj Ñ ɴ ᴎ Н'],
['nj', 'nj'],
['NJ', 'NJ'],
['o', 'ɵ ó ŏ ǒ ô ố ộ ồ ổ ỗ ö ȫ ȯ ȱ ọ ő ȍ ò ỏ ơ ớ ợ ờ ở ỡ ȏ ꝋ ꝍ ⱺ ō ṓ ṑ ǫ ǭ ø ǿ õ ṍ ṏ ȭ ɔ ᶗ ᴑ ᴓ ₒ о'],
['O', 'Ó Ŏ Ǒ Ô Ố Ộ Ồ Ổ Ỗ Ö Ȫ Ȯ Ȱ Ọ Ő Ȍ Ò Ỏ Ơ Ớ Ợ Ờ Ở Ỡ Ȏ Ꝋ Ꝍ Ō Ṓ Ṑ Ɵ Ǫ Ǭ Ø Ǿ Õ Ṍ Ṏ Ȭ Ɔ ᴏ ᴐ О'],
['oe', 'ᴔ œ'],
['OE', 'Œ ɶ'],
['oi', 'ƣ'],
['OI', 'Ƣ'],
['oo', 'ꝏ'],
['OO', 'Ꝏ'],
['ou', 'ȣ'],
['OU', 'Ȣ ᴕ'],
['p', 'ṕ ṗ ꝓ ƥ ᵱ ᶈ ꝕ ᵽ ꝑ п'],
['P', 'Ṕ Ṗ Ꝓ Ƥ Ꝕ Ᵽ Ꝑ ᴘ П'],
['q', 'ꝙ ʠ ɋ ꝗ'],
['Q', 'Ꝙ Ꝗ'],
['r', 'ꞃ ŕ ř ŗ ṙ ṛ ṝ ȑ ɾ ᵳ ȓ ṟ ɼ ᵲ ᶉ ɍ ɽ ɿ ɹ ɻ ɺ ⱹ ᵣ р'],
['R', 'Ꞃ Ŕ Ř Ŗ Ṙ Ṛ Ṝ Ȑ Ȓ Ṟ Ɍ Ɽ ʁ ʀ ᴙ ᴚ Р ®'],
['s', 'ꞅ ſ ẜ ẛ ẝ ś ṥ š ṧ ş ŝ ș ṡ ṣ ṩ ʂ ᵴ ᶊ ȿ с'],
['S', 'Ꞅ Ś Ṥ Š Ṧ Ş Ŝ Ș Ṡ Ṣ Ṩ ꜱ С $'],
['sch', 'щ'],
['SCH', 'Щ'],
['sh', 'ш'],
['SH', 'Ш'],
['ss', 'ß'],
['st', 'st'],
['t', 'ꞇ ť ţ ṱ ț ȶ ẗ ⱦ ṫ ṭ ƭ ṯ ᵵ ƫ ʈ ŧ ʇ т'],
['T', 'Ꞇ Ť Ţ Ṱ Ț Ⱦ Ṫ Ṭ Ƭ Ṯ Ʈ Ŧ ᴛ Т'],
['th', 'ᵺ'],
['ts', 'ц'],
['TS', 'Ц'],
['tz', 'ꜩ'],
['TZ', 'Ꜩ'],
['u', 'ᴝ ú ŭ ǔ û ṷ ü ǘ ǚ ǜ ǖ ṳ ụ ű ȕ ù ủ ư ứ ự ừ ử ữ ȗ ū ṻ ų ᶙ ů ũ ṹ ṵ ᵤ у'],
['U', 'Ú Ŭ Ǔ Û Ṷ Ü Ǘ Ǚ Ǜ Ǖ Ṳ Ụ Ű Ȕ Ù Ủ Ư Ứ Ự Ừ Ử Ữ Ȗ Ū Ṻ Ų Ů Ũ Ṹ Ṵ ᴜ У'],
['ue', 'ᵫ'],
['um', 'ꝸ'],
['v', 'ʌ ⱴ ꝟ ṿ ʋ ᶌ ⱱ ṽ ᵥ в'],
['V', 'Ʌ Ꝟ Ṿ Ʋ Ṽ ᴠ В'],
['vy', 'ꝡ'],
['VY', 'Ꝡ'],
['w', 'ʍ ẃ ŵ ẅ ẇ ẉ ẁ ⱳ ẘ'],
['W', 'Ẃ Ŵ Ẅ Ẇ Ẉ Ẁ Ⱳ ᴡ'],
['x', 'ẍ ẋ ᶍ ₓ'],
['X', 'Ẍ Ẋ'],
['y', 'ʎ ý ŷ ÿ ẏ ỵ ỳ ƴ ỷ ỿ ȳ ẙ ɏ ỹ'],
['Y', 'Ý Ŷ Ÿ Ẏ Ỵ Ỳ Ƴ Ỷ Ỿ Ȳ Ɏ Ỹ ʏ'],
['ya', 'я'],
['Ya', 'Я'],
['yo', 'ё'],
['YO', 'Ё'],
['yu', 'ю'],
['YU', 'Ю'],
['z', 'ź ž ẑ ʑ ⱬ ż ẓ ȥ ẕ ᵶ ᶎ ʐ ƶ ɀ з'],
['Z', 'Ź Ž Ẑ Ⱬ Ż Ẓ Ȥ Ẕ Ƶ ᴢ З'],
['zh', 'ж'],
['ZH', 'Ж'],
[`'`, 'Ъ ъ Ь ь'],
['a', 'á ă ắ ặ ằ ẳ ẵ ǎ â ấ ậ ầ ẩ ẫ ä ǟ ȧ ǡ ạ ȁ à ả ȃ ā ą ᶏ ẚ å ǻ ḁ ⱥ ã ɐ ₐ А а @ α'],
['A', 'Á Ă Ắ Ặ Ằ Ẳ Ẵ Ǎ Â Ấ Ậ Ầ Ẩ Ẫ Ä Ǟ Ȧ Ǡ Ạ Ȁ À Ả Ȃ Ā Ą Å Ǻ Ḁ Ⱥ Ã Ɐ ᴀ'],
['aa', 'ꜳ'],
['AA', 'Ꜳ'],
['ae', 'æ ǽ ǣ ᴂ'],
['AE', 'Æ Ǽ Ǣ ᴁ'],
['ao', 'ꜵ'],
['AO', 'Ꜵ'],
['au', 'ꜷ'],
['AU', 'Ꜷ'],
['av', 'ꜹ ꜻ'],
['AV', 'Ꜹ Ꜻ'],
['ay', 'ꜽ'],
['AY', 'Ꜽ'],
['b', 'ḃ ḅ ɓ ḇ ᵬ ᶀ ƀ ƃ б'],
['B', 'Ḃ Ḅ Ɓ Ḇ Ƀ Ƃ ʙ ᴃ Б'],
['c', 'ć č ç ḉ ĉ ɕ ċ ƈ ȼ ↄ ꜿ'],
['C', 'Ć Č Ç Ḉ Ĉ Ċ Ƈ Ȼ Ꜿ ᴄ'],
['ch', 'ч'],
['CH', 'Ч'],
['d', 'ď ḑ ḓ ȡ ḋ ḍ ɗ ᶑ ḏ ᵭ ᶁ đ ɖ ƌ ꝺ д'],
['D', 'Ď Ḑ Ḓ Ḋ Ḍ Ɗ Ḏ Dz Dž Đ Ƌ Ꝺ ᴅ Д'],
['dz', 'dz dž'],
['DZ', 'DZ DŽ'],
['e', 'é ĕ ě ȩ ḝ ê ế ệ ề ể ễ ḙ ë ė ẹ ȅ è ẻ ȇ ē ḗ ḕ ⱸ ę ᶒ ɇ ẽ ḛ ɛ ᶓ ɘ ǝ ₑ е э ε'],
['E', 'É Ĕ Ě Ȩ Ḝ Ê Ế Ệ Ề Ể Ễ Ḙ Ë Ė Ẹ Ȅ È Ẻ Ȇ Ē Ḗ Ḕ Ę Ɇ Ẽ Ḛ Ɛ Ǝ ᴇ ⱻ Е Э'],
['et', 'ꝫ'],
['ET', 'Ꝫ'],
['f', 'ḟ ƒ ᵮ ᶂ ꝼ ф'],
['F', 'Ḟ Ƒ Ꝼ ꜰ Ф'],
['ff', 'ff'],
['ffi', 'ffi'],
['ffl', 'ffl'],
['fi', 'fi'],
['fl', 'fl'],
['g', 'ǵ ğ ǧ ģ ĝ ġ ɠ ḡ ᶃ ǥ ᵹ ɡ ᵷ г'],
['G', 'Ǵ Ğ Ǧ Ģ Ĝ Ġ Ɠ Ḡ Ǥ Ᵹ ɢ ʛ Г'],
['h', 'ḫ ȟ ḩ ĥ ⱨ ḧ ḣ ḥ ɦ ẖ ħ ɥ ʮ ʯ х'],
['H', 'Ḫ Ȟ Ḩ Ĥ Ⱨ Ḧ Ḣ Ḥ Ħ ʜ Х'],
['hv', 'ƕ'],
['i', 'ı í ĭ ǐ î ï ḯ ị ȉ ì ỉ ȋ ī į ᶖ ɨ ĩ ḭ ᴉ ᵢ й ы и ι'],
['I', 'Í Ĭ Ǐ Î Ï Ḯ İ Ị Ȉ Ì Ỉ Ȋ Ī Į Ɨ Ĩ Ḭ ɪ Й Ы И'],
['ij', 'ij'],
['IJ', 'IJ'],
['is', 'ꝭ'],
['IS', 'Ꝭ'],
['j', 'ȷ ɟ ʄ ǰ ĵ ʝ ɉ ⱼ'],
['J', 'Ĵ Ɉ ᴊ'],
['k', 'ḱ ǩ ķ ⱪ ꝃ ḳ ƙ ḵ ᶄ ꝁ ꝅ ʞ к'],
['K', 'Ḱ Ǩ Ķ Ⱪ Ꝃ Ḳ Ƙ Ḵ Ꝁ Ꝅ ᴋ К'],
['l', 'ĺ ƚ ɬ ľ ļ ḽ ȴ ḷ ḹ ⱡ ꝉ ḻ ŀ ɫ ᶅ ɭ ł ꞁ л'],
['L', 'Ĺ Ƚ Ľ Ļ Ḽ Ḷ Ḹ Ⱡ Ꝉ Ḻ Ŀ Ɫ Lj Ł Ꞁ ʟ ᴌ Л'],
['lj', 'lj'],
['LJ', 'LJ'],
['m', 'ḿ ṁ ṃ ɱ ᵯ ᶆ ɯ ɰ м'],
['M', 'Ḿ Ṁ Ṃ Ɱ Ɯ ᴍ М'],
['n', 'ń ň ņ ṋ ȵ ṅ ṇ ǹ ɲ ṉ ƞ ᵰ ᶇ ɳ ñ н η'],
['N', 'Ń Ň Ņ Ṋ Ṅ Ṇ Ǹ Ɲ Ṉ Ƞ Nj Ñ ɴ ᴎ Н'],
['nj', 'nj'],
['NJ', 'NJ'],
['o', 'ɵ ó ŏ ǒ ô ố ộ ồ ổ ỗ ö ȫ ȯ ȱ ọ ő ȍ ò ỏ ơ ớ ợ ờ ở ỡ ȏ ꝋ ꝍ ⱺ ō ṓ ṑ ǫ ǭ ø ǿ õ ṍ ṏ ȭ ɔ ᶗ ᴑ ᴓ ₒ о'],
['O', 'Ó Ŏ Ǒ Ô Ố Ộ Ồ Ổ Ỗ Ö Ȫ Ȯ Ȱ Ọ Ő Ȍ Ò Ỏ Ơ Ớ Ợ Ờ Ở Ỡ Ȏ Ꝋ Ꝍ Ō Ṓ Ṑ Ɵ Ǫ Ǭ Ø Ǿ Õ Ṍ Ṏ Ȭ Ɔ ᴏ ᴐ О'],
['oe', 'ᴔ œ'],
['OE', 'Œ ɶ'],
['oi', 'ƣ'],
['OI', 'Ƣ'],
['oo', 'ꝏ'],
['OO', 'Ꝏ'],
['ou', 'ȣ'],
['OU', 'Ȣ ᴕ'],
['p', 'ṕ ṗ ꝓ ƥ ᵱ ᶈ ꝕ ᵽ ꝑ п'],
['P', 'Ṕ Ṗ Ꝓ Ƥ Ꝕ Ᵽ Ꝑ ᴘ П'],
['q', 'ꝙ ʠ ɋ ꝗ'],
['Q', 'Ꝙ Ꝗ'],
['r', 'ꞃ ŕ ř ŗ ṙ ṛ ṝ ȑ ɾ ᵳ ȓ ṟ ɼ ᵲ ᶉ ɍ ɽ ɿ ɹ ɻ ɺ ⱹ ᵣ р'],
['R', 'Ꞃ Ŕ Ř Ŗ Ṙ Ṛ Ṝ Ȑ Ȓ Ṟ Ɍ Ɽ ʁ ʀ ᴙ ᴚ Р ®'],
['s', 'ꞅ ſ ẜ ẛ ẝ ś ṥ š ṧ ş ŝ ș ṡ ṣ ṩ ʂ ᵴ ᶊ ȿ с'],
['S', 'Ꞅ Ś Ṥ Š Ṧ Ş Ŝ Ș Ṡ Ṣ Ṩ ꜱ С $'],
['sch', 'щ'],
['SCH', 'Щ'],
['sh', 'ш'],
['SH', 'Ш'],
['ss', 'ß'],
['st', 'st'],
['t', 'ꞇ ť ţ ṱ ț ȶ ẗ ⱦ ṫ ṭ ƭ ṯ ᵵ ƫ ʈ ŧ ʇ т'],
['T', 'Ꞇ Ť Ţ Ṱ Ț Ⱦ Ṫ Ṭ Ƭ Ṯ Ʈ Ŧ ᴛ Т'],
['th', 'ᵺ'],
['ts', 'ц'],
['TS', 'Ц'],
['tz', 'ꜩ'],
['TZ', 'Ꜩ'],
['u', 'ᴝ ú ŭ ǔ û ṷ ü ǘ ǚ ǜ ǖ ṳ ụ ű ȕ ù ủ ư ứ ự ừ ử ữ ȗ ū ṻ ų ᶙ ů ũ ṹ ṵ ᵤ у'],
['U', 'Ú Ŭ Ǔ Û Ṷ Ü Ǘ Ǚ Ǜ Ǖ Ṳ Ụ Ű Ȕ Ù Ủ Ư Ứ Ự Ừ Ử Ữ Ȗ Ū Ṻ Ų Ů Ũ Ṹ Ṵ ᴜ У'],
['ue', 'ᵫ'],
['um', 'ꝸ'],
['v', 'ʌ ⱴ ꝟ ṿ ʋ ᶌ ⱱ ṽ ᵥ в'],
['V', 'Ʌ Ꝟ Ṿ Ʋ Ṽ ᴠ В'],
['vy', 'ꝡ'],
['VY', 'Ꝡ'],
['w', 'ʍ ẃ ŵ ẅ ẇ ẉ ẁ ⱳ ẘ'],
['W', 'Ẃ Ŵ Ẅ Ẇ Ẉ Ẁ Ⱳ ᴡ'],
['x', 'ẍ ẋ ᶍ ₓ'],
['X', 'Ẍ Ẋ'],
['y', 'ʎ ý ŷ ÿ ẏ ỵ ỳ ƴ ỷ ỿ ȳ ẙ ɏ ỹ'],
['Y', 'Ý Ŷ Ÿ Ẏ Ỵ Ỳ Ƴ Ỷ Ỿ Ȳ Ɏ Ỹ ʏ'],
['ya', 'я'],
['Ya', 'Я'],
['yo', 'ё'],
['YO', 'Ё'],
['yu', 'ю'],
['YU', 'Ю'],
['z', 'ź ž ẑ ʑ ⱬ ż ẓ ȥ ẕ ᵶ ᶎ ʐ ƶ ɀ з'],
['Z', 'Ź Ž Ẑ Ⱬ Ż Ẓ Ȥ Ẕ Ƶ ᴢ З'],
['zh', 'ж'],
['ZH', 'Ж'],
]);
const nonAscii = /[^A-Za-z0-9]/g;
export function latinize(text: string): string {
return text
.replace(matchRomaji, replaceRomaji)
.replace(nonAscii, x => characters[x] || x);
return text
.replace(matchRomaji, replaceRomaji)
.replace(nonAscii, x => characters[x] || x);
}
export function latinize2(name: string): string {
return latinize(name
.replace(/[ǫ]/ui, 'q')
.replace(/[с]/ui, 'c')
.replace(/[н]|\|-\|/ui, 'h')
.replace(/[лпий]/ui, 'n'));
return latinize(name
.replace(/[ǫ]/ui, 'q')
.replace(/[с]/ui, 'c')
.replace(/[н]|\|-\|/ui, 'h')
.replace(/[лпий]/ui, 'n'));
}
+1171 -1171
View File
File diff suppressed because it is too large Load Diff
+75 -75
View File
@@ -1,122 +1,122 @@
import { Matrix2D } from './interfaces';
export function createMat2D(): Matrix2D {
const out = new Float32Array(6);
out[0] = 1;
out[3] = 1;
return out;
const out = new Float32Array(6);
out[0] = 1;
out[3] = 1;
return out;
}
export function identityMat2D(out: Matrix2D) {
out[0] = 1;
out[1] = 0;
out[2] = 0;
out[3] = 1;
out[4] = 0;
out[5] = 0;
return out;
out[0] = 1;
out[1] = 0;
out[2] = 0;
out[3] = 1;
out[4] = 0;
out[5] = 0;
return out;
}
export function copyMat2D(out: Matrix2D, a: Matrix2D) {
out.set(a);
return out;
out.set(a);
return out;
}
export function setMat2D(out: Matrix2D, a: number, b: number, c: number, d: number, tx: number, ty: number) {
out[0] = a;
out[1] = b;
out[2] = c;
out[3] = d;
out[4] = tx;
out[5] = ty;
return out;
out[0] = a;
out[1] = b;
out[2] = c;
out[3] = d;
out[4] = tx;
out[5] = ty;
return out;
}
export function mulMat2D(out: Matrix2D, a: Matrix2D, b: Matrix2D) {
const a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5];
const b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3], b4 = b[4], b5 = b[5];
out[0] = a0 * b0 + a2 * b1;
out[1] = a1 * b0 + a3 * b1;
out[2] = a0 * b2 + a2 * b3;
out[3] = a1 * b2 + a3 * b3;
out[4] = a0 * b4 + a2 * b5 + a4;
out[5] = a1 * b4 + a3 * b5 + a5;
return out;
const a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5];
const b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3], b4 = b[4], b5 = b[5];
out[0] = a0 * b0 + a2 * b1;
out[1] = a1 * b0 + a3 * b1;
out[2] = a0 * b2 + a2 * b3;
out[3] = a1 * b2 + a3 * b3;
out[4] = a0 * b4 + a2 * b5 + a4;
out[5] = a1 * b4 + a3 * b5 + a5;
return out;
}
export function translateMat2D(out: Matrix2D, a: Matrix2D, x: number, y: number) {
const a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5];
out[0] = a0;
out[1] = a1;
out[2] = a2;
out[3] = a3;
out[4] = a0 * x + a2 * y + a4;
out[5] = a1 * x + a3 * y + a5;
return out;
const a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5];
out[0] = a0;
out[1] = a1;
out[2] = a2;
out[3] = a3;
out[4] = a0 * x + a2 * y + a4;
out[5] = a1 * x + a3 * y + a5;
return out;
}
export function rotateMat2D(out: Matrix2D, a: Matrix2D, rad: number) {
const a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5];
const s = Math.sin(rad);
const c = Math.cos(rad);
out[0] = a0 * c + a2 * s;
out[1] = a1 * c + a3 * s;
out[2] = a0 * -s + a2 * c;
out[3] = a1 * -s + a3 * c;
out[4] = a4;
out[5] = a5;
return out;
const a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5];
const s = Math.sin(rad);
const c = Math.cos(rad);
out[0] = a0 * c + a2 * s;
out[1] = a1 * c + a3 * s;
out[2] = a0 * -s + a2 * c;
out[3] = a1 * -s + a3 * c;
out[4] = a4;
out[5] = a5;
return out;
}
export function scaleMat2D(out: Matrix2D, a: Matrix2D, x: number, y: number) {
const a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5];
out[0] = a0 * x;
out[1] = a1 * x;
out[2] = a2 * y;
out[3] = a3 * y;
out[4] = a4;
out[5] = a5;
return out;
const a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5];
out[0] = a0 * x;
out[1] = a1 * x;
out[2] = a2 * y;
out[3] = a3 * y;
out[4] = a4;
out[5] = a5;
return out;
}
const temp = createMat2D();
export function skewX(out: Matrix2D, a: Matrix2D, angle: number): Matrix2D {
setMat2D(temp, 1, 0, Math.tan(angle), 1, 0, 0);
mulMat2D(out, a, temp);
return out;
setMat2D(temp, 1, 0, Math.tan(angle), 1, 0, 0);
mulMat2D(out, a, temp);
return out;
}
export function skewY(out: Matrix2D, a: Matrix2D, angle: number): Matrix2D {
setMat2D(temp, 1, Math.tan(angle), 0, 1, 0, 0);
mulMat2D(out, a, temp);
return out;
setMat2D(temp, 1, Math.tan(angle), 0, 1, 0, 0);
mulMat2D(out, a, temp);
return out;
}
const tempMatrix = createMat2D();
export function skewTransform(base: Matrix2D | undefined, skew: number, ox: number, oy: number, x: number, y: number): Matrix2D {
identityMat2D(tempMatrix);
identityMat2D(tempMatrix);
if (skew) {
translateMat2D(tempMatrix, tempMatrix, ox + x, oy + y);
skewY(tempMatrix, tempMatrix, skew);
translateMat2D(tempMatrix, tempMatrix, -ox, -oy);
} else {
translateMat2D(tempMatrix, tempMatrix, x, y);
}
if (skew) {
translateMat2D(tempMatrix, tempMatrix, ox + x, oy + y);
skewY(tempMatrix, tempMatrix, skew);
translateMat2D(tempMatrix, tempMatrix, -ox, -oy);
} else {
translateMat2D(tempMatrix, tempMatrix, x, y);
}
if (base !== undefined) {
mulMat2D(tempMatrix, base, tempMatrix);
}
if (base !== undefined) {
mulMat2D(tempMatrix, base, tempMatrix);
}
return tempMatrix;
return tempMatrix;
}
export function isIdentity(m: Matrix2D) {
return m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0;
return m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0;
}
export function isTranslation(m: Matrix2D) {
return m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1;
return m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1;
}
+26 -26
View File
@@ -1,33 +1,33 @@
import { Matrix4 } from './interfaces';
export function createMat4(): Matrix4 {
const out = new Float32Array(16);
out[0] = 1;
out[5] = 1;
out[10] = 1;
out[15] = 1;
return out;
const out = new Float32Array(16);
out[0] = 1;
out[5] = 1;
out[10] = 1;
out[15] = 1;
return out;
}
export function ortho(out: Matrix4, left: number, right: number, bottom: number, top: number, near: number, far: number) {
const lr = 1 / (left - right);
const bt = 1 / (bottom - top);
const nf = 1 / (near - far);
out[0] = -2 * lr;
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = -2 * bt;
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[10] = 2 * nf;
out[11] = 0;
out[12] = (left + right) * lr;
out[13] = (top + bottom) * bt;
out[14] = (far + near) * nf;
out[15] = 1;
return out;
const lr = 1 / (left - right);
const bt = 1 / (bottom - top);
const nf = 1 / (near - far);
out[0] = -2 * lr;
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = -2 * bt;
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[10] = 2 * nf;
out[11] = 0;
out[12] = (left + right) * lr;
out[13] = (top + bottom) * bt;
out[14] = (far + near) * nf;
out[15] = 1;
return out;
}
+561 -561
View File
File diff suppressed because it is too large Load Diff
+72 -72
View File
@@ -5,22 +5,22 @@ import { toWorldX, toWorldY } from './positionUtils';
import { rect } from './rect';
const DIRS = [
[0, -1], // 0
[0.5, -1],
[1, -1],
[1, -0.5],
[1, 0], // 4
[1, 0.5],
[1, 1],
[0.5, 1],
[0, 1], // 8
[-0.5, 1],
[-1, 1],
[-1, 0.5],
[-1, 0], // 12
[-1, -0.5],
[-1, -1],
[-0.5, -1],
[0, -1], // 0
[0.5, -1],
[1, -1],
[1, -0.5],
[1, 0], // 4
[1, 0.5],
[1, 1],
[0.5, 1],
[0, 1], // 8
[-0.5, 1],
[-1, 1],
[-1, 0.5],
[-1, 0], // 12
[-1, -0.5],
[-1, -1],
[-0.5, -1],
];
const SECA = 0xcd3003ca;
@@ -32,92 +32,92 @@ const PI2 = Math.PI * 2;
const DIRS_ANGLE = DIRS.length / PI2;
export function flagsToSpeed(flags: EntityState): number {
const state = flags & EntityState.PonyStateMask;
const state = flags & EntityState.PonyStateMask;
if (state === EntityState.PonyTrotting) {
return PONY_SPEED_TROT;
} else if (state === EntityState.PonyWalking) {
return PONY_SPEED_WALK;
} else {
return 0;
}
if (state === EntityState.PonyTrotting) {
return PONY_SPEED_TROT;
} else if (state === EntityState.PonyWalking) {
return PONY_SPEED_WALK;
} else {
return 0;
}
}
export function dirToVector(dir: number): Point {
const [x, y] = DIRS[(dir | 0) % DIRS.length];
return { x, y };
const [x, y] = DIRS[(dir | 0) % DIRS.length];
return { x, y };
}
export function vectorToDir(x: number, y: number): number {
const angle = Math.atan2(x, -y);
return Math.round((angle < 0 ? angle + PI2 : angle) * DIRS_ANGLE) % DIRS.length;
const angle = Math.atan2(x, -y);
return Math.round((angle < 0 ? angle + PI2 : angle) * DIRS_ANGLE) % DIRS.length;
}
export interface Movement {
x: number;
y: number;
dir: number;
flags: EntityState;
time: number;
camera: Rect;
x: number;
y: number;
dir: number;
flags: EntityState;
time: number;
camera: Rect;
}
export const POSITION_MIN = 0;
export const POSITION_MAX = 100000;
export function encodeMovement(
x: number, y: number, dir: number, flags: EntityState, time: number, camera: Rect
x: number, y: number, dir: number, flags: EntityState, time: number, camera: Rect
): [number, number, number, number, number] {
const pixelX = Math.floor(clamp(x, POSITION_MIN, POSITION_MAX) * tileWidth);
const pixelY = Math.floor(clamp(y, POSITION_MIN, POSITION_MAX) * tileHeight);
const camX = ((pixelX - camera.x) & 0xfff) >>> 0;
const camY = ((pixelY - camera.y) & 0xfff) >>> 0;
const camW = (camera.w & 0xfff) >>> 0;
const camH = (camera.h & 0xfff) >>> 0;
const pixelX = Math.floor(clamp(x, POSITION_MIN, POSITION_MAX) * tileWidth);
const pixelY = Math.floor(clamp(y, POSITION_MIN, POSITION_MAX) * tileHeight);
const camX = ((pixelX - camera.x) & 0xfff) >>> 0;
const camY = ((pixelY - camera.y) & 0xfff) >>> 0;
const camW = (camera.w & 0xfff) >>> 0;
const camH = (camera.h & 0xfff) >>> 0;
const a = pixelX | ((dir & 0xff) << 24);
const b = pixelY | ((flags & 0xff) << 24);
const c = time;
const d = (camX << 20) | (camY << 8) | (camW >>> 4);
const e = ((camW & 0xf) << 12) | camH;
const a = pixelX | ((dir & 0xff) << 24);
const b = pixelY | ((flags & 0xff) << 24);
const c = time;
const d = (camX << 20) | (camY << 8) | (camW >>> 4);
const e = ((camW & 0xf) << 12) | camH;
return [
(a ^ SECA) >>> 0,
(b ^ SECB) >>> 0,
(c ^ SECC) >>> 0,
(d ^ SECD) >>> 0,
(e ^ SECE) >>> 0,
];
return [
(a ^ SECA) >>> 0,
(b ^ SECB) >>> 0,
(c ^ SECC) >>> 0,
(d ^ SECD) >>> 0,
(e ^ SECE) >>> 0,
];
}
export function decodeMovement(a: number, b: number, c: number, d: number, e: number): Movement {
a = (a >>> 0) ^ SECA;
b = (b >>> 0) ^ SECB;
c = (c >>> 0) ^ SECC;
d = (d >>> 0) ^ SECD;
e = (e >>> 0) ^ SECE;
a = (a >>> 0) ^ SECA;
b = (b >>> 0) ^ SECB;
c = (c >>> 0) ^ SECC;
d = (d >>> 0) ^ SECD;
e = (e >>> 0) ^ SECE;
const pixelX = a & 0xffffff;
const pixelY = b & 0xffffff;
const pixelX = a & 0xffffff;
const pixelY = b & 0xffffff;
const x = toWorldX(pixelX + 0.5);
const y = toWorldY(pixelY + 0.5);
const dir = (a >>> 24) & 0xff;
const flags = (b >>> 24) & 0xff;
const time = c;
const x = toWorldX(pixelX + 0.5);
const y = toWorldY(pixelY + 0.5);
const dir = (a >>> 24) & 0xff;
const flags = (b >>> 24) & 0xff;
const time = c;
const camX = pixelX - ((d >>> 20) & 0xfff);
const camY = pixelY - ((d >>> 8) & 0xfff);
const camW = ((d & 0xff) << 4) | ((e >>> 12) & 0xf);
const camH = e & 0xfff;
const camX = pixelX - ((d >>> 20) & 0xfff);
const camY = pixelY - ((d >>> 8) & 0xfff);
const camW = ((d & 0xff) << 4) | ((e >>> 12) & 0xf);
const camH = e & 0xfff;
return { x, y, dir, flags, time, camera: rect(camX, camY, camW, camH) };
return { x, y, dir, flags, time, camera: rect(camX, camY, camW, camH) };
}
export function isMovingRight(vx: number, right: boolean): boolean {
return vx < 0 ? false : (vx > 0 ? true : right);
return vx < 0 ? false : (vx > 0 ? true : right);
}
export function shouldBeFacingRight(entity: Entity): boolean {
return isMovingRight(entity.vx, hasFlag(entity.state, EntityState.FacingRight));
return isMovingRight(entity.vx, hasFlag(entity.state, EntityState.FacingRight));
}
+84 -84
View File
@@ -1,6 +1,6 @@
interface Point {
x: number;
y: number;
x: number;
y: number;
}
type Pt = [number, number];
@@ -20,19 +20,19 @@ export const waistAccessoryOffsets: Point[] = [];
export const chestAccessoryOffsets: Point[] = [];
function offsets(
_index: number, cm: Pt, head: Pt, tail: Pt, wing: Pt, frontLeg: Pt, backLeg: Pt,
neckAccessory: Pt, backAccessory: Pt, waistAccessory: Pt, chestAccessory: Pt
_index: number, cm: Pt, head: Pt, tail: Pt, wing: Pt, frontLeg: Pt, backLeg: Pt,
neckAccessory: Pt, backAccessory: Pt, waistAccessory: Pt, chestAccessory: Pt
) {
cmOffsets.push(createPoint(cm));
headOffsets.push(createPoint(head));
tailOffsets.push(createPoint(tail));
wingOffsets.push(createPoint(wing));
frontLegOffsets.push(createPoint(frontLeg));
backLegOffsets.push(createPoint(backLeg));
neckAccessoryOffsets.push(createPoint(neckAccessory));
backAccessoryOffsets.push(createPoint(backAccessory));
waistAccessoryOffsets.push(createPoint(waistAccessory));
chestAccessoryOffsets.push(createPoint(chestAccessory));
cmOffsets.push(createPoint(cm));
headOffsets.push(createPoint(head));
tailOffsets.push(createPoint(tail));
wingOffsets.push(createPoint(wing));
frontLegOffsets.push(createPoint(frontLeg));
backLegOffsets.push(createPoint(backLeg));
neckAccessoryOffsets.push(createPoint(neckAccessory));
backAccessoryOffsets.push(createPoint(backAccessory));
waistAccessoryOffsets.push(createPoint(waistAccessory));
chestAccessoryOffsets.push(createPoint(chestAccessory));
}
// stand: cm head tail wing frontLeg backLeg neck back waist chest
@@ -59,80 +59,80 @@ offsets(15, [8, 11], [7, 11], [9, 14], [7, 11], [6, 9], [8, 11], /***/[7, 11], [
offsets(16, [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], /***/[0, 0], [0, 0], [0, 0], [0, 0]);
export const EAR_ACCESSORY_OFFSETS = createPoints([
[0, 0], // 0
[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0], // 5
[0, 0], // 0
[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0], // 5
]);
export const EXTRA_ACCESSORY_OFFSETS = createPoints([
[0, 9], // 0
[0, 0],
[0, 0],
[0, 1],
[0, 2],
[0, 2], // 5
[0, 1],
[0, 1],
[0, 2],
[0, 3],
[0, 2], // 10
[0, 2],
[0, 3],
[0, 1],
[0, 1],
[0, 1], // 15
[0, 9],
[0, 3],
[0, 3],
[0, 3],
[0, 3], // 20
[0, 3],
[0, 2],
[0, 3],
[0, 3],
[0, 3], // 25
[0, 2],
[0, 3],
[-1, 3],
[0, 3],
[0, 3], // 30
[0, 3],
[0, 9], // 0
[0, 0],
[0, 0],
[0, 1],
[0, 2],
[0, 2], // 5
[0, 1],
[0, 1],
[0, 2],
[0, 3],
[0, 2], // 10
[0, 2],
[0, 3],
[0, 1],
[0, 1],
[0, 1], // 15
[0, 9],
[0, 3],
[0, 3],
[0, 3],
[0, 3], // 20
[0, 3],
[0, 2],
[0, 3],
[0, 3],
[0, 3], // 25
[0, 2],
[0, 3],
[-1, 3],
[0, 3],
[0, 3], // 30
[0, 3],
]);
export const HEAD_ACCESSORY_OFFSETS = createPoints([
[0, 0], // 0
[0, -5],
[0, -5],
[0, -4],
[0, -4],
[0, -4], // 5
[0, -4],
[1, -4],
[0, -4],
[0, -3],
[0, -4], // 10
[0, -4],
[0, -3],
[1, -5],
[0, -4],
[0, -4], // 15
[0, 0],
[0, -4],
[0, -4],
[0, -4],
[0, -4], // 20
[0, -4],
[0, -5],
[0, -5],
[0, -4],
[1, -3], // 25
[0, -4],
[0, -4],
[0, -4],
[0, -4],
[0, -4], // 30
[0, -3],
[0, 0], // 0
[0, -5],
[0, -5],
[0, -4],
[0, -4],
[0, -4], // 5
[0, -4],
[1, -4],
[0, -4],
[0, -3],
[0, -4], // 10
[0, -4],
[0, -3],
[1, -5],
[0, -4],
[0, -4], // 15
[0, 0],
[0, -4],
[0, -4],
[0, -4],
[0, -4], // 20
[0, -4],
[0, -5],
[0, -5],
[0, -4],
[1, -3], // 25
[0, -4],
[0, -4],
[0, -4],
[0, -4],
[0, -4], // 30
[0, -3],
]);
+473 -473
View File
File diff suppressed because it is too large Load Diff
+393 -393
View File
@@ -1,16 +1,16 @@
import * as sprites from '../generated/sprites';
import { releasePalette, createPalette } from '../graphics/paletteManager';
import {
PonyInfo, SpriteSet, PalettePonyInfo, PaletteSpriteSet, PaletteManager, Palette, ColorExtraSets, PonyInfoBase,
PonyInfoNumber, ColorExtra
PonyInfo, SpriteSet, PalettePonyInfo, PaletteSpriteSet, PaletteManager, Palette, ColorExtraSets, PonyInfoBase,
PonyInfoNumber, ColorExtra
} from './interfaces';
import { toInt, array, includes, att } from './utils';
import { CM_SIZE } from './constants';
import { parseColorFast, getR, getAlpha, colorFromRGBA, getG, getB, colorToHexRGB } from './color';
import { BLACK, fillToOutline, fillToOutlineColor, WHITE, TRANSPARENT, fillToOutlineWithDarken } from './colors';
import {
mergedManes, mergedBackManes, mergedFacialHair, mergedEarAccessories, mergedChestAccessories,
SLEEVED_ACCESSORIES, mergedBackAccessories, mergedExtraAccessories, mergedHeadAccessories
mergedManes, mergedBackManes, mergedFacialHair, mergedEarAccessories, mergedChestAccessories,
SLEEVED_ACCESSORIES, mergedBackAccessories, mergedExtraAccessories, mergedHeadAccessories
} from '../client/ponyUtils';
const MAX_COLORS = 6;
@@ -26,109 +26,109 @@ type Arr<T> = (T | undefined)[] | undefined;
type PonyInfoGeneric<T> = PonyInfoBase<T, SpriteSet<T>>;
export const mockPaletteManager: PaletteManager = {
add(colors: number[]): Palette {
return this.addArray(new Uint32Array(colors));
},
addArray(colors: Uint32Array): Palette {
return createPalette(colors);
},
init() {
}
add(colors: number[]): Palette {
return this.addArray(new Uint32Array(colors));
},
addArray(colors: Uint32Array): Palette {
return createPalette(colors);
},
init() {
}
};
export function spriteSet(type: number, lockFirstFill = true, fill = 'ffd700', otherFills = FILLS): SpriteSet<string> {
if (otherFills.length !== (MAX_COLORS - 1))
throw new Error('Invalid fills count');
if (otherFills.length !== (MAX_COLORS - 1))
throw new Error('Invalid fills count');
const fills = [fill, ...otherFills];
const outlines = fills.map(fillToOutline);
const fills = [fill, ...otherFills];
const outlines = fills.map(fillToOutline);
return {
type,
pattern: 0,
fills,
outlines,
lockFills: [lockFirstFill, ...array(MAX_COLORS - 1, false)],
lockOutlines: array(MAX_COLORS, true),
};
return {
type,
pattern: 0,
fills,
outlines,
lockFills: [lockFirstFill, ...array(MAX_COLORS - 1, false)],
lockOutlines: array(MAX_COLORS, true),
};
}
export function createDefaultPony(): PonyInfo {
const pony = createBasePony();
pony.mane!.type = 2;
pony.backMane!.type = 1;
pony.tail!.type = 1;
return pony;
const pony = createBasePony();
pony.mane!.type = 2;
pony.backMane!.type = 1;
pony.tail!.type = 1;
return pony;
}
export function createBasePony(): PonyInfo {
return syncLockedPonyInfo({
head: spriteSet(0, true, 'ff0000', ['800000', '32cd32', 'da70d6', 'dc143c', '7fffd4']),
nose: spriteSet(0, true, 'ff0000', ['800000', '32cd32', 'da70d6', 'dc143c', '7fffd4']),
ears: spriteSet(0, true, 'ff0000'),
horn: spriteSet(0, true, 'ff0000'),
wings: spriteSet(0, true, 'ff0000'),
frontHooves: spriteSet(0, false, 'ffa500', ['ffff00', '32cd32', 'da70d6', 'dc143c', '7fffd4']),
backHooves: spriteSet(0, true, 'ffa500'),
return syncLockedPonyInfo({
head: spriteSet(0, true, 'ff0000', ['800000', '32cd32', 'da70d6', 'dc143c', '7fffd4']),
nose: spriteSet(0, true, 'ff0000', ['800000', '32cd32', 'da70d6', 'dc143c', '7fffd4']),
ears: spriteSet(0, true, 'ff0000'),
horn: spriteSet(0, true, 'ff0000'),
wings: spriteSet(0, true, 'ff0000'),
frontHooves: spriteSet(0, false, 'ffa500', ['ffff00', '32cd32', 'da70d6', 'dc143c', '7fffd4']),
backHooves: spriteSet(0, true, 'ffa500'),
mane: spriteSet(0, false),
backMane: spriteSet(0),
tail: spriteSet(0),
facialHair: spriteSet(0),
mane: spriteSet(0, false),
backMane: spriteSet(0),
tail: spriteSet(0),
facialHair: spriteSet(0),
headAccessory: spriteSet(0, false, 'ee82ee'),
earAccessory: spriteSet(0, false, '808080'),
faceAccessory: spriteSet(0, false, '000000'),
neckAccessory: spriteSet(0, false, 'ee82ee'),
frontLegAccessory: spriteSet(0, false, 'ee82ee'),
backLegAccessory: spriteSet(0, false, 'ee82ee'),
frontLegAccessoryRight: spriteSet(0, false, 'ee82ee'),
backLegAccessoryRight: spriteSet(0, false, 'ee82ee'),
lockBackLegAccessory: true,
unlockFrontLegAccessory: false,
unlockBackLegAccessory: false,
backAccessory: spriteSet(0, false, 'ee82ee'),
waistAccessory: spriteSet(0, false, '95856f', ['674b43', '4f4f4f', '525252', 'c37850', '8a3d34']),
chestAccessory: spriteSet(0, false, 'ee82ee'),
sleeveAccessory: spriteSet(0, true, 'ee82ee'),
extraAccessory: {
...spriteSet(0, true, 'ff0000', ['daa520', 'ffd700', 'ffd700', 'ffd700', 'ffd700']),
lockFills: array(5, true),
},
headAccessory: spriteSet(0, false, 'ee82ee'),
earAccessory: spriteSet(0, false, '808080'),
faceAccessory: spriteSet(0, false, '000000'),
neckAccessory: spriteSet(0, false, 'ee82ee'),
frontLegAccessory: spriteSet(0, false, 'ee82ee'),
backLegAccessory: spriteSet(0, false, 'ee82ee'),
frontLegAccessoryRight: spriteSet(0, false, 'ee82ee'),
backLegAccessoryRight: spriteSet(0, false, 'ee82ee'),
lockBackLegAccessory: true,
unlockFrontLegAccessory: false,
unlockBackLegAccessory: false,
backAccessory: spriteSet(0, false, 'ee82ee'),
waistAccessory: spriteSet(0, false, '95856f', ['674b43', '4f4f4f', '525252', 'c37850', '8a3d34']),
chestAccessory: spriteSet(0, false, 'ee82ee'),
sleeveAccessory: spriteSet(0, true, 'ee82ee'),
extraAccessory: {
...spriteSet(0, true, 'ff0000', ['daa520', 'ffd700', 'ffd700', 'ffd700', 'ffd700']),
lockFills: array(5, true),
},
coatFill: 'ff0000',
coatOutline: '8b0000',
lockCoatOutline: true,
coatFill: 'ff0000',
coatOutline: '8b0000',
lockCoatOutline: true,
eyelashes: 0,
eyeColorLeft: 'daa520',
eyeColorRight: 'daa520',
eyeWhitesLeft: 'ffffff',
eyeWhites: 'ffffff',
eyeOpennessLeft: 1,
eyeOpennessRight: 1,
eyeshadow: false,
eyeshadowColor: '000000',
lockEyes: true,
lockEyeColor: true,
unlockEyeWhites: false,
unlockEyelashColor: false,
eyelashColor: '000000',
eyelashColorLeft: '000000',
eyelashes: 0,
eyeColorLeft: 'daa520',
eyeColorRight: 'daa520',
eyeWhitesLeft: 'ffffff',
eyeWhites: 'ffffff',
eyeOpennessLeft: 1,
eyeOpennessRight: 1,
eyeshadow: false,
eyeshadowColor: '000000',
lockEyes: true,
lockEyeColor: true,
unlockEyeWhites: false,
unlockEyelashColor: false,
eyelashColor: '000000',
eyelashColorLeft: '000000',
fangs: 0,
muzzle: 0,
freckles: 0,
frecklesColor: '8b0000',
magicColor: 'ffffff',
fangs: 0,
muzzle: 0,
freckles: 0,
frecklesColor: '8b0000',
magicColor: 'ffffff',
cm: [],
cmFlip: false,
cm: [],
cmFlip: false,
customOutlines: false,
freeOutlines: false,
darkenLockedOutlines: false,
});
customOutlines: false,
freeOutlines: false,
darkenLockedOutlines: false,
});
}
// sync
@@ -136,321 +136,321 @@ export function createBasePony(): PonyInfo {
type FillToOutline<T> = (fill: T | undefined) => T | undefined;
export function getBaseFill<T>(set?: SpriteSet<T>): T | undefined {
return set && set.fills && set.fills[0];
return set && set.fills && set.fills[0];
}
export function getBaseOutline<T>(set?: SpriteSet<T>): T | undefined {
return set && set.outlines && set.outlines[0];
return set && set.outlines && set.outlines[0];
}
export function syncLockedSpriteSet<T>(
set: SpriteSet<T> | undefined, customOutlines: boolean, fillToOutline: FillToOutline<T>, baseFill?: T,
baseOutline?: T
set: SpriteSet<T> | undefined, customOutlines: boolean, fillToOutline: FillToOutline<T>, baseFill?: T,
baseOutline?: T
) {
if (set === undefined)
return;
if (set === undefined)
return;
const fills = set.fills;
const fills = set.fills;
if (!fills)
return;
if (!fills)
return;
const lockFills = set.lockFills;
const lockFills = set.lockFills;
if (lockFills) {
for (let i = 0; i < lockFills.length; i++) {
if (lockFills[i]) {
fills[i] = i === 0 ? baseFill : fills[0];
}
}
}
if (lockFills) {
for (let i = 0; i < lockFills.length; i++) {
if (lockFills[i]) {
fills[i] = i === 0 ? baseFill : fills[0];
}
}
}
const outlines = set.outlines;
const lockOutlines = set.lockOutlines;
const outlines = set.outlines;
const lockOutlines = set.lockOutlines;
if (outlines && lockOutlines) {
for (let i = 0; i < lockOutlines.length; i++) {
if (!customOutlines) {
lockOutlines[i] = true;
}
if (outlines && lockOutlines) {
for (let i = 0; i < lockOutlines.length; i++) {
if (!customOutlines) {
lockOutlines[i] = true;
}
if (lockOutlines[i]) {
if (i === 0 && baseOutline && lockFills && lockFills[i]) {
outlines[i] = baseOutline;
} else {
outlines[i] = fillToOutline(fills[i]);
}
}
}
}
if (lockOutlines[i]) {
if (i === 0 && baseOutline && lockFills && lockFills[i]) {
outlines[i] = baseOutline;
} else {
outlines[i] = fillToOutline(fills[i]);
}
}
}
}
}
function syncLockedSpritesSet2<T>(
set: SpriteSet<T> | undefined, fillToOutline: FillToOutline<T>, baseFills: (T | undefined)[],
baseOutlines: (T | undefined)[]
set: SpriteSet<T> | undefined, fillToOutline: FillToOutline<T>, baseFills: (T | undefined)[],
baseOutlines: (T | undefined)[]
) {
if (set && set.fills && set.lockFills) {
set.lockFills.forEach((locked, i) => {
if (locked) {
set.fills![i] = baseFills[i];
}
});
}
if (set && set.fills && set.lockFills) {
set.lockFills.forEach((locked, i) => {
if (locked) {
set.fills![i] = baseFills[i];
}
});
}
if (set && set.fills && set.outlines && set.lockOutlines) {
set.lockOutlines.forEach((locked, i) => {
if (locked) {
if (baseOutlines[i] && set.lockFills && set.lockFills[i]) {
set.outlines![i] = baseOutlines[i];
} else {
set.outlines![i] = fillToOutline(set.fills![i]);
}
}
});
}
if (set && set.fills && set.outlines && set.lockOutlines) {
set.lockOutlines.forEach((locked, i) => {
if (locked) {
if (baseOutlines[i] && set.lockFills && set.lockFills[i]) {
set.outlines![i] = baseOutlines[i];
} else {
set.outlines![i] = fillToOutline(set.fills![i]);
}
}
});
}
}
function getFillOf2<T>(set: SpriteSet<T> | undefined, defaultColor: T): T | undefined {
return set && set.type && set.fills && set.fills[0] || defaultColor;
return set && set.type && set.fills && set.fills[0] || defaultColor;
}
function getOutlineOf2<T>(set: SpriteSet<T> | undefined, defaultColor: T): T | undefined {
return set && set.type && set.outlines && set.outlines[0] || defaultColor;
return set && set.type && set.outlines && set.outlines[0] || defaultColor;
}
function syncLockedBasePonyInfo<T>(
info: PonyInfoGeneric<T>, fillToOutline: FillToOutline<T>, defaultColor: T
info: PonyInfoGeneric<T>, fillToOutline: FillToOutline<T>, defaultColor: T
): PonyInfoGeneric<T> {
const customOutlines = !!info.customOutlines;
const customOutlines = !!info.customOutlines;
if (!customOutlines || info.lockCoatOutline) {
info.coatOutline = fillToOutline(info.coatFill);
}
if (!customOutlines || info.lockCoatOutline) {
info.coatOutline = fillToOutline(info.coatFill);
}
if (info.lockEyes) {
info.eyeOpennessLeft = info.eyeOpennessRight;
}
if (info.lockEyes) {
info.eyeOpennessLeft = info.eyeOpennessRight;
}
if (info.lockEyeColor) {
info.eyeColorLeft = info.eyeColorRight;
}
if (info.lockEyeColor) {
info.eyeColorLeft = info.eyeColorRight;
}
if (!info.unlockEyeWhites) {
info.eyeWhitesLeft = info.eyeWhites;
}
if (!info.unlockEyeWhites) {
info.eyeWhitesLeft = info.eyeWhites;
}
if (!info.unlockEyelashColor) {
info.eyelashColorLeft = info.eyelashColor;
}
if (!info.unlockEyelashColor) {
info.eyelashColorLeft = info.eyelashColor;
}
syncLockedSpriteSet<T>(info.head, customOutlines, fillToOutline, info.coatFill, info.coatOutline);
syncLockedSpriteSet<T>(info.nose, customOutlines, fillToOutline, info.coatFill, info.coatOutline);
syncLockedSpriteSet<T>(info.ears, customOutlines, fillToOutline, info.coatFill, info.coatOutline);
syncLockedSpriteSet<T>(info.horn, customOutlines, fillToOutline, info.coatFill, info.coatOutline);
syncLockedSpriteSet<T>(info.wings, customOutlines, fillToOutline, info.coatFill, info.coatOutline);
syncLockedSpriteSet<T>(info.frontHooves, customOutlines, fillToOutline, info.coatFill, info.coatOutline);
syncLockedSpriteSet<T>(
info.backHooves, customOutlines, fillToOutline, getBaseFill(info.frontHooves), getBaseOutline(info.frontHooves));
syncLockedSpriteSet<T>(info.head, customOutlines, fillToOutline, info.coatFill, info.coatOutline);
syncLockedSpriteSet<T>(info.nose, customOutlines, fillToOutline, info.coatFill, info.coatOutline);
syncLockedSpriteSet<T>(info.ears, customOutlines, fillToOutline, info.coatFill, info.coatOutline);
syncLockedSpriteSet<T>(info.horn, customOutlines, fillToOutline, info.coatFill, info.coatOutline);
syncLockedSpriteSet<T>(info.wings, customOutlines, fillToOutline, info.coatFill, info.coatOutline);
syncLockedSpriteSet<T>(info.frontHooves, customOutlines, fillToOutline, info.coatFill, info.coatOutline);
syncLockedSpriteSet<T>(
info.backHooves, customOutlines, fillToOutline, getBaseFill(info.frontHooves), getBaseOutline(info.frontHooves));
syncLockedSpriteSet<T>(info.mane, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.mane, customOutlines, fillToOutline);
const baseManeFill = getBaseFill(info.mane);
const baseManeOutline = getBaseOutline(info.mane);
const baseManeFill = getBaseFill(info.mane);
const baseManeOutline = getBaseOutline(info.mane);
syncLockedSpriteSet<T>(info.backMane, customOutlines, fillToOutline, baseManeFill, baseManeOutline);
syncLockedSpriteSet<T>(info.tail, customOutlines, fillToOutline, baseManeFill, baseManeOutline);
syncLockedSpriteSet<T>(info.facialHair, customOutlines, fillToOutline, baseManeFill, baseManeOutline);
syncLockedSpriteSet<T>(info.backMane, customOutlines, fillToOutline, baseManeFill, baseManeOutline);
syncLockedSpriteSet<T>(info.tail, customOutlines, fillToOutline, baseManeFill, baseManeOutline);
syncLockedSpriteSet<T>(info.facialHair, customOutlines, fillToOutline, baseManeFill, baseManeOutline);
syncLockedSpriteSet<T>(info.headAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.earAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.faceAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.neckAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.frontLegAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.backLegAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.frontLegAccessoryRight, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.backLegAccessoryRight, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.backAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.waistAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.chestAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.headAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.earAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.faceAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.neckAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.frontLegAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.backLegAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.frontLegAccessoryRight, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.backLegAccessoryRight, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.backAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.waistAccessory, customOutlines, fillToOutline);
syncLockedSpriteSet<T>(info.chestAccessory, customOutlines, fillToOutline);
if (info.chestAccessory && !info.sleeveAccessory && includes(SLEEVED_ACCESSORIES, info.chestAccessory.type)) {
info.sleeveAccessory = {
type: 0,
pattern: 0,
fills: [],
outlines: [],
lockFills: array(MAX_COLORS, true),
lockOutlines: array(MAX_COLORS, true),
};
}
if (info.chestAccessory && !info.sleeveAccessory && includes(SLEEVED_ACCESSORIES, info.chestAccessory.type)) {
info.sleeveAccessory = {
type: 0,
pattern: 0,
fills: [],
outlines: [],
lockFills: array(MAX_COLORS, true),
lockOutlines: array(MAX_COLORS, true),
};
}
syncLockedSpriteSet<T>(
info.sleeveAccessory, customOutlines, fillToOutline, getBaseFill(info.chestAccessory), getBaseOutline(info.chestAccessory));
syncLockedSpriteSet<T>(
info.sleeveAccessory, customOutlines, fillToOutline, getBaseFill(info.chestAccessory), getBaseOutline(info.chestAccessory));
syncLockedSpritesSet2<T>(info.extraAccessory, fillToOutline, [
info.coatFill,
info.eyeColorRight,
getFillOf2(info.mane, defaultColor),
getFillOf2(info.backMane, defaultColor),
getFillOf2(info.tail, defaultColor),
], [
info.coatOutline,
info.eyeColorRight,
getOutlineOf2(info.mane, defaultColor),
getOutlineOf2(info.backMane, defaultColor),
getOutlineOf2(info.tail, defaultColor),
]);
syncLockedSpritesSet2<T>(info.extraAccessory, fillToOutline, [
info.coatFill,
info.eyeColorRight,
getFillOf2(info.mane, defaultColor),
getFillOf2(info.backMane, defaultColor),
getFillOf2(info.tail, defaultColor),
], [
info.coatOutline,
info.eyeColorRight,
getOutlineOf2(info.mane, defaultColor),
getOutlineOf2(info.backMane, defaultColor),
getOutlineOf2(info.tail, defaultColor),
]);
return info;
return info;
}
export function syncLockedPonyInfo(info: PonyInfo): PonyInfo {
const darkenLocked = !!info.freeOutlines && !!info.darkenLockedOutlines;
const fillToOutlineFunc = darkenLocked ? fillToOutlineWithDarken : fillToOutline;
return syncLockedBasePonyInfo<string>(info, fillToOutlineFunc, '000000');
const darkenLocked = !!info.freeOutlines && !!info.darkenLockedOutlines;
const fillToOutlineFunc = darkenLocked ? fillToOutlineWithDarken : fillToOutline;
return syncLockedBasePonyInfo<string>(info, fillToOutlineFunc, '000000');
}
function fillToOutlineSafe(color: number | undefined) {
return fillToOutlineColor((color === undefined || color === 0) ? BLACK : color);
return fillToOutlineColor((color === undefined || color === 0) ? BLACK : color);
}
function fillToOutlineSafeWithDarken(color: number | undefined) {
return darkenForOutline(fillToOutlineColor((color === undefined || color === 0) ? BLACK : color));
return darkenForOutline(fillToOutlineColor((color === undefined || color === 0) ? BLACK : color));
}
export function syncLockedPonyInfoNumber(info: PonyInfoNumber): PonyInfoNumber {
const darkenLocked = !!info.freeOutlines && !!info.darkenLockedOutlines;
const fillToOutlineFunc = darkenLocked ? fillToOutlineSafeWithDarken : fillToOutlineSafe;
return syncLockedBasePonyInfo<number>(info, fillToOutlineFunc, BLACK);
const darkenLocked = !!info.freeOutlines && !!info.darkenLockedOutlines;
const fillToOutlineFunc = darkenLocked ? fillToOutlineSafeWithDarken : fillToOutlineSafe;
return syncLockedBasePonyInfo<number>(info, fillToOutlineFunc, BLACK);
}
// PalettePonyInfo
function parseFast(color: string | undefined): number {
return color ? parseColorFast(color) : BLACK;
return color ? parseColorFast(color) : BLACK;
}
function parseCMColor(color: string): number {
return color ? parseColorFast(color) : TRANSPARENT;
return color ? parseColorFast(color) : TRANSPARENT;
}
export function toColorList(colors: (string | undefined)[]): Uint32Array {
const result = new Uint32Array(colors.length + 1);
const result = new Uint32Array(colors.length + 1);
for (let i = 0; i < colors.length; i++) {
result[i + 1] = parseFast(colors[i]);
}
for (let i = 0; i < colors.length; i++) {
result[i + 1] = parseFast(colors[i]);
}
return result;
return result;
}
export function darkenForOutline(color: number) {
const mult = (159 / 255);
const r = (mult * getR(color)) | 0;
const g = (mult * getG(color)) | 0;
const b = (mult * getB(color)) | 0;
const a = getAlpha(color);
return colorFromRGBA(r, g, b, a);
const mult = (159 / 255);
const r = (mult * getR(color)) | 0;
const g = (mult * getG(color)) | 0;
const b = (mult * getB(color)) | 0;
const a = getAlpha(color);
return colorFromRGBA(r, g, b, a);
}
function getColorsGeneric(
fillColors: Arr<string>, outlineColors: Arr<string>, defaultColor: string, length: number, darken: boolean
fillColors: Arr<string>, outlineColors: Arr<string>, defaultColor: string, length: number, darken: boolean
): string[] {
const fills = fillColors || [];
const outlines = outlineColors || [];
const colors = array(length * 2, defaultColor);
const fills = fillColors || [];
const outlines = outlineColors || [];
const colors = array(length * 2, defaultColor);
for (let i = 0; i < length; i++) {
colors[i * 2] = fills[i] || defaultColor;
for (let i = 0; i < length; i++) {
colors[i * 2] = fills[i] || defaultColor;
if (darken) {
colors[i * 2 + 1] = outlines[i] ? colorToHexRGB(darkenForOutline(parseColorFast(outlines[i]!))) : defaultColor;
} else {
colors[i * 2 + 1] = outlines[i] || defaultColor;
}
}
if (darken) {
colors[i * 2 + 1] = outlines[i] ? colorToHexRGB(darkenForOutline(parseColorFast(outlines[i]!))) : defaultColor;
} else {
colors[i * 2 + 1] = outlines[i] || defaultColor;
}
}
return colors;
return colors;
}
export function getColorsFromSet({ fills, outlines }: SpriteSet<string>, defaultColor: string, darken: boolean): string[] {
const length = Math.max(fills ? fills.length : 0, outlines ? outlines.length : 0);
return getColorsGeneric(fills, outlines, defaultColor, length, darken);
const length = Math.max(fills ? fills.length : 0, outlines ? outlines.length : 0);
return getColorsGeneric(fills, outlines, defaultColor, length, darken);
}
export function toColorListNumber(colors: (number | undefined)[]): Uint32Array {
const result = new Uint32Array(colors.length + 1);
const result = new Uint32Array(colors.length + 1);
for (let i = 0; i < colors.length; i++) {
result[i + 1] = colors[i] || BLACK;
}
for (let i = 0; i < colors.length; i++) {
result[i + 1] = colors[i] || BLACK;
}
return result;
return result;
}
export type GetColorsForSet<T> = (set: SpriteSet<T>, count: number, darken: boolean) => Uint32Array;
export const getColorsForSet: GetColorsForSet<string> = (set, count, darken) => {
const t = getColorsGeneric(set.fills, set.outlines, '000000', count, darken);
return toColorList(t);
const t = getColorsGeneric(set.fills, set.outlines, '000000', count, darken);
return toColorList(t);
};
const emptyArray: number[] = [];
export const getColorsForSetNumber: GetColorsForSet<number> = (set, length, darken) => {
const fills = set.fills || emptyArray;
const outlines = set.outlines || emptyArray;
const result = new Uint32Array(length * 2 + 1);
const fills = set.fills || emptyArray;
const outlines = set.outlines || emptyArray;
const result = new Uint32Array(length * 2 + 1);
for (let i = 0; i < length; i++) {
result[((i << 1) + 1) | 0] = i < fills.length ? (fills[i] || BLACK) : BLACK;
for (let i = 0; i < length; i++) {
result[((i << 1) + 1) | 0] = i < fills.length ? (fills[i] || BLACK) : BLACK;
if (darken) {
result[((i << 1) + 2) | 0] = i < outlines.length ? darkenForOutline(outlines[i] || BLACK) : BLACK;
} else {
result[((i << 1) + 2) | 0] = i < outlines.length ? (outlines[i] || BLACK) : BLACK;
}
}
if (darken) {
result[((i << 1) + 2) | 0] = i < outlines.length ? darkenForOutline(outlines[i] || BLACK) : BLACK;
} else {
result[((i << 1) + 2) | 0] = i < outlines.length ? (outlines[i] || BLACK) : BLACK;
}
}
return result;
return result;
};
function getExtraPalette(pattern: ColorExtra | undefined, manager: PaletteManager): Palette | undefined {
const extraPalette = pattern && pattern.palettes && pattern.palettes[0];
return extraPalette && manager.addArray(new Uint32Array(extraPalette));
const extraPalette = pattern && pattern.palettes && pattern.palettes[0];
return extraPalette && manager.addArray(new Uint32Array(extraPalette));
}
export function toPaletteSet<T>(
set: SpriteSet<T>, sets: ColorExtraSets, manager: PaletteManager, getColorsForSet: GetColorsForSet<T>,
hasExtra: boolean, darken: boolean
set: SpriteSet<T>, sets: ColorExtraSets, manager: PaletteManager, getColorsForSet: GetColorsForSet<T>,
hasExtra: boolean, darken: boolean
): PaletteSpriteSet | undefined {
const pattern = att(att(sets, set.type), set.pattern);
const colorCount = pattern !== undefined && pattern.colors !== undefined ? ((pattern.colors - 1) >> 1) : 0;
const colors = getColorsForSet(set, colorCount, darken);
const pattern = att(att(sets, set.type), set.pattern);
const colorCount = pattern !== undefined && pattern.colors !== undefined ? ((pattern.colors - 1) >> 1) : 0;
const colors = getColorsForSet(set, colorCount, darken);
return {
type: toInt(set.type),
pattern: toInt(set.pattern),
palette: manager.addArray(colors),
extraPalette: hasExtra ? getExtraPalette(pattern, manager) : undefined,
};
return {
type: toInt(set.type),
pattern: toInt(set.pattern),
palette: manager.addArray(colors),
extraPalette: hasExtra ? getExtraPalette(pattern, manager) : undefined,
};
}
function createCMPalette<T>(
cm: T[] | undefined, manager: PaletteManager, parseColor: (color: T) => number
cm: T[] | undefined, manager: PaletteManager, parseColor: (color: T) => number
): Palette | undefined {
const size = CM_SIZE * CM_SIZE;
const size = CM_SIZE * CM_SIZE;
if (cm === undefined || cm.length === 0 || cm.length > size)
return undefined;
if (cm === undefined || cm.length === 0 || cm.length > size)
return undefined;
const result = new Uint32Array(size);
const result = new Uint32Array(size);
for (let i = 0; i < cm.length; i++) {
result[i] = parseColor(cm[i]);
}
for (let i = 0; i < cm.length; i++) {
result[i] = parseColor(cm[i]);
}
return manager.addArray(result);
return manager.addArray(result);
}
export type ToSet<T> = (set: SpriteSet<T> | undefined, sets: ColorExtraSets, extra?: boolean) => PaletteSpriteSet | undefined;
@@ -458,130 +458,130 @@ export type ToSet<T> = (set: SpriteSet<T> | undefined, sets: ColorExtraSets, ext
const defaultPalette = new Uint32Array(sprites.defaultPalette);
export const createToPaletteSet =
<T>(manager: PaletteManager, getColorsForSet: GetColorsForSet<T>, extra: boolean, darken: boolean): ToSet<T> =>
(set, sets) => set === undefined ? undefined : toPaletteSet(set, sets, manager, getColorsForSet, extra, darken);
<T>(manager: PaletteManager, getColorsForSet: GetColorsForSet<T>, extra: boolean, darken: boolean): ToSet<T> =>
(set, sets) => set === undefined ? undefined : toPaletteSet(set, sets, manager, getColorsForSet, extra, darken);
export function toPaletteGeneric<T>(
info: PonyInfoGeneric<T>, manager: PaletteManager, toColorList: (color: (T | undefined)[]) => Uint32Array,
getColorsForSet: GetColorsForSet<T>, blackColor: T, whiteColor: T, parseCMColor: (color: T) => number
info: PonyInfoGeneric<T>, manager: PaletteManager, toColorList: (color: (T | undefined)[]) => Uint32Array,
getColorsForSet: GetColorsForSet<T>, blackColor: T, whiteColor: T, parseCMColor: (color: T) => number
): PalettePonyInfo {
const darken = !info.freeOutlines;
const toSet = createToPaletteSet(manager, getColorsForSet, false, darken);
const toSetExtra = createToPaletteSet(manager, getColorsForSet, true, darken);
const darken = !info.freeOutlines;
const toSet = createToPaletteSet(manager, getColorsForSet, false, darken);
const toSetExtra = createToPaletteSet(manager, getColorsForSet, true, darken);
const defaultSet = { type: 0, pattern: 0, fills: [info.coatFill], outlines: [info.coatOutline] };
// const defaultSet = { type: 0, pattern: 1, fills: [info.coatFill, whiteColor], outlines: [info.coatOutline, blackColor] };
const defaultSet = { type: 0, pattern: 0, fills: [info.coatFill], outlines: [info.coatOutline] };
// const defaultSet = { type: 0, pattern: 1, fills: [info.coatFill, whiteColor], outlines: [info.coatOutline, blackColor] };
return {
body: toSet(defaultSet, sprites.body[1]),
head: toSet(info.head || defaultSet, sprites.head0[1]),
nose: toSet(info.nose, sprites.noses[0]),
ears: toSet(info.ears || defaultSet, sprites.ears),
horn: toSet(info.horn, sprites.horns),
wings: toSet(info.wings, sprites.wings[0]),
frontLegs: toSet(defaultSet, sprites.frontLegs[1]),
backLegs: toSet(defaultSet, sprites.backLegs[1]),
frontHooves: toSet(info.frontHooves, frontHooves),
backHooves: toSet(info.backHooves, backHooves),
return {
body: toSet(defaultSet, sprites.body[1]),
head: toSet(info.head || defaultSet, sprites.head0[1]),
nose: toSet(info.nose, sprites.noses[0]),
ears: toSet(info.ears || defaultSet, sprites.ears),
horn: toSet(info.horn, sprites.horns),
wings: toSet(info.wings, sprites.wings[0]),
frontLegs: toSet(defaultSet, sprites.frontLegs[1]),
backLegs: toSet(defaultSet, sprites.backLegs[1]),
frontHooves: toSet(info.frontHooves, frontHooves),
backHooves: toSet(info.backHooves, backHooves),
mane: toSet(info.mane, mergedManes),
backMane: toSet(info.backMane, mergedBackManes),
tail: toSet(info.tail, sprites.tails[0]),
facialHair: toSet(info.facialHair, mergedFacialHair),
mane: toSet(info.mane, mergedManes),
backMane: toSet(info.backMane, mergedBackManes),
tail: toSet(info.tail, sprites.tails[0]),
facialHair: toSet(info.facialHair, mergedFacialHair),
headAccessory: toSet(info.headAccessory, mergedHeadAccessories),
earAccessory: toSet(info.earAccessory, mergedEarAccessories),
faceAccessory: toSetExtra(info.faceAccessory, sprites.faceAccessories),
// faceAccessoryExtraPalette: getExtraPartPalette(info.faceAccessory, sprites.faceAccessoriesExtra, manager),
neckAccessory: toSet(info.neckAccessory, sprites.neckAccessories[1]),
frontLegAccessory: toSet(
info.frontLegAccessory, frontLegAccessories),
backLegAccessory: toSet(
info.lockBackLegAccessory ? info.frontLegAccessory : info.backLegAccessory, backLegAccessories),
frontLegAccessoryRight: toSet(
info.unlockFrontLegAccessory ? info.frontLegAccessoryRight : info.frontLegAccessory, frontLegAccessories),
backLegAccessoryRight: toSet(
info.lockBackLegAccessory ?
(info.unlockFrontLegAccessory ? info.frontLegAccessoryRight : info.frontLegAccessory) :
(info.unlockBackLegAccessory ? info.backLegAccessoryRight : info.backLegAccessory), backLegAccessories),
lockBackLegAccessory: info.lockBackLegAccessory,
unlockFrontLegAccessory: info.unlockFrontLegAccessory,
unlockBackLegAccessory: info.unlockBackLegAccessory,
backAccessory: toSet(info.backAccessory, mergedBackAccessories),
waistAccessory: toSet(info.waistAccessory, sprites.waistAccessories[1]),
chestAccessory: toSet(info.chestAccessory, mergedChestAccessories),
sleeveAccessory: toSet(info.sleeveAccessory, frontLegSleeves),
extraAccessory: toSet(info.extraAccessory, mergedExtraAccessories),
headAccessory: toSet(info.headAccessory, mergedHeadAccessories),
earAccessory: toSet(info.earAccessory, mergedEarAccessories),
faceAccessory: toSetExtra(info.faceAccessory, sprites.faceAccessories),
// faceAccessoryExtraPalette: getExtraPartPalette(info.faceAccessory, sprites.faceAccessoriesExtra, manager),
neckAccessory: toSet(info.neckAccessory, sprites.neckAccessories[1]),
frontLegAccessory: toSet(
info.frontLegAccessory, frontLegAccessories),
backLegAccessory: toSet(
info.lockBackLegAccessory ? info.frontLegAccessory : info.backLegAccessory, backLegAccessories),
frontLegAccessoryRight: toSet(
info.unlockFrontLegAccessory ? info.frontLegAccessoryRight : info.frontLegAccessory, frontLegAccessories),
backLegAccessoryRight: toSet(
info.lockBackLegAccessory ?
(info.unlockFrontLegAccessory ? info.frontLegAccessoryRight : info.frontLegAccessory) :
(info.unlockBackLegAccessory ? info.backLegAccessoryRight : info.backLegAccessory), backLegAccessories),
lockBackLegAccessory: info.lockBackLegAccessory,
unlockFrontLegAccessory: info.unlockFrontLegAccessory,
unlockBackLegAccessory: info.unlockBackLegAccessory,
backAccessory: toSet(info.backAccessory, mergedBackAccessories),
waistAccessory: toSet(info.waistAccessory, sprites.waistAccessories[1]),
chestAccessory: toSet(info.chestAccessory, mergedChestAccessories),
sleeveAccessory: toSet(info.sleeveAccessory, frontLegSleeves),
extraAccessory: toSet(info.extraAccessory, mergedExtraAccessories),
coatPalette: manager.addArray(toColorList([info.coatFill, info.coatOutline])),
coatFill: undefined,
coatOutline: undefined,
lockCoatOutline: !!info.lockCoatOutline,
coatPalette: manager.addArray(toColorList([info.coatFill, info.coatOutline])),
coatFill: undefined,
coatOutline: undefined,
lockCoatOutline: !!info.lockCoatOutline,
eyelashes: toInt(info.eyelashes),
eyePaletteLeft: manager.addArray(toColorList([
info.eyeWhitesLeft || whiteColor,
info.eyelashColor || blackColor
])),
eyePalette: manager.addArray(toColorList([
info.eyeWhites || whiteColor,
(info.unlockEyelashColor ? info.eyelashColorLeft : info.eyelashColor) || blackColor
])),
eyeColorLeft: manager.addArray(toColorList([info.eyeColorLeft])),
eyeColorRight: manager.addArray(toColorList([info.eyeColorRight])),
eyeWhitesLeft: undefined,
eyeWhites: undefined,
eyeOpennessLeft: toInt(info.eyeOpennessLeft),
eyeOpennessRight: toInt(info.eyeOpennessRight),
eyeshadow: info.eyeshadow,
eyeshadowColor: manager.addArray(toColorList([info.eyeshadowColor])),
lockEyes: !!info.lockEyes,
lockEyeColor: !!info.lockEyeColor,
unlockEyeWhites: !!info.unlockEyeWhites,
unlockEyelashColor: !!info.unlockEyelashColor,
eyelashColor: undefined,
eyelashColorLeft: undefined,
eyelashes: toInt(info.eyelashes),
eyePaletteLeft: manager.addArray(toColorList([
info.eyeWhitesLeft || whiteColor,
info.eyelashColor || blackColor
])),
eyePalette: manager.addArray(toColorList([
info.eyeWhites || whiteColor,
(info.unlockEyelashColor ? info.eyelashColorLeft : info.eyelashColor) || blackColor
])),
eyeColorLeft: manager.addArray(toColorList([info.eyeColorLeft])),
eyeColorRight: manager.addArray(toColorList([info.eyeColorRight])),
eyeWhitesLeft: undefined,
eyeWhites: undefined,
eyeOpennessLeft: toInt(info.eyeOpennessLeft),
eyeOpennessRight: toInt(info.eyeOpennessRight),
eyeshadow: info.eyeshadow,
eyeshadowColor: manager.addArray(toColorList([info.eyeshadowColor])),
lockEyes: !!info.lockEyes,
lockEyeColor: !!info.lockEyeColor,
unlockEyeWhites: !!info.unlockEyeWhites,
unlockEyelashColor: !!info.unlockEyelashColor,
eyelashColor: undefined,
eyelashColorLeft: undefined,
fangs: toInt(info.fangs),
muzzle: toInt(info.muzzle),
freckles: 0, // remove
frecklesColor: undefined, // TODO: remove
magicColor: undefined,
magicColorValue: typeof info.magicColor === 'string' ? parseColorFast(info.magicColor) : toInt(info.magicColor),
fangs: toInt(info.fangs),
muzzle: toInt(info.muzzle),
freckles: 0, // remove
frecklesColor: undefined, // TODO: remove
magicColor: undefined,
magicColorValue: typeof info.magicColor === 'string' ? parseColorFast(info.magicColor) : toInt(info.magicColor),
cm: undefined,
cmFlip: !!info.cmFlip,
cmPalette: createCMPalette<T>(info.cm, manager, parseCMColor),
cm: undefined,
cmFlip: !!info.cmFlip,
cmPalette: createCMPalette<T>(info.cm, manager, parseCMColor),
customOutlines: !!info.customOutlines,
freeOutlines: !!info.freeOutlines,
darkenLockedOutlines: !!info.darkenLockedOutlines,
defaultPalette: manager.addArray(defaultPalette),
waterPalette: manager.addArray(sprites.pony_wake_1.palette),
};
customOutlines: !!info.customOutlines,
freeOutlines: !!info.freeOutlines,
darkenLockedOutlines: !!info.darkenLockedOutlines,
defaultPalette: manager.addArray(defaultPalette),
waterPalette: manager.addArray(sprites.pony_wake_1.palette),
};
}
export function toPalette(info: PonyInfo, manager = mockPaletteManager): PalettePonyInfo {
return toPaletteGeneric(info, manager, toColorList, getColorsForSet, '000000', 'ffffff', parseCMColor);
return toPaletteGeneric(info, manager, toColorList, getColorsForSet, '000000', 'ffffff', parseCMColor);
}
export function toPaletteNumber(info: PonyInfoNumber, manager = mockPaletteManager): PalettePonyInfo {
return toPaletteGeneric<number>(info, manager, toColorListNumber, getColorsForSetNumber, BLACK, WHITE, x => x);
return toPaletteGeneric<number>(info, manager, toColorListNumber, getColorsForSetNumber, BLACK, WHITE, x => x);
}
export function releasePalettes(info: PalettePonyInfo): void {
for (const key of Object.keys(info)) {
const value = (info as any)[key]; // undefined | number | string | PaletteSpriteSet | Palette;
for (const key of Object.keys(info)) {
const value = (info as any)[key]; // undefined | number | string | PaletteSpriteSet | Palette;
if (value && typeof value === 'object') {
if ('refs' in value) {
const palette = value as Palette;
releasePalette(palette);
} else if ('palette' in value) {
const set = value as PaletteSpriteSet;
releasePalette(set.palette);
releasePalette(set.extraPalette);
}
}
}
if (value && typeof value === 'object') {
if ('refs' in value) {
const palette = value as Palette;
releasePalette(palette);
} else if ('palette' in value) {
const set = value as PaletteSpriteSet;
releasePalette(set.palette);
releasePalette(set.extraPalette);
}
}
}
}
+26 -26
View File
@@ -2,69 +2,69 @@ import { tileWidth, tileHeight, tileElevation } from './constants';
import { Point, Rect } from './interfaces';
export function toScreenX(x: number) {
return Math.floor(x * tileWidth) | 0;
return Math.floor(x * tileWidth) | 0;
}
export function toScreenY(y: number) {
return Math.floor(y * tileHeight) | 0;
return Math.floor(y * tileHeight) | 0;
}
export function toScreenYWithZ(y: number, z: number) {
return Math.floor(y * tileHeight - z * tileElevation) | 0;
return Math.floor(y * tileHeight - z * tileElevation) | 0;
}
export function toWorldX(x: number) {
return x / tileWidth;
return x / tileWidth;
}
export function toWorldY(y: number) {
return y / tileHeight;
return y / tileHeight;
}
export function toWorldZ(z: number) {
return z / tileElevation;
return z / tileElevation;
}
export function pointToScreen({ x, y }: Point): Point {
return {
x: toScreenX(x),
y: toScreenY(y),
};
return {
x: toScreenX(x),
y: toScreenY(y),
};
}
export function pointToWorld({ x, y }: Point): Point {
return {
x: toWorldX(x),
y: toWorldY(y),
};
return {
x: toWorldX(x),
y: toWorldY(y),
};
}
export function rectToScreen({ x, y, w, h }: Rect): Rect {
return {
x: toScreenX(x),
y: toScreenY(y),
w: toScreenX(w),
h: toScreenY(h),
};
return {
x: toScreenX(x),
y: toScreenY(y),
w: toScreenX(w),
h: toScreenY(h),
};
}
export function roundPositionX(x: number) {
return Math.floor(x * tileWidth) / tileWidth;
return Math.floor(x * tileWidth) / tileWidth;
}
export function roundPositionY(y: number) {
return Math.floor(y * tileHeight) / tileHeight;
return Math.floor(y * tileHeight) / tileHeight;
}
export function roundPositionXMidPixel(x: number) {
return (Math.floor(x * tileWidth) + 0.5) / tileWidth;
return (Math.floor(x * tileWidth) + 0.5) / tileWidth;
}
export function roundPositionYMidPixel(y: number) {
return (Math.floor(y * tileHeight) + 0.5) / tileHeight;
return (Math.floor(y * tileHeight) + 0.5) / tileHeight;
}
export function roundPosition(point: Point) {
point.x = roundPositionX(point.x);
point.y = roundPositionY(point.y);
point.x = roundPositionX(point.x);
point.y = roundPositionY(point.y);
}
+22 -22
View File
@@ -2,49 +2,49 @@ import { Rect, Point } from './interfaces';
import { intersect } from './utils';
export function rect(x: number, y: number, w: number, h: number): Rect {
return { x, y, w, h };
return { x, y, w, h };
}
export function centerPoint(rect: Rect): Point {
return { x: rect.x + rect.w / 2, y: rect.y + rect.h / 2 };
return { x: rect.x + rect.w / 2, y: rect.y + rect.h / 2 };
}
export function copyRect(dst: Rect, src: Rect) {
dst.x = src.x;
dst.y = src.y;
dst.w = src.w;
dst.h = src.h;
dst.x = src.x;
dst.y = src.y;
dst.w = src.w;
dst.h = src.h;
}
export function withBorder({ x, y, w, h }: Rect, border: number) {
return rect(x - border, y - border, w + border * 2, h + border * 2);
return rect(x - border, y - border, w + border * 2, h + border * 2);
}
export function withPadding({ x, y, w, h }: Rect, top: number, right: number, bottom: number, left: number) {
return rect(x - top, y - left, w + left + right, h + top + bottom);
return rect(x - top, y - left, w + left + right, h + top + bottom);
}
export function rectsIntersect(a: Rect, b: Rect): boolean {
return intersect(a.x, a.y, a.w, a.h, b.x, b.y, b.w, b.h);
return intersect(a.x, a.y, a.w, a.h, b.x, b.y, b.w, b.h);
}
export function addRect(a: Rect, b: Rect) {
const x = Math.min(a.x, b.x);
const y = Math.min(a.y, b.y);
const x = Math.min(a.x, b.x);
const y = Math.min(a.y, b.y);
a.w = Math.max(a.x + a.w, b.x + b.w) - x;
a.h = Math.max(a.y + a.h, b.y + b.h) - y;
a.x = x;
a.y = y;
a.w = Math.max(a.x + a.w, b.x + b.w) - x;
a.h = Math.max(a.y + a.h, b.y + b.h) - y;
a.x = x;
a.y = y;
}
export function addRects(a: Rect, b: Rect): Rect {
const x = Math.min(a.x, b.x);
const y = Math.min(a.y, b.y);
const x = Math.min(a.x, b.x);
const y = Math.min(a.y, b.y);
return {
x, y,
w: Math.max(a.x + a.w, b.x + b.w) - x,
h: Math.max(a.y + a.h, b.y + b.h) - y,
};
return {
x, y,
w: Math.max(a.x + a.w, b.x + b.w) - x,
h: Math.max(a.y + a.h, b.y + b.h) - y,
};
}
+135 -135
View File
@@ -9,194 +9,194 @@ import { decompressTiles } from './compress';
const { min, max, floor } = Math;
export function createRegion(x: number, y: number, tileData?: Uint8Array): Region {
const size = REGION_SIZE;
const tiles = tileData ? decompressTiles(tileData) : new Uint8Array(size * size);
const tileIndices = new Int16Array(size * size);
const randoms = new Uint8Array(size * size);
// const elevation = new Uint8Array(size * size);
const collider = new Uint8Array(size * size * tileWidth * tileHeight);
const size = REGION_SIZE;
const tiles = tileData ? decompressTiles(tileData) : new Uint8Array(size * size);
const tileIndices = new Int16Array(size * size);
const randoms = new Uint8Array(size * size);
// const elevation = new Uint8Array(size * size);
const collider = new Uint8Array(size * size * tileWidth * tileHeight);
if (!tileData) {
tiles.fill(TileType.Dirt);
}
if (!tileData) {
tiles.fill(TileType.Dirt);
}
tileIndices.fill(-1);
tileIndices.fill(-1);
for (let i = 0; i < randoms.length; i++) {
randoms[i] = (Math.random() * 256) | 0;
}
for (let i = 0; i < randoms.length; i++) {
randoms[i] = (Math.random() * 256) | 0;
}
return {
x, y, tiles, tileIndices,
randoms,
// elevation,
entities: [],
colliders: [],
collider,
colliderDirty: true,
tilesDirty: true,
};
return {
x, y, tiles, tileIndices,
randoms,
// elevation,
entities: [],
colliders: [],
collider,
colliderDirty: true,
tilesDirty: true,
};
}
export function getRegionTile(region: Region, x: number, y: number): TileType {
return region.tiles[x | (y << 3)];
return region.tiles[x | (y << 3)];
}
export function setRegionTile(region: Region, x: number, y: number, type: TileType) {
region.tiles[x | (y << 3)] = type;
region.tiles[x | (y << 3)] = type;
}
export function getRegionTileIndex(region: Region, x: number, y: number) {
return region.tileIndices[x | (y << 3)];
return region.tileIndices[x | (y << 3)];
}
export function setRegionTileDirty(region: Region, x: number, y: number) {
region.tileIndices[x | (y << 3)] = -1;
region.tilesDirty = true;
region.tileIndices[x | (y << 3)] = -1;
region.tilesDirty = true;
}
export function getRegionElevation(_region: Region, _x: number, _y: number) {
return 0; // region.elevation[x | (y << 3)];
return 0; // region.elevation[x | (y << 3)];
}
export function setRegionElevation(_region: Region, _x: number, _y: number, _value: number) {
// region.elevation[x | (y << 3)] = value;
// region.elevation[x | (y << 3)] = value;
}
export function worldToRegionX<T>(x: number, map: IMap<T>) {
return clamp(floor(x / REGION_SIZE), 0, map.regionsX - 1);
return clamp(floor(x / REGION_SIZE), 0, map.regionsX - 1);
}
export function worldToRegionY<T>(y: number, map: IMap<T>) {
return clamp(floor(y / REGION_SIZE), 0, map.regionsY - 1);
return clamp(floor(y / REGION_SIZE), 0, map.regionsY - 1);
}
export function invalidateRegionsCollider<T extends Region | undefined>(region: Region, map: IMap<T>) {
const minY = max(0, region.y - 1);
const maxY = min(map.regionsY - 1, region.y + 1);
const minX = max(0, region.x - 1);
const maxX = min(map.regionsX - 1, region.x + 1);
const minY = max(0, region.y - 1);
const maxY = min(map.regionsY - 1, region.y + 1);
const minX = max(0, region.x - 1);
const maxX = min(map.regionsX - 1, region.x + 1);
for (let ry = minY; ry <= maxY; ry++) {
for (let rx = minX; rx <= maxX; rx++) {
const r = getRegion(map, rx, ry);
for (let ry = minY; ry <= maxY; ry++) {
for (let rx = minX; rx <= maxX; rx++) {
const r = getRegion(map, rx, ry);
if (r) {
r.colliderDirty = true;
}
}
}
if (r) {
r.colliderDirty = true;
}
}
}
}
export function generateRegionCollider<T extends Region | undefined>(region: Region, map: IMap<T>) {
const regionCollider = region.collider;
const tileTypes = region.tiles;
const regionCollider = region.collider;
const tileTypes = region.tiles;
region.colliderDirty = false;
regionCollider.fill(0);
region.colliderDirty = false;
regionCollider.fill(0);
for (let ty = 0, i = 0; ty < REGION_SIZE; ty++) {
for (let tx = 0; tx < REGION_SIZE; tx++ , i++) {
const type = tileTypes[i];
for (let ty = 0, i = 0; ty < REGION_SIZE; ty++) {
for (let tx = 0; tx < REGION_SIZE; tx++ , i++) {
const type = tileTypes[i];
if (type === TileType.None) {
const x0 = (tx * tileWidth) | 0;
const y0 = (ty * tileHeight) | 0;
const x1 = (x0 + tileWidth) | 0;
const y1 = (y0 + tileHeight) | 0;
if (type === TileType.None) {
const x0 = (tx * tileWidth) | 0;
const y0 = (ty * tileHeight) | 0;
const x1 = (x0 + tileWidth) | 0;
const y1 = (y0 + tileHeight) | 0;
for (let y = y0; y < y1; y++) {
for (let x = x0; x < x1; x++) {
regionCollider[(x + ((y * REGION_WIDTH) | 0)) | 0] = 3;
}
}
}
}
}
for (let y = y0; y < y1; y++) {
for (let x = x0; x < x1; x++) {
regionCollider[(x + ((y * REGION_WIDTH) | 0)) | 0] = 3;
}
}
}
}
}
const minY = max(0, region.y - 1);
const maxY = min(map.regionsY - 1, region.y + 1);
const minX = max(0, region.x - 1);
const maxX = min(map.regionsX - 1, region.x + 1);
const minY = max(0, region.y - 1);
const maxY = min(map.regionsY - 1, region.y + 1);
const minX = max(0, region.x - 1);
const maxX = min(map.regionsX - 1, region.x + 1);
const pBounds = ponyCollidersBounds;
const pbX0 = pBounds.x | 0;
const pbY0 = pBounds.y | 0;
const pbX1 = (pbX0 + pBounds.w) | 0;
const pbY1 = (pbY0 + pBounds.h) | 0;
const pBounds = ponyCollidersBounds;
const pbX0 = pBounds.x | 0;
const pbY0 = pBounds.y | 0;
const pbX1 = (pbX0 + pBounds.w) | 0;
const pbY1 = (pbY0 + pBounds.h) | 0;
const baseX = region.x * REGION_SIZE;
const baseY = region.y * REGION_SIZE;
const baseX = region.x * REGION_SIZE;
const baseY = region.y * REGION_SIZE;
for (let ry = minY; ry <= maxY; ry++) {
for (let rx = minX; rx <= maxX; rx++) {
const r = getRegion(map, rx, ry);
for (let ry = minY; ry <= maxY; ry++) {
for (let rx = minX; rx <= maxX; rx++) {
const r = getRegion(map, rx, ry);
if (r === undefined)
continue;
if (r === undefined)
continue;
for (const entity of r.colliders) {
const entityX = toScreenX(entity.x - baseX) | 0;
const entityY = toScreenY(entity.y - baseY) | 0;
for (const entity of r.colliders) {
const entityX = toScreenX(entity.x - baseX) | 0;
const entityY = toScreenY(entity.y - baseY) | 0;
const cBounds = entity.collidersBounds!;
const ecbX = entityX + cBounds.x;
const ecbY = entityY + cBounds.y;
const cBounds = entity.collidersBounds!;
const ecbX = entityX + cBounds.x;
const ecbY = entityY + cBounds.y;
if (
(ecbX + pbX0) > REGION_WIDTH || (ecbY + pbY0) > REGION_HEIGHT ||
(ecbX + cBounds.w + pbX1) < 0 || (ecbY + cBounds.h + pbY1) < 0
) {
continue;
}
if (
(ecbX + pbX0) > REGION_WIDTH || (ecbY + pbY0) > REGION_HEIGHT ||
(ecbX + cBounds.w + pbX1) < 0 || (ecbY + cBounds.h + pbY1) < 0
) {
continue;
}
for (const c of entity.colliders!) {
const value = (c.tall ? 3 : 1) | 0;
const baseX0 = (entityX + c.x) | 0;
const baseY0 = (entityY + c.y) | 0;
const baseX1 = (baseX0 + c.w) | 0;
const baseY1 = (baseY0 + c.h) | 0;
for (const c of entity.colliders!) {
const value = (c.tall ? 3 : 1) | 0;
const baseX0 = (entityX + c.x) | 0;
const baseY0 = (entityY + c.y) | 0;
const baseX1 = (baseX0 + c.w) | 0;
const baseY1 = (baseY0 + c.h) | 0;
if (c.exact) {
const x0 = (baseX0 < 0 ? 0 : baseX0) | 0;
const y0 = (baseY0 < 0 ? 0 : baseY0) | 0;
const x1 = (baseX1 > REGION_WIDTH ? REGION_WIDTH : baseX1) | 0;
const y1 = (baseY1 > REGION_HEIGHT ? REGION_HEIGHT : baseY1) | 0;
if (c.exact) {
const x0 = (baseX0 < 0 ? 0 : baseX0) | 0;
const y0 = (baseY0 < 0 ? 0 : baseY0) | 0;
const x1 = (baseX1 > REGION_WIDTH ? REGION_WIDTH : baseX1) | 0;
const y1 = (baseY1 > REGION_HEIGHT ? REGION_HEIGHT : baseY1) | 0;
if (x1 > x0 && y1 > y0) {
for (let y = y0 | 0; y < y1; y = (y + 1) | 0) {
const oy = (y * REGION_WIDTH) | 0;
if (x1 > x0 && y1 > y0) {
for (let y = y0 | 0; y < y1; y = (y + 1) | 0) {
const oy = (y * REGION_WIDTH) | 0;
for (let x = x0 | 0; x < x1; x = (x + 1) | 0) {
regionCollider[(x + oy) | 0] |= value;
}
}
}
} else {
for (const pc of ponyColliders) {
const tx0 = (baseX0 + pc.x) | 0;
const ty0 = (baseY0 + pc.y) | 0;
const tx1 = (baseX1 + ((pc.x + pc.w) | 0)) | 0;
const ty1 = (baseY1 + ((pc.y + pc.h) | 0)) | 0;
for (let x = x0 | 0; x < x1; x = (x + 1) | 0) {
regionCollider[(x + oy) | 0] |= value;
}
}
}
} else {
for (const pc of ponyColliders) {
const tx0 = (baseX0 + pc.x) | 0;
const ty0 = (baseY0 + pc.y) | 0;
const tx1 = (baseX1 + ((pc.x + pc.w) | 0)) | 0;
const ty1 = (baseY1 + ((pc.y + pc.h) | 0)) | 0;
const x0 = (tx0 < 0 ? 0 : tx0) | 0;
const y0 = (ty0 < 0 ? 0 : ty0) | 0;
const x1 = (tx1 > REGION_WIDTH ? REGION_WIDTH : tx1) | 0;
const y1 = (ty1 > REGION_HEIGHT ? REGION_HEIGHT : ty1) | 0;
const x0 = (tx0 < 0 ? 0 : tx0) | 0;
const y0 = (ty0 < 0 ? 0 : ty0) | 0;
const x1 = (tx1 > REGION_WIDTH ? REGION_WIDTH : tx1) | 0;
const y1 = (ty1 > REGION_HEIGHT ? REGION_HEIGHT : ty1) | 0;
if (x1 > x0 && y1 > y0) {
for (let y = y0 | 0; y < y1; y = (y + 1) | 0) {
const oy = (y * REGION_WIDTH) | 0;
if (x1 > x0 && y1 > y0) {
for (let y = y0 | 0; y < y1; y = (y + 1) | 0) {
const oy = (y * REGION_WIDTH) | 0;
for (let x = x0 | 0; x < x1; x = (x + 1) | 0) {
regionCollider[(x + oy) | 0] |= value;
}
}
}
}
}
}
}
}
}
for (let x = x0 | 0; x < x1; x = (x + 1) | 0) {
regionCollider[(x + oy) | 0] |= value;
}
}
}
}
}
}
}
}
}
}
+75 -75
View File
@@ -3,100 +3,100 @@ import { LogArgument } from 'rollbar';
import { CHARACTER_LIMIT_ERROR } from './errors';
const IGNORE = new RegExp([
// adware / extensions
'plantsandplay', 'anyplacetrivial', 'surfbuyermac', 'hotshoppymac', 'GM_getValue', '__gCrWeb.autofill',
'.com/affs', 'advpartners', 'tlscdn', 'yaaknaa', 'mecash', 'digitaloptout',
'Script error', 'NS_ERROR_', 'davebestdeals', 'mflcdn', `'feedConf' of null`, 'n46gd0nenr1az.ru',
'googst2.ru', 'downloader12.ru', 'adsafeprotected', 'gobobr.info', 'elt.parentNode',
`getElementsByTagName('video')`, 'chrome-extension', 'bestpriceninja', `'tgt' of null`,
'jh8hrfnvs.ru', 'OperaIce', 'blueblockgames', 'adguard.com', 'kaspersky', 'igamesecrets.com',
'Unexpected identifier', 'UnknownError', 'diableNightMode', 'Unexpected end of script',
'Internal Server Error', 'hilitor', 'kejnoj7.ru', 'v207.info', 'inj_js',
'object is not a function', `'Float32Array' is undefined`, 'vertamedia', '.ru/', 'v24s.net',
'window.document.location is null', 'mediaonspot', 'ydpi.pw', 'moz-extension', 'trafficanalytics',
'amazonaws', 'adtelligent', 'searchsens.info', 'solid-waste.top', 'cdn.immereeako.info',
'technologiecoloniale.com', 'cloudcnfare.com', 'MyAppGet', 'rugged-r.top', `Can't find variable: webkit`,
`rgvqcsxqge.com`, 'all_small_polls', `Cannot read property 'document' of undefined`, `extAbbr is not defined`,
'__gCrWeb', 'DOMBnbPlug',
// adware / extensions
'plantsandplay', 'anyplacetrivial', 'surfbuyermac', 'hotshoppymac', 'GM_getValue', '__gCrWeb.autofill',
'.com/affs', 'advpartners', 'tlscdn', 'yaaknaa', 'mecash', 'digitaloptout',
'Script error', 'NS_ERROR_', 'davebestdeals', 'mflcdn', `'feedConf' of null`, 'n46gd0nenr1az.ru',
'googst2.ru', 'downloader12.ru', 'adsafeprotected', 'gobobr.info', 'elt.parentNode',
`getElementsByTagName('video')`, 'chrome-extension', 'bestpriceninja', `'tgt' of null`,
'jh8hrfnvs.ru', 'OperaIce', 'blueblockgames', 'adguard.com', 'kaspersky', 'igamesecrets.com',
'Unexpected identifier', 'UnknownError', 'diableNightMode', 'Unexpected end of script',
'Internal Server Error', 'hilitor', 'kejnoj7.ru', 'v207.info', 'inj_js',
'object is not a function', `'Float32Array' is undefined`, 'vertamedia', '.ru/', 'v24s.net',
'window.document.location is null', 'mediaonspot', 'ydpi.pw', 'moz-extension', 'trafficanalytics',
'amazonaws', 'adtelligent', 'searchsens.info', 'solid-waste.top', 'cdn.immereeako.info',
'technologiecoloniale.com', 'cloudcnfare.com', 'MyAppGet', 'rugged-r.top', `Can't find variable: webkit`,
`rgvqcsxqge.com`, 'all_small_polls', `Cannot read property 'document' of undefined`, `extAbbr is not defined`,
'__gCrWeb', 'DOMBnbPlug',
// GPU errors
`Failed to execute 'shaderSource'`,
'compiling shader',
'Failed to create WebGL context',
'CONTEXT_LOST_WEBGL',
'Framebuffer unsupported',
'Framebuffer failed for unspecified reason',
'Недостаточно ресурсов памяти для завершения операции.',
'Failed to initialize graphics device (Shader error)',
'Failed to initialize graphics device (Failed to create WebGL context)',
'Failed to initialize graphics device (Failed to create texture)',
'Shader error',
// GPU errors
`Failed to execute 'shaderSource'`,
'compiling shader',
'Failed to create WebGL context',
'CONTEXT_LOST_WEBGL',
'Framebuffer unsupported',
'Framebuffer failed for unspecified reason',
'Недостаточно ресурсов памяти для завершения операции.',
'Failed to initialize graphics device (Shader error)',
'Failed to initialize graphics device (Failed to create WebGL context)',
'Failed to initialize graphics device (Failed to create texture)',
'Shader error',
// GPU halt
'GPU device instance has been suspended',
'Die GPU-Geräteinstanz wurde angehalten',
'GPU zostało zawieszone',
`GPU приостановлен`,
'GPU se ha suspendido',
'GPU aygıt örneği askıya alınmış',
'GPU-enhetsinstansen har försatts',
// GPU halt
'GPU device instance has been suspended',
'Die GPU-Geräteinstanz wurde angehalten',
'GPU zostało zawieszone',
`GPU приостановлен`,
'GPU se ha suspendido',
'GPU aygıt örneği askıya alınmış',
'GPU-enhetsinstansen har försatts',
// other
'androidInterface is not defined',
'/images/',
'out of memory',
'object is not a function',
'Array buffer allocation failed',
'Server is offline',
'Failed to register a ServiceWorker',
'Permission denied to access property',
'Not enough storage is available',
'Failed to initialize graphics device',
'Not enough memory resources',
'Ikke nok minneressurser tilgjengelig', // out of memory
'suficientes recursos de memoria',
'Onvoldoende geheugenbronnen',
`Cannot read property 'version' of undefined`,
// other
'androidInterface is not defined',
'/images/',
'out of memory',
'object is not a function',
'Array buffer allocation failed',
'Server is offline',
'Failed to register a ServiceWorker',
'Permission denied to access property',
'Not enough storage is available',
'Failed to initialize graphics device',
'Not enough memory resources',
'Ikke nok minneressurser tilgjengelig', // out of memory
'suficientes recursos de memoria',
'Onvoldoende geheugenbronnen',
`Cannot read property 'version' of undefined`,
'Maximum call stack size exceeded', // howler error on chrome mobile
'Maximum call stack size exceeded', // howler error on chrome mobile
// user errors
CHARACTER_LIMIT_ERROR,
'Too many requests',
'Saving in progress',
'Too many requests, please try again in',
'Already waiting for join request',
// user errors
CHARACTER_LIMIT_ERROR,
'Too many requests',
'Saving in progress',
'Too many requests, please try again in',
'Already waiting for join request',
// server
'Range Not Satisfiable', 'Precondition Failed',
// server
'Range Not Satisfiable', 'Precondition Failed',
].map(escapeRegExp).join('|'), 'i');
export interface Person {
id: string;
username: string;
custom?: any;
id: string;
username: string;
custom?: any;
}
function getLabel(arg: LogArgument | undefined) {
if (typeof arg === 'string') {
return arg;
} else if (arg && 'message' in arg) {
return arg.message + (arg.stack || '');
} else {
return arg ? arg.toString() : '';
}
if (typeof arg === 'string') {
return arg;
} else if (arg && 'message' in arg) {
return arg.message + (arg.stack || '');
} else {
return arg ? arg.toString() : '';
}
}
export function isIgnoredMessage(message: string) {
return IGNORE.test(message);
return IGNORE.test(message);
}
export function isIgnoredError(error: Error) {
return isIgnoredMessage(error.message || `${error}` || '') || isIgnoredMessage(error.stack || '');
return isIgnoredMessage(error.message || `${error}` || '') || isIgnoredMessage(error.stack || '');
}
export function rollbarCheckIgnore(_isUncaught: boolean, args: LogArgument[], _payload: object): boolean {
return (Array.isArray(args) ? args : [args])
.map(getLabel)
.some(isIgnoredMessage);
return (Array.isArray(args) ? args : [args])
.map(getLabel)
.some(isIgnoredMessage);
}
+78 -78
View File
@@ -10,131 +10,131 @@ export const urlRegex = new RegExp(urlRegexTexts.join('|'), 'ui');
export const ipRegex = new RegExp(ipRegexText, 'ui');
function createRegExpFromList(list: string | undefined, wholeWords = false): RegExp | undefined {
const lines = list && compact(list.split(/\r?\n/).map(x => x.trim()));
const lines = list && compact(list.split(/\r?\n/).map(x => x.trim()));
if (lines && lines.length) {
const combined = lines.map(escapeRegExp).join('|');
if (lines && lines.length) {
const combined = lines.map(escapeRegExp).join('|');
if (wholeWords) {
return new RegExp(`\\b(${combined})\\b`, 'ui');
} else {
return new RegExp(combined, 'ui');
}
} else {
return undefined;
}
if (wholeWords) {
return new RegExp(`\\b(${combined})\\b`, 'ui');
} else {
return new RegExp(combined, 'ui');
}
} else {
return undefined;
}
}
export const createCachedTest = (wholeWords = false) => {
let cachedList: string | undefined = undefined;
let cachedRegex: RegExp | undefined = undefined;
let cachedList: string | undefined = undefined;
let cachedRegex: RegExp | undefined = undefined;
return (list: string | undefined, value: string) => {
if (cachedList !== list) {
cachedList = list;
cachedRegex = createRegExpFromList(list, wholeWords);
}
return (list: string | undefined, value: string) => {
if (cachedList !== list) {
cachedList = list;
cachedRegex = createRegExpFromList(list, wholeWords);
}
return cachedRegex ? cachedRegex.test(value) : false;
};
return cachedRegex ? cachedRegex.test(value) : false;
};
};
export const createIsSuspiciousMessage = (general: GeneralSettings) => {
const test = createCachedTest();
const testSafe = createCachedTest();
const testWhole = createCachedTest(true);
const testSafeInstant = createCachedTest();
const testWholeInstant = createCachedTest(true);
const test = createCachedTest();
const testSafe = createCachedTest();
const testWhole = createCachedTest(true);
const testSafeInstant = createCachedTest();
const testWholeInstant = createCachedTest(true);
return (text: string, { filterSwears }: GameServerSettings): Suspicious => {
if (test(general.suspiciousMessages, text))
return Suspicious.Very;
return (text: string, { filterSwears }: GameServerSettings): Suspicious => {
if (test(general.suspiciousMessages, text))
return Suspicious.Very;
if (filterSwears) {
if (testSafeInstant(general.suspiciousSafeInstantMessages, text) ||
testWholeInstant(general.suspiciousSafeInstantWholeMessages, text)) {
return Suspicious.Very;
}
if (filterSwears) {
if (testSafeInstant(general.suspiciousSafeInstantMessages, text) ||
testWholeInstant(general.suspiciousSafeInstantWholeMessages, text)) {
return Suspicious.Very;
}
if (testSafe(general.suspiciousSafeMessages, text) ||
testWhole(general.suspiciousSafeWholeMessages, text)) {
return Suspicious.Yes;
}
}
if (testSafe(general.suspiciousSafeMessages, text) ||
testWhole(general.suspiciousSafeWholeMessages, text)) {
return Suspicious.Yes;
}
}
return Suspicious.No;
};
return Suspicious.No;
};
};
export const createIsSuspiciousName =
(settings: GeneralSettings) => {
const test = createCachedTest();
return (name: string) => test(settings.suspiciousNames, name);
};
(settings: GeneralSettings) => {
const test = createCachedTest();
return (name: string) => test(settings.suspiciousNames, name);
};
export const createIsSuspiciousAuth =
(settings: GeneralSettings) => {
const test = createCachedTest();
return ({ name, emails = [] }: AuthBase<any>) =>
test(settings.suspiciousAuths, name) ||
emails.some(email => test(settings.suspiciousAuths, email));
};
(settings: GeneralSettings) => {
const test = createCachedTest();
return ({ name, emails = [] }: AuthBase<any>) =>
test(settings.suspiciousAuths, name) ||
emails.some(email => test(settings.suspiciousAuths, email));
};
// pony
function tryParseJSON(value: string): any {
try {
return JSON.parse(value);
} catch {
return undefined;
}
try {
return JSON.parse(value);
} catch {
return undefined;
}
}
function createMatchesFromList(list: string | undefined): Partial<PonyInfo>[] {
return compact((list || '').split(/\n/g).map(x => x.trim()).map(tryParseJSON));
return compact((list || '').split(/\n/g).map(x => x.trim()).map(tryParseJSON));
}
export const createIsSuspiciousPony =
(settings: GeneralSettings) =>
(info: PonyInfoNumber) => {
const matches = createMatchesFromList(settings.suspiciousPonies);
return matches.some(match => matchPony(info, match));
};
(settings: GeneralSettings) =>
(info: PonyInfoNumber) => {
const matches = createMatchesFromList(settings.suspiciousPonies);
return matches.some(match => matchPony(info, match));
};
function matchPony(info: PonyInfoNumber, match: Partial<PonyInfo>) {
return isMatchWith(info, match, comparePonyInfoFields);
return isMatchWith(info, match, comparePonyInfoFields);
}
function comparePonyInfoFields(a: any, b: any): boolean {
if (typeof a === 'number' && typeof b === 'string') {
return a === parseColorFast(b);
} else {
return undefined as any;
}
if (typeof a === 'number' && typeof b === 'string') {
return a === parseColorFast(b);
} else {
return undefined as any;
}
}
// forbidden messages
export function isForbiddenMessage(_message: string): boolean {
// NOTE: uncomment, to filter offensive messages
// if (/niggers$/.test(_message) || /faggots?/.test(_message)) return true;
// NOTE: uncomment, to filter offensive messages
// if (/niggers$/.test(_message) || /faggots?/.test(_message)) return true;
// NOTE: add more filters here
// NOTE: add more filters here
return false;
return false;
}
// forbidden name
export function isForbiddenName(_value: string): boolean {
// NOTE: uncomment, to filter offensive names
// if (/niggers$/.test(_value) || /faggots?/.test(_value) || /hitler/.test(_value)) return true;
// NOTE: uncomment, to filter offensive names
// if (/niggers$/.test(_value) || /faggots?/.test(_value) || /hitler/.test(_value)) return true;
// NOTE: uncomment, to filter links in names
// if (ipRegex.test(_value) && !ipExceptionRegex.test(_value)) return true;
// if (urlRegex.test(_value) && !urlExceptionRegex.test(_value)) return true;
// NOTE: uncomment, to filter links in names
// if (ipRegex.test(_value) && !ipExceptionRegex.test(_value)) return true;
// if (urlRegex.test(_value) && !urlExceptionRegex.test(_value)) return true;
// NOTE: add more filters here
// NOTE: add more filters here
return false;
return false;
}
+773 -773
View File
File diff suppressed because it is too large Load Diff
+45 -45
View File
@@ -3,88 +3,88 @@ const uppercaseCharacters = lowercaseCharacters + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const CARRIAGERETURN = '\r'.charCodeAt(0);
export function randomString(length: number, useUpperCase = false): string {
const characters = useUpperCase ? uppercaseCharacters : lowercaseCharacters;
let result = '';
const characters = useUpperCase ? uppercaseCharacters : lowercaseCharacters;
let result = '';
for (let i = 0; i < length; i++) {
result += characters[(Math.random() * characters.length) | 0];
}
for (let i = 0; i < length; i++) {
result += characters[(Math.random() * characters.length) | 0];
}
return result;
return result;
}
export function isSurrogate(code: number): boolean {
return code >= 0xd800 && code <= 0xdbff;
return code >= 0xd800 && code <= 0xdbff;
}
export function isLowSurrogate(code: number): boolean {
return (code & 0xfc00) === 0xdc00;
return (code & 0xfc00) === 0xdc00;
}
export function fromSurrogate(high: number, low: number): number {
return (((high & 0x3ff) << 10) + (low & 0x3ff) + 0x10000) | 0;
return (((high & 0x3ff) << 10) + (low & 0x3ff) + 0x10000) | 0;
}
export function charsToCodes(text: string) {
const chars: number[] = [];
const chars: number[] = [];
for (let i = 0; i < text.length; i++) {
let code = text.charCodeAt(i);
for (let i = 0; i < text.length; i++) {
let code = text.charCodeAt(i);
if (isSurrogate(code) && (i + 1) < text.length) {
const extra = text.charCodeAt(i + 1);
if (isSurrogate(code) && (i + 1) < text.length) {
const extra = text.charCodeAt(i + 1);
if (isLowSurrogate(extra)) {
code = fromSurrogate(code, extra);
i++;
}
}
if (isLowSurrogate(extra)) {
code = fromSurrogate(code, extra);
i++;
}
}
chars.push(code);
}
chars.push(code);
}
return chars;
return chars;
}
export function stringToCodes(buffer: Uint32Array, text: string): number {
const textLength = text.length | 0;
let length = 0 | 0;
const textLength = text.length | 0;
let length = 0 | 0;
for (let i = 0; i < textLength; i = (i + 1) | 0) {
let code = text.charCodeAt(i) | 0;
for (let i = 0; i < textLength; i = (i + 1) | 0) {
let code = text.charCodeAt(i) | 0;
if (isSurrogate(code) && ((i + 1) | 0) < textLength) {
const extra = text.charCodeAt(i + 1) | 0;
if (isSurrogate(code) && ((i + 1) | 0) < textLength) {
const extra = text.charCodeAt(i + 1) | 0;
if (isLowSurrogate(extra)) {
code = fromSurrogate(code, extra) | 0;
i = (i + 1) | 0;
}
}
if (isLowSurrogate(extra)) {
code = fromSurrogate(code, extra) | 0;
i = (i + 1) | 0;
}
}
if (isVisibleChar(code)) {
buffer[length] = code;
length = (length + 1) | 0;
}
}
if (isVisibleChar(code)) {
buffer[length] = code;
length = (length + 1) | 0;
}
}
return length;
return length;
}
export let codesBuffer = new Uint32Array(32);
export function stringToCodesTemp(text: string) {
while (text.length > codesBuffer.length) {
codesBuffer = new Uint32Array(codesBuffer.length * 2);
}
while (text.length > codesBuffer.length) {
codesBuffer = new Uint32Array(codesBuffer.length * 2);
}
return stringToCodes(codesBuffer, text);
return stringToCodes(codesBuffer, text);
}
export function matcher(regex: RegExp) {
return (text: string): boolean => !!text && regex.test(text);
return (text: string): boolean => !!text && regex.test(text);
}
export function isVisibleChar(code: number) {
return code !== CARRIAGERETURN && !(code >= 0xfe00 && code <= 0xfe0f);
return code !== CARRIAGERETURN && !(code >= 0xfe00 && code <= 0xfe0f);
}
+1473 -1473
View File
File diff suppressed because it is too large Load Diff
+27 -27
View File
@@ -4,51 +4,51 @@ import { MOD_COLOR, ADMIN_COLOR, PATREON_COLOR, ANNOUNCEMENT_COLOR, WHITE } from
const placeholder = { id: '', tagClass: '', label: '' };
const tags: { [key: string]: CharacterTag; } = {
'mod': { ...placeholder, name: 'moderator', className: 'mod', color: MOD_COLOR },
'dev': { ...placeholder, name: 'developer', className: 'dev', color: ADMIN_COLOR },
'dev:art': { ...placeholder, name: 'dev artist', className: 'dev', color: ADMIN_COLOR },
'dev:music': { ...placeholder, name: 'dev musician', className: 'dev', color: ADMIN_COLOR },
'sup1': { ...placeholder, name: 'supporter', className: 'sup1', color: PATREON_COLOR },
'sup2': { ...placeholder, name: 'supporter', className: 'sup2', color: WHITE },
'sup3': { ...placeholder, name: 'supporter', className: 'sup3', color: WHITE },
'hidden': { ...placeholder, name: 'hidden', className: 'hidden', color: ANNOUNCEMENT_COLOR },
'mod': { ...placeholder, name: 'moderator', className: 'mod', color: MOD_COLOR },
'dev': { ...placeholder, name: 'developer', className: 'dev', color: ADMIN_COLOR },
'dev:art': { ...placeholder, name: 'dev artist', className: 'dev', color: ADMIN_COLOR },
'dev:music': { ...placeholder, name: 'dev musician', className: 'dev', color: ADMIN_COLOR },
'sup1': { ...placeholder, name: 'supporter', className: 'sup1', color: PATREON_COLOR },
'sup2': { ...placeholder, name: 'supporter', className: 'sup2', color: WHITE },
'sup3': { ...placeholder, name: 'supporter', className: 'sup3', color: WHITE },
'hidden': { ...placeholder, name: 'hidden', className: 'hidden', color: ANNOUNCEMENT_COLOR },
};
Object.keys(tags).forEach(id => {
const tag = tags[id];
tag.id = id;
tag.label = `<${tag.name.toUpperCase()}>`;
tag.tagClass = `tag-${tag.className}`;
const tag = tags[id];
tag.id = id;
tag.label = `<${tag.name.toUpperCase()}>`;
tag.tagClass = `tag-${tag.className}`;
});
export const emptyTag: CharacterTag = { id: '', name: 'no tag', label: '', className: '', tagClass: '', color: 0 };
export function getAllTags() {
return Object.keys(tags).map(key => tags[key]);
return Object.keys(tags).map(key => tags[key]);
}
export function getTag(id: string | undefined): CharacterTag | undefined {
return id ? tags[id] : undefined;
return id ? tags[id] : undefined;
}
export function getTagPalette(tag: CharacterTag, palettes: FontPalettes) {
switch (tag.id) {
case 'sup2': return palettes.supporter2;
case 'sup3': return palettes.supporter3;
default: return palettes.white;
}
switch (tag.id) {
case 'sup2': return palettes.supporter2;
case 'sup3': return palettes.supporter3;
default: return palettes.white;
}
}
export function canUseTag(account: AccountRoles, tag: string) {
if (tag === 'mod') {
return hasRole(account, 'mod');
} else if (tag === 'dev' || /^dev:/.test(tag)) {
return hasRole(account, 'dev');
} else {
return false;
}
if (tag === 'mod') {
return hasRole(account, 'mod');
} else if (tag === 'dev' || /^dev:/.test(tag)) {
return hasRole(account, 'dev');
} else {
return false;
}
}
export function getAvailableTags(account: AccountRoles): CharacterTag[] {
return getAllTags().filter(tag => canUseTag(account, tag.id));
return getAllTags().filter(tag => canUseTag(account, tag.id));
}
+62 -62
View File
@@ -16,22 +16,22 @@ const getTimeOfDay = (time: number) => time % DAY_LENGTH;
const getHourOfDay = (timeOfDay: number) => timeOfDay * 24 / DAY_LENGTH;
export function getHour(time: number) {
const timeOfDay = getTimeOfDay(time);
const hourOfDay = getHourOfDay(timeOfDay);
return hourOfDay;
const timeOfDay = getTimeOfDay(time);
const hourOfDay = getHourOfDay(timeOfDay);
return hourOfDay;
}
export function formatHourMinutes(time: number): string {
const timeOfDay = getTimeOfDay(time);
const minutesInDay = 60 * 24;
const totalMinutes = Math.floor(timeOfDay * minutesInDay / DAY_LENGTH);
const minutes = totalMinutes % 60;
const hours = Math.floor(totalMinutes / 60);
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
const timeOfDay = getTimeOfDay(time);
const minutesInDay = 60 * 24;
const totalMinutes = Math.floor(timeOfDay * minutesInDay / DAY_LENGTH);
const minutes = totalMinutes % 60;
const hours = Math.floor(totalMinutes / 60);
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
}
const isHour = (test: (hour: number) => boolean) => (time: number) => {
return test(getHour(time));
return test(getHour(time));
};
export const isDay = isHour(hour => hour > DAY_START && hour <= DAY_END);
@@ -49,79 +49,79 @@ export const isNightTime = isHour(hour => hour < (DAY_START - SUN_HALF) || hour
// light color
export interface LightData {
lightColors: number[];
shadowColors: number[];
lightStops: number[];
lightColors: number[];
shadowColors: number[];
lightStops: number[];
}
export function createLightData(season: Season): LightData {
const lightDay = WHITE;
const lightNight = season === Season.Winter ? 0x253f76ff : 0x2b3374ff;
const lightDay = WHITE;
const lightNight = season === Season.Winter ? 0x253f76ff : 0x2b3374ff;
const sunrise1 = 0x853d7dff;
const sunrise2 = 0xc96161ff;
const sunrise3 = 0xeeb7a0ff;
const sunrise1 = 0x853d7dff;
const sunrise2 = 0xc96161ff;
const sunrise3 = 0xeeb7a0ff;
const sunset1 = sunrise3;
const sunset2 = sunrise2;
const sunset3 = sunrise1;
const sunset1 = sunrise3;
const sunset2 = sunrise2;
const sunset3 = sunrise1;
const shadowAlphaMultiplier = season === Season.Winter ? 0.7 : 1;
const shadowDay = withAlphaFloat(BLACK, 0.3 * shadowAlphaMultiplier);
const shadowNight = withAlphaFloat(BLACK, 0.2 * shadowAlphaMultiplier);
const shadowSunset = withAlphaFloat(BLACK, 0.25 * shadowAlphaMultiplier);
const shadowSunrise = withAlphaFloat(BLACK, 0.25 * shadowAlphaMultiplier);
const shadowAlphaMultiplier = season === Season.Winter ? 0.7 : 1;
const shadowDay = withAlphaFloat(BLACK, 0.3 * shadowAlphaMultiplier);
const shadowNight = withAlphaFloat(BLACK, 0.2 * shadowAlphaMultiplier);
const shadowSunset = withAlphaFloat(BLACK, 0.25 * shadowAlphaMultiplier);
const shadowSunrise = withAlphaFloat(BLACK, 0.25 * shadowAlphaMultiplier);
const lightPoints = [
// night
{ time: 0, light: lightNight, shadow: shadowNight },
const lightPoints = [
// night
{ time: 0, light: lightNight, shadow: shadowNight },
// transition to day
{ time: DAY_START - SUN_HALF, light: lightNight, shadow: shadowNight },
{ time: DAY_START - SUN_HALF + SUN_GAP, light: sunrise1, shadow: shadowSunrise },
{ time: DAY_START - SUN_HALF + SUN_GAP * 2, light: sunrise2, shadow: shadowSunrise },
{ time: DAY_START - SUN_HALF + SUN_GAP * 3, light: sunrise3, shadow: shadowSunrise },
{ time: DAY_START + SUN_HALF, light: lightDay, shadow: shadowDay },
// transition to day
{ time: DAY_START - SUN_HALF, light: lightNight, shadow: shadowNight },
{ time: DAY_START - SUN_HALF + SUN_GAP, light: sunrise1, shadow: shadowSunrise },
{ time: DAY_START - SUN_HALF + SUN_GAP * 2, light: sunrise2, shadow: shadowSunrise },
{ time: DAY_START - SUN_HALF + SUN_GAP * 3, light: sunrise3, shadow: shadowSunrise },
{ time: DAY_START + SUN_HALF, light: lightDay, shadow: shadowDay },
// transition to night
{ time: DAY_END - SUN_HALF, light: lightDay, shadow: shadowDay },
{ time: DAY_END - SUN_HALF + SUN_GAP, light: sunset1, shadow: shadowSunset },
{ time: DAY_END - SUN_HALF + SUN_GAP * 2, light: sunset2, shadow: shadowSunset },
{ time: DAY_END - SUN_HALF + SUN_GAP * 3, light: sunset3, shadow: shadowSunset },
{ time: DAY_END + SUN_HALF, light: lightNight, shadow: shadowNight },
// transition to night
{ time: DAY_END - SUN_HALF, light: lightDay, shadow: shadowDay },
{ time: DAY_END - SUN_HALF + SUN_GAP, light: sunset1, shadow: shadowSunset },
{ time: DAY_END - SUN_HALF + SUN_GAP * 2, light: sunset2, shadow: shadowSunset },
{ time: DAY_END - SUN_HALF + SUN_GAP * 3, light: sunset3, shadow: shadowSunset },
{ time: DAY_END + SUN_HALF, light: lightNight, shadow: shadowNight },
// night
{ time: 24, light: lightNight, shadow: shadowNight },
];
// night
{ time: 24, light: lightNight, shadow: shadowNight },
];
const lightColors = lightPoints.map(l => l.light);
const shadowColors = lightPoints.map(l => l.shadow);
const lightStops = lightPoints.map(l => l.time);
const lightColors = lightPoints.map(l => l.light);
const shadowColors = lightPoints.map(l => l.shadow);
const lightStops = lightPoints.map(l => l.time);
return { lightColors, shadowColors, lightStops };
return { lightColors, shadowColors, lightStops };
}
export function getLightColor(data: LightData, time: number): number {
return getColorForTime(time, data.lightStops, data.lightColors, WHITE);
return getColorForTime(time, data.lightStops, data.lightColors, WHITE);
}
export function getShadowColor(data: LightData, time: number): number {
return getColorForTime(time, data.lightStops, data.shadowColors, SHADOW_COLOR);
return getColorForTime(time, data.lightStops, data.shadowColors, SHADOW_COLOR);
}
function getColorForTime(time: number, stops: number[], colors: number[], defaultColor: number) {
const timeOfDay = getTimeOfDay(time);
const hourOfDay = getHourOfDay(timeOfDay);
const timeOfDay = getTimeOfDay(time);
const hourOfDay = getHourOfDay(timeOfDay);
for (let i = 1; i < stops.length; i++) {
if (stops[i] >= hourOfDay) {
const from = stops[i - 1];
const to = stops[i];
const fromLight = colors[i - 1];
const toLight = colors[i];
return lerpColors(fromLight, toLight, (hourOfDay - from) / (to - from));
}
}
for (let i = 1; i < stops.length; i++) {
if (stops[i] >= hourOfDay) {
const from = stops[i - 1];
const to = stops[i];
const fromLight = colors[i - 1];
const toLight = colors[i];
return lerpColors(fromLight, toLight, (hourOfDay - from) / (to - from));
}
}
return defaultColor;
return defaultColor;
}
+266 -266
View File
@@ -7,420 +7,420 @@ import { ACCESS_ERROR, NOT_FOUND_ERROR, OFFLINE_ERROR, PROTECTION_ERROR } from '
// enum
export function invalidEnum(value: never) {
if (DEVELOPMENT) {
throw new Error(`Invalid enum value: ${value}`);
}
if (DEVELOPMENT) {
throw new Error(`Invalid enum value: ${value}`);
}
}
export function invalidEnumReturn<T>(value: never, ret: T): T {
if (DEVELOPMENT && !TESTS) {
throw new Error(`Invalid enum value: ${value}`);
}
if (DEVELOPMENT && !TESTS) {
throw new Error(`Invalid enum value: ${value}`);
}
return ret;
return ret;
}
// date
export function fromDate(date: Date, duration: number): Date {
date.setTime(date.getTime() + duration);
return date;
date.setTime(date.getTime() + duration);
return date;
}
export function fromNow(duration: number): Date {
return fromDate(new Date(), duration);
return fromDate(new Date(), duration);
}
export function compareDates(a?: Date, b?: Date) {
return a ? (b ? a.getTime() - b.getTime() : 1) : (b ? -1 : 0);
return a ? (b ? a.getTime() - b.getTime() : 1) : (b ? -1 : 0);
}
export function maxDate(a?: Date, b?: Date) {
return (compareDates(a, b) > 0 ? a : b) || a || b;
return (compareDates(a, b) > 0 ? a : b) || a || b;
}
export function minDate(a?: Date, b?: Date) {
return (compareDates(a, b) < 0 ? a : b) || a || b;
return (compareDates(a, b) < 0 ? a : b) || a || b;
}
export function formatDuration(duration: number) {
const s = Math.floor(duration / SECOND) % 60;
const m = Math.floor(duration / MINUTE) % 60;
const h = Math.floor(duration / HOUR) % 24;
const d = Math.floor(duration / DAY);
const s = Math.floor(duration / SECOND) % 60;
const m = Math.floor(duration / MINUTE) % 60;
const h = Math.floor(duration / HOUR) % 24;
const d = Math.floor(duration / DAY);
if (d > 0) {
return h ? `${d}d ${h}h` : `${d}d`;
} else if (h > 0) {
return m ? `${h}h ${m}m` : `${h}h`;
} else if (m > 0) {
return s ? `${m}m ${s}s` : `${m}m`;
} else {
return `${s}s`;
}
if (d > 0) {
return h ? `${d}d ${h}h` : `${d}d`;
} else if (h > 0) {
return m ? `${h}h ${m}m` : `${h}h`;
} else if (m > 0) {
return s ? `${m}m ${s}s` : `${m}m`;
} else {
return `${s}s`;
}
}
export function formatISODate(date: Date) {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
return `${year.toString().padStart(4, '0')}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
return `${year.toString().padStart(4, '0')}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
}
export function parseISODate(value: string) {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
let day = 0;
let month = 0;
let year = 0;
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
let day = 0;
let month = 0;
let year = 0;
if (match) {
year = parseInt(match[1], 10);
month = parseInt(match[2], 10);
day = parseInt(match[3], 10);
}
if (match) {
year = parseInt(match[1], 10);
month = parseInt(match[2], 10);
day = parseInt(match[3], 10);
}
return { day, month, year };
return { day, month, year };
}
export function createValidBirthDate(day: number, month: number, year: number) {
const date = new Date(0);
const currentYear = (new Date()).getFullYear();
date.setFullYear(year, month - 1, day);
const date = new Date(0);
const currentYear = (new Date()).getFullYear();
date.setFullYear(year, month - 1, day);
if (
date.getFullYear() === year && date.getMonth() === (month - 1) && date.getDate() === day &&
year >= (currentYear - 120) && year < currentYear
) {
return date;
} else {
return undefined;
}
if (
date.getFullYear() === year && date.getMonth() === (month - 1) && date.getDate() === day &&
year >= (currentYear - 120) && year < currentYear
) {
return date;
} else {
return undefined;
}
}
// color
export function parseSpriteColor(str: string): number {
return str === '0' ? 0 : (str.length === 6 ? (((parseInt(str, 16) << 8) | 0xff) >>> 0) : (parseInt(str, 16) >>> 0));
return str === '0' ? 0 : (str.length === 6 ? (((parseInt(str, 16) << 8) | 0xff) >>> 0) : (parseInt(str, 16) >>> 0));
}
// numbers
export function clamp(value: number, min: number, max: number): number {
return value > min ? (value < max ? value : max) : min;
return value > min ? (value < max ? value : max) : min;
}
export function lerp(a: number, b: number, t: number) {
return a + t * (b - a);
return a + t * (b - a);
}
export function normalize(x: number, y: number): Point {
const d = Math.sqrt(x * x + y * y);
return { x: x / d, y: y / d };
const d = Math.sqrt(x * x + y * y);
return { x: x / d, y: y / d };
}
export function computeCRC(colors: Uint32Array): number {
let crc = 0;
let crc = 0;
for (let i = 0; i < colors.length; i++) {
crc ^= colors[i];
for (let i = 0; i < colors.length; i++) {
crc ^= colors[i];
for (let j = 0; j < 8; j++) {
crc = (crc & 1) ? ((crc >>> 1) ^ 0x82f63b78) : (crc >>> 1);
}
}
for (let j = 0; j < 8; j++) {
crc = (crc & 1) ? ((crc >>> 1) ^ 0x82f63b78) : (crc >>> 1);
}
}
return crc >>> 0;
return crc >>> 0;
}
export function computeFriendsCRC(friends: string[]) {
if (!friends.length) {
return 0;
}
if (!friends.length) {
return 0;
}
friends.sort();
const data = new Uint32Array(friends.length * 3);
friends.sort();
const data = new Uint32Array(friends.length * 3);
for (let i = 0; i < friends.length; i++) {
const id = friends[i];
data[i * 3] = parseInt(id.substr(0, 8), 16);
data[i * 3 + 1] = parseInt(id.substr(8, 8), 16);
data[i * 3 + 2] = parseInt(id.substr(16, 8), 16);
}
for (let i = 0; i < friends.length; i++) {
const id = friends[i];
data[i * 3] = parseInt(id.substr(0, 8), 16);
data[i * 3 + 1] = parseInt(id.substr(8, 8), 16);
data[i * 3 + 2] = parseInt(id.substr(16, 8), 16);
}
return computeCRC(data);
return computeCRC(data);
}
export function lerpColor(a: number[] | Float32Array, b: number[] | Float32Array, t: number) {
a[0] = t * b[0] + (1 - t) * a[0];
a[1] = t * b[1] + (1 - t) * a[1];
a[2] = t * b[2] + (1 - t) * a[2];
a[3] = t * b[3] + (1 - t) * a[3];
a[0] = t * b[0] + (1 - t) * a[0];
a[1] = t * b[1] + (1 - t) * a[1];
a[2] = t * b[2] + (1 - t) * a[2];
a[3] = t * b[3] + (1 - t) * a[3];
}
// common
export function toInt(value: any): number {
return value | 0;
return value | 0;
}
export function dispose<T extends { dispose(): void; }>(obj: T | undefined): undefined {
obj && obj.dispose();
return undefined;
obj && obj.dispose();
return undefined;
}
export function cloneDeep<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
return JSON.parse(JSON.stringify(obj));
}
// enums
export function hasFlag(value: number | undefined, flag: number): boolean {
return (value! & flag) === flag;
return (value! & flag) === flag;
}
export function setFlag(value: number | undefined, flag: number, on: boolean): number {
return (value! & ~flag) | (on ? flag : 0);
return (value! & ~flag) | (on ? flag : 0);
}
export function flagsToString(value: number, flags: { value: number; name: string; }[], none = 'None') {
return flags
.filter(flag => hasFlag(value, flag.value))
.map(flag => flag.name).join(' | ') || none;
return flags
.filter(flag => hasFlag(value, flag.value))
.map(flag => flag.name).join(' | ') || none;
}
// collections
export function includes<T>(array: T[] | undefined, item: T): boolean {
return array !== undefined && array.indexOf(item) !== -1;
return array !== undefined && array.indexOf(item) !== -1;
}
export function array<T>(size: number, defaultValue: T) {
const result: T[] = [];
const result: T[] = [];
for (let i = 0; i < size; i++) {
result.push(defaultValue);
}
for (let i = 0; i < size; i++) {
result.push(defaultValue);
}
return result;
return result;
}
export function repeat<T>(count: number, ...values: T[]): T[] {
const result: T[] = [];
const result: T[] = [];
for (let i = 0; i < count; i++) {
result.push(...values);
}
for (let i = 0; i < count; i++) {
result.push(...values);
}
return result;
return result;
}
export function times<T>(count: number, action: (index: number) => T) {
const result: T[] = [];
const result: T[] = [];
for (let i = 0; i < count; i++) {
result.push(action(i));
}
for (let i = 0; i < count; i++) {
result.push(action(i));
}
return result;
return result;
}
export function last<T>(array: T[]): T | undefined {
return array.length > 0 ? array[array.length - 1] : undefined;
return array.length > 0 ? array[array.length - 1] : undefined;
}
export function flatten<T>(arrays: T[][]): T[] {
return ([] as T[]).concat(...arrays);
return ([] as T[]).concat(...arrays);
}
export function at<T>(items: T[], index: any): T | undefined {
return items[clamp(index | 0, 0, items.length - 1)];
return items[clamp(index | 0, 0, items.length - 1)];
}
export function att<T>(items: T[] | null | undefined, index: any): T | undefined {
return items ? items[clamp(index | 0, 0, items.length - 1)] : undefined;
return items ? items[clamp(index | 0, 0, items.length - 1)] : undefined;
}
export function findById<U, T extends { id: U }>(items: T[], id: U): T | undefined {
for (let i = 0; i < items.length; i++) {
if (items[i].id === id) {
return items[i];
}
}
for (let i = 0; i < items.length; i++) {
if (items[i].id === id) {
return items[i];
}
}
return undefined;
return undefined;
}
export function findIndexById<U, T extends { id: U }>(items: T[], id: U): number {
for (let i = 0; i < items.length; i++) {
if (items[i].id === id) {
return i;
}
}
for (let i = 0; i < items.length; i++) {
if (items[i].id === id) {
return i;
}
}
return -1;
return -1;
}
export function removeItem<T>(items: T[], item: T): boolean {
const index = items.indexOf(item);
const index = items.indexOf(item);
if (index !== -1) {
items.splice(index, 1);
return true;
} else {
return false;
}
if (index !== -1) {
items.splice(index, 1);
return true;
} else {
return false;
}
}
export function removeItemFast<T>(items: T[], item: T): boolean {
const index = items.indexOf(item);
const index = items.indexOf(item);
if (index !== -1) {
items[index] = items[items.length - 1];
items.pop();
return true;
} else {
return false;
}
if (index !== -1) {
items[index] = items[items.length - 1];
items.pop();
return true;
} else {
return false;
}
}
export function removeById<U, T extends { id: U }>(items: T[], id: U): T | undefined {
const index = findIndexById(items, id);
const index = findIndexById(items, id);
if (index !== -1) {
const item = items[index];
items.splice(index, 1);
return item;
} else {
return undefined;
}
if (index !== -1) {
const item = items[index];
items.splice(index, 1);
return item;
} else {
return undefined;
}
}
export function arraysEqual<T>(a: T[], b: T[]): boolean {
if (a.length !== b.length) {
return false;
}
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
return true;
}
export function pushUniq<T>(array: T[], item: T) {
const index = array.indexOf(item);
const index = array.indexOf(item);
if (index === -1) {
array.push(item);
return array.length;
} else {
return index + 1;
}
if (index === -1) {
array.push(item);
return array.length;
} else {
return index + 1;
}
}
export function createPlainMap<T>(values: Dict<T>): Dict<T> {
return Object.keys(values).reduce((obj: Dict<T>, key: string) => (obj[key] = values[key], obj), Object.create(null));
return Object.keys(values).reduce((obj: Dict<T>, key: string) => (obj[key] = values[key], obj), Object.create(null));
}
// rects / points
export function point(x: number, y: number): Point {
return { x, y };
return { x, y };
}
export function contains(x: number, y: number, bounds: Rect, point: Point): boolean {
const bx = bounds.x / tileWidth + x;
const by = bounds.y / tileHeight + y;
const bw = bounds.w / tileWidth;
const bh = bounds.h / tileHeight;
return point.x > bx && point.x < bx + bw && point.y > by && point.y < by + bh;
const bx = bounds.x / tileWidth + x;
const by = bounds.y / tileHeight + y;
const bw = bounds.w / tileWidth;
const bh = bounds.h / tileHeight;
return point.x > bx && point.x < bx + bw && point.y > by && point.y < by + bh;
}
export function containsPoint(dx: number, dy: number, rect: Rect, px: number, py: number): boolean {
return pointInXYWH(px, py, rect.x + dx, rect.y + dy, rect.w, rect.h);
return pointInXYWH(px, py, rect.x + dx, rect.y + dy, rect.w, rect.h);
}
export function containsPointWitBorder(dx: number, dy: number, rect: Rect, px: number, py: number, border: number): boolean {
return pointInXYWH(px, py, rect.x + dx - border, rect.y + dy - border, rect.w + border * 2, rect.h + border * 2);
return pointInXYWH(px, py, rect.x + dx - border, rect.y + dy - border, rect.w + border * 2, rect.h + border * 2);
}
export function pointInRect(x: number, y: number, rect: Rect) {
return x > rect.x && x < rect.x + rect.w && y > rect.y && y < rect.y + rect.h;
return x > rect.x && x < rect.x + rect.w && y > rect.y && y < rect.y + rect.h;
}
export function pointInXYWH(px: number, py: number, rx: number, ry: number, rw: number, rh: number) {
return px > rx && px < rx + rw && py > ry && py < ry + rh;
return px > rx && px < rx + rw && py > ry && py < ry + rh;
}
export function randomPoint({ x, y, w, h }: Rect): Point {
return {
x: x + w * Math.random(),
y: y + h * Math.random(),
};
return {
x: x + w * Math.random(),
y: y + h * Math.random(),
};
}
export function lengthOfXY(dx: number, dy: number): number {
return Math.sqrt(dx * dx + dy * dy);
return Math.sqrt(dx * dx + dy * dy);
}
export function distanceXY(ax: number, ay: number, bx: number, by: number): number {
return lengthOfXY(ax - bx, ay - by);
return lengthOfXY(ax - bx, ay - by);
}
export function distanceSquaredXY(ax: number, ay: number, bx: number, by: number): number {
const dx = ax - bx;
const dy = ay - by;
return dx * dx + dy * dy;
const dx = ax - bx;
const dy = ay - by;
return dx * dx + dy * dy;
}
export function distance(a: Point, b: Point): number {
return distanceXY(a.x, a.y, b.x, b.y);
return distanceXY(a.x, a.y, b.x, b.y);
}
export function entitiesIntersect(a: Entity, b: Entity): boolean {
const aBounds = a.bounds;
const bBounds = b.bounds;
const aBounds = a.bounds;
const bBounds = b.bounds;
if (!aBounds || !bBounds) {
return false;
}
if (!aBounds || !bBounds) {
return false;
}
const ax = a.x * tileWidth + aBounds.x;
const ay = a.y * tileHeight + aBounds.y;
const bx = b.x * tileWidth + bBounds.x;
const by = b.y * tileHeight + bBounds.y;
const ax = a.x * tileWidth + aBounds.x;
const ay = a.y * tileHeight + aBounds.y;
const bx = b.x * tileWidth + bBounds.x;
const by = b.y * tileHeight + bBounds.y;
return intersect(ax, ay, aBounds.w, aBounds.h, bx, by, bBounds.w, bBounds.h);
return intersect(ax, ay, aBounds.w, aBounds.h, bx, by, bBounds.w, bBounds.h);
}
export function collidersIntersect(ax: number, ay: number, a: Rect, bx: number, by: number, b: Rect): boolean {
const axmin = Math.floor((ax + a.x) * tileWidth) | 0;
const axmax = Math.ceil((ax + a.x + a.w) * tileWidth) | 0;
const aymin = Math.floor((ay + a.y) * tileHeight) | 0;
const aymax = Math.ceil((ay + a.y + a.h) * tileHeight) | 0;
const axmin = Math.floor((ax + a.x) * tileWidth) | 0;
const axmax = Math.ceil((ax + a.x + a.w) * tileWidth) | 0;
const aymin = Math.floor((ay + a.y) * tileHeight) | 0;
const aymax = Math.ceil((ay + a.y + a.h) * tileHeight) | 0;
const bxmin = Math.floor((bx + b.x) * tileWidth) | 0;
const bxmax = Math.ceil((bx + b.x + b.w) * tileWidth) | 0;
const bymin = Math.floor((by + b.y) * tileHeight) | 0;
const bymax = Math.ceil((by + b.y + b.h) * tileHeight) | 0;
const bxmin = Math.floor((bx + b.x) * tileWidth) | 0;
const bxmax = Math.ceil((bx + b.x + b.w) * tileWidth) | 0;
const bymin = Math.floor((by + b.y) * tileHeight) | 0;
const bymax = Math.ceil((by + b.y + b.h) * tileHeight) | 0;
return axmin < bxmax && axmax > bxmin && aymin < bymax && aymax > bymin;
return axmin < bxmax && axmax > bxmin && aymin < bymax && aymax > bymin;
}
export function boundsIntersect(
ax: number, ay: number, a: Rect | undefined, bx: number, by: number, b: Rect | undefined
ax: number, ay: number, a: Rect | undefined, bx: number, by: number, b: Rect | undefined
): boolean {
return !!(a && b && intersect(
ax * tileWidth + a.x, ay * tileHeight + a.y, a.w, a.h,
bx * tileWidth + b.x, by * tileHeight + b.y, b.w, b.h));
return !!(a && b && intersect(
ax * tileWidth + a.x, ay * tileHeight + a.y, a.w, a.h,
bx * tileWidth + b.x, by * tileHeight + b.y, b.w, b.h));
}
export function intersect(
ax: number, ay: number, aw: number, ah: number, bx: number, by: number, bw: number, bh: number
ax: number, ay: number, aw: number, ah: number, bx: number, by: number, bw: number, bh: number
): boolean {
return ax <= (bx + bw) && (ax + aw) >= bx && ay <= (by + bh) && (ay + ah) >= by;
return ax <= (bx + bw) && (ax + aw) >= bx && ay <= (by + bh) && (ay + ah) >= by;
}
// requests
@@ -428,92 +428,92 @@ export function intersect(
export type RequestError = Error & { status?: number; text?: string; };
export function createError(status: number, data: string | { error: string; }): Error {
if (status > 500 && status < 600) {
return new Error(PROTECTION_ERROR);
// } else if (status === 400) {
// return new Error('Bad Request');
} else if (status === 403) {
return new Error(ACCESS_ERROR);
} else if (status === 404) {
return new Error(NOT_FOUND_ERROR);
} else if (typeof data === 'string') {
return new Error(data || OFFLINE_ERROR);
} else {
return new Error((data && data.error) || OFFLINE_ERROR);
}
if (status > 500 && status < 600) {
return new Error(PROTECTION_ERROR);
// } else if (status === 400) {
// return new Error('Bad Request');
} else if (status === 403) {
return new Error(ACCESS_ERROR);
} else if (status === 404) {
return new Error(NOT_FOUND_ERROR);
} else if (typeof data === 'string') {
return new Error(data || OFFLINE_ERROR);
} else {
return new Error((data && data.error) || OFFLINE_ERROR);
}
}
export function delay(timeout: number) {
return new Promise<void>(resolve => setTimeout(resolve, timeout));
return new Promise<void>(resolve => setTimeout(resolve, timeout));
}
export function observableToPromise<T>(observable: Observable<T>) {
return observable.toPromise()
.catch(({ status, error }: HttpErrorResponse) => {
const text = error && error.text;
return observable.toPromise()
.catch(({ status, error }: HttpErrorResponse) => {
const text = error && error.text;
try {
error = JSON.parse(error);
} catch { }
try {
error = JSON.parse(error);
} catch { }
const e: RequestError = createError(status || 0, error);
e.status = status;
e.text = text;
throw e;
});
const e: RequestError = createError(status || 0, error);
e.status = status;
e.text = text;
throw e;
});
}
// other
function setTransformDefault(element: HTMLElement | undefined, transform: string) {
if (element) {
element.style.transform = transform;
}
if (element) {
element.style.transform = transform;
}
}
function setTransformSafari(element: HTMLElement | undefined, transform: string) {
if (element) {
(element.style as any).webkitTransform = transform;
}
if (element) {
(element.style as any).webkitTransform = transform;
}
}
export const setTransform = (typeof document !== 'undefined' && 'transform' in document.body.style) ?
setTransformDefault : setTransformSafari;
setTransformDefault : setTransformSafari;
export class ObjectCache<T> {
private cache: T[] = [];
constructor(private limit: number, private ctor: () => T) {
}
get(): T {
return this.cache.pop() || this.ctor();
}
put(item: T) {
if (this.cache.length < this.limit) {
this.cache.push(item);
}
}
private cache: T[] = [];
constructor(private limit: number, private ctor: () => T) {
}
get(): T {
return this.cache.pop() || this.ctor();
}
put(item: T) {
if (this.cache.length < this.limit) {
this.cache.push(item);
}
}
}
export function bitmask(data: Uint8Array, key: number) {
if (key) {
for (let i = 0; i < data.length; i++) {
data[i] = data[i] ^ key;
}
}
if (key) {
for (let i = 0; i < data.length; i++) {
data[i] = data[i] ^ key;
}
}
return data;
return data;
}
export function isCommand(text: string) {
return /^\//.test(text);
return /^\//.test(text);
}
export function processCommand(text: string) {
text = text.substr(1);
const space = text.indexOf(' ');
const command = (space === -1 ? text : text.substr(0, space)).trim() as string | undefined;
const args = space === -1 ? '' : text.substr(space + 1).trim();
return { command, args };
text = text.substr(1);
const space = text.indexOf(' ');
const command = (space === -1 ? text : text.substr(0, space)).trim() as string | undefined;
const args = space === -1 ? '' : text.substr(space + 1).trim();
return { command, args };
}
// events
@@ -521,21 +521,21 @@ export function processCommand(text: string) {
export type AnyEvent = MouseEvent | PointerEvent | TouchEvent;
export function isTouch(e: AnyEvent): e is TouchEvent {
return /^touch/i.test(e.type);
return /^touch/i.test(e.type);
}
export function getButton(e: AnyEvent): number {
return ('button' in e) ? (e.button || 0) : 0;
return ('button' in e) ? (e.button || 0) : 0;
}
export function getX(e: AnyEvent): number {
return ('touches' in e && e.touches.length > 0) ? e.touches[0].pageX : (e as any).pageX;
return ('touches' in e && e.touches.length > 0) ? e.touches[0].pageX : (e as any).pageX;
}
export function getY(e: AnyEvent): number {
return ('touches' in e && e.touches.length > 0) ? e.touches[0].pageY : (e as any).pageY;
return ('touches' in e && e.touches.length > 0) ? e.touches[0].pageY : (e as any).pageY;
}
export function isKeyEventInvalid(e: KeyboardEvent) {
return e.target && /^(input|textarea|select)$/i.test((<any>e.target).tagName);
return e.target && /^(input|textarea|select)$/i.test((<any>e.target).tagName);
}
+477 -477
View File
File diff suppressed because it is too large Load Diff