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
File diff suppressed because it is too large Load Diff
+56 -56
View File
@@ -2,100 +2,100 @@ import { saveAs } from 'file-saver';
/* istanbul ignore next */
export let createCanvas = (width: number, height: number): HTMLCanvasElement => {
const canvas = document.createElement('canvas');
canvas.width = width | 0;
canvas.height = height | 0;
return canvas;
const canvas = document.createElement('canvas');
canvas.width = width | 0;
canvas.height = height | 0;
return canvas;
};
/* istanbul ignore next */
export let loadImage = (src: string): Promise<HTMLImageElement | ImageBitmap> => {
return new Promise<HTMLImageElement>((resolve, reject) => {
const img = new Image();
img.addEventListener('load', () => resolve(img));
img.addEventListener('error', () => reject(new Error(`Error loading image (${src})`)));
img.src = src;
});
return new Promise<HTMLImageElement>((resolve, reject) => {
const img = new Image();
img.addEventListener('load', () => resolve(img));
img.addEventListener('error', () => reject(new Error(`Error loading image (${src})`)));
img.src = src;
});
};
/* istanbul ignore next */
function canUseImageBitmap() {
return typeof fetch === 'function' &&
typeof createImageBitmap === 'function' &&
!/yabrowser/i.test(navigator.userAgent); // disabled due to yandex browser bug
return typeof fetch === 'function' &&
typeof createImageBitmap === 'function' &&
!/yabrowser/i.test(navigator.userAgent); // disabled due to yandex browser bug
}
/* istanbul ignore next */
if (canUseImageBitmap()) {
loadImage = src => fetch(src)
.then(response => response.blob())
.then(createImageBitmap);
loadImage = src => fetch(src)
.then(response => response.blob())
.then(createImageBitmap);
}
export function setup(methods: {
createCanvas(width: number, height: number): HTMLCanvasElement;
loadImage(src: string): Promise<HTMLImageElement>;
createCanvas(width: number, height: number): HTMLCanvasElement;
loadImage(src: string): Promise<HTMLImageElement>;
}) {
createCanvas = methods.createCanvas;
loadImage = methods.loadImage;
createCanvas = methods.createCanvas;
loadImage = methods.loadImage;
}
/* istanbul ignore next */
export const getPixelRatio = SERVER ? () => 1 : () => window.devicePixelRatio;
export function resizeCanvas(canvas: HTMLCanvasElement, width: number, height: number) {
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
}
export function resizeCanvasWithRatio(canvas: HTMLCanvasElement, width: number, height: number, updateStyle = true) {
const ratio = getPixelRatio();
const w = Math.round(width * ratio);
const h = Math.round(height * ratio);
let resized = false;
const ratio = getPixelRatio();
const w = Math.round(width * ratio);
const h = Math.round(height * ratio);
let resized = false;
if (canvas.width !== w || canvas.height !== h) {
canvas.width = w;
canvas.height = h;
resized = true;
}
if (canvas.width !== w || canvas.height !== h) {
canvas.width = w;
canvas.height = h;
resized = true;
}
if (updateStyle && (canvas.style.width !== width + 'px' || canvas.style.height !== height + 'px')) {
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
resized = true;
}
if (updateStyle && (canvas.style.width !== width + 'px' || canvas.style.height !== height + 'px')) {
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
resized = true;
}
return resized;
return resized;
}
/* istanbul ignore next */
export function canvasToSource(canvas: HTMLCanvasElement) {
return new Promise<string>((resolve, reject) => {
canvas.toBlob(blob => {
if (blob) {
resolve(URL.createObjectURL(blob));
} else {
reject(new Error('Failed to convert canvas'));
}
});
});
return new Promise<string>((resolve, reject) => {
canvas.toBlob(blob => {
if (blob) {
resolve(URL.createObjectURL(blob));
} else {
reject(new Error('Failed to convert canvas'));
}
});
});
}
/* istanbul ignore next */
export function saveCanvas(canvas: HTMLCanvasElement, name: string) {
canvas.toBlob(blob => blob && saveAs(blob, name));
canvas.toBlob(blob => blob && saveAs(blob, name));
}
/* istanbul ignore next */
export function disableImageSmoothing(context: CanvasRenderingContext2D) {
if ('imageSmoothingEnabled' in context) {
context.imageSmoothingEnabled = false;
} else {
(context as any).webkitImageSmoothingEnabled = false;
(context as any).mozImageSmoothingEnabled = false;
(context as any).msImageSmoothingEnabled = false;
}
if ('imageSmoothingEnabled' in context) {
context.imageSmoothingEnabled = false;
} else {
(context as any).webkitImageSmoothingEnabled = false;
(context as any).mozImageSmoothingEnabled = false;
(context as any).msImageSmoothingEnabled = false;
}
}
+241 -241
View File
@@ -1,8 +1,8 @@
import { NgZone } from '@angular/core';
import { Method, SocketClient, Bin, getMethods } from 'ag-sockets/dist/browser';
import {
MapInfo, WorldState, PartyMember, PartyFlags, Action, NotificationFlags, Pony, LeaveReason,
SayData, MapState, defaultMapState, Apply, InfoFlags, PonyData, FriendStatusData, WorldMap
MapInfo, WorldState, PartyMember, PartyFlags, Action, NotificationFlags, Pony, LeaveReason,
SayData, MapState, defaultMapState, Apply, InfoFlags, PonyData, FriendStatusData, WorldMap
} from '../common/interfaces';
import { hasFlag, findById } from '../common/utils';
import { isPony } from '../common/pony';
@@ -16,8 +16,8 @@ import { addNotification, removeNotification, markGameAsLoaded, resetGameFields,
import { Model } from '../components/services/model';
import { decodeUpdate } from '../common/encoders/updateDecoder';
import {
updatePonyInfoWithPoof, subscribeRegion, handleUpdates, handleUpdateEntity, handleRemoveEntity, handleSays,
handleEntityInfo, handleUpdatePonies, filterEntityName, handleUpdateFriends
updatePonyInfoWithPoof, subscribeRegion, handleUpdates, handleUpdateEntity, handleRemoveEntity, handleSays,
handleEntityInfo, handleUpdatePonies, filterEntityName, handleUpdateFriends
} from './handlers';
import { nameToHTML } from './emoji';
@@ -27,278 +27,278 @@ const BinNotificationId = Bin.U16;
const BinSayDatas = [BinEntityId, Bin.Str, Bin.U8];
function findPonyById(map: WorldMap, id: number) {
const entity = findEntityById(map, id);
return entity && isPony(entity) ? entity : undefined;
const entity = findEntityById(map, id);
return entity && isPony(entity) ? entity : undefined;
}
export class ClientActions implements SocketClient {
constructor(private gameService: GameService, private game: PonyTownGame, private model: Model, private zone: NgZone) {
}
private apply: Apply = func => this.zone.run(func);
connected() {
resetGameFields(this.game);
this.game.map = createWorldMap();
this.game.player = undefined;
this.game.joined();
this.apply(() => this.gameService.joined());
constructor(private gameService: GameService, private game: PonyTownGame, private model: Model, private zone: NgZone) {
}
private apply: Apply = func => this.zone.run(func);
connected() {
resetGameFields(this.game);
this.game.map = createWorldMap();
this.game.player = undefined;
this.game.joined();
this.apply(() => this.gameService.joined());
const supportsWasm = typeof WebAssembly !== 'undefined';
const info = 0 |
(isInIncognitoMode ? InfoFlags.Incognito : 0) |
(supportsWasm ? InfoFlags.SupportsWASM : 0) |
(supportsLetAndConst() ? InfoFlags.SupportsLetAndConst : 0);
const supportsWasm = typeof WebAssembly !== 'undefined';
const info = 0 |
(isInIncognitoMode ? InfoFlags.Incognito : 0) |
(supportsWasm ? InfoFlags.SupportsWASM : 0) |
(supportsLetAndConst() ? InfoFlags.SupportsLetAndConst : 0);
this.game.send(server => server.actionParam2(Action.Info, info));
}
disconnected() {
resetGameFields(this.game);
this.apply(() => this.gameService.disconnected());
}
invalidVersion() {
DEVELOPMENT && !TESTS && console.error('Invalid version');
}
@Method({ binary: [Bin.U32] })
queue(place: number) {
this.game.placeInQueue = place;
}
@Method({ binary: [Bin.Obj, Bin.Bool] })
worldState(state: WorldState, initial: boolean) {
this.game.placeInQueue = 0;
this.game.setWorldState(state, initial);
}
@Method({ binary: [Bin.Obj, Bin.Obj] })
mapState(info: MapInfo, state: MapState) {
this.game.map = createWorldMap(info, state);
this.game.player = undefined;
this.game.setupMap();
updateMapState(this.game.map, defaultMapState, this.game.map.state);
}
@Method({ binary: [Bin.Obj] })
mapUpdate(state: MapState) {
const prevState = this.game.map.state;
this.game.map.state = state;
updateMapState(this.game.map, prevState, this.game.map.state);
}
@Method({ binary: [] })
mapSwitching() {
this.game.loaded = false;
this.game.placeInQueue = 0;
this.game.send(server => server.actionParam2(Action.Info, info));
}
disconnected() {
resetGameFields(this.game);
this.apply(() => this.gameService.disconnected());
}
invalidVersion() {
DEVELOPMENT && !TESTS && console.error('Invalid version');
}
@Method({ binary: [Bin.U32] })
queue(place: number) {
this.game.placeInQueue = place;
}
@Method({ binary: [Bin.Obj, Bin.Bool] })
worldState(state: WorldState, initial: boolean) {
this.game.placeInQueue = 0;
this.game.setWorldState(state, initial);
}
@Method({ binary: [Bin.Obj, Bin.Obj] })
mapState(info: MapInfo, state: MapState) {
this.game.map = createWorldMap(info, state);
this.game.player = undefined;
this.game.setupMap();
updateMapState(this.game.map, defaultMapState, this.game.map.state);
}
@Method({ binary: [Bin.Obj] })
mapUpdate(state: MapState) {
const prevState = this.game.map.state;
this.game.map.state = state;
updateMapState(this.game.map, prevState, this.game.map.state);
}
@Method({ binary: [] })
mapSwitching() {
this.game.loaded = false;
this.game.placeInQueue = 0;
if (this.game.player) {
this.game.player.vx = 0;
this.game.player.vy = 0;
}
}
@Method({ binary: [Bin.I32, Bin.I32, Bin.U8Array] })
mapTest(width: number, height: number, buffer: Uint8Array) {
const data = new Uint32Array(width * height);
(new Uint8Array(data.buffer)).set(buffer);
this.game.minimap = { width, height, data };
}
@Method({ binary: [BinEntityId, Bin.Str, Bin.Str, Bin.Str, Bin.U16] })
myEntity(id: number, name: string, info: string, characterId: string, crc: number) {
this.game.playerId = id;
this.game.playerName = name;
this.game.playerInfo = info;
this.game.playerCRC = crc;
if (this.game.player) {
this.game.player.vx = 0;
this.game.player.vy = 0;
}
}
@Method({ binary: [Bin.I32, Bin.I32, Bin.U8Array] })
mapTest(width: number, height: number, buffer: Uint8Array) {
const data = new Uint32Array(width * height);
(new Uint8Array(data.buffer)).set(buffer);
this.game.minimap = { width, height, data };
}
@Method({ binary: [BinEntityId, Bin.Str, Bin.Str, Bin.Str, Bin.U16] })
myEntity(id: number, name: string, info: string, characterId: string, crc: number) {
this.game.playerId = id;
this.game.playerName = name;
this.game.playerInfo = info;
this.game.playerCRC = crc;
const pony = findById(this.model.ponies, characterId);
const pony = findById(this.model.ponies, characterId);
if (pony) {
this.model.selectPony(pony);
}
if (pony) {
this.model.selectPony(pony);
}
if (this.game.party) {
this.game.party.members.forEach(m => m.self = m.id === id);
this.game.onPartyUpdate.next();
}
if (this.game.party) {
this.game.party.members.forEach(m => m.self = m.id === id);
this.game.onPartyUpdate.next();
}
const entity = findEntityById(this.game.map, id) as Pony | undefined;
const entity = findEntityById(this.game.map, id) as Pony | undefined;
if (entity) {
entity.name = name;
updatePonyInfoWithPoof(this.game, entity, info, crc);
}
if (entity) {
entity.name = name;
updatePonyInfoWithPoof(this.game, entity, info, crc);
}
this.game.onActionsUpdate.next();
}
@Method({ binary: [[Bin.U8], [Bin.U8Array], Bin.U8Array, [Bin.U8Array], BinSayDatas] })
update(unsubscribes: number[], subscribes: Uint8Array[], updates: Uint8Array | null, regions: Uint8Array[], says: SayData[]) {
removeRegions(this.game.map, unsubscribes);
this.game.onActionsUpdate.next();
}
@Method({ binary: [[Bin.U8], [Bin.U8Array], Bin.U8Array, [Bin.U8Array], BinSayDatas] })
update(unsubscribes: number[], subscribes: Uint8Array[], updates: Uint8Array | null, regions: Uint8Array[], says: SayData[]) {
removeRegions(this.game.map, unsubscribes);
for (const subscribe of subscribes) {
subscribeRegion(this.game, subscribe);
}
for (const subscribe of subscribes) {
subscribeRegion(this.game, subscribe);
}
if (subscribes.length) {
markGameAsLoaded(this.game);
}
if (subscribes.length) {
markGameAsLoaded(this.game);
}
if (updates) {
handleUpdates(this.game, updates);
}
if (updates) {
handleUpdates(this.game, updates);
}
for (const region of regions) {
const { x, y, updates, removes, tiles } = decodeUpdate(region);
for (const region of regions) {
const { x, y, updates, removes, tiles } = decodeUpdate(region);
for (const update of updates) {
handleUpdateEntity(this.game, update);
}
for (const update of updates) {
handleUpdateEntity(this.game, update);
}
for (const id of removes) {
handleRemoveEntity(this.game, id);
}
for (const id of removes) {
handleRemoveEntity(this.game, id);
}
for (const tile of tiles) {
setTileAtRegion(this.game.map, x, y, tile.x, tile.y, tile.type);
}
}
for (const tile of tiles) {
setTileAtRegion(this.game.map, x, y, tile.x, tile.y, tile.type);
}
}
for (const [id, message, type] of says) {
handleSays(this.game, id, message, type);
}
}
@Method({ binary: [Bin.F32, Bin.F32, Bin.Bool] })
fixPosition(x: number, y: number, safe: boolean) {
if (DEVELOPMENT && !TESTS && !safe) {
console.error(`fix position (${x.toFixed(2)}, ${y.toFixed(2)})`);
}
for (const [id, message, type] of says) {
handleSays(this.game, id, message, type);
}
}
@Method({ binary: [Bin.F32, Bin.F32, Bin.Bool] })
fixPosition(x: number, y: number, safe: boolean) {
if (DEVELOPMENT && !TESTS && !safe) {
console.error(`fix position (${x.toFixed(2)}, ${y.toFixed(2)})`);
}
const player = this.game.player;
const player = this.game.player;
if (player) {
player.x = x;
player.y = y;
savePlayerPosition();
}
if (player) {
player.x = x;
player.y = y;
savePlayerPosition();
}
this.game.send(server => server.fixedPosition());
}
@Method({ binary: [BinEntityId, Bin.U8, Bin.Obj] })
actionParam(id: number, action: Action, param: any) {
switch (action) {
case Action.ACL:
if (id === this.game.playerId && param) {
setAclCookie(param);
}
break;
case Action.FriendsCRC:
this.game.nextFriendsCRC = 0;
break;
default:
DEVELOPMENT && !TESTS && console.error(`actionParam: Invalid action: ${action}`);
}
}
@Method({ binary: [Bin.U8] })
left(reason: LeaveReason) {
this.game.player = undefined;
this.game.map = createWorldMap();
this.apply(() => this.gameService.left('clientActions.left', reason));
}
@Method({ binary: [BinNotificationId, BinEntityId, Bin.Str, Bin.Str, Bin.Str, Bin.U8] })
addNotification(id: number, entityId: number, name: string, message: string, note: string, flags: NotificationFlags) {
const defaultCharacter = hasFlag(flags, NotificationFlags.Supporter) ? this.game.supporterPony : this.game.offlinePony;
const pony = (entityId && findPonyById(this.game.map, entityId)) || defaultCharacter;
this.game.send(server => server.fixedPosition());
}
@Method({ binary: [BinEntityId, Bin.U8, Bin.Obj] })
actionParam(id: number, action: Action, param: any) {
switch (action) {
case Action.ACL:
if (id === this.game.playerId && param) {
setAclCookie(param);
}
break;
case Action.FriendsCRC:
this.game.nextFriendsCRC = 0;
break;
default:
DEVELOPMENT && !TESTS && console.error(`actionParam: Invalid action: ${action}`);
}
}
@Method({ binary: [Bin.U8] })
left(reason: LeaveReason) {
this.game.player = undefined;
this.game.map = createWorldMap();
this.apply(() => this.gameService.left('clientActions.left', reason));
}
@Method({ binary: [BinNotificationId, BinEntityId, Bin.Str, Bin.Str, Bin.Str, Bin.U8] })
addNotification(id: number, entityId: number, name: string, message: string, note: string, flags: NotificationFlags) {
const defaultCharacter = hasFlag(flags, NotificationFlags.Supporter) ? this.game.supporterPony : this.game.offlinePony;
const pony = (entityId && findPonyById(this.game.map, entityId)) || defaultCharacter;
const filteredName = filterEntityName(this.game, name, hasFlag(flags, NotificationFlags.NameBad));
message = message.replace(/#NAME#/g, nameToHTML(filteredName || ''));
const filteredName = filterEntityName(this.game, name, hasFlag(flags, NotificationFlags.NameBad));
message = message.replace(/#NAME#/g, nameToHTML(filteredName || ''));
this.apply(() => addNotification(this.game, { id, message, note, pony, flags, open: false, fresh: true }));
}
@Method({ binary: [BinNotificationId] })
removeNotification(id: number) {
this.apply(() => removeNotification(this.game, id));
}
@Method({ binary: [BinEntityId, BinEntityId] })
updateSelection(currentId: number, newId: number) {
if (isSelected(this.game, currentId)) {
this.game.select(newId ? findPonyById(this.game.map, newId) : undefined);
}
}
@Method({ binary: [[BinEntityId, Bin.U8]] })
updateParty(party: [number, PartyFlags][] | undefined) {
const members = party && party.map<PartyMember>(([id, flags]) => ({
id,
pony: findPonyById(this.game.map, id) || this.game.fallbackPonies.get(id),
self: id === this.game.playerId,
leader: hasFlag(flags, PartyFlags.Leader),
pending: hasFlag(flags, PartyFlags.Pending),
offline: hasFlag(flags, PartyFlags.Offline),
}));
this.apply(() => addNotification(this.game, { id, message, note, pony, flags, open: false, fresh: true }));
}
@Method({ binary: [BinNotificationId] })
removeNotification(id: number) {
this.apply(() => removeNotification(this.game, id));
}
@Method({ binary: [BinEntityId, BinEntityId] })
updateSelection(currentId: number, newId: number) {
if (isSelected(this.game, currentId)) {
this.game.select(newId ? findPonyById(this.game.map, newId) : undefined);
}
}
@Method({ binary: [[BinEntityId, Bin.U8]] })
updateParty(party: [number, PartyFlags][] | undefined) {
const members = party && party.map<PartyMember>(([id, flags]) => ({
id,
pony: findPonyById(this.game.map, id) || this.game.fallbackPonies.get(id),
self: id === this.game.playerId,
leader: hasFlag(flags, PartyFlags.Leader),
pending: hasFlag(flags, PartyFlags.Pending),
offline: hasFlag(flags, PartyFlags.Offline),
}));
if (members) {
const missing = members.filter(p => !p.pony).map(p => p.id);
if (members) {
const missing = members.filter(p => !p.pony).map(p => p.id);
if (missing.length) {
this.game.send(server => server.getPonies(missing));
}
}
if (missing.length) {
this.game.send(server => server.getPonies(missing));
}
}
this.apply(() => {
this.game.party = updateParty(this.game.party, members);
this.game.onPartyUpdate.next();
});
}
@Method({ binary: [[BinEntityId, Bin.Obj, Bin.U8Array, Bin.U8Array, BinEntityPlayerState, Bin.Bool]] })
updatePonies(ponies: PonyData[]) {
handleUpdatePonies(this.game, ponies);
}
@Method({ binary: [Bin.Obj, Bin.Bool] })
updateFriends(friends: FriendStatusData[], removeMissing: boolean) {
handleUpdateFriends(this.game, friends, removeMissing);
}
@Method({ binary: [BinEntityId, Bin.Str, Bin.U32, Bin.Bool] })
entityInfo(id: number, name: string, crc: number, nameBad: boolean) {
handleEntityInfo(this.game, id, name, crc, nameBad);
}
@Method({ binary: [Bin.Obj] })
entityList(value: { name: string; x: number; y: number; }[]) {
if (DEVELOPMENT || BETA) {
const list = value.map(({ name, x, y }) => `${name}(${x.toFixed(2)}, ${y.toFixed(2)})`).join('\n');
console.log(`ENTITIES:\n${list}`);
}
}
@Method({ binary: [Bin.Obj] })
testPositions(data: { frame: number; x: number | undefined; y: number | undefined; moved: boolean; }[]) {
if (DEVELOPMENT) {
const round = (x: number) => Math.round(x * 100);
const same = (ax = 0, ay = 0, bx = 0, by = 0) => round(ax) === round(bx) && round(ay) === round(by);
const fmt = (x: number | undefined) => (x === undefined ? '-' : x.toFixed(2)).padStart(5);
this.apply(() => {
this.game.party = updateParty(this.game.party, members);
this.game.onPartyUpdate.next();
});
}
@Method({ binary: [[BinEntityId, Bin.Obj, Bin.U8Array, Bin.U8Array, BinEntityPlayerState, Bin.Bool]] })
updatePonies(ponies: PonyData[]) {
handleUpdatePonies(this.game, ponies);
}
@Method({ binary: [Bin.Obj, Bin.Bool] })
updateFriends(friends: FriendStatusData[], removeMissing: boolean) {
handleUpdateFriends(this.game, friends, removeMissing);
}
@Method({ binary: [BinEntityId, Bin.Str, Bin.U32, Bin.Bool] })
entityInfo(id: number, name: string, crc: number, nameBad: boolean) {
handleEntityInfo(this.game, id, name, crc, nameBad);
}
@Method({ binary: [Bin.Obj] })
entityList(value: { name: string; x: number; y: number; }[]) {
if (DEVELOPMENT || BETA) {
const list = value.map(({ name, x, y }) => `${name}(${x.toFixed(2)}, ${y.toFixed(2)})`).join('\n');
console.log(`ENTITIES:\n${list}`);
}
}
@Method({ binary: [Bin.Obj] })
testPositions(data: { frame: number; x: number | undefined; y: number | undefined; moved: boolean; }[]) {
if (DEVELOPMENT) {
const round = (x: number) => Math.round(x * 100);
const same = (ax = 0, ay = 0, bx = 0, by = 0) => round(ax) === round(bx) && round(ay) === round(by);
const fmt = (x: number | undefined) => (x === undefined ? '-' : x.toFixed(2)).padStart(5);
for (let i = 1; i < data.length; i++) {
if (data[i - 1].frame !== (data[i].frame - 1)) {
data.splice(i, 0, { frame: data[i - 1].frame + 1, x: undefined, y: undefined, moved: false });
}
}
for (let i = 1; i < data.length; i++) {
if (data[i - 1].frame !== (data[i].frame - 1)) {
data.splice(i, 0, { frame: data[i - 1].frame + 1, x: undefined, y: undefined, moved: false });
}
}
const clientIndex = this.game.positions.findIndex(p => p.moved);
const serverIndex = data.findIndex(p => p.moved);
const offset = serverIndex - clientIndex;
const clientIndex = this.game.positions.findIndex(p => p.moved);
const serverIndex = data.findIndex(p => p.moved);
const offset = serverIndex - clientIndex;
const dat = data.map((p, i) => {
const pt = this.game.positions[i - offset] || { x: undefined, y: undefined };
return { frame: p.frame, ax: p.x, ay: p.y, bx: pt.x, by: pt.y, serverMoved: p.moved, clientMoved: pt.moved };
});
const dat = data.map((p, i) => {
const pt = this.game.positions[i - offset] || { x: undefined, y: undefined };
return { frame: p.frame, ax: p.x, ay: p.y, bx: pt.x, by: pt.y, serverMoved: p.moved, clientMoved: pt.moved };
});
const log = dat.map(({ frame, ax, ay, bx, by, serverMoved, clientMoved }, i) =>
`${frame.toString().padStart(7)} | ` +
`${fmt(ax)}, ${fmt(ay)} ${serverMoved ? 'M' : ' '} | ` +
`${fmt(bx)}, ${fmt(by)} ${clientMoved ? 'M' : ' '} | ` +
`${same(ax, ay, bx, by) ? '= ' : ' '} ` +
`${i > 0 && dat[i - 1].frame !== (frame - 1) ? 'I ' : ' '}`)
.join('\n');
const log = dat.map(({ frame, ax, ay, bx, by, serverMoved, clientMoved }, i) =>
`${frame.toString().padStart(7)} | ` +
`${fmt(ax)}, ${fmt(ay)} ${serverMoved ? 'M' : ' '} | ` +
`${fmt(bx)}, ${fmt(by)} ${clientMoved ? 'M' : ' '} | ` +
`${same(ax, ay, bx, by) ? '= ' : ' '} ` +
`${i > 0 && dat[i - 1].frame !== (frame - 1) ? 'I ' : ' '}`)
.join('\n');
console.log(
` frame | server | client | \n` +
`-----------------------------------------------\n` +
`${log}`);
}
}
console.log(
` frame | server | client | \n` +
`-----------------------------------------------\n` +
`${log}`);
}
}
}
/* istanbul ignore next */
if (DEVELOPMENT) {
getMethods(ClientActions)
.filter(m => !m.options.binary)
.forEach(m => console.error(`Missing binary encoding for ClientActions.${m.name}()`));
getMethods(ClientActions)
.filter(m => !m.options.binary)
.forEach(m => console.error(`Missing binary encoding for ClientActions.${m.name}()`));
}
+23 -23
View File
@@ -4,31 +4,31 @@ import { ModelTypes } from '../common/adminInterfaces';
import { ModelSubscriber } from '../components/services/modelSubscriber';
export interface ClientUpdate {
type: ModelTypes;
id: string;
update: any;
type: ModelTypes;
id: string;
update: any;
}
export class ClientAdminActions {
constructor(private model: AdminModel) {
}
connected() {
this.model.initialize(true);
this.model.connectedToSocket();
}
disconnected() {
this.model.updateTitle();
}
@Method()
updates(updates: ClientUpdate[]) {
for (const { type, id, update } of updates) {
const model = this.model[type] as ModelSubscriber<any>;
constructor(private model: AdminModel) {
}
connected() {
this.model.initialize(true);
this.model.connectedToSocket();
}
disconnected() {
this.model.updateTitle();
}
@Method()
updates(updates: ClientUpdate[]) {
for (const { type, id, update } of updates) {
const model = this.model[type] as ModelSubscriber<any>;
if (model) {
model.update(id, update);
} else {
console.error(`Invalid model type "${type}"`);
}
}
}
if (model) {
model.update(id, update);
} else {
console.error(`Invalid model type "${type}"`);
}
}
}
}
+295 -295
View File
@@ -1,11 +1,11 @@
import { clamp } from 'lodash';
import {
SocialSite, SocialSiteInfo, Eye, Muzzle, Iris, ExpressionExtra, Expression, ServerInfo, ServerFeatureFlags,
AccountData, AccountDataFlags
SocialSite, SocialSiteInfo, Eye, Muzzle, Iris, ExpressionExtra, Expression, ServerInfo, ServerFeatureFlags,
AccountData, AccountDataFlags
} from '../common/interfaces';
import {
PLAYER_NAME_MAX_LENGTH, SAY_MAX_LENGTH, SAYS_TIME_MIN, SAYS_TIME_MAX, isChatlogRangeUnlimited, SUPPORTER_REWARDS,
PAST_SUPPORTER_REWARDS
PLAYER_NAME_MAX_LENGTH, SAY_MAX_LENGTH, SAYS_TIME_MIN, SAYS_TIME_MAX, isChatlogRangeUnlimited, SUPPORTER_REWARDS,
PAST_SUPPORTER_REWARDS
} from '../common/constants';
import { matcher, isSurrogate, fromSurrogate, isLowSurrogate } from '../common/stringUtils';
import { oauthProviders } from './data';
@@ -18,85 +18,85 @@ export const matchCyrillic = /[\u0400-\u04FF]/g;
export const containsCyrillic = matcher(matchCyrillic);
const otherValid = [
'♂♀⚲⚥⚧☿♁⚨⚩⚦⚢⚣⚤', // gender symbols
'™®♥♦♣♠❥♡♢♤♧ღஐ·´°•◦✿❀◆◇◈◉◊。¥€«»,:■□—', // other
'〈〉「」『』【】《》♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼№●○◌★☆✰✦✧▪▫・', // other 2
'\u1160\u3000\u3164', // spaces (replaced later)
'♂♀⚲⚥⚧☿♁⚨⚩⚦⚢⚣⚤', // gender symbols
'™®♥♦♣♠❥♡♢♤♧ღஐ·´°•◦✿❀◆◇◈◉◊。¥€«»,:■□—', // other
'〈〉「」『』【】《》♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼№●○◌★☆✰✦✧▪▫・', // other 2
'\u1160\u3000\u3164', // spaces (replaced later)
].join('').split('').reduce((set, c) => (set.add(c.charCodeAt(0)), set), new Set<number>());
export function isValid(c: number): boolean {
return (c >= 0x0020 && c <= 0x007e) // latin
|| (c >= 0x00a0 && c <= 0x00ff) // latin 1 supplement
|| (c >= 0x0100 && c <= 0x017F) // Latin Extended-A
|| (c >= 0x0180 && c <= 0x024F) // Latin Extended-B
|| (c >= 0x1e00 && c <= 0x1eff) // Latin Extended Additional
|| (c >= 0x0370 && c <= 0x03FF) // Greek and Coptic
|| (c >= 0x0400 && c <= 0x0481) || (c >= 0x048A && c <= 0x04FF) // cyrillic
|| (c >= 0x3041 && c <= 0x3096) // hiragana
|| (c >= 0x30A0 && c <= 0x30FF) // hatakana
|| (c >= 0x3400 && c <= 0x4DB5) || (c >= 0x4E00 && c <= 0x9FCB) || (c >= 0xF900 && c <= 0xFA6A) // kanji
|| (c >= 0x2F00 && c <= 0x2FDF) // Kangxi Radicals
|| (c >= 0x3000 && c <= 0x302D) // CJK Symbols and Punctuation
|| (c >= 0x1D00 && c <= 0x1D7F) // Phonetic Extensions
|| (c >= 0x0250 && c <= 0x02AF) // IPA Extensions
|| (c >= 0xA720 && c <= 0xA7FF) // Latin Extended-D
|| (c >= 0x0E00 && c <= 0x0E7F) // Thai
|| (c >= 0xff01 && c <= 0xff5e) // Romaji (replaced later)
|| (c >= 0x2200 && c <= 0x22FF) // Mathematical Operators
|| (c >= 0x25A0 && c <= 0x25FF) // Geometric Shapes
|| (c >= 0x2600 && c <= 0x26ff) || (c >= 0x2700 && c <= 0x27bf) || (c >= 0x2b00 && c <= 0x2bef) // emoji
|| (c >= 0x1f600 && c <= 0x1f64f) || (c >= 0x1f680 && c <= 0x1f6f6) || (c >= 0x1f300 && c <= 0x1f5ff) // emoji
|| (c >= 0x231a && c <= 0x231b) || (c >= 0x23e9 && c <= 0x23fa) // emoji
|| (c >= 0x1f900 && c <= 0x1f9ff) // Supplemental Symbols and Pictographs
|| otherValid.has(c) // other symbols
;
return (c >= 0x0020 && c <= 0x007e) // latin
|| (c >= 0x00a0 && c <= 0x00ff) // latin 1 supplement
|| (c >= 0x0100 && c <= 0x017F) // Latin Extended-A
|| (c >= 0x0180 && c <= 0x024F) // Latin Extended-B
|| (c >= 0x1e00 && c <= 0x1eff) // Latin Extended Additional
|| (c >= 0x0370 && c <= 0x03FF) // Greek and Coptic
|| (c >= 0x0400 && c <= 0x0481) || (c >= 0x048A && c <= 0x04FF) // cyrillic
|| (c >= 0x3041 && c <= 0x3096) // hiragana
|| (c >= 0x30A0 && c <= 0x30FF) // hatakana
|| (c >= 0x3400 && c <= 0x4DB5) || (c >= 0x4E00 && c <= 0x9FCB) || (c >= 0xF900 && c <= 0xFA6A) // kanji
|| (c >= 0x2F00 && c <= 0x2FDF) // Kangxi Radicals
|| (c >= 0x3000 && c <= 0x302D) // CJK Symbols and Punctuation
|| (c >= 0x1D00 && c <= 0x1D7F) // Phonetic Extensions
|| (c >= 0x0250 && c <= 0x02AF) // IPA Extensions
|| (c >= 0xA720 && c <= 0xA7FF) // Latin Extended-D
|| (c >= 0x0E00 && c <= 0x0E7F) // Thai
|| (c >= 0xff01 && c <= 0xff5e) // Romaji (replaced later)
|| (c >= 0x2200 && c <= 0x22FF) // Mathematical Operators
|| (c >= 0x25A0 && c <= 0x25FF) // Geometric Shapes
|| (c >= 0x2600 && c <= 0x26ff) || (c >= 0x2700 && c <= 0x27bf) || (c >= 0x2b00 && c <= 0x2bef) // emoji
|| (c >= 0x1f600 && c <= 0x1f64f) || (c >= 0x1f680 && c <= 0x1f6f6) || (c >= 0x1f300 && c <= 0x1f5ff) // emoji
|| (c >= 0x231a && c <= 0x231b) || (c >= 0x23e9 && c <= 0x23fa) // emoji
|| (c >= 0x1f900 && c <= 0x1f9ff) // Supplemental Symbols and Pictographs
|| otherValid.has(c) // other symbols
;
}
export function isValid2(c: number): boolean {
return (c >= 0x2b0 && c <= 0x2ff) // Spacing Modifier Letters
|| (c >= 0x531 && c <= 0x556) || (c >= 0x559 && c <= 0x55f) || (c >= 0x561 && c <= 0x587)
|| (c >= 0x589 && c <= 0x58a) || (c >= 0x58c && c <= 0x58f) // Armenian
|| (c >= 0x591 && c <= 0x5c7) || (c >= 0x5d0 && c <= 0x5ea) || (c >= 0x5f0 && c <= 0x5f4) // Hebrew
|| (c >= 0x600 && c <= 0x6ff) // Arabic
|| (c >= 0x7c0 && c <= 0x7fa) // NKo
|| (c >= 0x900 && c <= 0x97f) // Devanagari
|| (c === 0xb90) || (c === 0xb9c) // Tamil
|| (c >= 0xc85 && c <= 0xc8c) || (c >= 0xc8e && c <= 0xc90) || (c >= 0xc91 && c <= 0xca8)
|| (c >= 0xcaa && c <= 0xcb3) || (c >= 0xcb5 && c <= 0xcb9) || (c >= 0xce6 && c <= 0xcef) // Kannada
|| (c >= 0x10a0 && c <= 0x10c5) || (c === 0x10c7) || (c === 0x10cd) || (c >= 0x10d0 && c <= 0x10ff) // Georgian
|| (c >= 0x1100 && c <= 0x11ff) || (c >= 0x3130 && c <= 0x318f) || (c >= 0xac00 && c <= 0xd7af) // Hangul
|| (c >= 0x1400 && c <= 0x167f) // Unified Canadian Aboriginal Syllabics
|| (c >= 0x2010 && c <= 0x2027) || (c >= 0x2030 && c <= 0x205e) // General Punctuation
|| (c >= 0x20a0 && c <= 0x20bf) // Currency Symbols
|| (c >= 0x2100 && c <= 0x214f) // Letterlike Symbols
|| (c >= 0x2150 && c <= 0x218b) // Number Forms
|| (c >= 0x2300 && c <= 0x239a) || (c >= 0x23b4 && c <= 0x23fa) // Miscellaneous Technical
|| (c >= 0x2500 && c <= 0x257f) // Box Drawing
|| (c >= 0x2800 && c <= 0x28ff) // Braille Patterns
|| (c >= 0x3000 && c <= 0x303f) // CJK Symbols and Punctuation
|| (c >= 0x3105 && c <= 0x312d) // Bopomofo
|| (c >= 0xfe30 && c <= 0xfe4f) // CJK Compatibility Forms
|| (c >= 0xff01 && c <= 0xffef) // Halfwidth and Fullwidth Forms
// || (c >= 0x1f170 && c < 0x1f189) // Enclosed Alphanumeric Supplement [a-z]
|| (c >= 0x1f000 && c <= 0x1f02b) // Mahjong Tiles
|| (c >= 0x1f0a0 && c <= 0x1f0ae) || (c >= 0x1f0b1 && c <= 0x1f0bf) || (c >= 0x1f0c1 && c <= 0x1f0cf)
|| (c >= 0x1f0d1 && c <= 0x1f0df) || (c >= 0x1f0e0 && c <= 0x1f0f5) // Playing Cards
|| (c >= 0x1f1e6 && c <= 0x1f1ff) // Enclosed Alphanumeric Supplement (regional indicators)
;
return (c >= 0x2b0 && c <= 0x2ff) // Spacing Modifier Letters
|| (c >= 0x531 && c <= 0x556) || (c >= 0x559 && c <= 0x55f) || (c >= 0x561 && c <= 0x587)
|| (c >= 0x589 && c <= 0x58a) || (c >= 0x58c && c <= 0x58f) // Armenian
|| (c >= 0x591 && c <= 0x5c7) || (c >= 0x5d0 && c <= 0x5ea) || (c >= 0x5f0 && c <= 0x5f4) // Hebrew
|| (c >= 0x600 && c <= 0x6ff) // Arabic
|| (c >= 0x7c0 && c <= 0x7fa) // NKo
|| (c >= 0x900 && c <= 0x97f) // Devanagari
|| (c === 0xb90) || (c === 0xb9c) // Tamil
|| (c >= 0xc85 && c <= 0xc8c) || (c >= 0xc8e && c <= 0xc90) || (c >= 0xc91 && c <= 0xca8)
|| (c >= 0xcaa && c <= 0xcb3) || (c >= 0xcb5 && c <= 0xcb9) || (c >= 0xce6 && c <= 0xcef) // Kannada
|| (c >= 0x10a0 && c <= 0x10c5) || (c === 0x10c7) || (c === 0x10cd) || (c >= 0x10d0 && c <= 0x10ff) // Georgian
|| (c >= 0x1100 && c <= 0x11ff) || (c >= 0x3130 && c <= 0x318f) || (c >= 0xac00 && c <= 0xd7af) // Hangul
|| (c >= 0x1400 && c <= 0x167f) // Unified Canadian Aboriginal Syllabics
|| (c >= 0x2010 && c <= 0x2027) || (c >= 0x2030 && c <= 0x205e) // General Punctuation
|| (c >= 0x20a0 && c <= 0x20bf) // Currency Symbols
|| (c >= 0x2100 && c <= 0x214f) // Letterlike Symbols
|| (c >= 0x2150 && c <= 0x218b) // Number Forms
|| (c >= 0x2300 && c <= 0x239a) || (c >= 0x23b4 && c <= 0x23fa) // Miscellaneous Technical
|| (c >= 0x2500 && c <= 0x257f) // Box Drawing
|| (c >= 0x2800 && c <= 0x28ff) // Braille Patterns
|| (c >= 0x3000 && c <= 0x303f) // CJK Symbols and Punctuation
|| (c >= 0x3105 && c <= 0x312d) // Bopomofo
|| (c >= 0xfe30 && c <= 0xfe4f) // CJK Compatibility Forms
|| (c >= 0xff01 && c <= 0xffef) // Halfwidth and Fullwidth Forms
// || (c >= 0x1f170 && c < 0x1f189) // Enclosed Alphanumeric Supplement [a-z]
|| (c >= 0x1f000 && c <= 0x1f02b) // Mahjong Tiles
|| (c >= 0x1f0a0 && c <= 0x1f0ae) || (c >= 0x1f0b1 && c <= 0x1f0bf) || (c >= 0x1f0c1 && c <= 0x1f0cf)
|| (c >= 0x1f0d1 && c <= 0x1f0df) || (c >= 0x1f0e0 && c <= 0x1f0f5) // Playing Cards
|| (c >= 0x1f1e6 && c <= 0x1f1ff) // Enclosed Alphanumeric Supplement (regional indicators)
;
}
function isInvalid(c: number): boolean {
return c === 0x1f595 // middle finger emoji
|| c === 0x00ad // soft hyphen
;
return c === 0x1f595 // middle finger emoji
|| c === 0x00ad // soft hyphen
;
}
function isValidForName(c: number): boolean {
return isValid(c) && !isInvalid(c);
return isValid(c) && !isInvalid(c);
}
function isValidForMessage(c: number): boolean {
return (isValid(c) || isValid2(c)) && !isInvalid(c);
return (isValid(c) || isValid2(c)) && !isInvalid(c);
}
export const matchRomaji = /[\uff01-\uff5e]/g;
@@ -104,333 +104,333 @@ export const matchRomaji = /[\uff01-\uff5e]/g;
const matchOtherWhitespace = /[\u1160\u2800\u3000\u3164\uffa0]+/g;
export function replaceRomaji(match: string): string {
return String.fromCharCode(match.charCodeAt(0) - 0xfee0);
return String.fromCharCode(match.charCodeAt(0) - 0xfee0);
}
export function cleanName(name: string | undefined): string {
return filterString(name, isValidForName)
.replace(matchOtherWhitespace, ' ') // whitespace characters
.replace(/\s+/g, ' ')
.replace(matchRomaji, replaceRomaji)
.trim();
return filterString(name, isValidForName)
.replace(matchOtherWhitespace, ' ') // whitespace characters
.replace(/\s+/g, ' ')
.replace(matchRomaji, replaceRomaji)
.trim();
}
export function cleanMessage(text: string | undefined): string {
return filterString(text, isValidForMessage)
.replace(matchOtherWhitespace, ' ') // whitespace characters
.replace(/[\r\n]/g, '')
.replace(matchRomaji, replaceRomaji)
.trim()
.substr(0, SAY_MAX_LENGTH);
return filterString(text, isValidForMessage)
.replace(matchOtherWhitespace, ' ') // whitespace characters
.replace(/[\r\n]/g, '')
.replace(matchRomaji, replaceRomaji)
.trim()
.substr(0, SAY_MAX_LENGTH);
}
export function filterString(value: string | undefined, filter: (code: number) => boolean): string {
value = value || '';
value = value || '';
for (let i = 0; i < value.length; i++) {
let code = value.charCodeAt(i);
let size = 1;
let invalidSurrogate = false;
for (let i = 0; i < value.length; i++) {
let code = value.charCodeAt(i);
let size = 1;
let invalidSurrogate = false;
if (isSurrogate(code) && (i + 1) < value.length) {
const extra = value.charCodeAt(i + 1);
if (isSurrogate(code) && (i + 1) < value.length) {
const extra = value.charCodeAt(i + 1);
if (isLowSurrogate(extra)) {
code = fromSurrogate(code, extra);
i++;
size++;
} else {
invalidSurrogate = true;
}
}
if (isLowSurrogate(extra)) {
code = fromSurrogate(code, extra);
i++;
size++;
} else {
invalidSurrogate = true;
}
}
if (invalidSurrogate || !filter(code)) {
i -= size;
value = value.substr(0, i + 1) + value.substr(i + size + 1);
}
}
if (invalidSurrogate || !filter(code)) {
i -= size;
value = value.substr(0, i + 1) + value.substr(i + size + 1);
}
}
return value;
return value;
}
export function validatePonyName(name: string | undefined): boolean {
return !!name && !!name.length && name.length <= PLAYER_NAME_MAX_LENGTH && !/^[.,_-]+$/.test(name);
return !!name && !!name.length && name.length <= PLAYER_NAME_MAX_LENGTH && !/^[.,_-]+$/.test(name);
}
export function toSocialSiteInfo({ id, name, url, provider }: SocialSite): SocialSiteInfo {
const oauth = oauthProviders.find(p => p.id === provider);
const oauth = oauthProviders.find(p => p.id === provider);
return {
id,
name,
url,
icon: oauth && oauth.id,
color: oauth && oauth.color,
};
return {
id,
name,
url,
icon: oauth && oauth.id,
color: oauth && oauth.color,
};
}
function isMultipleMatch(message: string, last: string): boolean {
const minMessageLength = 4;
const minMessageLength = 4;
if (message.length >= minMessageLength && last.length >= minMessageLength) {
let current = last;
if (message.length >= minMessageLength && last.length >= minMessageLength) {
let current = last;
while (current.length < message.length) {
current += last;
}
while (current.length < message.length) {
current += last;
}
return message === current.substr(0, SAY_MAX_LENGTH);
} else {
return false;
}
return message === current.substr(0, SAY_MAX_LENGTH);
} else {
return false;
}
}
function checkTrailing(message: string, last: string) {
return message.indexOf(last) === 0 && (message.length - last.length) < 3;
return message.indexOf(last) === 0 && (message.length - last.length) < 3;
}
function isTrailingMatch(message: string, last: string) {
const minMessageLength = 5;
const minMessageLength = 5;
if (message.length > last.length && last.length > minMessageLength) {
return checkTrailing(message, last);
} else if (message.length < last.length && message.length > minMessageLength) {
return checkTrailing(last, message);
} else {
return false;
}
if (message.length > last.length && last.length > minMessageLength) {
return checkTrailing(message, last);
} else if (message.length < last.length && message.length > minMessageLength) {
return checkTrailing(last, message);
} else {
return false;
}
}
export function isSpamMessage(message: string, lastMessages: string[]): boolean {
if (!/^\//.test(message) && lastMessages.length) {
return lastMessages.some(last => message === last || isMultipleMatch(message, last) || isTrailingMatch(message, last));
} else {
return false;
}
if (!/^\//.test(message) && lastMessages.length) {
return lastMessages.some(last => message === last || isMultipleMatch(message, last) || isTrailingMatch(message, last));
} else {
return false;
}
}
export function getSaysTime(message: string): number {
return SAYS_TIME_MIN + clamp(message.length / SAY_MAX_LENGTH, 0, 1) * (SAYS_TIME_MAX - SAYS_TIME_MIN);
return SAYS_TIME_MIN + clamp(message.length / SAY_MAX_LENGTH, 0, 1) * (SAYS_TIME_MAX - SAYS_TIME_MIN);
}
export function createExpression(
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 };
}
export const isAndroidBrowser = (() => {
const ua = typeof navigator === 'undefined' ? '' : navigator.userAgent;
const ua = typeof navigator === 'undefined' ? '' : navigator.userAgent;
// Android browser
// Mozilla/5.0 (Linux; U; Android 4.4.2; es-ar; LG-D375AR Build/KOT49I)
// AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.1599.103 Mobile Safari/537.36
if (/Android /.test(ua) && /AppleWebKit/.test(ua) && (!/chrome/i.test(ua) || /Chrome\/30\./.test(ua))) {
return true;
}
// Android browser
// Mozilla/5.0 (Linux; U; Android 4.4.2; es-ar; LG-D375AR Build/KOT49I)
// AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.1599.103 Mobile Safari/537.36
if (/Android /.test(ua) && /AppleWebKit/.test(ua) && (!/chrome/i.test(ua) || /Chrome\/30\./.test(ua))) {
return true;
}
return false;
return false;
})();
/* istanbul ignore next */
export const isBrowserOutdated = (() => {
const ua = typeof navigator === 'undefined' ? '' : navigator.userAgent;
const ua = typeof navigator === 'undefined' ? '' : navigator.userAgent;
// Safari <= 8
// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1)
// AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25
const safari = /Version\/(\d+)\.[0-9.]+ Safari/.exec(ua);
// Safari <= 8
// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1)
// AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25
const safari = /Version\/(\d+)\.[0-9.]+ Safari/.exec(ua);
if (safari && parseInt(safari[1], 10) <= 8) {
return true;
}
if (safari && parseInt(safari[1], 10) <= 8) {
return true;
}
// Android browser
// Mozilla/5.0 (Linux; U; Android 4.4.2; es-ar; LG-D375AR Build/KOT49I)
// AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.1599.103 Mobile Safari/537.36
if (isAndroidBrowser) {
return true;
}
// Android browser
// Mozilla/5.0 (Linux; U; Android 4.4.2; es-ar; LG-D375AR Build/KOT49I)
// AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.1599.103 Mobile Safari/537.36
if (isAndroidBrowser) {
return true;
}
if (!supportsLetAndConst()) {
return true;
}
if (!supportsLetAndConst()) {
return true;
}
return false;
return false;
})();
export function getLocale() {
return (navigator.languages ? navigator.languages[0] : navigator.language) || 'en-US';
return (navigator.languages ? navigator.languages[0] : navigator.language) || 'en-US';
}
/* istanbul ignore next */
export function isLanguage(lang: string) {
const languages = navigator.languages || [navigator.language];
return languages.some(l => l === lang);
const languages = navigator.languages || [navigator.language];
return languages.some(l => l === lang);
}
/* istanbul ignore next */
export function sortServersForRussian(a: ServerInfo, b: ServerInfo) {
if (a.flag === 'ru' && a.flag !== b.flag) {
return -1;
}
if (a.flag === 'ru' && a.flag !== b.flag) {
return -1;
}
if (b.flag === 'ru' && a.flag !== b.flag) {
return 1;
}
if (b.flag === 'ru' && a.flag !== b.flag) {
return 1;
}
return a.id.localeCompare(b.id);
return a.id.localeCompare(b.id);
}
export function readFileAsText(file: File) {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e: any) => resolve(e.target && e.target.result || '');
reader.onerror = () => reject(new Error('Failed to read file'));
reader.readAsText(file);
});
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e: any) => resolve(e.target && e.target.result || '');
reader.onerror = () => reject(new Error('Failed to read file'));
reader.readAsText(file);
});
}
/* istanbul ignore next */
export function isFileSaverSupported() {
try {
return !!new Blob;
} catch {
return false;
}
try {
return !!new Blob;
} catch {
return false;
}
}
export let isInIncognitoMode = false;
export function setIsIncognitoMode(value: boolean) {
isInIncognitoMode = value;
isInIncognitoMode = value;
}
/* istanbul ignore next */
function checkIncognitoMode(wnd: any) {
if (!wnd || !wnd.chrome)
return;
if (!wnd || !wnd.chrome)
return;
const fs = wnd.RequestFileSystem || wnd.webkitRequestFileSystem;
const fs = wnd.RequestFileSystem || wnd.webkitRequestFileSystem;
if (!fs)
return;
if (!fs)
return;
fs(wnd.TEMPORARY, 100, () => { }, () => isInIncognitoMode = true);
fs(wnd.TEMPORARY, 100, () => { }, () => isInIncognitoMode = true);
}
let focused = true;
/* istanbul ignore next */
export function isFocused() {
return focused;
return focused;
}
/* istanbul ignore next */
if (typeof window !== 'undefined') {
checkIncognitoMode(window);
window.addEventListener('focus', () => focused = true);
window.addEventListener('blur', () => focused = false);
checkIncognitoMode(window);
window.addEventListener('focus', () => focused = true);
window.addEventListener('blur', () => focused = false);
}
/* istanbul ignore next */
export function isStandalone() {
return !!window.matchMedia('(display-mode: standalone)').matches ||
(window.navigator as any).standalone === true; // safari
return !!window.matchMedia('(display-mode: standalone)').matches ||
(window.navigator as any).standalone === true; // safari
}
/* istanbul ignore next */
export function supportsLetAndConst() {
try {
return (new Function('let x = true; return x;'))();
} catch {
return false;
}
try {
return (new Function('let x = true; return x;'))();
} catch {
return false;
}
}
/* istanbul ignore next */
export function registerServiceWorker(url: string, onUpdate: () => void) {
try {
if ('serviceWorker' in navigator && typeof navigator.serviceWorker.register === 'function') {
let hadWorker = false;
try {
if ('serviceWorker' in navigator && typeof navigator.serviceWorker.register === 'function') {
let hadWorker = false;
navigator.serviceWorker.register(url)
.then(worker => {
hadWorker = !!worker.active;
navigator.serviceWorker.register(url)
.then(worker => {
hadWorker = !!worker.active;
worker.addEventListener('updatefound', () => {
if (hadWorker) {
onUpdate();
}
});
});
worker.addEventListener('updatefound', () => {
if (hadWorker) {
onUpdate();
}
});
});
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (hadWorker) {
location.reload();
}
});
}
} catch (e) {
console.error(e);
}
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (hadWorker) {
location.reload();
}
});
}
} catch (e) {
console.error(e);
}
}
/* istanbul ignore next */
export function unregisterServiceWorker() {
if ('serviceWorker' in navigator && typeof navigator.serviceWorker.getRegistrations === 'function') {
return navigator.serviceWorker.getRegistrations()
.then(registrations => {
for (const registration of registrations) {
registration.unregister();
}
});
} else {
return Promise.resolve();
}
if ('serviceWorker' in navigator && typeof navigator.serviceWorker.getRegistrations === 'function') {
return navigator.serviceWorker.getRegistrations()
.then(registrations => {
for (const registration of registrations) {
registration.unregister();
}
});
} else {
return Promise.resolve();
}
}
/* istanbul ignore next */
export function attachDebugMethod(name: string, method: any) {
if (typeof window !== 'undefined') {
(window as any)[name] = method;
}
if (typeof window !== 'undefined') {
(window as any)[name] = method;
}
}
/* istanbul ignore next */
export function updateRangeIndicator(range: number | undefined, { player, scale, camera }: PonyTownGame) {
const e = document.getElementById('range-indicator')!;
const e = document.getElementById('range-indicator')!;
if (player && !isChatlogRangeUnlimited(range)) {
const x = (toScreenX(player.x) - camera.x) * scale;
const y = (toScreenY(player.y) - camera.actualY) * scale;
const w = toScreenX(range!) * scale * 2;
const h = toScreenY(range!) * scale * 2;
e.style.width = `${w}px`;
e.style.height = `${h}px`;
e.style.left = `${-w / 2}px`;
e.style.top = `${-h / 2}px`;
e.style.transform = `translate3d(${x}px, ${y}px, 0)`;
e.style.display = 'block';
} else {
e.style.display = 'none';
}
if (player && !isChatlogRangeUnlimited(range)) {
const x = (toScreenX(player.x) - camera.x) * scale;
const y = (toScreenY(player.y) - camera.actualY) * scale;
const w = toScreenX(range!) * scale * 2;
const h = toScreenY(range!) * scale * 2;
e.style.width = `${w}px`;
e.style.height = `${h}px`;
e.style.left = `${-w / 2}px`;
e.style.top = `${-h / 2}px`;
e.style.transform = `translate3d(${x}px, ${y}px, 0)`;
e.style.display = 'block';
} else {
e.style.display = 'none';
}
}
/* istanbul ignore next */
export function checkIframeKey(iframeId: string, expectedKey: string) {
try {
const iframe = document.getElementById(iframeId) as HTMLIFrameElement;
const doc = iframe && iframe.contentWindow && iframe.contentWindow.document;
const key = doc && doc.body && doc.body.getAttribute('data-key');
return key === expectedKey;
} catch (e) {
if (DEVELOPMENT) {
console.error(e);
}
try {
const iframe = document.getElementById(iframeId) as HTMLIFrameElement;
const doc = iframe && iframe.contentWindow && iframe.contentWindow.document;
const key = doc && doc.body && doc.body.getAttribute('data-key');
return key === expectedKey;
} catch (e) {
if (DEVELOPMENT) {
console.error(e);
}
return false;
}
return false;
}
}
let flags: ServerFeatureFlags = {};
@@ -438,17 +438,17 @@ let flags: ServerFeatureFlags = {};
export const featureFlagsChanged = new Subject<ServerFeatureFlags>();
export function initFeatureFlags(newFlags: ServerFeatureFlags) {
flags = newFlags;
featureFlagsChanged.next(newFlags);
flags = newFlags;
featureFlagsChanged.next(newFlags);
}
export function hasFeatureFlag(flag: keyof ServerFeatureFlags) {
return !!flags[flag];
return !!flags[flag];
}
export function hardReload() {
unregisterServiceWorker()
.then(() => location.reload(true));
unregisterServiceWorker()
.then(() => location.reload(true));
}
const LOGGING = false;
@@ -456,47 +456,47 @@ const LOGGING = false;
let logger = (_: string) => { };
export function initLogger(newLogger: (message: string) => void) {
if (LOGGING) {
logger = newLogger;
}
if (LOGGING) {
logger = newLogger;
}
}
export function log(message: string) {
if (LOGGING) {
logger(message);
}
if (LOGGING) {
logger(message);
}
}
export function isSupporterOrPastSupporter(account: AccountData | undefined) {
return !!account && (!!account.supporter || hasFlag(account.flags, AccountDataFlags.PastSupporter));
return !!account && (!!account.supporter || hasFlag(account.flags, AccountDataFlags.PastSupporter));
}
export function supporterTitle(account: AccountData | undefined) {
if (account && account.supporter) {
return `Supporter Tier ${account.supporter}`;
} else if (account && hasFlag(account.flags, AccountDataFlags.PastSupporter)) {
return 'Past supporter';
} else {
return '';
}
if (account && account.supporter) {
return `Supporter Tier ${account.supporter}`;
} else if (account && hasFlag(account.flags, AccountDataFlags.PastSupporter)) {
return 'Past supporter';
} else {
return '';
}
}
export function supporterClass(account: AccountData | undefined) {
if (account && account.supporter) {
return `supporter-${account.supporter}`;
} else if (account && hasFlag(account.flags, AccountDataFlags.PastSupporter)) {
return 'supporter-past';
} else {
return 'd-none';
}
if (account && account.supporter) {
return `supporter-${account.supporter}`;
} else if (account && hasFlag(account.flags, AccountDataFlags.PastSupporter)) {
return 'supporter-past';
} else {
return 'd-none';
}
}
export function supporterRewards(account: AccountData | undefined) {
if (account && account.supporter) {
return SUPPORTER_REWARDS[account.supporter];
} else if (account && hasFlag(account.flags, AccountDataFlags.PastSupporter)) {
return PAST_SUPPORTER_REWARDS;
} else {
return SUPPORTER_REWARDS[0];
}
if (account && account.supporter) {
return SUPPORTER_REWARDS[account.supporter];
} else if (account && hasFlag(account.flags, AccountDataFlags.PastSupporter)) {
return PAST_SUPPORTER_REWARDS;
} else {
return SUPPORTER_REWARDS[0];
}
}
+91 -91
View File
@@ -1,105 +1,105 @@
export interface Credit {
name: string;
title: string;
avatarIndex: number;
links: string[];
name: string;
title: string;
avatarIndex: number;
links: string[];
}
export interface Contributor {
name: string;
links?: string[];
name: string;
links?: string[];
}
export interface Contributors {
group: string;
contributors: Contributor[];
group: string;
contributors: Contributor[];
}
export const CREDITS: Credit[] = [
// example:
// {
// name: 'Your name',
// title: 'Your role on the team',
// avatarIndex: 0, // place of the avatar in /assets/images/avatars.jpg
// links: ['https://twitter.com/your_twitter_handle'],
// },
{
name: 'Bytewave',
title: 'Programmer / Moderator',
avatarIndex: 0,
links: ['https://twitter.com/BytewaveMLP', 'https://github.com/BytewaveMLP']
},
{
name: 'Cloud Hop',
title: 'Programmer / Moderator',
avatarIndex: 1,
links: ['https://twitter.com/blackhole0173', 'https://github.com/blackhole12']
},
{
name: 'CyberPon3',
title: 'Programmer / Moderator',
avatarIndex: 2,
links: ['https://twitter.com/CyberPon3']
},
{
name: 'NotMyWing',
title: 'Programmer / Moderator',
avatarIndex: 3,
links: ['https://twitter.com/NotMyWing', 'https://github.com/Neeve01']
},
{
name: 'Stubenhocker',
title: 'Programmer',
avatarIndex: 4,
links: ['https://github.com/Stubenhocker1399']
},
{
name: 'Luney',
title: 'Programmer',
avatarIndex: 5,
links: ['https://twitter.com/luneythesnep', 'https://github.com/LunarMist']
}
// example:
// {
// name: 'Your name',
// title: 'Your role on the team',
// avatarIndex: 0, // place of the avatar in /assets/images/avatars.jpg
// links: ['https://twitter.com/your_twitter_handle'],
// },
{
name: 'Bytewave',
title: 'Programmer / Moderator',
avatarIndex: 0,
links: ['https://twitter.com/BytewaveMLP', 'https://github.com/BytewaveMLP']
},
{
name: 'Cloud Hop',
title: 'Programmer / Moderator',
avatarIndex: 1,
links: ['https://twitter.com/blackhole0173', 'https://github.com/blackhole12']
},
{
name: 'CyberPon3',
title: 'Programmer / Moderator',
avatarIndex: 2,
links: ['https://twitter.com/CyberPon3']
},
{
name: 'NotMyWing',
title: 'Programmer / Moderator',
avatarIndex: 3,
links: ['https://twitter.com/NotMyWing', 'https://github.com/Neeve01']
},
{
name: 'Stubenhocker',
title: 'Programmer',
avatarIndex: 4,
links: ['https://github.com/Stubenhocker1399']
},
{
name: 'Luney',
title: 'Programmer',
avatarIndex: 5,
links: ['https://twitter.com/luneythesnep', 'https://github.com/LunarMist']
}
];
export const CONTRIBUTORS: Contributors[] = [
{
group: 'Artists & Animators',
contributors: [
{ name: 'Shino', links: ['https://www.deviantart.com/shinodage'] },
{ name: 'ChiraChan', links: ['https://www.deviantart.com/chiramii-chan', 'https://chirachan-art.tumblr.com/'] },
{ name: 'Goodly', links: ['https://www.deviantart.com/goodlyay'] },
{ name: 'TioRafaJP', links: ['https://www.deviantart.com/tiorafajp', 'https://www.youtube.com/user/RafaelJP2'] },
{ name: 'ShareMyShipment', links: ['https://www.deviantart.com/sharemyshipment'] },
{ name: 'Velenor', links: ['https://www.deviantart.com/velenor'] },
{ name: 'OtakuAP', links: ['https://www.deviantart.com/otakuap'] },
],
},
{
group: 'Artists',
contributors: [
{ name: 'Disastral' },
{ name: 'Meno', links: ['https://www.deviantart.com/menojar'] },
{ name: 'Paulpeoples', links: ['https://www.deviantart.com/paulpeopless'] },
{ name: 'Velvet-Frost', links: ['https://www.deviantart.com/velvet-frost'] },
{ name: 'Jet7Wave', links: ['https://www.deviantart.com/jetwave'] },
{ name: 'Lalieri', links: ['https://lalieri.tumblr.com/'] },
{ name: 'Ruef-bae', links: ['https://www.deviantart.com/ruef-bae'] },
{ name: 'Alchemist3rd' },
{ name: 'Firecracker' },
{ name: 'ZippySqrl', links: ['https://www.deviantart.com/zippysqrl'] },
{ name: 'Karnel333' },
{ name: 'Wellfugzee' },
{ name: 'ScribblesHeart', links: ['https://www.deviantart.com/scribblesdesu'] },
{ name: 'dsp2003', links: ['https://dsp2003.tumblr.com/', 'http://www.deviantart.com/dsp2003'] },
{ name: 'MysticBlare', links: ['https://twitter.com/MysticBlare'] },
{ name: 'Towmacow Waffles', links: ['https://www.deviantart.com/towmacowwaffles'] },
{ name: 'OrchidPony', links: ['https://www.deviantart.com/orchidpony'] },
{ name: 'Cherry Cerise', links: ['https://www.deviantart.com/cherryceriseart'] },
{ name: 'Radio' },
{ name: 'Ultimate Fluff' },
{ name: 'SC', links: ['https://0somecunt0.tumblr.com/tagged/sfw'] },
{ name: 'SailorDolpin', links: ['https://vk.com/id324582699'] },
{ name: 'Deeraw', links: ['https://www.deviantart.com/deerdraw', 'https://twitter.com/TheOnlyDeeraw'] },
],
},
{
group: 'Artists & Animators',
contributors: [
{ name: 'Shino', links: ['https://www.deviantart.com/shinodage'] },
{ name: 'ChiraChan', links: ['https://www.deviantart.com/chiramii-chan', 'https://chirachan-art.tumblr.com/'] },
{ name: 'Goodly', links: ['https://www.deviantart.com/goodlyay'] },
{ name: 'TioRafaJP', links: ['https://www.deviantart.com/tiorafajp', 'https://www.youtube.com/user/RafaelJP2'] },
{ name: 'ShareMyShipment', links: ['https://www.deviantart.com/sharemyshipment'] },
{ name: 'Velenor', links: ['https://www.deviantart.com/velenor'] },
{ name: 'OtakuAP', links: ['https://www.deviantart.com/otakuap'] },
],
},
{
group: 'Artists',
contributors: [
{ name: 'Disastral' },
{ name: 'Meno', links: ['https://www.deviantart.com/menojar'] },
{ name: 'Paulpeoples', links: ['https://www.deviantart.com/paulpeopless'] },
{ name: 'Velvet-Frost', links: ['https://www.deviantart.com/velvet-frost'] },
{ name: 'Jet7Wave', links: ['https://www.deviantart.com/jetwave'] },
{ name: 'Lalieri', links: ['https://lalieri.tumblr.com/'] },
{ name: 'Ruef-bae', links: ['https://www.deviantart.com/ruef-bae'] },
{ name: 'Alchemist3rd' },
{ name: 'Firecracker' },
{ name: 'ZippySqrl', links: ['https://www.deviantart.com/zippysqrl'] },
{ name: 'Karnel333' },
{ name: 'Wellfugzee' },
{ name: 'ScribblesHeart', links: ['https://www.deviantart.com/scribblesdesu'] },
{ name: 'dsp2003', links: ['https://dsp2003.tumblr.com/', 'http://www.deviantart.com/dsp2003'] },
{ name: 'MysticBlare', links: ['https://twitter.com/MysticBlare'] },
{ name: 'Towmacow Waffles', links: ['https://www.deviantart.com/towmacowwaffles'] },
{ name: 'OrchidPony', links: ['https://www.deviantart.com/orchidpony'] },
{ name: 'Cherry Cerise', links: ['https://www.deviantart.com/cherryceriseart'] },
{ name: 'Radio' },
{ name: 'Ultimate Fluff' },
{ name: 'SC', links: ['https://0somecunt0.tumblr.com/tagged/sfw'] },
{ name: 'SailorDolpin', links: ['https://vk.com/id324582699'] },
{ name: 'Deeraw', links: ['https://www.deviantart.com/deerdraw', 'https://twitter.com/TheOnlyDeeraw'] },
],
},
];
+25 -25
View File
@@ -4,17 +4,17 @@ import { OAuthProvider } from '../common/interfaces';
/* istanbul ignore next */
function attr(name: string): string | undefined {
return typeof document !== 'undefined' ? (document.body.getAttribute(name) || undefined) : undefined;
return typeof document !== 'undefined' ? (document.body.getAttribute(name) || undefined) : undefined;
}
/* istanbul ignore next */
function data(id: string): string | undefined {
const element = typeof document !== 'undefined' ? document.getElementById(id) : undefined;
return element ? element.innerHTML : undefined;
const element = typeof document !== 'undefined' ? document.getElementById(id) : undefined;
return element ? element.innerHTML : undefined;
}
function json<T>(id: string, def: string): T {
return JSON.parse(data(id) || def);
return JSON.parse(data(id) || def);
}
export let isMobile = false;
@@ -31,7 +31,7 @@ export const copyrightName = attr('data-copyright');
/* istanbul ignore next */
export const oauthProviders = json<OAuthProvider[]>('oauth-providers', '[]')
.map(a => <OAuthProvider>{ ...a, url: `/auth/${a.id}` });
.map(a => <OAuthProvider>{ ...a, url: `/auth/${a.id}` });
/* istanbul ignore next */
export const signUpProviders = oauthProviders.filter(i => !i.connectOnly);
/* istanbul ignore next */
@@ -39,35 +39,35 @@ export const signInProviders = oauthProviders.filter(i => i.connectOnly);
/* istanbul ignore next */
export function socketOptions(): ClientOptions {
const options = data('socket-options');
const options = data('socket-options');
if (options) {
const buffer = toByteArray(options);
const reader = createBinaryReader(buffer);
return readObject(reader);
} else {
throw new Error('Missing socket options');
}
if (options) {
const buffer = toByteArray(options);
const reader = createBinaryReader(buffer);
return readObject(reader);
} else {
throw new Error('Missing socket options');
}
}
/* istanbul ignore next */
function setMobile() {
isMobile = true;
window.removeEventListener('touchstart', setMobile);
document.body.classList.add('is-mobile');
isMobile = true;
window.removeEventListener('touchstart', setMobile);
document.body.classList.add('is-mobile');
}
/* istanbul ignore next */
if (typeof window !== 'undefined') {
if (!/windows/i.test(navigator.userAgent)) {
window.addEventListener('touchstart', setMobile);
}
if (!/windows/i.test(navigator.userAgent)) {
window.addEventListener('touchstart', setMobile);
}
if (/Trident/.test(navigator.userAgent)) {
document.body.classList.add('is-msie');
}
if (/Trident/.test(navigator.userAgent)) {
document.body.classList.add('is-msie');
}
if (/YaBrowser/.test(navigator.userAgent)) {
document.body.classList.add('is-yandex');
}
if (/YaBrowser/.test(navigator.userAgent)) {
document.body.classList.add('is-yandex');
}
}
+208 -208
View File
@@ -1,5 +1,5 @@
import {
Entity, DrawOptions, Camera, PaletteSpriteBatch, SpriteBatch, TileSets, Engine, Pony, WorldMap, EntityState
Entity, DrawOptions, Camera, PaletteSpriteBatch, SpriteBatch, TileSets, Engine, Pony, WorldMap, EntityState
} from '../common/interfaces';
import { isBoundsVisible } from '../common/camera';
import { drawBounds, drawPixelText, drawBoundsOutline, drawOutlineRect, drawWorldBounds } from '../graphics/graphicsUtils';
@@ -16,282 +16,282 @@ import { timeStart, timeEnd } from './timing';
const SELECTED_ENTITY_BOUNDS = withAlphaFloat(ORANGE, 0.5);
function drawEntities(batch: PaletteSpriteBatch, entities: Entity[], camera: Camera, options: DrawOptions) {
const drawHidden = options.drawHidden;
let entitiesDrawn = 0;
const drawHidden = options.drawHidden;
let entitiesDrawn = 0;
for (const entity of entities) {
if ((!isHidden(entity) || drawHidden) && isBoundsVisible(camera, entity.bounds, entity.x, entity.y)) {
if (entity.type === PONY_TYPE) {
drawPonyEntity(batch, entity as Pony, options);
entitiesDrawn++;
} else if (entity.draw !== undefined) {
entity.draw(batch, options);
entitiesDrawn++;
}
} else {
if (entity.type === PONY_TYPE) {
const pony = entity as Pony;
for (const entity of entities) {
if ((!isHidden(entity) || drawHidden) && isBoundsVisible(camera, entity.bounds, entity.x, entity.y)) {
if (entity.type === PONY_TYPE) {
drawPonyEntity(batch, entity as Pony, options);
entitiesDrawn++;
} else if (entity.draw !== undefined) {
entity.draw(batch, options);
entitiesDrawn++;
}
} else {
if (entity.type === PONY_TYPE) {
const pony = entity as Pony;
if (pony.batch !== undefined) {
batch.releaseBatch(pony.batch);
pony.batch = undefined;
}
}
}
}
if (pony.batch !== undefined) {
batch.releaseBatch(pony.batch);
pony.batch = undefined;
}
}
}
}
return entitiesDrawn;
return entitiesDrawn;
}
export function drawEntityLights(batch: SpriteBatch, entities: Entity[], camera: Camera, options: DrawOptions) {
const drawHidden = options.drawHidden;
const drawHidden = options.drawHidden;
for (const entity of entities) {
if (DEVELOPMENT && (entity.type !== PONY_TYPE && !entity.drawLight)) {
console.error('Cannot draw entity light', entity);
}
for (const entity of entities) {
if (DEVELOPMENT && (entity.type !== PONY_TYPE && !entity.drawLight)) {
console.error('Cannot draw entity light', entity);
}
if ((!isHidden(entity) || drawHidden) && isBoundsVisible(camera, entity.lightBounds, entity.x, entity.y)) {
if (entity.type === PONY_TYPE) {
drawPonyEntityLight(batch, entity as Pony, options);
} else {
entity.drawLight!(batch, options);
}
}
}
if ((!isHidden(entity) || drawHidden) && isBoundsVisible(camera, entity.lightBounds, entity.x, entity.y)) {
if (entity.type === PONY_TYPE) {
drawPonyEntityLight(batch, entity as Pony, options);
} else {
entity.drawLight!(batch, options);
}
}
}
}
export function drawEntityLightSprites(batch: SpriteBatch, entities: Entity[], camera: Camera, options: DrawOptions) {
const drawHidden = options.drawHidden;
const drawHidden = options.drawHidden;
for (const entity of entities) {
if (DEVELOPMENT && (entity.type !== PONY_TYPE && !entity.drawLightSprite)) {
console.error('Cannot draw entity light sprite', entity);
}
for (const entity of entities) {
if (DEVELOPMENT && (entity.type !== PONY_TYPE && !entity.drawLightSprite)) {
console.error('Cannot draw entity light sprite', entity);
}
if ((!isHidden(entity) || drawHidden) && isBoundsVisible(camera, entity.lightSpriteBounds, entity.x, entity.y)) {
if (entity.type === PONY_TYPE) {
drawPonyEntityLightSprite(batch, entity as Pony, options);
} else {
entity.drawLightSprite!(batch, options);
}
}
}
if ((!isHidden(entity) || drawHidden) && isBoundsVisible(camera, entity.lightSpriteBounds, entity.x, entity.y)) {
if (entity.type === PONY_TYPE) {
drawPonyEntityLightSprite(batch, entity as Pony, options);
} else {
entity.drawLightSprite!(batch, options);
}
}
}
}
export function hasDrawLight(entity: Entity) {
if (entity.type === PONY_TYPE) {
const pony = entity as Pony;
return (pony.ponyState.holding !== undefined && pony.ponyState.holding.drawLight !== undefined) ||
((pony.state & EntityState.Magic) !== 0);
} else {
return entity.drawLight !== undefined;
}
if (entity.type === PONY_TYPE) {
const pony = entity as Pony;
return (pony.ponyState.holding !== undefined && pony.ponyState.holding.drawLight !== undefined) ||
((pony.state & EntityState.Magic) !== 0);
} else {
return entity.drawLight !== undefined;
}
}
export function hasLightSprite(entity: Entity) {
if (entity.type === PONY_TYPE) {
const pony = entity as Pony;
return (pony.ponyState.holding !== undefined && pony.ponyState.holding.drawLightSprite !== undefined);
} else {
return entity.drawLightSprite !== undefined;
}
if (entity.type === PONY_TYPE) {
const pony = entity as Pony;
return (pony.ponyState.holding !== undefined && pony.ponyState.holding.drawLightSprite !== undefined);
} else {
return entity.drawLightSprite !== undefined;
}
}
export function drawMap(
batch: PaletteSpriteBatch, map: WorldMap, camera: Camera, player: Pony, options: DrawOptions,
tileSets: TileSets, selectedEntities: Entity[],
batch: PaletteSpriteBatch, map: WorldMap, camera: Camera, player: Pony, options: DrawOptions,
tileSets: TileSets, selectedEntities: Entity[],
) {
TIMING && timeStart('forEachRegion');
if (BETA && options.engine === Engine.Whiteness) {
batch.drawRect(WHITE, 0, 0, toScreenX(map.width), toScreenY(map.height));
} else if (BETA && options.engine === Engine.LayeredTiles) {
forEachRegion(map, region => drawTilesNew(batch, region, camera, map, tileSets, options));
} else {
forEachRegion(map, region => drawTiles(batch, region, camera, map, tileSets, options));
}
TIMING && timeEnd();
TIMING && timeStart('forEachRegion');
if (BETA && options.engine === Engine.Whiteness) {
batch.drawRect(WHITE, 0, 0, toScreenX(map.width), toScreenY(map.height));
} else if (BETA && options.engine === Engine.LayeredTiles) {
forEachRegion(map, region => drawTilesNew(batch, region, camera, map, tileSets, options));
} else {
forEachRegion(map, region => drawTiles(batch, region, camera, map, tileSets, options));
}
TIMING && timeEnd();
TIMING && timeStart('sortEntities');
sortEntities(map.entitiesDrawable);
TIMING && timeEnd();
TIMING && timeStart('sortEntities');
sortEntities(map.entitiesDrawable);
TIMING && timeEnd();
TIMING && timeStart('drawEntities');
const entitiesDrawn = drawEntities(batch, map.entitiesDrawable, camera, options);
TIMING && timeEnd();
TIMING && timeStart('drawEntities');
const entitiesDrawn = drawEntities(batch, map.entitiesDrawable, camera, options);
TIMING && timeEnd();
if (BETA || TOOLS) {
forEachRegion(map, region => drawTilesDebugInfo(batch, region, camera, options));
}
if (BETA || TOOLS) {
forEachRegion(map, region => drawTilesDebugInfo(batch, region, camera, options));
}
if (BETA && options.debug.showHelpers) {
drawDebugHelpers(batch, map.entities, options);
}
if (BETA && options.debug.showHelpers) {
drawDebugHelpers(batch, map.entities, options);
}
if (BETA) {
for (const entity of selectedEntities) {
const bounds = getAnyBounds(entity);
drawBoundsOutline(batch, entity, bounds, SELECTED_ENTITY_BOUNDS, 2);
}
}
if (BETA) {
for (const entity of selectedEntities) {
const bounds = getAnyBounds(entity);
drawBoundsOutline(batch, entity, bounds, SELECTED_ENTITY_BOUNDS, 2);
}
}
if (BETA && options.debug.showHelpers) {
drawOutlineRect(batch, PURPLE, getInteractBounds(player));
drawOutlineRect(batch, 0xff000066, getSitOnBounds(player));
}
if (BETA && options.debug.showHelpers) {
drawOutlineRect(batch, PURPLE, getInteractBounds(player));
drawOutlineRect(batch, 0xff000066, getSitOnBounds(player));
}
if (BETA && options.showColliderMap) {
drawDebugCollider(batch, map, camera);
batch.drawRect(PURPLE, toScreenX(player.x) - 1, toScreenY(player.y), 3, 1);
batch.drawRect(PURPLE, toScreenX(player.x), toScreenY(player.y) - 1, 1, 3);
}
if (BETA && options.showColliderMap) {
drawDebugCollider(batch, map, camera);
batch.drawRect(PURPLE, toScreenX(player.x) - 1, toScreenY(player.y), 3, 1);
batch.drawRect(PURPLE, toScreenX(player.x), toScreenY(player.y) - 1, 1, 3);
}
if (BETA && options.showHeightmap) {
drawDebugInWater(batch, map, camera);
}
if (BETA && options.showHeightmap) {
drawDebugInWater(batch, map, camera);
}
return entitiesDrawn;
return entitiesDrawn;
}
// debug
function drawDebugHelpers(batch: PaletteSpriteBatch, entities: Entity[], options: DrawOptions) {
const textColor = 0x000000b2;
const show = options.debug;
const textColor = 0x000000b2;
const show = options.debug;
for (const e of entities) {
batch.globalAlpha = 0.3;
show.bounds && drawBounds(batch, e, e.bounds, ORANGE);
show.cover && drawBounds(batch, e, e.coverBounds, BLUE);
show.interact && drawBounds(batch, e, e.interactBounds, PURPLE);
show.trigger && drawWorldBounds(batch, e, e.triggerBounds, CYAN);
for (const e of entities) {
batch.globalAlpha = 0.3;
show.bounds && drawBounds(batch, e, e.bounds, ORANGE);
show.cover && drawBounds(batch, e, e.coverBounds, BLUE);
show.interact && drawBounds(batch, e, e.interactBounds, PURPLE);
show.trigger && drawWorldBounds(batch, e, e.triggerBounds, CYAN);
if (show.collider) {
batch.globalAlpha = 0.5;
if (show.collider) {
batch.globalAlpha = 0.5;
const x = Math.floor(e.x * tileWidth);
const y = Math.floor(e.y * tileHeight);
const x = Math.floor(e.x * tileWidth);
const y = Math.floor(e.y * tileHeight);
if (e.colliders !== undefined) {
for (const collider of e.colliders) {
const x1 = x + collider.x;
const x2 = x + collider.x + collider.w;
const y1 = y + collider.y;
const y2 = y + collider.y + collider.h;
batch.drawRect(collider.tall ? RED : HOTPINK, x1, y1, x2 - x1, y2 - y1);
}
}
}
if (e.colliders !== undefined) {
for (const collider of e.colliders) {
const x1 = x + collider.x;
const x2 = x + collider.x + collider.w;
const y1 = y + collider.y;
const y2 = y + collider.y + collider.h;
batch.drawRect(collider.tall ? RED : HOTPINK, x1, y1, x2 - x1, y2 - y1);
}
}
}
batch.globalAlpha = 1;
batch.drawRect(BLACK, toScreenX(e.x), toScreenY(e.y), 1, 1); // anchor
batch.globalAlpha = 1;
batch.drawRect(BLACK, toScreenX(e.x), toScreenY(e.y), 1, 1); // anchor
if (show.id) {
drawPixelText(batch, toScreenX(e.x) + 2, toScreenY(e.y) + 2, textColor, e.id.toFixed());
}
}
if (show.id) {
drawPixelText(batch, toScreenX(e.x) + 2, toScreenY(e.y) + 2, textColor, e.id.toFixed());
}
}
}
function drawDebugInWater(batch: PaletteSpriteBatch, map: WorldMap, camera: Camera) {
const color = withAlphaFloat(ORANGE, 0.4);
const color = withAlphaFloat(ORANGE, 0.4);
forEachRegion(map, region => {
const sx = toScreenX(region.x * REGION_SIZE);
const sy = toScreenY(region.y * REGION_SIZE);
const w = REGION_WIDTH;
const h = REGION_HEIGHT;
forEachRegion(map, region => {
const sx = toScreenX(region.x * REGION_SIZE);
const sy = toScreenY(region.y * REGION_SIZE);
const w = REGION_WIDTH;
const h = REGION_HEIGHT;
const cameraLeft = camera.x;
const cameraRight = camera.x + camera.w;
const cameraTop = camera.actualY;
const cameraBottom = camera.actualY + camera.h;
const cameraLeft = camera.x;
const cameraRight = camera.x + camera.w;
const cameraTop = camera.actualY;
const cameraBottom = camera.actualY + camera.h;
if (sx > cameraRight || sy > cameraBottom || (sx + w) < cameraLeft || (sy + h) < cameraTop)
return;
if (sx > cameraRight || sy > cameraBottom || (sx + w) < cameraLeft || (sy + h) < cameraTop)
return;
for (let y = 0; y < h; y++) {
if ((sy + y + 1) < cameraTop || (sy + y) > cameraBottom)
continue;
for (let y = 0; y < h; y++) {
if ((sy + y + 1) < cameraTop || (sy + y) > cameraBottom)
continue;
for (let x = 0; x < w; x++) {
if ((sx + x + 1) < cameraLeft || (sx + x) > cameraRight)
continue;
for (let x = 0; x < w; x++) {
if ((sx + x + 1) < cameraLeft || (sx + x) > cameraRight)
continue;
const tx = x;
const tx = x;
while (isInWaterAt(map, toWorldX(sx + x + 0.5), toWorldY(sy + y + 0.5)) && x < w) {
x++;
}
while (isInWaterAt(map, toWorldX(sx + x + 0.5), toWorldY(sy + y + 0.5)) && x < w) {
x++;
}
if (x > tx) {
batch.drawRect(color, sx + tx, sy + y, x - tx, 1);
}
}
}
});
if (x > tx) {
batch.drawRect(color, sx + tx, sy + y, x - tx, 1);
}
}
}
});
}
function drawDebugCollider(batch: PaletteSpriteBatch, map: WorldMap, camera: Camera) {
const color = withAlphaFloat(PURPLE, 0.4);
const color = withAlphaFloat(PURPLE, 0.4);
forEachRegion(map, ({ x, y, collider }) => {
const sx = toScreenX(x * REGION_SIZE);
const sy = toScreenY(y * REGION_SIZE);
const w = REGION_WIDTH;
const h = REGION_HEIGHT;
forEachRegion(map, ({ x, y, collider }) => {
const sx = toScreenX(x * REGION_SIZE);
const sy = toScreenY(y * REGION_SIZE);
const w = REGION_WIDTH;
const h = REGION_HEIGHT;
const cameraLeft = camera.x;
const cameraRight = camera.x + camera.w;
const cameraTop = camera.actualY;
const cameraBottom = camera.actualY + camera.h;
const cameraLeft = camera.x;
const cameraRight = camera.x + camera.w;
const cameraTop = camera.actualY;
const cameraBottom = camera.actualY + camera.h;
if (sx > cameraRight || sy > cameraBottom || (sx + w) < cameraLeft || (sy + h) < cameraTop)
return;
if (sx > cameraRight || sy > cameraBottom || (sx + w) < cameraLeft || (sy + h) < cameraTop)
return;
for (let y = 0; y < h; y++) {
if ((sy + y + 1) < cameraTop || (sy + y) > cameraBottom)
continue;
for (let y = 0; y < h; y++) {
if ((sy + y + 1) < cameraTop || (sy + y) > cameraBottom)
continue;
for (let x = 0; x < w; x++) {
if ((sx + x + 1) < cameraLeft || (sx + x) > cameraRight)
continue;
for (let x = 0; x < w; x++) {
if ((sx + x + 1) < cameraLeft || (sx + x) > cameraRight)
continue;
const tx = x;
const tx = x;
while (collider[x + y * w] !== 0 && x < w) {
x++;
}
while (collider[x + y * w] !== 0 && x < w) {
x++;
}
if (x > tx) {
batch.drawRect(color, sx + tx, sy + y, x - tx, 1);
}
}
}
});
if (x > tx) {
batch.drawRect(color, sx + tx, sy + y, x - tx, 1);
}
}
}
});
}
export function drawDebugRegions(batch: SpriteBatch, map: WorldMap, player: Pony, { w, h }: Camera) {
const rw = 10;
const rh = 8;
const width = rw * map.regionsX;
const height = rh * map.regionsY;
const x = w - width - 10;
const y = h - height - 30;
const rw = 10;
const rh = 8;
const width = rw * map.regionsX;
const height = rh * map.regionsY;
const x = w - width - 10;
const y = h - height - 30;
for (let i = 0; i < map.regionsY; i++) {
for (let j = 0; j < map.regionsX; j++) {
if (getRegion(map, j, i)) {
const inside = j === Math.floor(player.x / REGION_SIZE) && i === Math.floor(player.y / REGION_SIZE);
batch.drawRect(inside ? ORANGE : RED, x + rw * j, y + rh * i, rw, rh);
}
}
}
for (let i = 0; i < map.regionsY; i++) {
for (let j = 0; j < map.regionsX; j++) {
if (getRegion(map, j, i)) {
const inside = j === Math.floor(player.x / REGION_SIZE) && i === Math.floor(player.y / REGION_SIZE);
batch.drawRect(inside ? ORANGE : RED, x + rw * j, y + rh * i, rw, rh);
}
}
}
for (let i = 0; i <= map.regionsY; i++) {
batch.drawRect(GRAY, x, y + rh * i, width + 1, 1);
}
for (let i = 0; i <= map.regionsY; i++) {
batch.drawRect(GRAY, x, y + rh * i, width + 1, 1);
}
for (let i = 0; i <= map.regionsX; i++) {
batch.drawRect(GRAY, x + rw * i, y, 1, height);
}
for (let i = 0; i <= map.regionsX; i++) {
batch.drawRect(GRAY, x + rw * i, y, 1, height);
}
}
+132 -132
View File
@@ -7,108 +7,108 @@ import { normalSpriteSheet } from '../generated/sprites';
import { includes } from '../common/utils';
export interface Emoji {
names: string[];
symbol: string;
names: string[];
symbol: string;
}
export const emojis: Emoji[] = [
// faces
['🙂', 'face', 'tiny', 'tinyface', 'slight_smile'],
['😵', 'derp', 'dizzy_face'],
['😠', 'angry'],
['😐', 'neutral', 'neutral_face'],
['😑', 'expressionless'],
['😆', 'laughing'],
['😍', 'heart_eyes'],
['😟', 'worried'],
['🤔', 'thinking'],
['🙃', 'upside_down'],
['😈', 'evil', 'smiling_imp'],
['👿', 'imp', 'angry_evil'],
['👃', 'nose', 'c'],
// faces
['🙂', 'face', 'tiny', 'tinyface', 'slight_smile'],
['😵', 'derp', 'dizzy_face'],
['😠', 'angry'],
['😐', 'neutral', 'neutral_face'],
['😑', 'expressionless'],
['😆', 'laughing'],
['😍', 'heart_eyes'],
['😟', 'worried'],
['🤔', 'thinking'],
['🙃', 'upside_down'],
['😈', 'evil', 'smiling_imp'],
['👿', 'imp', 'angry_evil'],
['👃', 'nose', 'c'],
// cat faces
['🐱', 'cat'],
['😺', 'smiley_cat'],
['😸', 'smile_cat'],
['😹', 'joy_cat'],
['😻', 'heart_eyes_cat'],
['😼', 'smirk_cat'],
['😽', 'kissing_cat'],
['🙀', 'scream_cat'],
['😿', 'cryingcat', 'crying_cat_face'],
['😾', 'pouting_cat'],
// cat faces
['🐱', 'cat'],
['😺', 'smiley_cat'],
['😸', 'smile_cat'],
['😹', 'joy_cat'],
['😻', 'heart_eyes_cat'],
['😼', 'smirk_cat'],
['😽', 'kissing_cat'],
['🙀', 'scream_cat'],
['😿', 'cryingcat', 'crying_cat_face'],
['😾', 'pouting_cat'],
// hearts
['❤', 'heart'],
['💙', 'blue_heart', 'meno'],
['💚', 'green_heart', 'chira'],
['💛', 'yellow_heart'],
['💜', 'purple_heart'],
['🖤', 'black_heart', 'shino'],
['💔', 'broken_heart'],
['💖', 'sparkling_heart'],
['💗', 'heartpulse'],
['💕', 'two_hearts'],
// hearts
['❤', 'heart'],
['💙', 'blue_heart', 'meno'],
['💚', 'green_heart', 'chira'],
['💛', 'yellow_heart'],
['💜', 'purple_heart'],
['🖤', 'black_heart', 'shino'],
['💔', 'broken_heart'],
['💖', 'sparkling_heart'],
['💗', 'heartpulse'],
['💕', 'two_hearts'],
// food / objects
['🥌', 'rock', 'stone'],
['🍕', 'pizza'],
['🍎', 'apple'],
['🍏', 'gapple', 'green_apple'],
['🍊', 'orange', 'tangerine'],
['🍐', 'pear'],
['🥭', 'mango'],
['🥕', 'carrot'],
['🍇', 'grapes'],
['🍌', 'banana'],
['⛏', 'pick'],
['🥚', 'egg'],
['💮', 'flower', 'white_flower'],
['🌸', 'cherry_blossom'],
['🍬', 'candy'],
['🍡', 'candy_cane'],
['🍭', 'lollipop'],
['⭐', 'star'],
['🌟', 'star2'],
['🌠', 'shooting_star'],
['⚡', 'zap'],
['❄', 'snow', 'snowflake'],
['⛄', 'snowpony', 'snowman'],
['🏀', 'pumpkin'],
['🎃', 'jacko', 'jack_o_lantern'],
['🌲', 'evergreen_tree', 'pinetree'],
['🎄', 'christmas_tree'],
['🕯', 'candle'],
['🎅', 'santa_hat', 'santa_claus'],
['💐', 'holly'],
['🌿', 'mistletoe'],
['🎲', 'die', 'dice', 'game_die'],
['✨', 'sparkles'],
['🎁', 'gift', 'present'],
['🔥', 'fire'],
['🎵', 'musical_note'],
['🎶', 'notes'],
['🌈', 'rainbow'],
['🐾', 'feet', 'paw', 'paws'],
['👑', 'crown'],
['💎', 'gem'],
['☘', 'shamrock', 'clover'],
['🍀', 'four_leaf_clover'],
['🍪', 'cookie'],
// food / objects
['🥌', 'rock', 'stone'],
['🍕', 'pizza'],
['🍎', 'apple'],
['🍏', 'gapple', 'green_apple'],
['🍊', 'orange', 'tangerine'],
['🍐', 'pear'],
['🥭', 'mango'],
['🥕', 'carrot'],
['🍇', 'grapes'],
['🍌', 'banana'],
['⛏', 'pick'],
['🥚', 'egg'],
['💮', 'flower', 'white_flower'],
['🌸', 'cherry_blossom'],
['🍬', 'candy'],
['🍡', 'candy_cane'],
['🍭', 'lollipop'],
['⭐', 'star'],
['🌟', 'star2'],
['🌠', 'shooting_star'],
['⚡', 'zap'],
['❄', 'snow', 'snowflake'],
['⛄', 'snowpony', 'snowman'],
['🏀', 'pumpkin'],
['🎃', 'jacko', 'jack_o_lantern'],
['🌲', 'evergreen_tree', 'pinetree'],
['🎄', 'christmas_tree'],
['🕯', 'candle'],
['🎅', 'santa_hat', 'santa_claus'],
['💐', 'holly'],
['🌿', 'mistletoe'],
['🎲', 'die', 'dice', 'game_die'],
['✨', 'sparkles'],
['🎁', 'gift', 'present'],
['🔥', 'fire'],
['🎵', 'musical_note'],
['🎶', 'notes'],
['🌈', 'rainbow'],
['🐾', 'feet', 'paw', 'paws'],
['👑', 'crown'],
['💎', 'gem'],
['☘', 'shamrock', 'clover'],
['🍀', 'four_leaf_clover'],
['🍪', 'cookie'],
// animals
['🦋', 'butterfly'],
['🦇', 'bat'],
['🕷', 'spider'],
['👻', 'ghost'],
['🐈', 'cat2'],
// animals
['🦋', 'butterfly'],
['🦇', 'bat'],
['🕷', 'spider'],
['👻', 'ghost'],
['🐈', 'cat2'],
// other
['™', 'tm'],
['♂', 'male'],
['♀', 'female'],
['⚧', 'trans', 'transgender'],
// other
['™', 'tm'],
['♂', 'male'],
['♀', 'female'],
['⚧', 'trans', 'transgender'],
].map(createEmoji);
export const emojiMap = new Map<string, string>();
@@ -116,79 +116,79 @@ export const emojiNames = emojis.slice().sort().map(e => `:${e.names[0]}:`);
emojis.forEach(e => e.names.forEach(name => emojiMap.set(`:${name}:`, e.symbol)));
export function findEmoji(name: string): Emoji | undefined {
return emojis.find(e => name === e.symbol || includes(e.names, name));
return emojis.find(e => name === e.symbol || includes(e.names, name));
}
export function replaceEmojis(text: string | undefined): string {
return (text || '').replace(/:[a-z0-9_]+:/ig, match => emojiMap.get(match) || match);
return (text || '').replace(/:[a-z0-9_]+:/ig, match => emojiMap.get(match) || match);
}
function createEmoji([symbol, ...names]: string[]): Emoji {
return { symbol, names: [...names, ...names.filter(n => /_/.test(n)).map(n => n.replace(/_/g, ''))] };
return { symbol, names: [...names, ...names.filter(n => /_/.test(n)).map(n => n.replace(/_/g, ''))] };
}
const emojiImages = new Map<Sprite, string>();
const emojiImagePromises = new Map<Sprite, Promise<string>>();
export function getEmojiImageAsync(sprite: Sprite, callback: (str: string) => void) {
const src = emojiImages.get(sprite);
const src = emojiImages.get(sprite);
if (src) {
callback(src);
return;
}
if (src) {
callback(src);
return;
}
const promise = emojiImagePromises.get(sprite);
const promise = emojiImagePromises.get(sprite);
if (promise) {
promise.then(callback);
return;
}
if (promise) {
promise.then(callback);
return;
}
const width = sprite.w + sprite.ox;
// const height = sprite.h + sprite.oy;
const canvas = drawCanvas(width, 10, normalSpriteSheet, undefined, batch => batch.drawSprite(sprite, WHITE, 0, 0));
const newPromise = canvasToSource(canvas);
emojiImagePromises.set(sprite, newPromise);
const width = sprite.w + sprite.ox;
// const height = sprite.h + sprite.oy;
const canvas = drawCanvas(width, 10, normalSpriteSheet, undefined, batch => batch.drawSprite(sprite, WHITE, 0, 0));
const newPromise = canvasToSource(canvas);
emojiImagePromises.set(sprite, newPromise);
newPromise
.then(src => {
emojiImages.set(sprite, src);
emojiImagePromises.delete(sprite);
return src;
})
.then(callback);
newPromise
.then(src => {
emojiImages.set(sprite, src);
emojiImagePromises.delete(sprite);
return src;
})
.then(callback);
}
const emojisRegex = new RegExp(`(${[
...emojis.map(e => e.symbol),
'♈', '♉', '♊', '♋', '♌', '♍', '♎', '♏', '♐', '♑', '♒', '♓', '⛎',
...emojis.map(e => e.symbol),
'♈', '♉', '♊', '♋', '♌', '♍', '♎', '♏', '♐', '♑', '♒', '♓', '⛎',
].join('|')})`, 'g');
export function splitEmojis(text: string) {
return text.split(emojisRegex);
return text.split(emojisRegex);
}
export function hasEmojis(text: string) {
return emojisRegex.test(text);
return emojisRegex.test(text);
}
export function nameToHTML(name: string) {
return escape(name);
return escape(name);
}
export interface AutocompleteState {
lastEmoji?: string;
lastEmoji?: string;
}
const names = emojiNames.slice().sort();
export function autocompleteMesssage(message: string, shift: boolean, state: AutocompleteState): string {
return message.replace(/:[a-z0-9_]+:?$/, match => {
state.lastEmoji = state.lastEmoji || match;
const matches = names.filter(e => e.indexOf(state.lastEmoji!) === 0);
const index = matches.indexOf(match);
const offset = index === -1 ? 0 : (index + matches.length + (shift ? -1 : 1)) % matches.length;
return matches[offset] || match;
});
return message.replace(/:[a-z0-9_]+:?$/, match => {
state.lastEmoji = state.lastEmoji || match;
const matches = names.filter(e => e.indexOf(state.lastEmoji!) === 0);
const index = matches.indexOf(match);
const offset = index === -1 ? 0 : (index + matches.length + (shift ? -1 : 1)) % matches.length;
return matches[offset] || match;
});
}
+22 -22
View File
@@ -9,31 +9,31 @@ export let fontMono: SpriteFont;
export let fontMonoPal: SpriteFont;
export function createFonts() {
font = createSpriteFont(sprites.font, sprites.emoji, 3);
font.lineSpacing = 3;
font.letterShiftY = -2;
font = createSpriteFont(sprites.font, sprites.emoji, 3);
font.lineSpacing = 3;
font.letterShiftY = -2;
fontPal = createSpriteFont(sprites.fontPal, sprites.emojiPal, 3);
fontPal.lineSpacing = 3;
fontPal.letterShiftY = -2;
fontPal = createSpriteFont(sprites.fontPal, sprites.emojiPal, 3);
fontPal.lineSpacing = 3;
fontPal.letterShiftY = -2;
fontSmall = createSpriteFont(sprites.fontSmall, [], 2);
fontSmall.lineSpacing = 4;
fontSmall.letterShiftY = -2;
fontSmall.letterHeightReal += 2;
fontSmall = createSpriteFont(sprites.fontSmall, [], 2);
fontSmall.lineSpacing = 4;
fontSmall.letterShiftY = -2;
fontSmall.letterHeightReal += 2;
fontSmallPal = createSpriteFont(sprites.fontSmallPal, [], 2);
fontSmallPal.lineSpacing = 4;
fontSmallPal.letterShiftY = -2;
fontSmallPal.letterHeightReal += 2;
fontSmallPal = createSpriteFont(sprites.fontSmallPal, [], 2);
fontSmallPal.lineSpacing = 4;
fontSmallPal.letterShiftY = -2;
fontSmallPal.letterHeightReal += 2;
fontMono = createSpriteFont(sprites.fontMono, [], 4);
fontMono.lineSpacing = 4;
fontMono.letterShiftY = -2;
fontMono.letterHeightReal += 2;
fontMono = createSpriteFont(sprites.fontMono, [], 4);
fontMono.lineSpacing = 4;
fontMono.letterShiftY = -2;
fontMono.letterHeightReal += 2;
fontMonoPal = createSpriteFont(sprites.fontMonoPal, [], 4);
fontMonoPal.lineSpacing = 4;
fontMonoPal.letterShiftY = -2;
fontMonoPal.letterHeightReal += 2;
fontMonoPal = createSpriteFont(sprites.fontMonoPal, [], 4);
fontMonoPal.lineSpacing = 4;
fontMonoPal.letterShiftY = -2;
fontMonoPal.letterHeightReal += 2;
}
+1675 -1675
View File
File diff suppressed because it is too large Load Diff
+65 -65
View File
@@ -1,89 +1,89 @@
export interface Game {
fps: number;
load(): any;
init(): void;
update(delta: number, now: number, last: number): void;
draw(): void;
fps: number;
load(): any;
init(): void;
update(delta: number, now: number, last: number): void;
draw(): void;
}
export interface GameLoop {
started: Promise<void>;
cancel(): void;
started: Promise<void>;
cancel(): void;
}
let gameLoop: GameLoop | undefined = undefined;
export function startGameLoop(game: Game, onError = (e: Error) => console.error(e)): GameLoop {
let handle: any;
let backup: any;
let cancelled = false;
let handle: any;
let backup: any;
let cancelled = false;
let last = Math.round(performance.now());
let lastFps = last;
let frames = 0;
let fps = 0;
let last = Math.round(performance.now());
let lastFps = last;
let frames = 0;
let fps = 0;
function step(now: number, draw: boolean) {
if (draw) {
handle = requestAnimationFrame(onFrame);
}
function step(now: number, draw: boolean) {
if (draw) {
handle = requestAnimationFrame(onFrame);
}
frames++;
frames++;
if ((now - lastFps) > 1000) {
fps = frames * 1000 / (now - lastFps);
frames = 0;
lastFps = now;
}
if ((now - lastFps) > 1000) {
fps = frames * 1000 / (now - lastFps);
frames = 0;
lastFps = now;
}
try {
game.fps = fps;
game.update((now - last) / 1000, now, last);
try {
game.fps = fps;
game.update((now - last) / 1000, now, last);
if (draw) {
game.draw();
}
} catch (e) {
onError(e);
}
if (draw) {
game.draw();
}
} catch (e) {
onError(e);
}
last = now;
}
last = now;
}
function onTimer() {
step(Math.round(performance.now()), false);
backup = setTimeout(onTimer, 1000 / 10);
}
function onTimer() {
step(Math.round(performance.now()), false);
backup = setTimeout(onTimer, 1000 / 10);
}
function onFrame() {
clearTimeout(backup);
step(Math.round(performance.now()), true);
backup = setTimeout(onTimer, 1000 / 10);
}
function onFrame() {
clearTimeout(backup);
step(Math.round(performance.now()), true);
backup = setTimeout(onTimer, 1000 / 10);
}
function cancel() {
cancelAnimationFrame(handle);
clearTimeout(backup);
cancelled = true;
}
function cancel() {
cancelAnimationFrame(handle);
clearTimeout(backup);
cancelled = true;
}
if (gameLoop) {
gameLoop.cancel();
}
if (gameLoop) {
gameLoop.cancel();
}
const started = Promise.resolve()
.then(() => game.load())
.then(() => {
if (cancelled) {
throw new Error('Cancelled (loop)');
} else {
game.init();
handle = requestAnimationFrame(onFrame);
backup = setTimeout(onTimer, 1000 / 10);
}
});
const started = Promise.resolve()
.then(() => game.load())
.then(() => {
if (cancelled) {
throw new Error('Cancelled (loop)');
} else {
game.init();
handle = requestAnimationFrame(onFrame);
backup = setTimeout(onTimer, 1000 / 10);
}
});
gameLoop = { started, cancel };
gameLoop = { started, cancel };
return gameLoop;
return gameLoop;
}
+28 -28
View File
@@ -3,47 +3,47 @@ import { PonyTownGame } from './game';
import { removeById } from '../common/utils';
export function addNotification({ notifications }: PonyTownGame, notification: Notification) {
const open = notifications.length === 0;
const open = notifications.length === 0;
notifications.push(notification);
notifications.push(notification);
setTimeout(() => {
notification.open = open;
notification.fresh = false;
}, 500);
setTimeout(() => {
notification.open = open;
notification.fresh = false;
}, 500);
}
export function removeNotification({ notifications }: PonyTownGame, id: number) {
const notification = removeById(notifications, id);
const notification = removeById(notifications, id);
if (notification && notification.open && notifications.length) {
notifications[0].open = true;
}
if (notification && notification.open && notifications.length) {
notifications[0].open = true;
}
}
export function resetGameFields(game: PonyTownGame) {
game.loaded = false;
game.placeInQueue = 0;
game.playerId = undefined;
game.playerName = undefined;
game.playerInfo = undefined;
game.playerCRC = undefined;
game.party = undefined;
game.whisperTo = undefined;
game.messageQueue = [];
game.lastWhisperFrom = undefined;
game.onPartyUpdate.next();
game.fallbackPonies.clear();
game.loaded = false;
game.placeInQueue = 0;
game.playerId = undefined;
game.playerName = undefined;
game.playerInfo = undefined;
game.playerCRC = undefined;
game.party = undefined;
game.whisperTo = undefined;
game.messageQueue = [];
game.lastWhisperFrom = undefined;
game.onPartyUpdate.next();
game.fallbackPonies.clear();
}
export function markGameAsLoaded(game: PonyTownGame) {
if (!game.loaded) {
game.loaded = true;
game.fullyLoaded = false;
setTimeout(() => game.fullyLoaded = true, 300);
}
if (!game.loaded) {
game.loaded = true;
game.fullyLoaded = false;
setTimeout(() => game.fullyLoaded = true, 300);
}
}
export function isSelected(game: PonyTownGame, id: number) {
return game.selected && game.selected.id === id;
return game.selected && game.selected.id === id;
}
+558 -558
View File
File diff suppressed because it is too large Load Diff
+100 -100
View File
@@ -4,171 +4,171 @@ import { font } from './fonts';
import { getCharacterSprite } from '../graphics/spriteFont';
export function createHtmlNodes(value: string | undefined, scale: number): Node[] {
return value ? splitEmojis(value).map(x => {
const sprite = hasEmojis(x) && font && getCharacterSprite(x, font);
return value ? splitEmojis(value).map(x => {
const sprite = hasEmojis(x) && font && getCharacterSprite(x, font);
if (sprite) {
const emote = findEmoji(x);
const img = document.createElement('img');
img.className = 'pixelart';
img.style.display = 'inline-block';
img.style.visibility = 'hidden';
img.style.width = `${(sprite.w + sprite.ox) * scale}px`;
img.style.height = `${10 * scale}px`;
if (sprite) {
const emote = findEmoji(x);
const img = document.createElement('img');
img.className = 'pixelart';
img.style.display = 'inline-block';
img.style.visibility = 'hidden';
img.style.width = `${(sprite.w + sprite.ox) * scale}px`;
img.style.height = `${10 * scale}px`;
if (emote) {
img.setAttribute('aria-label', emote.names[0]);
}
if (emote) {
img.setAttribute('aria-label', emote.names[0]);
}
getEmojiImageAsync(sprite, src => {
img.alt = x;
img.src = src;
img.style.visibility = 'visible';
});
getEmojiImageAsync(sprite, src => {
img.alt = x;
img.src = src;
img.style.visibility = 'visible';
});
return img;
} else {
return document.createTextNode(x);
}
}) : [];
return img;
} else {
return document.createTextNode(x);
}
}) : [];
}
export function textNode(text: string) {
return document.createTextNode(text);
return document.createTextNode(text);
}
export function element(
tag: string, className?: string, nodes?: (Node | undefined)[], attrs?: Dict<any>, events?: Dict<() => any>
tag: string, className?: string, nodes?: (Node | undefined)[], attrs?: Dict<any>, events?: Dict<() => any>
) {
const element = document.createElement(tag);
const element = document.createElement(tag);
if (className) {
element.className = className;
}
if (className) {
element.className = className;
}
if (nodes !== undefined) {
appendAllNodes(element, nodes);
}
if (nodes !== undefined) {
appendAllNodes(element, nodes);
}
if (attrs !== undefined) {
Object.keys(attrs).forEach(key => element.setAttribute(key, attrs[key]));
}
if (attrs !== undefined) {
Object.keys(attrs).forEach(key => element.setAttribute(key, attrs[key]));
}
if (events !== undefined) {
Object.keys(events).forEach(key => element.addEventListener(key, events[key]));
}
if (events !== undefined) {
Object.keys(events).forEach(key => element.addEventListener(key, events[key]));
}
return element;
return element;
}
export function appendAllNodes(element: Element, nodes: (Node | undefined)[]) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node !== undefined) {
element.appendChild(node);
}
}
if (node !== undefined) {
element.appendChild(node);
}
}
}
export function removeAllNodes(element: Element) {
let child: Node | null;
let child: Node | null;
while (child = element.lastChild) {
element.removeChild(child);
}
while (child = element.lastChild) {
element.removeChild(child);
}
}
export function removeFirstChild(element: HTMLElement) {
let child: Node | null;
let child: Node | null;
if (child = element.firstChild) {
element.removeChild(child);
}
if (child = element.firstChild) {
element.removeChild(child);
}
}
export function removeElement(element: HTMLElement) {
element.parentElement && element.parentElement.removeChild(element);
element.parentElement && element.parentElement.removeChild(element);
}
export function replaceNodes(element: HTMLElement, text: string) {
while (element.lastChild && element.lastChild !== element.firstChild) {
element.removeChild(element.lastChild);
}
while (element.lastChild && element.lastChild !== element.firstChild) {
element.removeChild(element.lastChild);
}
let firstChild = element.firstChild;
let firstChild = element.firstChild;
if (!firstChild) {
element.appendChild(firstChild = textNode(''));
}
if (!firstChild) {
element.appendChild(firstChild = textNode(''));
}
if (hasEmojis(text)) {
firstChild.nodeValue = '';
appendAllNodes(element, createHtmlNodes(text, 2));
} else {
firstChild.nodeValue = text;
}
if (hasEmojis(text)) {
firstChild.nodeValue = '';
appendAllNodes(element, createHtmlNodes(text, 2));
} else {
firstChild.nodeValue = text;
}
}
export function findParentElement(element: HTMLElement, selector: string) {
const elements = Array.from(document.querySelectorAll(selector));
let current = element.parentElement;
const elements = Array.from(document.querySelectorAll(selector));
let current = element.parentElement;
while (current && elements.indexOf(current) === -1) {
current = current.parentElement;
}
while (current && elements.indexOf(current) === -1) {
current = current.parentElement;
}
return current;
return current;
}
export function findFocusableElements(root: HTMLElement) {
const elements = root.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
return Array.from(elements) as HTMLElement[];
const elements = root.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
return Array.from(elements) as HTMLElement[];
}
export function focusFirstElement(root: HTMLElement) {
const elements = findFocusableElements(root);
const elements = findFocusableElements(root);
if (elements.length) {
elements[0].focus();
return elements[0];
}
if (elements.length) {
elements[0].focus();
return elements[0];
}
return undefined;
return undefined;
}
export function focusElement(root: HTMLElement, selector: string) {
const target = root.querySelector(selector) as HTMLElement | null;
const target = root.querySelector(selector) as HTMLElement | null;
if (target) {
target.focus();
}
if (target) {
target.focus();
}
}
export function focusElementAfterTimeout(root: HTMLElement, selector: string) {
setTimeout(() => focusElement(root, selector), 10);
setTimeout(() => focusElement(root, selector), 10);
}
export function isParentOf(parent: Element, child: Element) {
for (let current = child.parentElement; current; current = current.parentElement) {
if (current === parent) {
return true;
}
}
for (let current = child.parentElement; current; current = current.parentElement) {
if (current === parent) {
return true;
}
}
return false;
return false;
}
export function showTextInNewTab(text: string) {
const wnd = window.open()!;
const pre = wnd.document.createElement('pre');
pre.innerText = text;
wnd.document.body.appendChild(pre);
const wnd = window.open()!;
const pre = wnd.document.createElement('pre');
pre.innerText = text;
wnd.document.body.appendChild(pre);
}
export function addStyle(style: string) {
const styleElement = document.createElement('style');
styleElement.appendChild(document.createTextNode(style));
document.head.appendChild(styleElement);
return styleElement;
const styleElement = document.createElement('style');
styleElement.appendChild(document.createTextNode(style));
document.head.appendChild(styleElement);
return styleElement;
}
+184 -184
View File
@@ -1,206 +1,206 @@
import {
faCrown,
faPlug,
faGamepad,
faMobile,
faTablet,
faTv,
faCrown,
faPlug,
faGamepad,
faMobile,
faTablet,
faTv,
} from '../generated/fa-icons';
export {
faHashtag,
faCog,
faCogs,
faMinus,
faPlus,
faCheck,
faFlag,
faStickyNote,
faCertificate,
faGlobe,
faGamepad,
faDesktop,
faQuestionCircle,
faInfo,
faSync,
faUserSecret,
faTrash,
faLock,
faApple,
faEdit,
faImage,
faLaughBeam,
faLanguage,
faCircle,
faEyeSlash,
faEnvelope,
faFont,
faCompressArrowsAlt,
faIdBadge,
faFilter,
faEraser,
faBell,
faClock,
faComment,
faComments,
faCommentSlash,
faHdd,
faMicrochip,
faUser,
faUsers,
faUserFriends,
faSpinner,
faBan,
faMicrophoneSlash,
faFileAlt,
faTimes,
faSearch,
faClipboard,
faChevronUp,
faChevronDown,
faChevronLeft,
faChevronRight,
faStar,
faAngleDoubleUp,
faAngleDoubleDown,
faAngleDoubleLeft,
faAngleDoubleRight,
faPlay,
faRedo,
faSave,
faArrowLeft,
faArrowRight,
faArrowUp,
faArrowDown,
faEyeDropper,
faPaintBrush,
faEllipsisV,
faExclamationCircle,
faUserPlus,
faUserMinus,
faUserTimes,
faSignOutAlt,
faStepForward,
faVolumeOff,
faVolumeDown,
faVolumeUp,
faHome,
faStop,
faRetweet,
faFile,
faCopy,
faShare,
faCode,
faTerminal,
faClone,
faPause,
faCrosshairs,
faFileImage,
faHeart,
faPlusCircle,
faMinusCircle,
faInfoCircle,
faCaretUp,
faCaretSquareUp,
faCaretSquareDown,
faCheckCircle,
faWrench,
faDrawPolygon,
faUserCog,
faSlidersH,
faExchangeAlt,
faDatabase,
faHorseHead,
faMapMarkerAlt,
faChartPie,
faCalendar,
faHashtag,
faCog,
faCogs,
faMinus,
faPlus,
faCheck,
faFlag,
faStickyNote,
faCertificate,
faGlobe,
faGamepad,
faDesktop,
faQuestionCircle,
faInfo,
faSync,
faUserSecret,
faTrash,
faLock,
faApple,
faEdit,
faImage,
faLaughBeam,
faLanguage,
faCircle,
faEyeSlash,
faEnvelope,
faFont,
faCompressArrowsAlt,
faIdBadge,
faFilter,
faEraser,
faBell,
faClock,
faComment,
faComments,
faCommentSlash,
faHdd,
faMicrochip,
faUser,
faUsers,
faUserFriends,
faSpinner,
faBan,
faMicrophoneSlash,
faFileAlt,
faTimes,
faSearch,
faClipboard,
faChevronUp,
faChevronDown,
faChevronLeft,
faChevronRight,
faStar,
faAngleDoubleUp,
faAngleDoubleDown,
faAngleDoubleLeft,
faAngleDoubleRight,
faPlay,
faRedo,
faSave,
faArrowLeft,
faArrowRight,
faArrowUp,
faArrowDown,
faEyeDropper,
faPaintBrush,
faEllipsisV,
faExclamationCircle,
faUserPlus,
faUserMinus,
faUserTimes,
faSignOutAlt,
faStepForward,
faVolumeOff,
faVolumeDown,
faVolumeUp,
faHome,
faStop,
faRetweet,
faFile,
faCopy,
faShare,
faCode,
faTerminal,
faClone,
faPause,
faCrosshairs,
faFileImage,
faHeart,
faPlusCircle,
faMinusCircle,
faInfoCircle,
faCaretUp,
faCaretSquareUp,
faCaretSquareDown,
faCheckCircle,
faWrench,
faDrawPolygon,
faUserCog,
faSlidersH,
faExchangeAlt,
faDatabase,
faHorseHead,
faMapMarkerAlt,
faChartPie,
faCalendar,
} from '../generated/fa-icons';
import {
faPatreon,
faDeviantart,
faTwitter,
faTumblr,
faFacebook,
faGithub,
faVk,
faGoogle,
faChrome,
faInternetExplorer,
faEdge,
faAndroid,
faFirefox,
faSafari,
faOpera,
faWindows,
faApple,
faLinux,
faAmilia,
faYandexInternational,
faPatreon,
faDeviantart,
faTwitter,
faTumblr,
faFacebook,
faGithub,
faVk,
faGoogle,
faChrome,
faInternetExplorer,
faEdge,
faAndroid,
faFirefox,
faSafari,
faOpera,
faWindows,
faApple,
faLinux,
faAmilia,
faYandexInternational,
} from '../generated/fa-icons';
export {
faPatreon,
faDeviantart,
faTwitter,
faTumblr,
faGithub,
faPatreon,
faDeviantart,
faTwitter,
faTumblr,
faGithub,
} from '../generated/fa-icons';
export const partyLeaderIcon = faCrown;
export const offlineIcon = faPlug;
export const emptyIcon = {
prefix: 'fas',
iconName: 'empty-icon',
icon: [512, 512, [], 'ffff', ''],
prefix: 'fas',
iconName: 'empty-icon',
icon: [512, 512, [], 'ffff', ''],
};
export const oauthIcons: { [key: string]: any; } = {
patreon: faPatreon,
deviantart: faDeviantart,
twitter: faTwitter,
tumblr: faTumblr,
facebook: faFacebook,
github: faGithub,
vkontakte: faVk,
google: faGoogle,
patreon: faPatreon,
deviantart: faDeviantart,
twitter: faTwitter,
tumblr: faTumblr,
facebook: faFacebook,
github: faGithub,
vkontakte: faVk,
google: faGoogle,
};
export const uaIcons: { [key: string]: any; } = {
// browser
'Chrome': faChrome,
'Chromium': faChrome,
'IE': faInternetExplorer,
'Edge': faEdge,
'Android Browser': faAndroid,
'Firefox': faFirefox,
'Safari': faSafari,
'Mobile Safari': faSafari,
'Opera': faOpera,
'Opera Mini': faOpera,
'Amigo': faAmilia,
'YaBrowser': faYandexInternational,
// os
'Windows': faWindows,
'Windows Phone': faWindows,
'Android': faAndroid,
'iOS': faApple,
'Mac OS': faApple,
'Arch': faLinux,
'CentOS': faLinux,
'Fedora': faLinux,
'FreeBSD': faLinux,
'OpenBSD': faLinux,
'Debian': faLinux,
'Ubuntu': faLinux,
'Linux': faLinux,
'Chromium OS': faChrome,
'Firefox OS': faFirefox,
'Playstation': faGamepad,
'Nintendo': faGamepad,
// device
'console': faGamepad,
'mobile': faMobile,
'tablet': faTablet,
'smarttv': faTv,
// browser
'Chrome': faChrome,
'Chromium': faChrome,
'IE': faInternetExplorer,
'Edge': faEdge,
'Android Browser': faAndroid,
'Firefox': faFirefox,
'Safari': faSafari,
'Mobile Safari': faSafari,
'Opera': faOpera,
'Opera Mini': faOpera,
'Amigo': faAmilia,
'YaBrowser': faYandexInternational,
// os
'Windows': faWindows,
'Windows Phone': faWindows,
'Android': faAndroid,
'iOS': faApple,
'Mac OS': faApple,
'Arch': faLinux,
'CentOS': faLinux,
'Fedora': faLinux,
'FreeBSD': faLinux,
'OpenBSD': faLinux,
'Debian': faLinux,
'Ubuntu': faLinux,
'Linux': faLinux,
'Chromium OS': faChrome,
'Firefox OS': faFirefox,
'Playstation': faGamepad,
'Nintendo': faGamepad,
// device
'console': faGamepad,
'mobile': faMobile,
'tablet': faTablet,
'smarttv': faTv,
};
+113 -113
View File
@@ -4,157 +4,157 @@ import { InputManager } from './inputManager';
import { isFocused } from '../clientUtils';
interface GamepadInstance {
gamepad: Gamepad;
mapping: GamepadMapping;
gamepad: Gamepad;
mapping: GamepadMapping;
}
const JOYSTICK_THRESHHOLD = 0.2;
function createGamepad(gamepad: Gamepad): GamepadInstance {
const mapping = detectMapping(gamepad.id, navigator.userAgent);
return { gamepad, mapping };
const mapping = detectMapping(gamepad.id, navigator.userAgent);
return { gamepad, mapping };
}
function isCompatible(mapping: any, id: string, browser: string) {
for (let i = 0; i < mapping.supported.length; i++) {
const supported = mapping.supported[i];
for (let i = 0; i < mapping.supported.length; i++) {
const supported = mapping.supported[i];
if (id.indexOf(supported.id) !== -1 && browser.indexOf(supported.os) !== -1 && browser.indexOf(browser) !== -1) {
return true;
}
}
if (id.indexOf(supported.id) !== -1 && browser.indexOf(supported.os) !== -1 && browser.indexOf(browser) !== -1) {
return true;
}
}
return false;
return false;
}
function detectMapping(id: string, browser: string) {
for (let i = 0; i < GAMEPAD_MAPPINGS.length; i++) {
if (isCompatible(GAMEPAD_MAPPINGS[i], id, browser)) {
return GAMEPAD_MAPPINGS[i];
}
}
for (let i = 0; i < GAMEPAD_MAPPINGS.length; i++) {
if (isCompatible(GAMEPAD_MAPPINGS[i], id, browser)) {
return GAMEPAD_MAPPINGS[i];
}
}
return GAMEPAD_MAPPINGS[0];
return GAMEPAD_MAPPINGS[0];
}
function axis({ mapping, gamepad }: GamepadInstance, name: GamepadAxes) {
const axe = mapping.axes[name] as any;
return axe ? gamepad.axes[axe.index] : 0;
const axe = mapping.axes[name] as any;
return axe ? gamepad.axes[axe.index] : 0;
}
function button({ mapping, gamepad }: GamepadInstance, name: GamepadButtons) {
const button = mapping.buttons[name] as any;
const button = mapping.buttons[name] as any;
if (!button) {
return false;
}
if (!button) {
return false;
}
if (button.index !== undefined) {
return gamepad.buttons[button.index] && gamepad.buttons[button.index].pressed;
}
if (button.index !== undefined) {
return gamepad.buttons[button.index] && gamepad.buttons[button.index].pressed;
}
if (button.axis !== undefined) {
if (button.direction < 0) {
return gamepad.axes[button.axis] < -0.75;
} else {
return gamepad.axes[button.axis] > 0.75;
}
}
if (button.axis !== undefined) {
if (button.direction < 0) {
return gamepad.axes[button.axis] < -0.75;
} else {
return gamepad.axes[button.axis] > 0.75;
}
}
return false;
return false;
}
export class GamePadController implements InputController {
private initialized = false;
private gamepadIndex = -1;
private zeroed1 = false;
private zeroed2 = false;
constructor(private manager: InputManager) {
}
initialize() {
if (!this.initialized) {
this.initialized = true;
window.addEventListener('gamepadconnected', this.gamepadconnected);
window.addEventListener('gamepaddisconnected', this.gamepaddisconnected);
this.scanGamepads();
}
}
release() {
this.initialized = false;
window.removeEventListener('gamepadconnected', this.gamepadconnected);
window.removeEventListener('gamepaddisconnected', this.gamepaddisconnected);
}
update() {
if (this.manager.disabledGamepad || !isFocused() || this.gamepadIndex === -1)
return;
private initialized = false;
private gamepadIndex = -1;
private zeroed1 = false;
private zeroed2 = false;
constructor(private manager: InputManager) {
}
initialize() {
if (!this.initialized) {
this.initialized = true;
window.addEventListener('gamepadconnected', this.gamepadconnected);
window.addEventListener('gamepaddisconnected', this.gamepaddisconnected);
this.scanGamepads();
}
}
release() {
this.initialized = false;
window.removeEventListener('gamepadconnected', this.gamepadconnected);
window.removeEventListener('gamepaddisconnected', this.gamepaddisconnected);
}
update() {
if (this.manager.disabledGamepad || !isFocused() || this.gamepadIndex === -1)
return;
const gamepads = navigator.getGamepads();
const gamepad = gamepads[this.gamepadIndex];
const gamepads = navigator.getGamepads();
const gamepad = gamepads[this.gamepadIndex];
if (!gamepad) {
this.scanGamepads();
return;
}
if (!gamepad) {
this.scanGamepads();
return;
}
const pad = createGamepad(gamepad);
const pad = createGamepad(gamepad);
this.zeroed1 = readAxis(
this.manager, Key.GAMEPAD_AXIS1_X, Key.GAMEPAD_AXIS1_Y,
axis(pad, GamepadAxes.LeftStickX), axis(pad, GamepadAxes.LeftStickY), this.zeroed1);
this.zeroed2 = readAxis(
this.manager, Key.GAMEPAD_AXIS2_X, Key.GAMEPAD_AXIS2_Y,
axis(pad, GamepadAxes.RightStickX), axis(pad, GamepadAxes.RightStickY), this.zeroed2);
this.zeroed1 = readAxis(
this.manager, Key.GAMEPAD_AXIS1_X, Key.GAMEPAD_AXIS1_Y,
axis(pad, GamepadAxes.LeftStickX), axis(pad, GamepadAxes.LeftStickY), this.zeroed1);
this.zeroed2 = readAxis(
this.manager, Key.GAMEPAD_AXIS2_X, Key.GAMEPAD_AXIS2_Y,
axis(pad, GamepadAxes.RightStickX), axis(pad, GamepadAxes.RightStickY), this.zeroed2);
this.manager.setValue(Key.GAMEPAD_BUTTON_X, button(pad, GamepadButtons.X) ? 1 : 0);
this.manager.setValue(Key.GAMEPAD_BUTTON_Y, button(pad, GamepadButtons.Y) ? 1 : 0);
this.manager.setValue(Key.GAMEPAD_BUTTON_A, button(pad, GamepadButtons.A) ? 1 : 0);
this.manager.setValue(Key.GAMEPAD_BUTTON_B, button(pad, GamepadButtons.B) ? 1 : 0);
this.manager.setValue(Key.GAMEPAD_BUTTON_X, button(pad, GamepadButtons.X) ? 1 : 0);
this.manager.setValue(Key.GAMEPAD_BUTTON_Y, button(pad, GamepadButtons.Y) ? 1 : 0);
this.manager.setValue(Key.GAMEPAD_BUTTON_A, button(pad, GamepadButtons.A) ? 1 : 0);
this.manager.setValue(Key.GAMEPAD_BUTTON_B, button(pad, GamepadButtons.B) ? 1 : 0);
this.manager.setValue(Key.GAMEPAD_BUTTON_DOWN, button(pad, GamepadButtons.DpadDown) ? 1 : 0);
this.manager.setValue(Key.GAMEPAD_BUTTON_LEFT, button(pad, GamepadButtons.DpadLeft) ? 1 : 0);
this.manager.setValue(Key.GAMEPAD_BUTTON_RIGHT, button(pad, GamepadButtons.DpadRight) ? 1 : 0);
this.manager.setValue(Key.GAMEPAD_BUTTON_UP, button(pad, GamepadButtons.DpadUp) ? 1 : 0);
}
clear() {
}
private scanGamepads() {
const gamepads = navigator.getGamepads();
this.manager.setValue(Key.GAMEPAD_BUTTON_DOWN, button(pad, GamepadButtons.DpadDown) ? 1 : 0);
this.manager.setValue(Key.GAMEPAD_BUTTON_LEFT, button(pad, GamepadButtons.DpadLeft) ? 1 : 0);
this.manager.setValue(Key.GAMEPAD_BUTTON_RIGHT, button(pad, GamepadButtons.DpadRight) ? 1 : 0);
this.manager.setValue(Key.GAMEPAD_BUTTON_UP, button(pad, GamepadButtons.DpadUp) ? 1 : 0);
}
clear() {
}
private scanGamepads() {
const gamepads = navigator.getGamepads();
// Using regular loop because of issues with iterating over gamepads
for (let i = 0; i < gamepads.length; i++) {
const gamepad = gamepads[i];
// Using regular loop because of issues with iterating over gamepads
for (let i = 0; i < gamepads.length; i++) {
const gamepad = gamepads[i];
if (gamepad) {
this.gamepadIndex = gamepad.index;
return;
}
}
if (gamepad) {
this.gamepadIndex = gamepad.index;
return;
}
}
this.gamepadIndex = -1;
}
private gamepadconnected = (e: Event) => {
this.gamepadIndex = (e as GamepadEvent).gamepad.index;
}
private gamepaddisconnected = (e: Event) => {
if (this.gamepadIndex === (e as GamepadEvent).gamepad.index) {
this.scanGamepads();
}
}
this.gamepadIndex = -1;
}
private gamepadconnected = (e: Event) => {
this.gamepadIndex = (e as GamepadEvent).gamepad.index;
}
private gamepaddisconnected = (e: Event) => {
if (this.gamepadIndex === (e as GamepadEvent).gamepad.index) {
this.scanGamepads();
}
}
}
function readAxis(manager: InputManager, keyX: Key, keyY: Key, axisX: number, axisY: number, zeroed: boolean): boolean {
const dist = Math.sqrt(axisX * axisX + axisY * axisY);
const dist = Math.sqrt(axisX * axisX + axisY * axisY);
if (dist > JOYSTICK_THRESHHOLD) {
const scaledDist = Math.min((dist - JOYSTICK_THRESHHOLD) / (1 - JOYSTICK_THRESHHOLD), 1);
const theta = Math.atan2(axisY, axisX);
manager.setValue(keyX, Math.cos(theta) * scaledDist);
manager.setValue(keyY, Math.sin(theta) * scaledDist);
return false;
} else if (!zeroed) {
manager.setValue(keyX, 0);
manager.setValue(keyY, 0);
return true;
}
if (dist > JOYSTICK_THRESHHOLD) {
const scaledDist = Math.min((dist - JOYSTICK_THRESHHOLD) / (1 - JOYSTICK_THRESHHOLD), 1);
const theta = Math.atan2(axisY, axisX);
manager.setValue(keyX, Math.cos(theta) * scaledDist);
manager.setValue(keyY, Math.sin(theta) * scaledDist);
return false;
} else if (!zeroed) {
manager.setValue(keyX, 0);
manager.setValue(keyY, 0);
return true;
}
return zeroed;
return zeroed;
}
+139 -139
View File
@@ -1,144 +1,144 @@
export const enum Key {
// Keyboard
BACKSPACE = 8,
TAB = 9,
ENTER = 13,
SHIFT = 16,
CTRL = 17,
ALT = 18,
PAUSE = 19,
CAPS_LOCK = 20,
ESCAPE = 27,
SPACE = 32,
PAGE_UP = 33,
PAGE_DOWN = 34,
END = 35,
HOME = 36,
LEFT = 37,
UP = 38,
RIGHT = 39,
DOWN = 40,
INSERT = 45,
DELETE = 46,
KEY_0 = 48,
KEY_1 = 49,
KEY_2 = 50,
KEY_3 = 51,
KEY_4 = 52,
KEY_5 = 53,
KEY_6 = 54,
KEY_7 = 55,
KEY_8 = 56,
KEY_9 = 57,
KEY_A = 65,
KEY_B = 66,
KEY_C = 67,
KEY_D = 68,
KEY_E = 69,
KEY_F = 70,
KEY_G = 71,
KEY_H = 72,
KEY_I = 73,
KEY_J = 74,
KEY_K = 75,
KEY_L = 76,
KEY_M = 77,
KEY_N = 78,
KEY_O = 79,
KEY_P = 80,
KEY_Q = 81,
KEY_R = 82,
KEY_S = 83,
KEY_T = 84,
KEY_U = 85,
KEY_V = 86,
KEY_W = 87,
KEY_X = 88,
KEY_Y = 89,
KEY_Z = 90,
LEFT_META = 91,
RIGHT_META = 92,
SELECT = 93,
NUMPAD_0 = 96,
NUMPAD_1 = 97,
NUMPAD_2 = 98,
NUMPAD_3 = 99,
NUMPAD_4 = 100,
NUMPAD_5 = 101,
NUMPAD_6 = 102,
NUMPAD_7 = 103,
NUMPAD_8 = 104,
NUMPAD_9 = 105,
MULTIPLY = 106,
ADD = 107,
SUBTRACT = 109,
DECIMAL = 110,
DIVIDE = 111,
F1 = 112,
F2 = 113,
F3 = 114,
F4 = 115,
F5 = 116,
F6 = 117,
F7 = 118,
F8 = 119,
F9 = 120,
F10 = 121,
F11 = 122,
F12 = 123,
NUM_LOCK = 144,
SCROLL_LOCK = 145,
SEMICOLON = 186,
EQUALS = 187,
COMMA = 188,
DASH = 189,
PERIOD = 190,
FORWARD_SLASH = 191,
GRAVE_ACCENT = 192,
OPEN_BRACKET = 219,
BACK_SLASH = 220,
CLOSE_BRACKET = 221,
SINGLE_QUOTE = 222,
// Mouse
MOUSE_X = 300,
MOUSE_Y,
MOUSE_BUTTON1,
MOUSE_BUTTON2,
MOUSE_BUTTON3,
MOUSE_WHEEL_X,
MOUSE_WHEEL_Y,
// Gamepad
GAMEPAD_AXIS1_X,
GAMEPAD_AXIS1_Y,
GAMEPAD_AXIS2_X,
GAMEPAD_AXIS2_Y,
GAMEPAD_BUTTON_A,
GAMEPAD_BUTTON_B,
GAMEPAD_BUTTON_X,
GAMEPAD_BUTTON_Y,
GAMEPAD_BUTTON_L1,
GAMEPAD_BUTTON_R1,
GAMEPAD_BUTTON_L2,
GAMEPAD_BUTTON_R2,
GAMEPAD_BUTTON_START,
GAMEPAD_BUTTON_SELECT,
GAMEPAD_BUTTON_ANALOG1,
GAMEPAD_BUTTON_ANALOG2,
GAMEPAD_BUTTON_UP,
GAMEPAD_BUTTON_DOWN,
GAMEPAD_BUTTON_LEFT,
GAMEPAD_BUTTON_RIGHT,
// Touch
TOUCH,
TOUCH_CLICK,
TOUCH_SECOND_CLICK,
// Other
MAX_VALUE,
// Keyboard
BACKSPACE = 8,
TAB = 9,
ENTER = 13,
SHIFT = 16,
CTRL = 17,
ALT = 18,
PAUSE = 19,
CAPS_LOCK = 20,
ESCAPE = 27,
SPACE = 32,
PAGE_UP = 33,
PAGE_DOWN = 34,
END = 35,
HOME = 36,
LEFT = 37,
UP = 38,
RIGHT = 39,
DOWN = 40,
INSERT = 45,
DELETE = 46,
KEY_0 = 48,
KEY_1 = 49,
KEY_2 = 50,
KEY_3 = 51,
KEY_4 = 52,
KEY_5 = 53,
KEY_6 = 54,
KEY_7 = 55,
KEY_8 = 56,
KEY_9 = 57,
KEY_A = 65,
KEY_B = 66,
KEY_C = 67,
KEY_D = 68,
KEY_E = 69,
KEY_F = 70,
KEY_G = 71,
KEY_H = 72,
KEY_I = 73,
KEY_J = 74,
KEY_K = 75,
KEY_L = 76,
KEY_M = 77,
KEY_N = 78,
KEY_O = 79,
KEY_P = 80,
KEY_Q = 81,
KEY_R = 82,
KEY_S = 83,
KEY_T = 84,
KEY_U = 85,
KEY_V = 86,
KEY_W = 87,
KEY_X = 88,
KEY_Y = 89,
KEY_Z = 90,
LEFT_META = 91,
RIGHT_META = 92,
SELECT = 93,
NUMPAD_0 = 96,
NUMPAD_1 = 97,
NUMPAD_2 = 98,
NUMPAD_3 = 99,
NUMPAD_4 = 100,
NUMPAD_5 = 101,
NUMPAD_6 = 102,
NUMPAD_7 = 103,
NUMPAD_8 = 104,
NUMPAD_9 = 105,
MULTIPLY = 106,
ADD = 107,
SUBTRACT = 109,
DECIMAL = 110,
DIVIDE = 111,
F1 = 112,
F2 = 113,
F3 = 114,
F4 = 115,
F5 = 116,
F6 = 117,
F7 = 118,
F8 = 119,
F9 = 120,
F10 = 121,
F11 = 122,
F12 = 123,
NUM_LOCK = 144,
SCROLL_LOCK = 145,
SEMICOLON = 186,
EQUALS = 187,
COMMA = 188,
DASH = 189,
PERIOD = 190,
FORWARD_SLASH = 191,
GRAVE_ACCENT = 192,
OPEN_BRACKET = 219,
BACK_SLASH = 220,
CLOSE_BRACKET = 221,
SINGLE_QUOTE = 222,
// Mouse
MOUSE_X = 300,
MOUSE_Y,
MOUSE_BUTTON1,
MOUSE_BUTTON2,
MOUSE_BUTTON3,
MOUSE_WHEEL_X,
MOUSE_WHEEL_Y,
// Gamepad
GAMEPAD_AXIS1_X,
GAMEPAD_AXIS1_Y,
GAMEPAD_AXIS2_X,
GAMEPAD_AXIS2_Y,
GAMEPAD_BUTTON_A,
GAMEPAD_BUTTON_B,
GAMEPAD_BUTTON_X,
GAMEPAD_BUTTON_Y,
GAMEPAD_BUTTON_L1,
GAMEPAD_BUTTON_R1,
GAMEPAD_BUTTON_L2,
GAMEPAD_BUTTON_R2,
GAMEPAD_BUTTON_START,
GAMEPAD_BUTTON_SELECT,
GAMEPAD_BUTTON_ANALOG1,
GAMEPAD_BUTTON_ANALOG2,
GAMEPAD_BUTTON_UP,
GAMEPAD_BUTTON_DOWN,
GAMEPAD_BUTTON_LEFT,
GAMEPAD_BUTTON_RIGHT,
// Touch
TOUCH,
TOUCH_CLICK,
TOUCH_SECOND_CLICK,
// Other
MAX_VALUE,
}
export interface InputController {
initialize(element: HTMLElement): void;
release(): void;
update(): void;
clear(): void;
initialize(element: HTMLElement): void;
release(): void;
update(): void;
clear(): void;
}
+148 -148
View File
@@ -11,160 +11,160 @@ type Handler = (input: Key, value: number) => boolean | void;
const KEYS = Key.MAX_VALUE;
export class InputManager {
disabledGamepad = false;
disabledKeyboard = false;
disableArrows = false;
usingTouch = false;
private state: number[];
private prevState: number[];
private actions: Handler[][];
private controllers: InputController[] = [];
constructor() {
this.state = array(KEYS, 0);
this.prevState = array(KEYS, 0);
this.actions = times(KEYS, () => []);
}
get axisX() {
const axisX = this.getRange(Key.GAMEPAD_AXIS1_X);
const left = this.disableArrows ? this.getState(Key.KEY_A) : this.getState(Key.LEFT, Key.KEY_A);
const right = this.disableArrows ? this.getState(Key.KEY_D) : this.getState(Key.RIGHT, Key.KEY_D);
const x = axisX + (left ? -1 : (right ? 1 : 0));
return clamp(x, -1, 1);
}
get axisY() {
const axisY = this.getRange(Key.GAMEPAD_AXIS1_Y);
const up = this.disableArrows ? this.getState(Key.KEY_W) : this.getState(Key.UP, Key.KEY_W);
const down = this.disableArrows ? this.getState(Key.KEY_S) : this.getState(Key.DOWN, Key.KEY_S);
const y = axisY + (up ? -1 : (down ? 1 : 0));
return clamp(y, -1, 1);
}
get isMovementFromButtons() {
const up = this.getState(Key.UP, Key.KEY_W);
const down = this.getState(Key.DOWN, Key.KEY_S);
const left = this.getState(Key.LEFT, Key.KEY_A);
const right = this.getState(Key.RIGHT, Key.KEY_D);
return up || down || left || right;
}
get axis2X() {
return clamp(this.getRange(Key.GAMEPAD_AXIS2_X), -1, 1);
}
get axis2Y() {
return clamp(this.getRange(Key.GAMEPAD_AXIS2_Y), -1, 1);
}
get pointerX() {
return this.getRange(Key.MOUSE_X);
}
get pointerY() {
return this.getRange(Key.MOUSE_Y);
}
get wheelX() {
return this.getRange(Key.MOUSE_WHEEL_X);
}
get wheelY() {
return this.getRange(Key.MOUSE_WHEEL_Y);
}
initialize(element: HTMLElement) {
this.controllers = [
new KeyboardController(this),
new MouseController(this),
new TouchController(this),
new GamePadController(this),
];
disabledGamepad = false;
disabledKeyboard = false;
disableArrows = false;
usingTouch = false;
private state: number[];
private prevState: number[];
private actions: Handler[][];
private controllers: InputController[] = [];
constructor() {
this.state = array(KEYS, 0);
this.prevState = array(KEYS, 0);
this.actions = times(KEYS, () => []);
}
get axisX() {
const axisX = this.getRange(Key.GAMEPAD_AXIS1_X);
const left = this.disableArrows ? this.getState(Key.KEY_A) : this.getState(Key.LEFT, Key.KEY_A);
const right = this.disableArrows ? this.getState(Key.KEY_D) : this.getState(Key.RIGHT, Key.KEY_D);
const x = axisX + (left ? -1 : (right ? 1 : 0));
return clamp(x, -1, 1);
}
get axisY() {
const axisY = this.getRange(Key.GAMEPAD_AXIS1_Y);
const up = this.disableArrows ? this.getState(Key.KEY_W) : this.getState(Key.UP, Key.KEY_W);
const down = this.disableArrows ? this.getState(Key.KEY_S) : this.getState(Key.DOWN, Key.KEY_S);
const y = axisY + (up ? -1 : (down ? 1 : 0));
return clamp(y, -1, 1);
}
get isMovementFromButtons() {
const up = this.getState(Key.UP, Key.KEY_W);
const down = this.getState(Key.DOWN, Key.KEY_S);
const left = this.getState(Key.LEFT, Key.KEY_A);
const right = this.getState(Key.RIGHT, Key.KEY_D);
return up || down || left || right;
}
get axis2X() {
return clamp(this.getRange(Key.GAMEPAD_AXIS2_X), -1, 1);
}
get axis2Y() {
return clamp(this.getRange(Key.GAMEPAD_AXIS2_Y), -1, 1);
}
get pointerX() {
return this.getRange(Key.MOUSE_X);
}
get pointerY() {
return this.getRange(Key.MOUSE_Y);
}
get wheelX() {
return this.getRange(Key.MOUSE_WHEEL_X);
}
get wheelY() {
return this.getRange(Key.MOUSE_WHEEL_Y);
}
initialize(element: HTMLElement) {
this.controllers = [
new KeyboardController(this),
new MouseController(this),
new TouchController(this),
new GamePadController(this),
];
this.controllers.forEach(c => c.initialize(element));
this.clear();
}
release() {
this.controllers.forEach(c => c.release());
this.controllers = [];
this.clear();
}
update() {
for (const controller of this.controllers) {
controller.update();
}
}
end() {
for (let i = 0; i < KEYS; i++) {
this.prevState[i] = this.state[i];
}
this.controllers.forEach(c => c.initialize(element));
this.clear();
}
release() {
this.controllers.forEach(c => c.release());
this.controllers = [];
this.clear();
}
update() {
for (const controller of this.controllers) {
controller.update();
}
}
end() {
for (let i = 0; i < KEYS; i++) {
this.prevState[i] = this.state[i];
}
this.setValue(Key.TOUCH_CLICK, 0);
this.setValue(Key.TOUCH_SECOND_CLICK, 0);
this.setValue(Key.MOUSE_WHEEL_X, 0);
this.setValue(Key.MOUSE_WHEEL_Y, 0);
}
clear() {
for (let i = 0; i < KEYS; i++) {
this.state[i] = 0;
this.prevState[i] = 0;
}
this.setValue(Key.TOUCH_CLICK, 0);
this.setValue(Key.TOUCH_SECOND_CLICK, 0);
this.setValue(Key.MOUSE_WHEEL_X, 0);
this.setValue(Key.MOUSE_WHEEL_Y, 0);
}
clear() {
for (let i = 0; i < KEYS; i++) {
this.state[i] = 0;
this.prevState[i] = 0;
}
for (const controller of this.controllers) {
controller.clear();
}
}
onPressed(inputs: Key[] | Key, handler: () => void) {
this.onAction(inputs, (_, v) => {
if (v === 1) {
handler();
}
});
}
onReleased(inputs: Key[] | Key, handler: () => void) {
this.onAction(inputs, (_, v) => {
if (v === 0) {
handler();
}
});
}
isPressed(key: Key) {
return this.state[key] !== 0;
}
wasPressed(key: Key): boolean {
return this.state[key] === 1 && this.prevState[key] === 0;
}
private onAction(inputs: Key[] | Key, handler: Handler) {
const inputsArray = Array.isArray(inputs) ? inputs : [inputs];
for (const controller of this.controllers) {
controller.clear();
}
}
onPressed(inputs: Key[] | Key, handler: () => void) {
this.onAction(inputs, (_, v) => {
if (v === 1) {
handler();
}
});
}
onReleased(inputs: Key[] | Key, handler: () => void) {
this.onAction(inputs, (_, v) => {
if (v === 0) {
handler();
}
});
}
isPressed(key: Key) {
return this.state[key] !== 0;
}
wasPressed(key: Key): boolean {
return this.state[key] === 1 && this.prevState[key] === 0;
}
private onAction(inputs: Key[] | Key, handler: Handler) {
const inputsArray = Array.isArray(inputs) ? inputs : [inputs];
for (const i of inputsArray) {
this.actions[i].push(handler);
}
}
private getState(...inputs: Key[]): boolean {
for (const i of inputs) {
if (this.state[i] !== 0) {
return true;
}
}
for (const i of inputsArray) {
this.actions[i].push(handler);
}
}
private getState(...inputs: Key[]): boolean {
for (const i of inputs) {
if (this.state[i] !== 0) {
return true;
}
}
return false;
}
private getRange(input: Key): number {
return this.state[input];
}
setValue(input: Key, value: number): boolean {
if (input < 0 || input >= KEYS) {
console.warn(`Input out of range: ${input}`);
} else if (this.state[input] !== value) {
this.state[input] = value;
return false;
}
private getRange(input: Key): number {
return this.state[input];
}
setValue(input: Key, value: number): boolean {
if (input < 0 || input >= KEYS) {
console.warn(`Input out of range: ${input}`);
} else if (this.state[input] !== value) {
this.state[input] = value;
if (this.actions[input] && this.actions[input].length) {
for (const action of this.actions[input]) {
action(input, value);
}
if (this.actions[input] && this.actions[input].length) {
for (const action of this.actions[input]) {
action(input, value);
}
return true;
}
}
return true;
}
}
return false;
}
addValue(input: Key, value: number) {
if (input < 0 || input >= KEYS) {
console.warn(`Input out of range: ${input}`);
} else {
this.state[input] += value;
}
}
return false;
}
addValue(input: Key, value: number) {
if (input < 0 || input >= KEYS) {
console.warn(`Input out of range: ${input}`);
} else {
this.state[input] += value;
}
}
}
+67 -67
View File
@@ -5,92 +5,92 @@ import { removeItem, includes } from '../../common/utils';
const firefox = !SERVER && /firefox/i.test(navigator.userAgent);
function isKeyEventInvalid(e: KeyboardEvent) {
return e.target && /^(input|textarea|select)$/i.test((e.target as HTMLElement).tagName);
return e.target && /^(input|textarea|select)$/i.test((e.target as HTMLElement).tagName);
}
function allowKey(key: number) {
return key === Key.ESCAPE || key === Key.F5 || key === Key.F12 || key === Key.F11 || key === Key.TAB;
return key === Key.ESCAPE || key === Key.F5 || key === Key.F12 || key === Key.F11 || key === Key.TAB;
}
function fixKeyCode(key: number) {
if (firefox) {
if (key === 173) return Key.DASH;
if (key === 61) return Key.EQUALS;
}
if (firefox) {
if (key === 173) return Key.DASH;
if (key === 61) return Key.EQUALS;
}
return key;
return key;
}
const iosKeyToKeyCode: { [key: string]: number | undefined; } = {
UIKeyInputEscape: Key.ESCAPE,
UIKeyInputUpArrow: Key.UP,
UIKeyInputLeftArrow: Key.LEFT,
UIKeyInputRightArrow: Key.RIGHT,
UIKeyInputDownArrow: Key.DOWN,
UIKeyInputEscape: Key.ESCAPE,
UIKeyInputUpArrow: Key.UP,
UIKeyInputLeftArrow: Key.LEFT,
UIKeyInputRightArrow: Key.RIGHT,
UIKeyInputDownArrow: Key.DOWN,
};
const iosHandledKeyCodes = [Key.ESCAPE, Key.UP, Key.LEFT, Key.RIGHT, Key.DOWN];
export class KeyboardController implements InputController {
private initialized = false;
private stack: number[] = [];
constructor(private manager: InputManager) {
}
initialize() {
if (!this.initialized) {
this.initialized = true;
window.addEventListener('keydown', this.keydown);
window.addEventListener('keyup', this.keyup);
window.addEventListener('blur', this.blur);
}
}
release() {
this.initialized = false;
window.removeEventListener('keydown', this.keydown);
window.removeEventListener('keyup', this.keyup);
window.removeEventListener('blur', this.blur);
this.clear();
}
update() {
}
clear() {
this.stack.length = 0;
}
private keydown = (e: KeyboardEvent) => {
if (!this.manager.disabledKeyboard && !isKeyEventInvalid(e)) {
const code = fixKeyCode(e.keyCode);
this.manager.setValue(code, 1);
private initialized = false;
private stack: number[] = [];
constructor(private manager: InputManager) {
}
initialize() {
if (!this.initialized) {
this.initialized = true;
window.addEventListener('keydown', this.keydown);
window.addEventListener('keyup', this.keyup);
window.addEventListener('blur', this.blur);
}
}
release() {
this.initialized = false;
window.removeEventListener('keydown', this.keydown);
window.removeEventListener('keyup', this.keyup);
window.removeEventListener('blur', this.blur);
this.clear();
}
update() {
}
clear() {
this.stack.length = 0;
}
private keydown = (e: KeyboardEvent) => {
if (!this.manager.disabledKeyboard && !isKeyEventInvalid(e)) {
const code = fixKeyCode(e.keyCode);
this.manager.setValue(code, 1);
if (!allowKey(code)) {
e.preventDefault();
e.stopPropagation();
}
if (!allowKey(code)) {
e.preventDefault();
e.stopPropagation();
}
if (!includes(this.stack, code) && !includes(iosHandledKeyCodes, code)) {
this.stack.push(code);
}
}
}
private keyup = (e: KeyboardEvent) => {
let code = fixKeyCode(e.keyCode);
if (!includes(this.stack, code) && !includes(iosHandledKeyCodes, code)) {
this.stack.push(code);
}
}
}
private keyup = (e: KeyboardEvent) => {
let code = fixKeyCode(e.keyCode);
// fix keyCode on iOS bluetooth keyboard
if (code === 0) {
code = iosKeyToKeyCode[e.key] || 0;
// fix keyCode on iOS bluetooth keyboard
if (code === 0) {
code = iosKeyToKeyCode[e.key] || 0;
if (code === 0) {
code = this.stack.pop() || 0;
}
}
if (code === 0) {
code = this.stack.pop() || 0;
}
}
if (this.manager.setValue(code, 0)) {
e.preventDefault();
e.stopPropagation();
}
if (this.manager.setValue(code, 0)) {
e.preventDefault();
e.stopPropagation();
}
removeItem(this.stack, code);
}
private blur = () => {
this.manager.clear();
}
removeItem(this.stack, code);
}
private blur = () => {
this.manager.clear();
}
}
+73 -73
View File
@@ -5,83 +5,83 @@ import { clamp } from '../../common/utils';
const MOUSE_BUTTONS = [Key.MOUSE_BUTTON1, Key.MOUSE_BUTTON3, Key.MOUSE_BUTTON2];
export class MouseController implements InputController {
private initialized = false;
private element?: HTMLElement;
constructor(private manager: InputManager) {
}
initialize(element: HTMLElement) {
if (!this.initialized) {
this.initialized = true;
this.element = element;
element.addEventListener('mousemove', this.mousemove);
element.addEventListener('mousedown', this.mousedown);
element.addEventListener('mouseup', this.mouseup);
element.addEventListener('mousewheel', this.mousewheel);
element.addEventListener('contextmenu', this.contextmenu);
element.addEventListener('click', this.click);
window.addEventListener('blur', this.blur);
}
}
release() {
this.initialized = false;
private initialized = false;
private element?: HTMLElement;
constructor(private manager: InputManager) {
}
initialize(element: HTMLElement) {
if (!this.initialized) {
this.initialized = true;
this.element = element;
element.addEventListener('mousemove', this.mousemove);
element.addEventListener('mousedown', this.mousedown);
element.addEventListener('mouseup', this.mouseup);
element.addEventListener('mousewheel', this.mousewheel);
element.addEventListener('contextmenu', this.contextmenu);
element.addEventListener('click', this.click);
window.addEventListener('blur', this.blur);
}
}
release() {
this.initialized = false;
if (this.element) {
this.element.removeEventListener('mousemove', this.mousemove);
this.element.removeEventListener('mousedown', this.mousedown);
this.element.removeEventListener('mouseup', this.mouseup);
this.element.removeEventListener('mousewheel', this.mousewheel);
this.element.removeEventListener('contextmenu', this.contextmenu);
this.element.removeEventListener('click', this.click);
this.element = undefined;
}
if (this.element) {
this.element.removeEventListener('mousemove', this.mousemove);
this.element.removeEventListener('mousedown', this.mousedown);
this.element.removeEventListener('mouseup', this.mouseup);
this.element.removeEventListener('mousewheel', this.mousewheel);
this.element.removeEventListener('contextmenu', this.contextmenu);
this.element.removeEventListener('click', this.click);
this.element = undefined;
}
window.removeEventListener('blur', this.blur);
}
update() {
}
clear() {
}
private mousemove = (e: MouseEvent) => {
if (this.element) {
const rect = this.element.getBoundingClientRect();
this.manager.setValue(Key.MOUSE_X, Math.floor(e.clientX - rect.left));
this.manager.setValue(Key.MOUSE_Y, Math.floor(e.clientY - rect.top));
}
}
private mousedown = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
window.removeEventListener('blur', this.blur);
}
update() {
}
clear() {
}
private mousemove = (e: MouseEvent) => {
if (this.element) {
const rect = this.element.getBoundingClientRect();
this.manager.setValue(Key.MOUSE_X, Math.floor(e.clientX - rect.left));
this.manager.setValue(Key.MOUSE_Y, Math.floor(e.clientY - rect.top));
}
}
private mousedown = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
this.manager.usingTouch = false;
this.manager.usingTouch = false;
const button = MOUSE_BUTTONS[e.button];
const button = MOUSE_BUTTONS[e.button];
if (button) {
this.manager.setValue(button, 1);
}
}
private mouseup = (e: MouseEvent) => {
const button = MOUSE_BUTTONS[e.button];
if (button) {
this.manager.setValue(button, 1);
}
}
private mouseup = (e: MouseEvent) => {
const button = MOUSE_BUTTONS[e.button];
if (button) {
this.manager.setValue(button, 0);
}
}
private mousewheel: any = (e: MouseWheelEvent) => {
this.manager.addValue(Key.MOUSE_WHEEL_X, clamp(e.deltaX, -1, 1));
this.manager.addValue(Key.MOUSE_WHEEL_Y, clamp(e.deltaY, -1, 1));
}
private contextmenu = (e: Event) => {
e.preventDefault();
e.stopPropagation();
}
private click = (e: Event) => {
e.preventDefault();
e.stopPropagation();
}
private blur = () => {
for (const button of MOUSE_BUTTONS) {
this.manager.setValue(button, 0);
}
}
if (button) {
this.manager.setValue(button, 0);
}
}
private mousewheel: any = (e: MouseWheelEvent) => {
this.manager.addValue(Key.MOUSE_WHEEL_X, clamp(e.deltaX, -1, 1));
this.manager.addValue(Key.MOUSE_WHEEL_Y, clamp(e.deltaY, -1, 1));
}
private contextmenu = (e: Event) => {
e.preventDefault();
e.stopPropagation();
}
private click = (e: Event) => {
e.preventDefault();
e.stopPropagation();
}
private blur = () => {
for (const button of MOUSE_BUTTONS) {
this.manager.setValue(button, 0);
}
}
}
+166 -166
View File
@@ -4,200 +4,200 @@ import { setTransform } from '../../common/utils';
import { InputManager } from './inputManager';
function getTouch(e: TouchEvent, id: number) {
if (id !== -1) {
for (let i = 0; i < e.changedTouches.length; ++i) {
const touch = e.changedTouches.item(i);
if (id !== -1) {
for (let i = 0; i < e.changedTouches.length; ++i) {
const touch = e.changedTouches.item(i);
if (touch && touch.identifier === id) {
return touch;
}
}
}
if (touch && touch.identifier === id) {
return touch;
}
}
}
return undefined;
return undefined;
}
const TOUCH_DEADZONE = 15;
const TOUCH_MAX = 100;
export class TouchController implements InputController {
private initialized = false;
private touchId = -1;
private touch2Id = -1;
private touchStart: Point = { x: 0, y: 0 };
private touchCurrent: Point = { x: 0, y: 0 };
private touchIsDrag = false;
private tapInvalidated = false;
private origin?: HTMLElement;
private position?: HTMLElement;
private originShown = false;
private originTransform?: string;
private positionShown = false;
private positionTransform?: string;
private element?: HTMLElement;
constructor(private manager: InputManager) {
}
initialize(element: HTMLElement) {
if (!this.initialized) {
this.initialized = true;
this.element = element;
this.origin = document.getElementById('touch-origin')!;
this.position = document.getElementById('touch-position')!;
private initialized = false;
private touchId = -1;
private touch2Id = -1;
private touchStart: Point = { x: 0, y: 0 };
private touchCurrent: Point = { x: 0, y: 0 };
private touchIsDrag = false;
private tapInvalidated = false;
private origin?: HTMLElement;
private position?: HTMLElement;
private originShown = false;
private originTransform?: string;
private positionShown = false;
private positionTransform?: string;
private element?: HTMLElement;
constructor(private manager: InputManager) {
}
initialize(element: HTMLElement) {
if (!this.initialized) {
this.initialized = true;
this.element = element;
this.origin = document.getElementById('touch-origin')!;
this.position = document.getElementById('touch-position')!;
element.addEventListener('touchstart', this.touchstart);
element.addEventListener('touchmove', this.touchmove);
element.addEventListener('touchend', this.touchend);
window.addEventListener('touchend', this.blur);
window.addEventListener('blur', this.blur);
}
}
release() {
this.initialized = false;
element.addEventListener('touchstart', this.touchstart);
element.addEventListener('touchmove', this.touchmove);
element.addEventListener('touchend', this.touchend);
window.addEventListener('touchend', this.blur);
window.addEventListener('blur', this.blur);
}
}
release() {
this.initialized = false;
if (this.element) {
this.element.removeEventListener('touchstart', this.touchstart);
this.element.removeEventListener('touchmove', this.touchmove);
this.element.removeEventListener('touchend', this.touchend);
this.element = undefined;
}
if (this.element) {
this.element.removeEventListener('touchstart', this.touchstart);
this.element.removeEventListener('touchmove', this.touchmove);
this.element.removeEventListener('touchend', this.touchend);
this.element = undefined;
}
window.removeEventListener('touchend', this.blur);
window.removeEventListener('blur', this.blur);
}
update() {
const showOrigin = this.touchIsDrag && this.touchId !== -1;
const showPosition = this.touchId !== -1;
window.removeEventListener('touchend', this.blur);
window.removeEventListener('blur', this.blur);
}
update() {
const showOrigin = this.touchIsDrag && this.touchId !== -1;
const showPosition = this.touchId !== -1;
if (this.origin && this.position) {
if (this.originShown !== showOrigin) {
this.originShown = showOrigin;
this.origin.style.display = showOrigin ? 'block' : 'none';
}
if (this.origin && this.position) {
if (this.originShown !== showOrigin) {
this.originShown = showOrigin;
this.origin.style.display = showOrigin ? 'block' : 'none';
}
if (this.positionShown !== showPosition) {
this.positionShown = showPosition;
this.position.style.display = showPosition ? 'block' : 'none';
}
if (this.positionShown !== showPosition) {
this.positionShown = showPosition;
this.position.style.display = showPosition ? 'block' : 'none';
}
if (showOrigin) {
const transform = `translate3d(${this.touchStart.x - 50}px, ${this.touchStart.y - 50}px, 0px)`;
if (showOrigin) {
const transform = `translate3d(${this.touchStart.x - 50}px, ${this.touchStart.y - 50}px, 0px)`;
if (this.originTransform !== transform) {
this.originTransform = transform;
setTransform(this.origin, transform);
}
}
if (this.originTransform !== transform) {
this.originTransform = transform;
setTransform(this.origin, transform);
}
}
if (showPosition) {
const transform = `translate3d(${this.touchCurrent.x - 25}px, ${this.touchCurrent.y - 25}px, 0px)`;
if (showPosition) {
const transform = `translate3d(${this.touchCurrent.x - 25}px, ${this.touchCurrent.y - 25}px, 0px)`;
if (this.positionTransform !== transform) {
this.positionTransform = transform;
setTransform(this.position, transform);
}
}
}
}
clear() {
}
private reset() {
this.touch2Id = -1;
this.resetTouch();
}
private resetTouch() {
this.touchId = -1;
this.touchStart = this.touchCurrent = { x: 0, y: 0 };
this.touchIsDrag = false;
this.manager.setValue(Key.TOUCH, 0);
this.updateInput();
}
private updateInput() {
const dy = this.touchStart.y - this.touchCurrent.y;
const dx = this.touchStart.x - this.touchCurrent.x;
const theta = Math.atan2(dy, dx);
const dist = Math.sqrt(dy * dy + dx * dx);
if (this.positionTransform !== transform) {
this.positionTransform = transform;
setTransform(this.position, transform);
}
}
}
}
clear() {
}
private reset() {
this.touch2Id = -1;
this.resetTouch();
}
private resetTouch() {
this.touchId = -1;
this.touchStart = this.touchCurrent = { x: 0, y: 0 };
this.touchIsDrag = false;
this.manager.setValue(Key.TOUCH, 0);
this.updateInput();
}
private updateInput() {
const dy = this.touchStart.y - this.touchCurrent.y;
const dx = this.touchStart.x - this.touchCurrent.x;
const theta = Math.atan2(dy, dx);
const dist = Math.sqrt(dy * dy + dx * dx);
if (dist > TOUCH_DEADZONE) {
const scaledDist = Math.min((dist - TOUCH_DEADZONE) / (TOUCH_MAX - TOUCH_DEADZONE), 1);
this.touchIsDrag = true;
this.manager.setValue(Key.GAMEPAD_AXIS1_X, -Math.cos(theta) * scaledDist);
this.manager.setValue(Key.GAMEPAD_AXIS1_Y, -Math.sin(theta) * scaledDist);
} else {
this.manager.setValue(Key.GAMEPAD_AXIS1_X, 0);
this.manager.setValue(Key.GAMEPAD_AXIS1_Y, 0);
}
}
private touchstart = (e: any) => {
e.cancellable && e.preventDefault();
e.stopPropagation();
if (dist > TOUCH_DEADZONE) {
const scaledDist = Math.min((dist - TOUCH_DEADZONE) / (TOUCH_MAX - TOUCH_DEADZONE), 1);
this.touchIsDrag = true;
this.manager.setValue(Key.GAMEPAD_AXIS1_X, -Math.cos(theta) * scaledDist);
this.manager.setValue(Key.GAMEPAD_AXIS1_Y, -Math.sin(theta) * scaledDist);
} else {
this.manager.setValue(Key.GAMEPAD_AXIS1_X, 0);
this.manager.setValue(Key.GAMEPAD_AXIS1_Y, 0);
}
}
private touchstart = (e: any) => {
e.cancellable && e.preventDefault();
e.stopPropagation();
this.manager.usingTouch = true;
this.manager.usingTouch = true;
if (this.touchId === -1) {
const touch = e.changedTouches.item(0);
if (this.touchId === -1) {
const touch = e.changedTouches.item(0);
if (touch) {
this.tapInvalidated = false;
this.touchId = touch.identifier;
this.touchStart = this.touchCurrent = this.getTouchXY(touch);
this.manager.setValue(Key.MOUSE_X, this.touchStart.x);
this.manager.setValue(Key.MOUSE_Y, this.touchStart.y);
this.manager.setValue(Key.TOUCH, 1);
}
} else if (this.touch2Id === -1) {
const touch = e.changedTouches.item(0);
if (touch) {
this.tapInvalidated = false;
this.touchId = touch.identifier;
this.touchStart = this.touchCurrent = this.getTouchXY(touch);
this.manager.setValue(Key.MOUSE_X, this.touchStart.x);
this.manager.setValue(Key.MOUSE_Y, this.touchStart.y);
this.manager.setValue(Key.TOUCH, 1);
}
} else if (this.touch2Id === -1) {
const touch = e.changedTouches.item(0);
if (touch) {
this.tapInvalidated = true;
this.touch2Id = touch.identifier;
}
}
}
private touchmove = (e: any) => {
e.preventDefault();
e.stopPropagation();
if (touch) {
this.tapInvalidated = true;
this.touch2Id = touch.identifier;
}
}
}
private touchmove = (e: any) => {
e.preventDefault();
e.stopPropagation();
const touch = getTouch(e, this.touchId);
const touch = getTouch(e, this.touchId);
if (touch) {
this.touchCurrent = this.getTouchXY(touch);
this.manager.setValue(Key.MOUSE_X, this.touchCurrent.x);
this.manager.setValue(Key.MOUSE_Y, this.touchCurrent.y);
this.updateInput();
}
}
private touchend = (e: any) => {
e.preventDefault();
e.stopPropagation();
if (touch) {
this.touchCurrent = this.getTouchXY(touch);
this.manager.setValue(Key.MOUSE_X, this.touchCurrent.x);
this.manager.setValue(Key.MOUSE_Y, this.touchCurrent.y);
this.updateInput();
}
}
private touchend = (e: any) => {
e.preventDefault();
e.stopPropagation();
const touch = getTouch(e, this.touchId);
const touch = getTouch(e, this.touchId);
if (touch) {
if (!this.touchIsDrag && !this.tapInvalidated) {
this.manager.setValue(Key.MOUSE_X, this.touchStart.x);
this.manager.setValue(Key.MOUSE_Y, this.touchStart.y);
this.manager.setValue(Key.TOUCH_CLICK, 1);
}
if (touch) {
if (!this.touchIsDrag && !this.tapInvalidated) {
this.manager.setValue(Key.MOUSE_X, this.touchStart.x);
this.manager.setValue(Key.MOUSE_Y, this.touchStart.y);
this.manager.setValue(Key.TOUCH_CLICK, 1);
}
this.resetTouch();
}
this.resetTouch();
}
const touch2 = getTouch(e, this.touch2Id);
const touch2 = getTouch(e, this.touch2Id);
if (touch2) {
this.manager.setValue(Key.TOUCH_SECOND_CLICK, 1);
this.touch2Id = -1;
}
}
private blur = () => {
this.reset();
}
private getTouchXY(touch: Touch) {
const { left, top } = this.element!.getBoundingClientRect();
if (touch2) {
this.manager.setValue(Key.TOUCH_SECOND_CLICK, 1);
this.touch2Id = -1;
}
}
private blur = () => {
this.reset();
}
private getTouchXY(touch: Touch) {
const { left, top } = this.element!.getBoundingClientRect();
return {
x: touch.clientX - left,
y: touch.clientY - top,
};
}
return {
x: touch.clientX - left,
y: touch.clientY - top,
};
}
}
+24 -24
View File
@@ -3,42 +3,42 @@ import { PartyMember, PartyInfo, Pony } from '../common/interfaces';
import { PonyTownGame } from './game';
export function updateParty(current: PartyInfo | undefined, info: PartyMember[] | undefined): PartyInfo | undefined {
if (!info || !info.length) {
return undefined;
} else {
const party = current || {
leaderId: 0,
members: [],
};
if (!info || !info.length) {
return undefined;
} else {
const party = current || {
leaderId: 0,
members: [],
};
remove(party.members, p => !info.some(m => p.id === m.id));
remove(party.members, p => !info.some(m => p.id === m.id));
info.forEach(m => {
const existing = party.members.find(x => m.id === x.id);
info.forEach(m => {
const existing = party.members.find(x => m.id === x.id);
if (existing) {
Object.assign(existing, m);
} else {
party.members.push(m);
}
if (existing) {
Object.assign(existing, m);
} else {
party.members.push(m);
}
if (m.leader) {
party.leaderId = m.id;
}
});
if (m.leader) {
party.leaderId = m.id;
}
});
return party;
}
return party;
}
}
export function isPonyInParty(party: PartyInfo | undefined, pony: Pony, pending: boolean) {
return !!party && party.members.some(m => m.pony === pony && (pending || !m.pending));
return !!party && party.members.some(m => m.pony === pony && (pending || !m.pending));
}
export function isPartyLeader(game: PonyTownGame): boolean {
return game.party !== undefined && game.player !== undefined && game.player.id === game.party.leaderId;
return game.party !== undefined && game.player !== undefined && game.player.id === game.party.leaderId;
}
export function isInParty(game: PonyTownGame): boolean {
return game.party !== undefined && game.party.members.length > 0;
return game.party !== undefined && game.party.members.length > 0;
}
+165 -165
View File
@@ -2,8 +2,8 @@ import { isCommand, processCommand, hasFlag, includes, point } from '../common/u
import { canPonyLie, canPonyFlyUp, canPonyStand, canPonySit, doBoopPonyAction } from '../common/pony';
import { PonyTownGame } from './game';
import {
setPonyState, canBoop, isPonyLying, isPonyFlying, isPonyStanding, isPonySitting, getInteractBounds,
isFacingRight, closestEntity, entityInRange
setPonyState, canBoop, isPonyLying, isPonyFlying, isPonyStanding, isPonySitting, getInteractBounds,
isFacingRight, closestEntity, entityInRange
} from '../common/entityUtils';
import { EntityState, Action, Pony, ChatType, EntityFlags, Point, TileType } from '../common/interfaces';
import { FLY_DELAY } from '../common/constants';
@@ -14,216 +14,216 @@ import { pointToWorld, roundPositionX, roundPositionY } from '../common/position
import { hammer, shovel } from '../common/entities';
export function handleActionCommand(message: string, game: PonyTownGame): boolean {
if (isCommand(message)) {
const { command = '' } = processCommand(message);
const player = game.player;
if (isCommand(message)) {
const { command = '' } = processCommand(message);
const player = game.player;
if (DEVELOPMENT) {
if (command === 'spammessages') {
let i = 0;
setInterval(() => game.send(server => server.say(0, randomString(5) + ` #${i++}`, ChatType.Say)), 100);
return true;
}
}
if (DEVELOPMENT) {
if (command === 'spammessages') {
let i = 0;
setInterval(() => game.send(server => server.say(0, randomString(5) + ` #${i++}`, ChatType.Say)), 100);
return true;
}
}
switch (command.toLowerCase()) {
case 'testerrorreporting':
throw new Error('test error');
case 'disablepixelratio':
game.togglePixelRatio();
return true;
case 'lie':
case 'lay':
if (player) {
if (isPonyLying(player)) {
sitAction(player, game);
} else {
lieAction(player, game);
}
}
return true;
case 'sit':
if (player) {
if (isPonyFlying(player)) {
standAction(player, game);
} else {
sitAction(player, game);
}
}
return true;
case 'stand':
if (player) {
standAction(player, game);
}
return true;
case 'fly':
if (player) {
if (isPonyFlying(player)) {
standAction(player, game);
} else {
flyAction(player, game);
}
}
return true;
}
}
switch (command.toLowerCase()) {
case 'testerrorreporting':
throw new Error('test error');
case 'disablepixelratio':
game.togglePixelRatio();
return true;
case 'lie':
case 'lay':
if (player) {
if (isPonyLying(player)) {
sitAction(player, game);
} else {
lieAction(player, game);
}
}
return true;
case 'sit':
if (player) {
if (isPonyFlying(player)) {
standAction(player, game);
} else {
sitAction(player, game);
}
}
return true;
case 'stand':
if (player) {
standAction(player, game);
}
return true;
case 'fly':
if (player) {
if (isPonyFlying(player)) {
standAction(player, game);
} else {
flyAction(player, game);
}
}
return true;
}
}
return false;
return false;
}
export function upAction(game: PonyTownGame) {
const player = game.player;
const player = game.player;
if (player) {
if (isPonyLying(player)) {
sitAction(player, game);
} else if (isPonySitting(player)) {
standAction(player, game);
} else if (isPonyStanding(player)) {
flyAction(player, game);
}
}
if (player) {
if (isPonyLying(player)) {
sitAction(player, game);
} else if (isPonySitting(player)) {
standAction(player, game);
} else if (isPonyStanding(player)) {
flyAction(player, game);
}
}
}
export function downAction(game: PonyTownGame) {
const player = game.player;
const player = game.player;
if (player) {
if (isPonySitting(player)) {
lieAction(player, game);
} else if (isPonyStanding(player)) {
sitAction(player, game);
} else if (isPonyFlying(player)) {
standAction(player, game);
}
}
if (player) {
if (isPonySitting(player)) {
lieAction(player, game);
} else if (isPonyStanding(player)) {
sitAction(player, game);
} else if (isPonyFlying(player)) {
standAction(player, game);
}
}
}
export function sitAction(player: Pony, game: PonyTownGame) {
if (canPonySit(player, game.map) && game.send(server => server.action(Action.Sit))) {
player.state = setPonyState(player.state, EntityState.PonySitting);
game.stateOverride = EntityState.PonySitting;
game.onActionsUpdate.next();
}
if (canPonySit(player, game.map) && game.send(server => server.action(Action.Sit))) {
player.state = setPonyState(player.state, EntityState.PonySitting);
game.stateOverride = EntityState.PonySitting;
game.onActionsUpdate.next();
}
}
export function standAction(player: Pony, game: PonyTownGame) {
if (canPonyStand(player, game.map) && game.send(server => server.action(Action.Stand))) {
player.state = setPonyState(player.state, EntityState.PonyStanding);
game.stateOverride = EntityState.PonyStanding;
game.onActionsUpdate.next();
}
if (canPonyStand(player, game.map) && game.send(server => server.action(Action.Stand))) {
player.state = setPonyState(player.state, EntityState.PonyStanding);
game.stateOverride = EntityState.PonyStanding;
game.onActionsUpdate.next();
}
}
export function lieAction(player: Pony, game: PonyTownGame) {
if (canPonyLie(player, game.map) && game.send(server => server.action(Action.Lie))) {
player.state = setPonyState(player.state, EntityState.PonyLying);
game.stateOverride = EntityState.PonyLying;
game.onActionsUpdate.next();
}
if (canPonyLie(player, game.map) && game.send(server => server.action(Action.Lie))) {
player.state = setPonyState(player.state, EntityState.PonyLying);
game.stateOverride = EntityState.PonyLying;
game.onActionsUpdate.next();
}
}
export function flyAction(player: Pony, game: PonyTownGame) {
if (canPonyFlyUp(player) && game.send(server => server.action(Action.Fly))) {
player.state = setPonyState(player.state, EntityState.PonyFlying);
player.inTheAirDelay = FLY_DELAY;
game.stateOverride = EntityState.PonyFlying;
game.onActionsUpdate.next();
}
if (canPonyFlyUp(player) && game.send(server => server.action(Action.Fly))) {
player.state = setPonyState(player.state, EntityState.PonyFlying);
player.inTheAirDelay = FLY_DELAY;
game.stateOverride = EntityState.PonyFlying;
game.onActionsUpdate.next();
}
}
export function boopAction(game: PonyTownGame) {
if (game.player && canBoop(game.player) && game.send(server => server.action(Action.Boop))) {
doBoopPonyAction(game, game.player);
}
if (game.player && canBoop(game.player) && game.send(server => server.action(Action.Boop))) {
doBoopPonyAction(game, game.player);
}
}
export function turnHeadAction(game: PonyTownGame) {
if (game.player && game.send(server => server.action(Action.TurnHead))) {
game.player.state = game.player.state ^ EntityState.HeadTurned;
game.headTurnedOverride = hasFlag(game.player.state, EntityState.HeadTurned);
game.onActionsUpdate.next();
}
if (game.player && game.send(server => server.action(Action.TurnHead))) {
game.player.state = game.player.state ^ EntityState.HeadTurned;
game.headTurnedOverride = hasFlag(game.player.state, EntityState.HeadTurned);
game.onActionsUpdate.next();
}
}
export function interact(game: PonyTownGame, shift: boolean) {
const player = game.player;
const player = game.player;
if (player) {
const bounds = getInteractBounds(player);
const entities = pickEntitiesByRect(game.map, bounds, true, false);
const center = centerPoint(bounds);
center.x += (bounds.w / 4) * (isFacingRight(player) ? -1 : 1);
const entity = closestEntity(pointToWorld(center), entities);
if (player) {
const bounds = getInteractBounds(player);
const entities = pickEntitiesByRect(game.map, bounds, true, false);
const center = centerPoint(bounds);
center.x += (bounds.w / 4) * (isFacingRight(player) ? -1 : 1);
const entity = closestEntity(pointToWorld(center), entities);
if (entity && entityInRange(entity, player)) {
game.send(server => server.interact(entity.id));
} else if (player.hold === hammer.type) {
game.changePlaceEntity(shift);
} else if (player.hold === shovel.type) {
game.changePlaceTile(shift);
} else if (player.ponyState.holding && hasFlag(player.ponyState.holding.flags, EntityFlags.Usable)) {
game.send(server => server.use());
}
}
if (entity && entityInRange(entity, player)) {
game.send(server => server.interact(entity.id));
} else if (player.hold === hammer.type) {
game.changePlaceEntity(shift);
} else if (player.hold === shovel.type) {
game.changePlaceTile(shift);
} else if (player.ponyState.holding && hasFlag(player.ponyState.holding.flags, EntityFlags.Usable)) {
game.send(server => server.use());
}
}
}
export function toggleWall(game: PonyTownGame, hover: Point) {
const x = hover.x | 0;
const y = hover.y | 0;
const dx = hover.x - x;
const dy = hover.y - y;
const x = hover.x | 0;
const y = hover.y | 0;
const dx = hover.x - x;
const dy = hover.y - y;
if (dx > dy) {
if ((dx + dy) < 1) {
game.send(server => server.changeTile(x, y, TileType.WallH));
} else {
game.send(server => server.changeTile(x + 1, y, TileType.WallV));
}
} else {
if ((dx + dy) < 1) {
game.send(server => server.changeTile(x, y, TileType.WallV));
} else {
game.send(server => server.changeTile(x, y + 1, TileType.WallH));
}
}
if (dx > dy) {
if ((dx + dy) < 1) {
game.send(server => server.changeTile(x, y, TileType.WallH));
} else {
game.send(server => server.changeTile(x + 1, y, TileType.WallV));
}
} else {
if ((dx + dy) < 1) {
game.send(server => server.changeTile(x, y, TileType.WallV));
} else {
game.send(server => server.changeTile(x, y + 1, TileType.WallH));
}
}
}
export function editorSelectEntities(game: PonyTownGame, hover: Point, shift: boolean) {
game.apply(() => {
const entities = pickAnyEntities(game.map, hover);
game.apply(() => {
const entities = pickAnyEntities(game.map, hover);
if (shift) {
const entity = entities.filter(e => !includes(game.editor.selectedEntities, e))[0];
entity && game.editor.selectedEntities.push(entity);
} else {
const index = entities.findIndex(e => includes(game.editor.selectedEntities, e));
const entity = entities[(index + 1) % entities.length];
game.editor.selectedEntities = entity ? [entity] : [];
}
});
if (shift) {
const entity = entities.filter(e => !includes(game.editor.selectedEntities, e))[0];
entity && game.editor.selectedEntities.push(entity);
} else {
const index = entities.findIndex(e => includes(game.editor.selectedEntities, e));
const entity = entities[(index + 1) % entities.length];
game.editor.selectedEntities = entity ? [entity] : [];
}
});
}
export function editorDragEntities(game: PonyTownGame, hover: Point, buttonPressed: boolean) {
if (buttonPressed) {
const dx = hover.x - game.editor.draggingStart.x;
const dy = hover.y - game.editor.draggingStart.y;
if (buttonPressed) {
const dx = hover.x - game.editor.draggingStart.x;
const dy = hover.y - game.editor.draggingStart.y;
game.editor.selectedEntities.forEach(e => {
e.x = roundPositionX(e.draggingStart!.x + dx);
e.y = roundPositionY(e.draggingStart!.y + dy);
});
} else {
game.apply(() => game.editor.draggingEntities = false);
game.send(server => server.editorAction({
type: 'move',
entities: game.editor.selectedEntities.map(({ id, x, y }) => ({ id, x, y })),
}));
}
game.editor.selectedEntities.forEach(e => {
e.x = roundPositionX(e.draggingStart!.x + dx);
e.y = roundPositionY(e.draggingStart!.y + dy);
});
} else {
game.apply(() => game.editor.draggingEntities = false);
game.send(server => server.editorAction({
type: 'move',
entities: game.editor.selectedEntities.map(({ id, x, y }) => ({ id, x, y })),
}));
}
}
export function editorMoveEntities(game: PonyTownGame, hover: Point) {
game.editor.draggingEntities = true;
game.editor.draggingStart = hover;
game.editor.selectedEntities.forEach(e => e.draggingStart = point(e.x, e.y));
game.editor.draggingEntities = true;
game.editor.draggingStart = hover;
game.editor.selectedEntities.forEach(e => e.draggingStart = point(e.x, e.y));
}
+15 -15
View File
@@ -2,32 +2,32 @@
// Safari <= 8.4, Android
try {
if (!('performance' in window && 'now' in performance)) {
(window as any).performance = Date;
}
if (!('performance' in window && 'now' in performance)) {
(window as any).performance = Date;
}
} catch { }
try {
if (!('getGamepads' in navigator)) {
(window.navigator as any).getGamepads = () => [];
}
if (!('getGamepads' in navigator)) {
(window.navigator as any).getGamepads = () => [];
}
} catch { }
try {
if (!('requestAnimationFrame' in window)) {
(window as any).requestAnimationFrame = (callback: any) => setTimeout(() => callback(performance.now()), 1000 / 60) as any;
}
if (!('requestAnimationFrame' in window)) {
(window as any).requestAnimationFrame = (callback: any) => setTimeout(() => callback(performance.now()), 1000 / 60) as any;
}
} catch { }
try {
if (!('cancelAnimationFrame' in window)) {
(window as any).cancelAnimationFrame = clearTimeout;
}
if (!('cancelAnimationFrame' in window)) {
(window as any).cancelAnimationFrame = clearTimeout;
}
} catch { }
// IE <= 10
try {
if (!('devicePixelRatio' in window)) {
(window as any).devicePixelRatio = 1;
}
if (!('devicePixelRatio' in window)) {
(window as any).devicePixelRatio = 1;
}
} catch { }
+367 -367
View File
@@ -4,459 +4,459 @@ import { repeat, flatten } from '../common/utils';
// body animations
export function createBodyFrame([
body = 0, head = 0, wing = 0, tail = 0,
frontLeg = 0, frontFarLeg = 0, backLeg = 0, backFarLeg = 0,
bodyX = 0, bodyY = 0, headX = 0, headY = 0,
frontLegX = 0, frontLegY = 0, frontFarLegX = 0, frontFarLegY = 0,
backLegX = 0, backLegY = 0, backFarLegX = 0, backFarLegY = 0
body = 0, head = 0, wing = 0, tail = 0,
frontLeg = 0, frontFarLeg = 0, backLeg = 0, backFarLeg = 0,
bodyX = 0, bodyY = 0, headX = 0, headY = 0,
frontLegX = 0, frontLegY = 0, frontFarLegX = 0, frontFarLegY = 0,
backLegX = 0, backLegY = 0, backFarLegX = 0, backFarLegY = 0
]: number[]): Readonly<BodyAnimationFrame> {
return {
body, head, wing, tail,
frontLeg, frontFarLeg, backLeg, backFarLeg,
bodyX, bodyY, headX, headY,
frontLegX, frontLegY, frontFarLegX, frontFarLegY,
backLegX, backLegY, backFarLegX, backFarLegY
};
return {
body, head, wing, tail,
frontLeg, frontFarLeg, backLeg, backFarLeg,
bodyX, bodyY, headX, headY,
frontLegX, frontLegY, frontFarLegX, frontFarLegY,
backLegX, backLegY, backFarLegX, backFarLegY
};
}
export function createBodyAnimation(
name: string, fps: number, loop: boolean, frames: number[][], shadowOffsets?: number[][]
name: string, fps: number, loop: boolean, frames: number[][], shadowOffsets?: number[][]
): Readonly<BodyAnimation> {
if (shadowOffsets && shadowOffsets.length !== frames.length) {
throw new Error(`Incorrect frame count for shadowOffsets for ${name}`);
}
if (shadowOffsets && shadowOffsets.length !== frames.length) {
throw new Error(`Incorrect frame count for shadowOffsets for ${name}`);
}
const shadow = shadowOffsets && shadowOffsets.map<BodyShadow>(([frame, offset]) => ({ frame, offset }));
const shadow = shadowOffsets && shadowOffsets.map<BodyShadow>(([frame, offset]) => ({ frame, offset }));
return { name, loop, fps, frames: frames.map(createBodyFrame), shadow };
return { name, loop, fps, frames: frames.map(createBodyFrame), shadow };
}
export const stand = createBodyAnimation('stand', 24, true, [
[1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 0, 0, 1, 1, 1, 1],
]);
export const swim = createBodyAnimation('swim', 4, true, [
[1, 1, 0, 0, 8, 10, 6, 5, 0, 14],
[1, 1, 0, 0, 8, 10, 6, 5, 0, 13],
[1, 1, 0, 0, 8, 10, 6, 5, 0, 12],
[1, 1, 0, 0, 8, 10, 6, 5, 0, 13]
[1, 1, 0, 0, 8, 10, 6, 5, 0, 14],
[1, 1, 0, 0, 8, 10, 6, 5, 0, 13],
[1, 1, 0, 0, 8, 10, 6, 5, 0, 12],
[1, 1, 0, 0, 8, 10, 6, 5, 0, 13]
]);
export const trotToSwim = createBodyAnimation('trot-to-swim', 24, false, [
[1, 1, 0, 0, 8, 10, 6, 5, 0, 2],
[1, 1, 0, 0, 8, 10, 6, 5, 0, 8],
[1, 1, 0, 0, 8, 10, 6, 5, 0, 10],
[1, 1, 0, 0, 8, 10, 6, 5, 0, 16]
[1, 1, 0, 0, 8, 10, 6, 5, 0, 2],
[1, 1, 0, 0, 8, 10, 6, 5, 0, 8],
[1, 1, 0, 0, 8, 10, 6, 5, 0, 10],
[1, 1, 0, 0, 8, 10, 6, 5, 0, 16]
]);
export const swimToTrot = createBodyAnimation('swim-to-trot', 24, false, [
[1, 1, 0, 0, 8, 10, 6, 5, 0, 12],
[1, 1, 0, 0, 12, 3, 4, 23, 0, 8],
[1, 1, 0, 0, 14, 26, 3, 24, 0, 4],
[1, 1, 0, 0, 18, 27, 2, 5]
[1, 1, 0, 0, 8, 10, 6, 5, 0, 12],
[1, 1, 0, 0, 12, 3, 4, 23, 0, 8],
[1, 1, 0, 0, 14, 26, 3, 24, 0, 4],
[1, 1, 0, 0, 18, 27, 2, 5]
]);
export const flyToSwim = createBodyAnimation('fly-to-swim', 16, false, [
[1, 1, 3, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -12],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -8],
[1, 1, 6, 0, 8, 10, 6, 5, 0, -2],
[1, 1, 7, 0, 8, 10, 6, 5, 0, 4],
[1, 1, 11, 0, 8, 10, 6, 5, 0, 10],
[1, 1, 1, 0, 8, 10, 6, 5, 0, 14]
[1, 1, 3, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -12],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -8],
[1, 1, 6, 0, 8, 10, 6, 5, 0, -2],
[1, 1, 7, 0, 8, 10, 6, 5, 0, 4],
[1, 1, 11, 0, 8, 10, 6, 5, 0, 10],
[1, 1, 1, 0, 8, 10, 6, 5, 0, 14]
]);
export const flyToSwimBug = createBodyAnimation('fly-to-swim-bug', 16, false, [
[1, 1, 3, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -12],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -8],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -2],
[1, 1, 3, 0, 8, 10, 6, 5, 0, 4],
[1, 1, 4, 0, 8, 10, 6, 5, 0, 10],
[1, 1, 1, 0, 8, 10, 6, 5, 0, 14]
[1, 1, 3, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -12],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -8],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -2],
[1, 1, 3, 0, 8, 10, 6, 5, 0, 4],
[1, 1, 4, 0, 8, 10, 6, 5, 0, 10],
[1, 1, 1, 0, 8, 10, 6, 5, 0, 14]
]);
export const swimToFly = createBodyAnimation('swim-to-fly', 16, false, [
[1, 1, 11, 0, 8, 10, 6, 5, 0, 13],
[1, 1, 12, 0, 8, 10, 6, 5, 0, 14, 0, 0, 0, -1, 0, -1, 0, -1, 0, -1],
[2, 1, 3, 0, 8, 10, 6, 5, 0, 15, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 4, 0, 8, 10, 6, 5, 0, 15, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 5, 0, 8, 10, 6, 5, 0, 15, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[1, 1, 6, 0, 8, 10, 6, 1, 0, 13],
[1, 1, 7, 0, 6, 7, 5, 5, 0, -7, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1],
[1, 1, 8, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 9, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 10, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 11, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 12, 0, 8, 10, 6, 5, 0, -17]
[1, 1, 11, 0, 8, 10, 6, 5, 0, 13],
[1, 1, 12, 0, 8, 10, 6, 5, 0, 14, 0, 0, 0, -1, 0, -1, 0, -1, 0, -1],
[2, 1, 3, 0, 8, 10, 6, 5, 0, 15, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 4, 0, 8, 10, 6, 5, 0, 15, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 5, 0, 8, 10, 6, 5, 0, 15, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[1, 1, 6, 0, 8, 10, 6, 1, 0, 13],
[1, 1, 7, 0, 6, 7, 5, 5, 0, -7, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1],
[1, 1, 8, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 9, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 10, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 11, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 12, 0, 8, 10, 6, 5, 0, -17]
]);
export const swimToFlyBug = createBodyAnimation('swim-to-fly-bug', 16, false, [
[1, 1, 3, 0, 8, 10, 6, 5, 0, 13],
[1, 1, 4, 0, 8, 10, 6, 5, 0, 14, 0, 0, 0, -1, 0, -1, 0, -1, 0, -1],
[2, 1, 5, 0, 8, 10, 6, 5, 0, 15, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 4, 0, 8, 10, 6, 5, 0, 15, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 5, 0, 8, 10, 6, 5, 0, 15, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[1, 1, 4, 0, 8, 10, 6, 1, 0, 13],
[1, 1, 5, 0, 6, 7, 5, 5, 0, -7, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 3, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -17]
[1, 1, 3, 0, 8, 10, 6, 5, 0, 13],
[1, 1, 4, 0, 8, 10, 6, 5, 0, 14, 0, 0, 0, -1, 0, -1, 0, -1, 0, -1],
[2, 1, 5, 0, 8, 10, 6, 5, 0, 15, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 4, 0, 8, 10, 6, 5, 0, 15, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 5, 0, 8, 10, 6, 5, 0, 15, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[1, 1, 4, 0, 8, 10, 6, 1, 0, 13],
[1, 1, 5, 0, 6, 7, 5, 5, 0, -7, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 3, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -17]
]);
//const trotSkew = [-1, 0, 1, 0, -1, -2, -3, -2, -1, 0, 1, 0, -1, -2, -3, -2].map(x => (x + 2) * 0.25);
export const trot = createBodyAnimation('trot', 24, true, [
[1, 1, 0, 0, 2, 10, 2, 10, 0, 1, 0, -1],
[1, 1, 0, 0, 3, 11, 3, 11],
[1, 1, 0, 0, 4, 12, 4, 12, 0, -1],
[1, 1, 0, 0, 5, 13, 5, 13, 0, -2],
[1, 1, 0, 0, 6, 14, 6, 14, 0, -2],
[1, 1, 0, 0, 7, 15, 7, 15, 0, -2],
[1, 1, 0, 0, 8, 16, 8, 16, 0, -1],
[1, 1, 0, 0, 9, 17, 9, 17],
[1, 1, 0, 0, 10, 2, 10, 2, 0, 1, 0, -1],
[1, 1, 0, 0, 11, 3, 11, 3],
[1, 1, 0, 0, 12, 4, 12, 4, 0, -1],
[1, 1, 0, 0, 13, 5, 13, 5, 0, -2],
[1, 1, 0, 0, 14, 6, 14, 6, 0, -2],
[1, 1, 0, 0, 15, 7, 15, 7, 0, -2],
[1, 1, 0, 0, 16, 8, 16, 8, 0, -1],
[1, 1, 0, 0, 17, 9, 17, 9],
[1, 1, 0, 0, 2, 10, 2, 10, 0, 1, 0, -1],
[1, 1, 0, 0, 3, 11, 3, 11],
[1, 1, 0, 0, 4, 12, 4, 12, 0, -1],
[1, 1, 0, 0, 5, 13, 5, 13, 0, -2],
[1, 1, 0, 0, 6, 14, 6, 14, 0, -2],
[1, 1, 0, 0, 7, 15, 7, 15, 0, -2],
[1, 1, 0, 0, 8, 16, 8, 16, 0, -1],
[1, 1, 0, 0, 9, 17, 9, 17],
[1, 1, 0, 0, 10, 2, 10, 2, 0, 1, 0, -1],
[1, 1, 0, 0, 11, 3, 11, 3],
[1, 1, 0, 0, 12, 4, 12, 4, 0, -1],
[1, 1, 0, 0, 13, 5, 13, 5, 0, -2],
[1, 1, 0, 0, 14, 6, 14, 6, 0, -2],
[1, 1, 0, 0, 15, 7, 15, 7, 0, -2],
[1, 1, 0, 0, 16, 8, 16, 8, 0, -1],
[1, 1, 0, 0, 17, 9, 17, 9],
]);
export const boop = createBodyAnimation('boop', 24, false, [
[1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 0, 0, 18, 1, 1, 1],
[1, 1, 0, 0, 19, 1, 1, 1],
[1, 1, 0, 0, 20, 1, 1, 1],
[1, 1, 0, 0, 21, 1, 1, 1],
[1, 1, 0, 0, 22, 28, 18, 18, -1],
[1, 1, 0, 0, 23, 26, 19, 19, -2, -1],
...repeat(5, [1, 1, 0, 0, 23, 27, 20, 20, -3, -1]),
[1, 1, 0, 0, 23, 26, 19, 19, -2, -1],
[1, 1, 0, 0, 22, 1, 1, 1],
[1, 1, 0, 0, 24, 1, 1, 1],
[1, 1, 0, 0, 25, 1, 1, 1],
[1, 1, 0, 0, 18, 1, 1, 1],
[1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 0, 0, 18, 1, 1, 1],
[1, 1, 0, 0, 19, 1, 1, 1],
[1, 1, 0, 0, 20, 1, 1, 1],
[1, 1, 0, 0, 21, 1, 1, 1],
[1, 1, 0, 0, 22, 28, 18, 18, -1],
[1, 1, 0, 0, 23, 26, 19, 19, -2, -1],
...repeat(5, [1, 1, 0, 0, 23, 27, 20, 20, -3, -1]),
[1, 1, 0, 0, 23, 26, 19, 19, -2, -1],
[1, 1, 0, 0, 22, 1, 1, 1],
[1, 1, 0, 0, 24, 1, 1, 1],
[1, 1, 0, 0, 25, 1, 1, 1],
[1, 1, 0, 0, 18, 1, 1, 1],
[1, 1, 0, 0, 1, 1, 1, 1],
]);
export const boopSit = createBodyAnimation('boop-sit', 24, false, [
[9, 1, 2, 2, 34, 34, 26, 26],
[9, 1, 2, 2, 13, 34, 26, 26, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 19, 34, 26, 26, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 20, 34, 26, 26, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 21, 34, 26, 26, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 22, 34, 26, 26, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 23, 34, 26, 26, 0, -1, 0, 0, -1, -1, 0, 1, 0, 1, 0, -1],
...repeat(5, [9, 1, 2, 2, 23, 34, 26, 26, -1, -2, 0, 0, -2, -2, 1, 2, 1, 2, 1]),
[9, 1, 2, 2, 23, 34, 26, 26, 0, -1, 0, 0, -1, -1, 0, 1, 0, 1, 0, -1],
[9, 1, 2, 2, 22, 34, 26, 26, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 24, 34, 26, 26, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 25, 34, 26, 26, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 12, 34, 26, 26, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 34, 34, 26, 26],
[9, 1, 2, 2, 34, 34, 26, 26],
[9, 1, 2, 2, 13, 34, 26, 26, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 19, 34, 26, 26, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 20, 34, 26, 26, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 21, 34, 26, 26, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 22, 34, 26, 26, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 23, 34, 26, 26, 0, -1, 0, 0, -1, -1, 0, 1, 0, 1, 0, -1],
...repeat(5, [9, 1, 2, 2, 23, 34, 26, 26, -1, -2, 0, 0, -2, -2, 1, 2, 1, 2, 1]),
[9, 1, 2, 2, 23, 34, 26, 26, 0, -1, 0, 0, -1, -1, 0, 1, 0, 1, 0, -1],
[9, 1, 2, 2, 22, 34, 26, 26, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 24, 34, 26, 26, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 25, 34, 26, 26, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 12, 34, 26, 26, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, -2],
[9, 1, 2, 2, 34, 34, 26, 26],
], repeat(18, [0, 6]));
export const boopLie = createBodyAnimation('boop-lie', 24, false, [
[15, 1, 0, 2, 38, 38, 26, 26],
...repeat(2, [15, 1, 0, 2, 24, 38, 26, 26, 0, 0, 0, 0, 0, 1]),
[15, 1, 0, 2, 21, 38, 26, 26, 0, 0, 0, 0, 0, 1],
[15, 1, 0, 2, 22, 38, 26, 26, 0, 0, 0, 0, 0, 1],
[15, 1, 0, 2, 23, 38, 26, 26, 0, 0, -1, -1, 0, 1],
[12, 1, 0, 2, 23, 37, 26, 26, -1, 0, 0, 0, -1, 1, 0, 0, 1, 0, 1],
...repeat(4, [12, 1, 0, 2, 23, 37, 26, 26, -1, 0, 0, 0, -2, 1, 0, 0, 1, 0, 1]),
[15, 1, 0, 2, 23, 38, 26, 26, 0, 0, 0, 0, 0, 1],
[15, 1, 0, 2, 22, 38, 26, 26, 0, 0, 0, 0, 0, 1],
[15, 1, 0, 2, 21, 38, 26, 26, 0, 0, 0, 0, 0, 1],
[15, 1, 0, 2, 38, 38, 26, 26],
...repeat(2, [15, 1, 0, 2, 24, 38, 26, 26, 0, 0, 0, 0, 0, 1]),
[15, 1, 0, 2, 21, 38, 26, 26, 0, 0, 0, 0, 0, 1],
[15, 1, 0, 2, 22, 38, 26, 26, 0, 0, 0, 0, 0, 1],
[15, 1, 0, 2, 23, 38, 26, 26, 0, 0, -1, -1, 0, 1],
[12, 1, 0, 2, 23, 37, 26, 26, -1, 0, 0, 0, -1, 1, 0, 0, 1, 0, 1],
...repeat(4, [12, 1, 0, 2, 23, 37, 26, 26, -1, 0, 0, 0, -2, 1, 0, 0, 1, 0, 1]),
[15, 1, 0, 2, 23, 38, 26, 26, 0, 0, 0, 0, 0, 1],
[15, 1, 0, 2, 22, 38, 26, 26, 0, 0, 0, 0, 0, 1],
[15, 1, 0, 2, 21, 38, 26, 26, 0, 0, 0, 0, 0, 1],
], repeat(14, [3, 3]));
export const boopSwim = createBodyAnimation('boop-swim', 24, false, [
[1, 1, 0, 0, 1, 10, 6, 5, 0, 13],
[1, 1, 0, 0, 18, 10, 6, 5, 0, 13],
[1, 1, 0, 0, 19, 10, 6, 5, 0, 13],
[1, 1, 0, 0, 20, 10, 6, 5, 0, 13],
[1, 1, 0, 0, 21, 10, 6, 5, 0, 12],
[1, 1, 0, 0, 22, 9, 6, 5, -1, 12],
[1, 1, 0, 0, 23, 8, 6, 5, -2, 11],
...repeat(5, [1, 1, 0, 0, 23, 8, 6, 5, -3, 11]),
[1, 1, 0, 0, 23, 9, 6, 5, -2, 11],
[1, 1, 0, 0, 22, 10, 6, 5, 0, 13],
[1, 1, 0, 0, 24, 10, 6, 5, 0, 13],
[1, 1, 0, 0, 25, 10, 6, 5, 0, 14],
[1, 1, 0, 0, 18, 10, 6, 5, 0, 14],
[1, 1, 0, 0, 1, 10, 6, 5, 0, 14]
[1, 1, 0, 0, 1, 10, 6, 5, 0, 13],
[1, 1, 0, 0, 18, 10, 6, 5, 0, 13],
[1, 1, 0, 0, 19, 10, 6, 5, 0, 13],
[1, 1, 0, 0, 20, 10, 6, 5, 0, 13],
[1, 1, 0, 0, 21, 10, 6, 5, 0, 12],
[1, 1, 0, 0, 22, 9, 6, 5, -1, 12],
[1, 1, 0, 0, 23, 8, 6, 5, -2, 11],
...repeat(5, [1, 1, 0, 0, 23, 8, 6, 5, -3, 11]),
[1, 1, 0, 0, 23, 9, 6, 5, -2, 11],
[1, 1, 0, 0, 22, 10, 6, 5, 0, 13],
[1, 1, 0, 0, 24, 10, 6, 5, 0, 13],
[1, 1, 0, 0, 25, 10, 6, 5, 0, 14],
[1, 1, 0, 0, 18, 10, 6, 5, 0, 14],
[1, 1, 0, 0, 1, 10, 6, 5, 0, 14]
]);
export const sit = createBodyAnimation('sit', 24, true, [
[9, 1, 2, 2, 34, 34, 26, 26],
[9, 1, 2, 2, 34, 34, 26, 26],
], [[0, 6]]);
const sitShadow = [0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 6].map(offset => [0, offset]);
export const sitDown = createBodyAnimation('sit-down', 24, false, [
[1, 1, 0, 0, 1, 1, 1, 1],
...repeat(2, [2, 1, 0, 0, 29, 29, 1, 1]),
...repeat(2, [3, 1, 0, 0, 30, 30, 21, 21]),
[4, 1, 0, 0, 31, 31, 22, 22],
[5, 1, 0, 1, 32, 32, 23, 23],
[6, 1, 1, 2, 33, 33, 24, 24],
[7, 1, 2, 2, 34, 34, 25, 25],
[8, 1, 2, 2, 34, 34, 25, 25],
[9, 1, 2, 2, 34, 34, 26, 26],
[1, 1, 0, 0, 1, 1, 1, 1],
...repeat(2, [2, 1, 0, 0, 29, 29, 1, 1]),
...repeat(2, [3, 1, 0, 0, 30, 30, 21, 21]),
[4, 1, 0, 0, 31, 31, 22, 22],
[5, 1, 0, 1, 32, 32, 23, 23],
[6, 1, 1, 2, 33, 33, 24, 24],
[7, 1, 2, 2, 34, 34, 25, 25],
[8, 1, 2, 2, 34, 34, 25, 25],
[9, 1, 2, 2, 34, 34, 26, 26],
], sitShadow);
export const standUp = createBodyAnimation('stand-up', 24, false, [
[9, 1, 2, 2, 34, 34, 26, 26],
[8, 1, 2, 2, 34, 34, 25, 25],
[7, 1, 2, 2, 34, 34, 25, 25],
[6, 1, 1, 2, 33, 33, 24, 24],
[5, 1, 0, 1, 32, 32, 23, 23],
[4, 1, 0, 0, 31, 31, 22, 22],
...repeat(2, [3, 1, 0, 0, 30, 30, 21, 21]),
[1, 1, 0, 0, 1, 1, 1, 1],
[9, 1, 2, 2, 34, 34, 26, 26],
[8, 1, 2, 2, 34, 34, 25, 25],
[7, 1, 2, 2, 34, 34, 25, 25],
[6, 1, 1, 2, 33, 33, 24, 24],
[5, 1, 0, 1, 32, 32, 23, 23],
[4, 1, 0, 0, 31, 31, 22, 22],
...repeat(2, [3, 1, 0, 0, 30, 30, 21, 21]),
[1, 1, 0, 0, 1, 1, 1, 1],
], sitShadow.slice(2).reverse());
export const sitToTrot = createBodyAnimation('sit-to-trot', 24, false, [
[7, 1, 2, 2, 34, 35, 24, 25, 0, -1, 0, 0, 0, 2, 1, 1, 0, -1],
[6, 1, 1, 2, 27, 36, 23, 24, 0, -2, 0, 0, 0, -2, 0, 0, 0, 1],
[5, 1, 0, 1, 5, 13, 5, 23, 0, -2],
[4, 1, 0, 0, 6, 14, 6, 5, 0, -2],
[3, 1, 0, 0, 7, 15, 7, 15, 0, -2],
[2, 1, 0, 0, 8, 16, 8, 16, 0, -1],
[7, 1, 2, 2, 34, 35, 24, 25, 0, -1, 0, 0, 0, 2, 1, 1, 0, -1],
[6, 1, 1, 2, 27, 36, 23, 24, 0, -2, 0, 0, 0, -2, 0, 0, 0, 1],
[5, 1, 0, 1, 5, 13, 5, 23, 0, -2],
[4, 1, 0, 0, 6, 14, 6, 5, 0, -2],
[3, 1, 0, 0, 7, 15, 7, 15, 0, -2],
[2, 1, 0, 0, 8, 16, 8, 16, 0, -1],
], [[0, 6], [0, 5], [0, 4], [0, 3], [0, 1], [0, 0]]);
export const lie = createBodyAnimation('lie', 24, true, [
[15, 1, 0, 2, 38, 38, 26, 26],
[15, 1, 0, 2, 38, 38, 26, 26],
], [[3, 3]]);
const lieShadow = [[0, 6], [0, 6], [1, 5], [2, 4], [3, 3], [3, 3], [3, 3]];
export const lieDown = createBodyAnimation('lie-down', 24, false, [
[9, 1, 2, 2, 34, 34, 26, 26],
[10, 1, 2, 2, 35, 34, 26, 26, 0, 0, 0, 0, 0, 0, 0, -1],
[11, 1, 1, 2, 36, 36, 26, 26, 0, 0, 0, 0, 0, 0, 1],
[12, 1, 1, 2, 37, 37, 26, 26, 0, 0, 0, 0, 0, 0, 1],
[13, 1, 0, 2, 38, 38, 26, 26, 0, 0, 0, 0, 0, 0, 1],
...repeat(2, [14, 1, 0, 2, 38, 38, 26, 26]),
[9, 1, 2, 2, 34, 34, 26, 26],
[10, 1, 2, 2, 35, 34, 26, 26, 0, 0, 0, 0, 0, 0, 0, -1],
[11, 1, 1, 2, 36, 36, 26, 26, 0, 0, 0, 0, 0, 0, 1],
[12, 1, 1, 2, 37, 37, 26, 26, 0, 0, 0, 0, 0, 0, 1],
[13, 1, 0, 2, 38, 38, 26, 26, 0, 0, 0, 0, 0, 0, 1],
...repeat(2, [14, 1, 0, 2, 38, 38, 26, 26]),
], lieShadow);
export const sitUp = createBodyAnimation('sit-up', 24, false, [
...repeat(2, [14, 1, 0, 2, 38, 38, 26, 26]),
[13, 1, 0, 2, 38, 38, 26, 26],
[12, 1, 1, 2, 37, 37, 26, 26],
[11, 1, 1, 2, 36, 36, 26, 26],
[10, 1, 2, 2, 35, 34, 26, 26, 0, 0, 0, 0, 0, 0, 0, -1],
[9, 1, 2, 2, 34, 34, 26, 26],
...repeat(2, [14, 1, 0, 2, 38, 38, 26, 26]),
[13, 1, 0, 2, 38, 38, 26, 26],
[12, 1, 1, 2, 37, 37, 26, 26],
[11, 1, 1, 2, 36, 36, 26, 26],
[10, 1, 2, 2, 35, 34, 26, 26, 0, 0, 0, 0, 0, 0, 0, -1],
[9, 1, 2, 2, 34, 34, 26, 26],
], lieShadow.slice().reverse());
export const lieToTrot = createBodyAnimation('lie-to-trot', 24, false, [
[1, 1, 0, 1, 36, 37, 24, 25, 4, 8, 0, 1, 0, 0, 0, 0, 0, 1],
[1, 1, 0, 0, 30, 12, 23, 24, 2, 5, 0, 0, 0, -3],
[1, 1, 0, 0, 5, 13, 5, 23, 1, 1],
[1, 1, 0, 0, 6, 14, 6, 21, 0, 0, 0, -1],
[1, 1, 0, 0, 7, 15, 7, 15, 0, -1, 0, -1],
[1, 1, 0, 0, 8, 16, 8, 16, 0, -2],
[1, 1, 0, 1, 36, 37, 24, 25, 4, 8, 0, 1, 0, 0, 0, 0, 0, 1],
[1, 1, 0, 0, 30, 12, 23, 24, 2, 5, 0, 0, 0, -3],
[1, 1, 0, 0, 5, 13, 5, 23, 1, 1],
[1, 1, 0, 0, 6, 14, 6, 21, 0, 0, 0, -1],
[1, 1, 0, 0, 7, 15, 7, 15, 0, -1, 0, -1],
[1, 1, 0, 0, 8, 16, 8, 16, 0, -2],
], [[2, 1], [1, 0], ...repeat(4, [0, 0])]);
export const fly = createBodyAnimation('fly', 16, true, [
[1, 1, 3, 0, 8, 10, 6, 5, 0, -16],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 6, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 7, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 8, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 9, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 10, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 11, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 12, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 3, 0, 8, 10, 6, 5, 0, -16],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 6, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 7, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 8, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 9, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 10, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 11, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 12, 0, 8, 10, 6, 5, 0, -17],
]);
export const boopFly = createBodyAnimation('boop-fly', 16, false, [
[1, 1, 3, 0, 8, 10, 6, 5, 0, -16],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 5, 0, 20, 10, 6, 5, 0, -14],
[1, 1, 6, 0, 21, 10, 6, 5, 0, -14],
[1, 1, 7, 0, 22, 10, 6, 5, -1, -15],
[1, 1, 8, 0, 23, 10, 5, 5, -1, -17, -1, 0, -1, -1, 2],
[1, 1, 9, 0, 23, 10, 4, 5, -1, -18, -1, 0, -1, -1, 2],
[1, 1, 10, 0, 23, 10, 4, 4, -1, -18, -1, 0, -1, -1, 2],
[1, 1, 11, 0, 23, 10, 4, 3, -1, -18, -1, 0, -1, -1, 2],
[1, 1, 12, 0, 22, 10, 4, 3, 0, -17, -1, 0, 0, 0, 2],
[1, 1, 3, 0, 21, 10, 5, 4, 0, -16, 0, 0, 0, 0, 2],
[1, 1, 4, 0, 14, 10, 6, 5, 0, -15, 0, 0, 0, 0, 2]
[1, 1, 3, 0, 8, 10, 6, 5, 0, -16],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 5, 0, 20, 10, 6, 5, 0, -14],
[1, 1, 6, 0, 21, 10, 6, 5, 0, -14],
[1, 1, 7, 0, 22, 10, 6, 5, -1, -15],
[1, 1, 8, 0, 23, 10, 5, 5, -1, -17, -1, 0, -1, -1, 2],
[1, 1, 9, 0, 23, 10, 4, 5, -1, -18, -1, 0, -1, -1, 2],
[1, 1, 10, 0, 23, 10, 4, 4, -1, -18, -1, 0, -1, -1, 2],
[1, 1, 11, 0, 23, 10, 4, 3, -1, -18, -1, 0, -1, -1, 2],
[1, 1, 12, 0, 22, 10, 4, 3, 0, -17, -1, 0, 0, 0, 2],
[1, 1, 3, 0, 21, 10, 5, 4, 0, -16, 0, 0, 0, 0, 2],
[1, 1, 4, 0, 14, 10, 6, 5, 0, -15, 0, 0, 0, 0, 2]
]);
export const boopFlyBug = createBodyAnimation('boop-fly-bug', 20, false, [
[1, 1, 3, 0, 8, 10, 6, 5, 0, -16],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 5, 0, 20, 10, 6, 5, 0, -14],
[1, 1, 4, 0, 21, 10, 6, 5, 0, -14],
[1, 1, 3, 0, 22, 10, 6, 5, -1, -15],
[1, 1, 4, 0, 23, 10, 5, 5, -1, -17, -1, 0, -1, -1, 2],
[1, 1, 5, 0, 23, 10, 4, 5, -1, -18, -1, 0, -1, -1, 2],
[1, 1, 4, 0, 23, 10, 4, 4, -1, -18, -1, 0, -1, -1, 2],
[1, 1, 3, 0, 23, 10, 4, 3, -1, -18, -1, 0, -1, -1, 2],
[1, 1, 4, 0, 22, 10, 4, 3, 0, -17, -1, 0, 0, 0, 2],
[1, 1, 5, 0, 21, 10, 5, 4, 0, -16, 0, 0, 0, 0, 2],
[1, 1, 4, 0, 14, 10, 6, 5, 0, -15, 0, 0, 0, 0, 2]
[1, 1, 3, 0, 8, 10, 6, 5, 0, -16],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 5, 0, 20, 10, 6, 5, 0, -14],
[1, 1, 4, 0, 21, 10, 6, 5, 0, -14],
[1, 1, 3, 0, 22, 10, 6, 5, -1, -15],
[1, 1, 4, 0, 23, 10, 5, 5, -1, -17, -1, 0, -1, -1, 2],
[1, 1, 5, 0, 23, 10, 4, 5, -1, -18, -1, 0, -1, -1, 2],
[1, 1, 4, 0, 23, 10, 4, 4, -1, -18, -1, 0, -1, -1, 2],
[1, 1, 3, 0, 23, 10, 4, 3, -1, -18, -1, 0, -1, -1, 2],
[1, 1, 4, 0, 22, 10, 4, 3, 0, -17, -1, 0, 0, 0, 2],
[1, 1, 5, 0, 21, 10, 5, 4, 0, -16, 0, 0, 0, 0, 2],
[1, 1, 4, 0, 14, 10, 6, 5, 0, -15, 0, 0, 0, 0, 2]
]);
export const flyUp = createBodyAnimation('fly-up', 16, false, [
[1, 1, 11, 0, 1, 1, 1, 1],
[1, 1, 12, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, -1, 0, -1, 0, -1, 0, -1],
[2, 1, 3, 0, 29, 29, 21, 21, 0, 2, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 4, 0, 29, 29, 21, 21, 0, 2, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 5, 0, 29, 29, 21, 21, 0, 2, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[1, 1, 6, 0, 1, 1, 1, 1],
[1, 1, 7, 0, 6, 7, 5, 5, 0, -10, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1],
[1, 1, 8, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 9, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 10, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 11, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 12, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 11, 0, 1, 1, 1, 1],
[1, 1, 12, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, -1, 0, -1, 0, -1, 0, -1],
[2, 1, 3, 0, 29, 29, 21, 21, 0, 2, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 4, 0, 29, 29, 21, 21, 0, 2, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 5, 0, 29, 29, 21, 21, 0, 2, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[1, 1, 6, 0, 1, 1, 1, 1],
[1, 1, 7, 0, 6, 7, 5, 5, 0, -10, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1],
[1, 1, 8, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 9, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 10, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 11, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 12, 0, 8, 10, 6, 5, 0, -17],
]);
export const trotToFly = createBodyAnimation('trot-to-fly', 20, false, [
[1, 1, 11, 0, 6, 14, 6, 14, 0, -2],
[1, 1, 12, 0, 7, 15, 7, 15, 0, -2],
[1, 1, 3, 0, 8, 16, 8, 16, 0, -1],
[1, 1, 4, 0, 9, 17, 9, 17, 0, 1, 0, 1, 0, 0, 0, -1, 0, -1],
[1, 1, 5, 0, 10, 2, 10, 2, 0, 3, 0, 1, 0, 0, 0, -2, 0, -2],
[1, 1, 6, 0, 11, 3, 11, 3],
[1, 1, 7, 0, 11, 4, 11, 4, 0, -10],
[1, 1, 8, 0, 10, 5, 9, 5, 0, -15],
[1, 1, 9, 0, 9, 10, 6, 6, 0, -17],
[1, 1, 10, 0, 8, 10, 6, 7, 0, -18],
[1, 1, 11, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 12, 0, 8, 10, 6, 5, 0, -17]
[1, 1, 11, 0, 6, 14, 6, 14, 0, -2],
[1, 1, 12, 0, 7, 15, 7, 15, 0, -2],
[1, 1, 3, 0, 8, 16, 8, 16, 0, -1],
[1, 1, 4, 0, 9, 17, 9, 17, 0, 1, 0, 1, 0, 0, 0, -1, 0, -1],
[1, 1, 5, 0, 10, 2, 10, 2, 0, 3, 0, 1, 0, 0, 0, -2, 0, -2],
[1, 1, 6, 0, 11, 3, 11, 3],
[1, 1, 7, 0, 11, 4, 11, 4, 0, -10],
[1, 1, 8, 0, 10, 5, 9, 5, 0, -15],
[1, 1, 9, 0, 9, 10, 6, 6, 0, -17],
[1, 1, 10, 0, 8, 10, 6, 7, 0, -18],
[1, 1, 11, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 12, 0, 8, 10, 6, 5, 0, -17]
]);
export const trotToFlyBug = createBodyAnimation('trot-to-fly-bug', 20, false, [
[1, 1, 3, 0, 6, 14, 6, 14, 0, -2],
[1, 1, 4, 0, 7, 15, 7, 15, 0, -2],
[1, 1, 5, 0, 8, 16, 8, 16, 0, -1],
[1, 1, 3, 0, 9, 17, 9, 17, 0, 1, 0, 1, 0, 0, 0, -1, 0, -1],
[1, 1, 4, 0, 10, 2, 10, 2, 0, 3, 0, 1, 0, 0, 0, -2, 0, -2],
[1, 1, 5, 0, 11, 3, 11, 3],
[1, 1, 3, 0, 11, 4, 11, 4, 0, -10],
[1, 1, 4, 0, 10, 5, 9, 5, 0, -15],
[1, 1, 5, 0, 8, 10, 6, 6, 0, -17],
[1, 1, 3, 0, 8, 10, 6, 7, 0, -18],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -17]
[1, 1, 3, 0, 6, 14, 6, 14, 0, -2],
[1, 1, 4, 0, 7, 15, 7, 15, 0, -2],
[1, 1, 5, 0, 8, 16, 8, 16, 0, -1],
[1, 1, 3, 0, 9, 17, 9, 17, 0, 1, 0, 1, 0, 0, 0, -1, 0, -1],
[1, 1, 4, 0, 10, 2, 10, 2, 0, 3, 0, 1, 0, 0, 0, -2, 0, -2],
[1, 1, 5, 0, 11, 3, 11, 3],
[1, 1, 3, 0, 11, 4, 11, 4, 0, -10],
[1, 1, 4, 0, 10, 5, 9, 5, 0, -15],
[1, 1, 5, 0, 8, 10, 6, 6, 0, -17],
[1, 1, 3, 0, 8, 10, 6, 7, 0, -18],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -17]
]);
export const flyToTrot = createBodyAnimation('fly-to-trot', 20, false, [
[1, 1, 3, 0, 8, 10, 6, 10, 0, -16],
[1, 1, 4, 0, 8, 11, 5, 11, 0, -15],
[1, 1, 5, 0, 8, 12, 4, 12, 0, -12],
[1, 1, 6, 0, 7, 13, 5, 13, 0, -8],
[1, 1, 6, 0, 7, 14, 6, 14, 0, -6],
[1, 1, 6, 0, 7, 15, 7, 15, 0, -4],
[1, 1, 7, 0, 8, 16, 8, 16, 0, -1],
[1, 1, 11, 0, 9, 17, 9, 17],
[1, 1, 0, 0, 10, 2, 10, 2, 0, 3, 0, -1, 0, 0, 0, -2, 0, -2]
[1, 1, 3, 0, 8, 10, 6, 10, 0, -16],
[1, 1, 4, 0, 8, 11, 5, 11, 0, -15],
[1, 1, 5, 0, 8, 12, 4, 12, 0, -12],
[1, 1, 6, 0, 7, 13, 5, 13, 0, -8],
[1, 1, 6, 0, 7, 14, 6, 14, 0, -6],
[1, 1, 6, 0, 7, 15, 7, 15, 0, -4],
[1, 1, 7, 0, 8, 16, 8, 16, 0, -1],
[1, 1, 11, 0, 9, 17, 9, 17],
[1, 1, 0, 0, 10, 2, 10, 2, 0, 3, 0, -1, 0, 0, 0, -2, 0, -2]
// [1, 1, 4, 0, 8, 10, 6, 10, 0, -16],
// [1, 1, 5, 0, 8, 10, 6, 10, 0, -18],
// [1, 1, 7, 0, 8, 11, 5, 11, 0, -20],
// [1, 1, 8, 0, 10, 12, 4, 12, 0, -21],
// [1, 1, 9, 0, 11, 13, 4, 13, 0, -20],
// [1, 1, 10, 0, 12, 14, 5, 14, 0, -18],
// [1, 1, 11, 0, 13, 15, 7, 15, 0, -14, 0, -1],
// [1, 1, 4, 0, 14, 16, 8, 16, 0, 0, 0, -1],
// [1, 1, 5, 0, 2, 17, 9, 17, 0, 2, 0, 0, 0, -1, 0, -2, 0, -2, 0, -2],
// [1, 1, 6, 0, 3, 2, 10, 2, 0, 1, 0, 0, 0, -1, 0, 0, 0, 0, 0, 2]
// [1, 1, 4, 0, 8, 10, 6, 10, 0, -16],
// [1, 1, 5, 0, 8, 10, 6, 10, 0, -18],
// [1, 1, 7, 0, 8, 11, 5, 11, 0, -20],
// [1, 1, 8, 0, 10, 12, 4, 12, 0, -21],
// [1, 1, 9, 0, 11, 13, 4, 13, 0, -20],
// [1, 1, 10, 0, 12, 14, 5, 14, 0, -18],
// [1, 1, 11, 0, 13, 15, 7, 15, 0, -14, 0, -1],
// [1, 1, 4, 0, 14, 16, 8, 16, 0, 0, 0, -1],
// [1, 1, 5, 0, 2, 17, 9, 17, 0, 2, 0, 0, 0, -1, 0, -2, 0, -2, 0, -2],
// [1, 1, 6, 0, 3, 2, 10, 2, 0, 1, 0, 0, 0, -1, 0, 0, 0, 0, 0, 2]
]);
export const flyToTrotBug = createBodyAnimation('fly-to-trot-bug', 20, false, [
[1, 1, 3, 0, 8, 10, 6, 10, 0, -16],
[1, 1, 4, 0, 8, 11, 5, 11, 0, -15],
[1, 1, 5, 0, 8, 12, 4, 12, 0, -12],
[1, 1, 4, 0, 7, 13, 5, 13, 0, -8],
[1, 1, 3, 0, 7, 14, 6, 14, 0, -6],
[1, 1, 4, 0, 7, 15, 7, 15, 0, -4],
[1, 1, 5, 0, 8, 16, 8, 16, 0, -1],
[1, 1, 4, 0, 9, 17, 9, 17],
[1, 1, 3, 0, 10, 2, 10, 2, 0, 3, 0, -1, 0, 0, 0, -2, 0, -2]
[1, 1, 3, 0, 8, 10, 6, 10, 0, -16],
[1, 1, 4, 0, 8, 11, 5, 11, 0, -15],
[1, 1, 5, 0, 8, 12, 4, 12, 0, -12],
[1, 1, 4, 0, 7, 13, 5, 13, 0, -8],
[1, 1, 3, 0, 7, 14, 6, 14, 0, -6],
[1, 1, 4, 0, 7, 15, 7, 15, 0, -4],
[1, 1, 5, 0, 8, 16, 8, 16, 0, -1],
[1, 1, 4, 0, 9, 17, 9, 17],
[1, 1, 3, 0, 10, 2, 10, 2, 0, 3, 0, -1, 0, 0, 0, -2, 0, -2]
// [1, 1, 4, 0, 8, 10, 6, 10, 0, -16],
// [1, 1, 5, 0, 8, 10, 6, 10, 0, -18],
// [1, 1, 4, 0, 8, 11, 5, 11, 0, -20],
// [1, 1, 3, 0, 10, 12, 4, 12, 0, -21],
// [1, 1, 4, 0, 11, 13, 4, 13, 0, -20],
// [1, 1, 5, 0, 12, 14, 5, 14, 0, -18],
// [1, 1, 4, 0, 13, 15, 7, 15, 0, -14, 0, -1],
// [1, 1, 3, 0, 14, 16, 8, 16, 0, 0, 0, -1],
// [1, 1, 4, 0, 2, 17, 9, 17, 0, 2, 0, 0, 0, -1, 0, -2, 0, -2, 0, -2],
// [1, 1, 5, 0, 3, 2, 10, 2, 0, 1, 0, 0, 0, -1, 0, 0, 0, 0, 0, 2]
// [1, 1, 4, 0, 8, 10, 6, 10, 0, -16],
// [1, 1, 5, 0, 8, 10, 6, 10, 0, -18],
// [1, 1, 4, 0, 8, 11, 5, 11, 0, -20],
// [1, 1, 3, 0, 10, 12, 4, 12, 0, -21],
// [1, 1, 4, 0, 11, 13, 4, 13, 0, -20],
// [1, 1, 5, 0, 12, 14, 5, 14, 0, -18],
// [1, 1, 4, 0, 13, 15, 7, 15, 0, -14, 0, -1],
// [1, 1, 3, 0, 14, 16, 8, 16, 0, 0, 0, -1],
// [1, 1, 4, 0, 2, 17, 9, 17, 0, 2, 0, 0, 0, -1, 0, -2, 0, -2, 0, -2],
// [1, 1, 5, 0, 3, 2, 10, 2, 0, 1, 0, 0, 0, -1, 0, 0, 0, 0, 0, 2]
]);
export const flyDown = createBodyAnimation('fly-down', 16, false, [
[1, 1, 3, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -12],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -10],
[1, 1, 6, 0, 8, 10, 6, 5, 0, -8],
[1, 1, 7, 0, 8, 10, 6, 5, 0, -6],
[1, 1, 11, 0, 8, 10, 6, 5, 0, -4],
[1, 1, 1, 0, 8, 10, 6, 5, 0, -2]
[1, 1, 3, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -12],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -10],
[1, 1, 6, 0, 8, 10, 6, 5, 0, -8],
[1, 1, 7, 0, 8, 10, 6, 5, 0, -6],
[1, 1, 11, 0, 8, 10, 6, 5, 0, -4],
[1, 1, 1, 0, 8, 10, 6, 5, 0, -2]
]);
export const flyBug = createBodyAnimation('fly-bug', 24, true, [
[1, 1, 3, 0, 8, 10, 6, 5, 0, -16],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -16],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 3, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 3, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -16],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 3, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 3, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -17]
[1, 1, 3, 0, 8, 10, 6, 5, 0, -16],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -16],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 3, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 3, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -16],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 3, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 3, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -17]
]);
export const flyUpBug = createBodyAnimation('fly-up-bug', 16, false, [
[1, 1, 3, 0, 1, 1, 1, 1],
[1, 1, 4, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, -1, 0, -1, 0, -1, 0, -1],
[2, 1, 5, 0, 29, 29, 21, 21, 0, 2, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 4, 0, 29, 29, 21, 21, 0, 2, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 5, 0, 29, 29, 21, 21, 0, 2, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[1, 1, 4, 0, 1, 1, 1, 1],
[1, 1, 5, 0, 6, 7, 5, 5, 0, -10, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 3, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -17]
[1, 1, 3, 0, 1, 1, 1, 1],
[1, 1, 4, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, -1, 0, -1, 0, -1, 0, -1],
[2, 1, 5, 0, 29, 29, 21, 21, 0, 2, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 4, 0, 29, 29, 21, 21, 0, 2, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[2, 1, 5, 0, 29, 29, 21, 21, 0, 2, 0, 2, 0, -2, 0, -2, 2, -2, 2, -2],
[1, 1, 4, 0, 1, 1, 1, 1],
[1, 1, 5, 0, 6, 7, 5, 5, 0, -10, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15],
[1, 1, 3, 0, 8, 10, 6, 5, 0, -17],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -18],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -17]
]);
export const flyDownBug = createBodyAnimation('fly-down-bug', 16, false, [
[1, 1, 3, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -12],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -10],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -8],
[1, 1, 3, 0, 8, 10, 6, 5, 0, -6],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -4],
[1, 1, 1, 0, 8, 10, 6, 5, 0, -2]
[1, 1, 3, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -12],
[1, 1, 5, 0, 8, 10, 6, 5, 0, -10],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -8],
[1, 1, 3, 0, 8, 10, 6, 5, 0, -6],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -4],
[1, 1, 1, 0, 8, 10, 6, 5, 0, -2]
]);
export const swing = createBodyAnimation('swing', 12, false, [
...repeat(1, [1, 1, 0, 0, 1, 1, 1, 1]),
...repeat(3, [2, 1, 0, 0, 12, 17, 11, 11, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1]),
...repeat(1, [1, 1, 0, 0, 1, 1, 1, 1]),
...repeat(3, [2, 1, 0, 0, 12, 17, 11, 11, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1]),
]);
export const flyAnims = [undefined, fly, fly, fly, flyBug];
@@ -464,78 +464,78 @@ export const flyUpAnims = [undefined, flyUp, flyUp, flyUp, flyUpBug];
export const flyDownAnims = [undefined, flyDown, flyDown, flyDown, flyDownBug];
export const animations = [
stand, trot, boop, boopSit, boopLie, boopSwim, boopFly, boopFlyBug, sit, sitDown, standUp, lie, lieDown, sitUp,
fly, flyBug, flyUp, flyUpBug, flyDown, flyDownBug, sitToTrot, lieToTrot, flyToTrot, flyToTrotBug,
swim, trotToSwim, swimToTrot, flyToSwim, swimToFly,
stand, trot, boop, boopSit, boopLie, boopSwim, boopFly, boopFlyBug, sit, sitDown, standUp, lie, lieDown, sitUp,
fly, flyBug, flyUp, flyUpBug, flyDown, flyDownBug, sitToTrot, lieToTrot, flyToTrot, flyToTrotBug,
swim, trotToSwim, swimToTrot, flyToSwim, swimToFly,
];
export const sitDownUp = mergeAnimations('sit', 24, false, [...repeat(12, stand), sitDown, ...repeat(12, sit), standUp]);
export const lieDownUp = mergeAnimations('lie', 24, false, [...repeat(12, sit), lieDown, ...repeat(12, lie), sitUp]);
export function mergeAnimations(name: string, fps: number, loop: boolean, animations: BodyAnimation[]): BodyAnimation {
return {
name,
fps,
loop,
frames: flatten(animations.map(a => a.frames)),
shadow: flatten(animations.map(a => a.shadow || a.frames.map(() => ({ frame: 0, offset: 0 })))),
};
return {
name,
fps,
loop,
frames: flatten(animations.map(a => a.frames)),
shadow: flatten(animations.map(a => a.shadow || a.frames.map(() => ({ frame: 0, offset: 0 })))),
};
}
// head animations
export function createHeadFrame([headX = 0, headY = 0, left = 0, right = 0, mouth = 0]: number[]): HeadAnimationFrame {
return { headX, headY, left, right, mouth };
return { headX, headY, left, right, mouth };
}
export function createHeadAnimation(name: string, fps: number, loop: boolean, frames: number[][]): HeadAnimation {
return { name, fps, loop, frames: frames.map(createHeadFrame) };
return { name, fps, loop, frames: frames.map(createHeadFrame) };
}
export const smile = createHeadAnimation('smile', 24, true, [
[0, 0, 1, 1, 0],
[0, 0, 1, 1, 0],
]);
export const nom = createHeadAnimation('nom', 12, true, [
[0, 0, 1, 1, 0],
[0, 0, 1, 1, 25],
[0, 0, 1, 1, 0],
[0, 0, 1, 1, 25],
]);
export const laugh = createHeadAnimation('laugh', 8, false, [
...repeat(4, [0, 0, 14, 14, 5], [0, 1, 14, 14, 5]),
...repeat(4, [0, 0, 14, 14, 5], [0, 1, 14, 14, 5]),
]);
export const yawn = createHeadAnimation('yawn', 12, false, [
[0, 0, 3, 3, 8],
...repeat(18, [1, -1, 12, 12, 16]),
...repeat(8, [0, 0, 12, 12, 12]),
[0, 0, 18, 18, 2],
[0, 0, 3, 3, 8],
...repeat(18, [1, -1, 12, 12, 16]),
...repeat(8, [0, 0, 12, 12, 12]),
[0, 0, 18, 18, 2],
]);
export const surprise = createHeadAnimation('surprise', 8, false, [
[0, 1, 6, 6, 1],
...repeat(10, [0, 0, 1, 1, 12]),
[0, 1, 6, 6, 1],
...repeat(10, [0, 0, 1, 1, 12]),
]);
export const excite = createHeadAnimation('excite', 8, false, [
[0, 1, 6, 6, 0],
...repeat(10, [0, 0, 1, 1, 5]),
[0, 1, 6, 6, 0],
...repeat(10, [0, 0, 1, 1, 5]),
]);
export const surpriseSad = createHeadAnimation('surpriseSad', 8, false, [
[0, 1, 15, 15, 8],
...repeat(8, [0, 0, 15, 15, 8]),
[0, 1, 15, 15, 8],
...repeat(8, [0, 0, 15, 15, 8]),
]);
export const sneeze = createHeadAnimation('sneeze', 12, false, [
[0, 0, 18, 18, 8],
...repeat(2, [1, -1, 18, 18, 16]),
...repeat(8, [-1, 1, 23, 23, 13]),
...repeat(4, [0, 0, 18, 18, 7]),
[0, 0, 18, 18, 8],
...repeat(2, [1, -1, 18, 18, 16]),
...repeat(8, [-1, 1, 23, 23, 13]),
...repeat(4, [0, 0, 18, 18, 7]),
]);
export const headAnimations = [
smile, nom, laugh, yawn, surprise, surpriseSad, sneeze, excite,
smile, nom, laugh, yawn, surprise, surpriseSad, sneeze, excite,
];
// default animations
+554 -554
View File
File diff suppressed because it is too large Load Diff
+42 -42
View File
@@ -5,52 +5,52 @@ import { stand } from './ponyAnimations';
const defaultBlushColor = blushColor(0);
export function defaultPonyState(): PonyState {
return {
animation: stand,
animationFrame: 0,
headAnimation: undefined,
headAnimationFrame: 0,
headTurned: false,
headTilt: 0,
headTurn: 0,
blinkFrame: 0,
blushColor: defaultBlushColor,
holding: undefined,
expression: undefined,
drawFaceExtra: undefined,
flags: PonyStateFlags.None,
};
return {
animation: stand,
animationFrame: 0,
headAnimation: undefined,
headAnimationFrame: 0,
headTurned: false,
headTilt: 0,
headTurn: 0,
blinkFrame: 0,
blushColor: defaultBlushColor,
holding: undefined,
expression: undefined,
drawFaceExtra: undefined,
flags: PonyStateFlags.None,
};
}
export function isStateEqual(a: PonyState, b: PonyState) {
return a.animation === b.animation &&
a.animationFrame === b.animationFrame &&
a.headAnimation === b.headAnimation &&
a.headAnimationFrame === b.headAnimationFrame &&
a.headTurned === b.headTurned &&
a.headTilt === b.headTilt &&
a.headTurn === b.headTurn &&
a.blinkFrame === b.blinkFrame &&
a.blushColor === b.blushColor &&
a.holding === b.holding &&
a.expression === b.expression &&
a.drawFaceExtra === b.drawFaceExtra &&
a.flags === b.flags;
return a.animation === b.animation &&
a.animationFrame === b.animationFrame &&
a.headAnimation === b.headAnimation &&
a.headAnimationFrame === b.headAnimationFrame &&
a.headTurned === b.headTurned &&
a.headTilt === b.headTilt &&
a.headTurn === b.headTurn &&
a.blinkFrame === b.blinkFrame &&
a.blushColor === b.blushColor &&
a.holding === b.holding &&
a.expression === b.expression &&
a.drawFaceExtra === b.drawFaceExtra &&
a.flags === b.flags;
}
export function defaultDrawPonyOptions(): DrawPonyOptions {
return {
flipped: false,
selected: false,
shadow: false,
extra: false,
toy: 0,
swimming: false,
bounce: false,
shadowColor: SHADOW_COLOR,
noEars: false,
no: NoDraw.None,
useAllHooves: false,
gameTime: 0,
};
return {
flipped: false,
selected: false,
shadow: false,
extra: false,
toy: 0,
swimming: false,
bounce: false,
shadowColor: SHADOW_COLOR,
noEars: false,
no: NoDraw.None,
useAllHooves: false,
gameTime: 0,
};
}
+25 -25
View File
@@ -1,13 +1,13 @@
import { animatorState as state, animatorTransition as transition, anyState, AnimatorState } from '../common/animator';
import {
stand, sit, sitDown, standUp, lie, lieDown, sitUp, flyBug, fly, flyUp, flyDown, flyUpBug, flyDownBug,
trot, boop, boopSit, swim, sitToTrot, lieToTrot, boopLie, trotToFly, trotToFlyBug, boopFly, boopFlyBug,
flyToTrot, flyToTrotBug, swing, swimToTrot, trotToSwim, swimToFly, flyToSwim, boopSwim, swimToFlyBug, flyToSwimBug
stand, sit, sitDown, standUp, lie, lieDown, sitUp, flyBug, fly, flyUp, flyDown, flyUpBug, flyDownBug,
trot, boop, boopSit, swim, sitToTrot, lieToTrot, boopLie, trotToFly, trotToFlyBug, boopFly, boopFlyBug,
flyToTrot, flyToTrotBug, swing, swimToTrot, trotToSwim, swimToFly, flyToSwim, boopSwim, swimToFlyBug, flyToSwimBug
} from './ponyAnimations';
import { BodyAnimation } from '../common/interfaces';
function n(value: string) {
return (DEVELOPMENT || SERVER) ? value : '';
return (DEVELOPMENT || SERVER) ? value : '';
}
export const standing = state(n('standing'), stand);
@@ -46,12 +46,12 @@ export const flyingToTrotting = state(n('flying-to-trotting'), flyToTrot, { bug:
export const swinging = state(n('swinging'), swing);
export const ponyStates = [
anyState, standing, trotting, swimming, swimmingToTrotting, trottingToSwimming,
booping, boopingSitting, boopingLying, boopingFlying,
sitting, sittingDown, standingUp, sittingToTrotting,
lying, lyingDown, sittingUp, lyingToTrotting,
hovering, flying, flyingUp, flyingDown, trottingToFlying, flyingToTrotting,
swinging, swimmingToFlying, flyingToSwimming, boopingSwimming,
anyState, standing, trotting, swimming, swimmingToTrotting, trottingToSwimming,
booping, boopingSitting, boopingLying, boopingFlying,
sitting, sittingDown, standingUp, sittingToTrotting,
lying, lyingDown, sittingUp, lyingToTrotting,
hovering, flying, flyingUp, flyingDown, trottingToFlying, flyingToTrotting,
swinging, swimmingToFlying, flyingToSwimming, boopingSwimming,
];
transition(hovering, flyingDown, { exitAfter: 0 });
@@ -120,36 +120,36 @@ transition(standing, swinging, { exitAfter: 0 });
transition(swinging, standing);
export function isFlyingUp(state: AnimatorState<BodyAnimation> | undefined) {
return state === flyingUp || state === trottingToFlying || state === swimmingToFlying;
return state === flyingUp || state === trottingToFlying || state === swimmingToFlying;
}
export function isFlyingDown(state: AnimatorState<BodyAnimation> | undefined) {
return state === flyingDown || state === flyingToTrotting || state === flyingToSwimming;
return state === flyingDown || state === flyingToTrotting || state === flyingToSwimming;
}
export function isSwimmingState(state: AnimatorState<BodyAnimation> | undefined) {
return state === swimming || state === trottingToSwimming || state === swimmingToTrotting ||
state === flyingToSwimming || state === swimmingToFlying || state === boopingSwimming;
return state === swimming || state === trottingToSwimming || state === swimmingToTrotting ||
state === flyingToSwimming || state === swimmingToFlying || state === boopingSwimming;
}
export function isFlyingUpOrDown(state: AnimatorState<BodyAnimation> | undefined) {
return isFlyingUp(state) || isFlyingDown(state);
return isFlyingUp(state) || isFlyingDown(state);
}
export function isSittingDown(state: AnimatorState<BodyAnimation> | undefined) {
return state === sittingDown;
return state === sittingDown;
}
export function isSittingUp(state: AnimatorState<BodyAnimation> | undefined) {
return state === sittingUp;
return state === sittingUp;
}
export function toBoopState(state: AnimatorState<BodyAnimation>) {
switch (state) {
case standing: return booping;
case sitting: return boopingSitting;
case lying: return boopingLying;
case hovering: return boopingFlying;
case swimming: return boopingSwimming;
default: return undefined;
}
switch (state) {
case standing: return booping;
case sitting: return boopingSitting;
case lying: return boopingLying;
case hovering: return boopingFlying;
case swimming: return boopingSwimming;
default: return undefined;
}
}
+64 -64
View File
@@ -2,7 +2,7 @@
import { range, dropRight, compact, max, zip } from 'lodash';
import {
Eye, Iris, Muzzle, ExpressionExtra, Sprite, ColorExtraSets, PonyInfoBase, SpriteSetBase, ColorExtra, ColorExtraSet
Eye, Iris, Muzzle, ExpressionExtra, Sprite, ColorExtraSets, PonyInfoBase, SpriteSetBase, ColorExtra, ColorExtraSet
} from '../common/interfaces';
import * as sprites from '../generated/sprites';
import { HEAD_ACCESSORY_OFFSETS, EXTRA_ACCESSORY_OFFSETS, EAR_ACCESSORY_OFFSETS } from '../common/offsets';
@@ -20,14 +20,14 @@ export type Sprites = (Sprite | undefined)[];
export type Sets = ColorExtraSets[]; // [frame][type][pattern]
export const headCenter = [
undefined,
[[0].map(i => sprites.head[2]![0]![i])],
undefined,
[[0].map(i => sprites.head[2]![0]![i])],
];
export const claws: Sets = sprites.frontLegHooves
.map(f => f && [undefined, undefined, undefined, f[4], undefined, undefined]);
.map(f => f && [undefined, undefined, undefined, f[4], undefined, undefined]);
export const frontHooves: Sets = sprites.frontLegHooves
.map(f => f && [...f.slice(0, 4), ...f.slice(5)]);
.map(f => f && [...f.slice(0, 4), ...f.slice(5)]);
export const frontHoovesInFront = [false, false, true, true, false, false];
export const backHoovesInFront = [false, false, true, false, false];
@@ -44,77 +44,77 @@ export const neckAccessories = createCompleteSets(sprites.neckAccessories, bodyF
export const waistAccessories = createCompleteSets(sprites.waistAccessories, bodyFrames + 1);
function frameType(sets: Sets, frame: number, type: number) {
const set = sets[frame];
return set && set[type];
const set = sets[frame];
return set && set[type];
}
function createCompleteSets(sets: Sets, frameCount: number): Sets {
const typeCount = sets.reduce((max, s) => Math.max(max, s ? s.length : 0), 0);
const typeRange = range(0, typeCount);
const result: Sets = [];
const typeCount = sets.reduce((max, s) => Math.max(max, s ? s.length : 0), 0);
const typeRange = range(0, typeCount);
const result: Sets = [];
for (let frame = 0; frame < frameCount; frame++) {
result.push(typeRange.map(type => frameType(sets, frame, type) || frameType(result, frame - 1, type)));
}
for (let frame = 0; frame < frameCount; frame++) {
result.push(typeRange.map(type => frameType(sets, frame, type) || frameType(result, frame - 1, type)));
}
return result;
return result;
}
export function canFly(info: PonyInfoBase<any, SpriteSetBase>) {
const type = info.wings && info.wings.type || 0;
return type > 0;
const type = info.wings && info.wings.type || 0;
return type > 0;
}
export function canMagic(info: PonyInfoBase<any, SpriteSetBase>) {
const type = info.horn && info.horn.type || 0;
return type === 1 || type === 2 || type === 3 || type === 14;
const type = info.horn && info.horn.type || 0;
return type === 1 || type === 2 || type === 3 || type === 14;
}
export function flipIris(iris: Iris): Iris {
if (iris === Iris.Left || iris === Iris.UpLeft) {
return iris + 1;
} else if (iris === Iris.Right || iris === Iris.UpRight) {
return iris - 1;
} else {
return iris;
}
if (iris === Iris.Left || iris === Iris.UpLeft) {
return iris + 1;
} else if (iris === Iris.Right || iris === Iris.UpRight) {
return iris - 1;
} else {
return iris;
}
}
export function flipFaceAccessoryType(type: number) {
if (type === 6) return 7;
if (type === 7) return 6;
if (type === 6) return 7;
if (type === 7) return 6;
if (type === 9) return 10;
if (type === 10) return 9;
if (type === 9) return 10;
if (type === 10) return 9;
return type;
return type;
}
export function flipFaceAccessoryPattern(type: number, pattern: number) {
if (type === 2) { // dark glasses
if (pattern === 1) return 2;
if (pattern === 2) return 1;
} else if (type === 11) { // large dark glasses
if (pattern === 1) return 2;
if (pattern === 2) return 1;
}
if (type === 2) { // dark glasses
if (pattern === 1) return 2;
if (pattern === 2) return 1;
} else if (type === 11) { // large dark glasses
if (pattern === 1) return 2;
if (pattern === 2) return 1;
}
return pattern;
return pattern;
}
export const defaultExpression = {
left: Eye.Neutral,
leftIris: Iris.Forward,
right: Eye.Neutral,
rightIris: Iris.Forward,
muzzle: Muzzle.Neutral,
extra: ExpressionExtra.None,
left: Eye.Neutral,
leftIris: Iris.Forward,
right: Eye.Neutral,
rightIris: Iris.Forward,
muzzle: Muzzle.Neutral,
extra: ExpressionExtra.None,
};
export const blinkFrames: Eye[][] = [];
function setupBlinkFrames(frames: Eye[]) {
dropRight(frames, 1).forEach((f, i) => blinkFrames[f] = blinkFrames[f] || frames.slice(i + 1));
dropRight(frames, 1).forEach((f, i) => blinkFrames[f] = blinkFrames[f] || frames.slice(i + 1));
}
setupBlinkFrames([Eye.Neutral, Eye.Neutral2, Eye.Neutral3, Eye.Neutral4, Eye.Neutral5, Eye.Closed]);
@@ -125,25 +125,25 @@ setupBlinkFrames([Eye.Angry, Eye.Angry2, Eye.Neutral4, Eye.Neutral5, Eye.Closed]
// sets
function mergeColorExtras(sprites: (ColorExtra | undefined)[]): ColorExtra | undefined {
const filtered = compact(sprites);
const filtered = compact(sprites);
return {
...filtered[0],
colors: max(filtered.map(x => x.colors || 0)),
colorMany: filtered.length > 1 ? filtered.map(x => x.color) : undefined,
};
return {
...filtered[0],
colors: max(filtered.map(x => x.colors || 0)),
colorMany: filtered.length > 1 ? filtered.map(x => x.color) : undefined,
};
}
function mergeSprites(sets: ColorExtraSet[]): ColorExtraSet {
return zip(...sets).map(mergeColorExtras);
return zip(...sets).map(mergeColorExtras);
}
function mergeSpriteSets(...sets: ColorExtraSets[]): ColorExtraSets {
return zip(...sets).map(mergeSprites);
return zip(...sets).map(mergeSprites);
}
export const backLegSleeves: Sets = sprites.backLegSleeves
.map(sets => sets && [undefined, undefined, undefined, undefined, undefined, ...sets]);
.map(sets => sets && [undefined, undefined, undefined, undefined, undefined, ...sets]);
// TEMP: remove summer hat
sprites.headAccessoriesBehind.pop();
@@ -157,20 +157,20 @@ export const mergedHeadAccessories = mergeSpriteSets(sprites.headAccessoriesBehi
export const mergedFaceAccessories = mergeSpriteSets(sprites.faceAccessories, sprites.faceAccessories2)!;
export const mergedChestAccessories = mergeSpriteSets(sprites.chestAccessoriesBehind[1], sprites.chestAccessories[1])!;
export const mergedBackAccessories = mergeSpriteSets(
backAccessories[1], [undefined, undefined, undefined, undefined, undefined, ...sprites.backLegSleeves[1]!])!;
backAccessories[1], [undefined, undefined, undefined, undefined, undefined, ...sprites.backLegSleeves[1]!])!;
export const mergedExtraAccessories = mergeSpriteSets(sprites.extraAccessoriesBehind, sprites.extraAccessories)!
.slice(0, DEVELOPMENT ? 100 : 2);
.slice(0, DEVELOPMENT ? 100 : 2);
if (DEVELOPMENT) {
assertSizes('HEAD_ACCESSORY_OFFSETS', HEAD_ACCESSORY_OFFSETS, mergedManes);
assertSizes('EXTRA_ACCESSORY_OFFSETS', EXTRA_ACCESSORY_OFFSETS, mergedManes);
assertSizes('EAR_ACCESSORY_OFFSETS', EAR_ACCESSORY_OFFSETS, sprites.ears);
assertSizes('frontHoovesInFront', frontHoovesInFront, frontHooves[1]!);
assertSizes('backHoovesInFront', backHoovesInFront, sprites.backLegHooves[1]!);
assertSizes('HEAD_ACCESSORY_OFFSETS', HEAD_ACCESSORY_OFFSETS, mergedManes);
assertSizes('EXTRA_ACCESSORY_OFFSETS', EXTRA_ACCESSORY_OFFSETS, mergedManes);
assertSizes('EAR_ACCESSORY_OFFSETS', EAR_ACCESSORY_OFFSETS, sprites.ears);
assertSizes('frontHoovesInFront', frontHoovesInFront, frontHooves[1]!);
assertSizes('backHoovesInFront', backHoovesInFront, sprites.backLegHooves[1]!);
}
function assertSizes(name: string, a: any[], b: any[]) {
if (a.length !== b.length) {
throw new Error(`Invalid ${name} length (${a.length} !== ${b.length})`);
}
if (a.length !== b.length) {
throw new Error(`Invalid ${name} length (${a.length} !== ${b.length})`);
}
}
+5 -5
View File
@@ -2,11 +2,11 @@ import { REV } from '../generated/rev';
/* istanbul ignore next */
export function getUrl(name: string): string {
if (DEVELOPMENT)
return `/assets/${name}`;
if (DEVELOPMENT)
return `/assets/${name}`;
if (!REV[name])
throw new Error(`Cannot find file url (${name})`);
if (!REV[name])
throw new Error(`Cannot find file url (${name})`);
return `/assets/${name.replace(/(\.\S+)$/, `-${REV[name]}$1`)}`;
return `/assets/${name.replace(/(\.\S+)$/, `-${REV[name]}$1`)}`;
}
+20 -20
View File
@@ -9,36 +9,36 @@ let setX = 0;
let setY = 0;
export function setupPlayer(game: PonyTownGame, player: Pony) {
const pony = player;
pony.flags = setFlag(pony.flags, EntityFlags.Interactive, false);
const pony = player;
pony.flags = setFlag(pony.flags, EntityFlags.Interactive, false);
if (isStaticCollision(player, game.map, false)) {
fixCollision(player, game.map);
}
if (isStaticCollision(player, game.map, false)) {
fixCollision(player, game.map);
}
game.setPlayer(pony);
currentPlayer = player;
savePlayerPosition();
game.setPlayer(pony);
currentPlayer = player;
savePlayerPosition();
}
export function savePlayerPosition() {
if (currentPlayer) {
setX = currentPlayer.x;
setY = currentPlayer.y;
}
if (currentPlayer) {
setX = currentPlayer.x;
setY = currentPlayer.y;
}
}
export function restorePlayerPosition() {
if (currentPlayer) {
if (currentPlayer.x !== setX || currentPlayer.y !== setY) {
currentPlayer.x = setX;
currentPlayer.y = setY;
DEVELOPMENT && console.warn('Restoring player position');
}
}
if (currentPlayer) {
if (currentPlayer.x !== setX || currentPlayer.y !== setY) {
currentPlayer.x = setX;
currentPlayer.y = setY;
DEVELOPMENT && console.warn('Restoring player position');
}
}
}
// Account creation lock
export const setAclCookie = (acl: string) => {
document.cookie = `acl=${acl}; expires=${fromNow(WEEK).toUTCString()}; path=/`;
document.cookie = `acl=${acl}; expires=${fromNow(WEEK).toUTCString()}; path=/`;
};
+5 -5
View File
@@ -4,9 +4,9 @@ import { AnimatedRenderable } from '../common/mixins';
import { Sprite } from '../common/interfaces';
export const zzzAnimation1 = createSpriteAnimation(
sprites.emote_sleep1, 8, 8, 4, 7, true, sprites.emote_sleep1_flip.frames);
sprites.emote_sleep1, 8, 8, 4, 7, true, sprites.emote_sleep1_flip.frames);
export const zzzAnimation2 = createSpriteAnimation(
sprites.emote_sleep2, 12, 13, 13, 12, true, sprites.emote_sleep2_flip.frames);
sprites.emote_sleep2, 12, 13, 13, 12, true, sprites.emote_sleep2_flip.frames);
export const zzzAnimations = [zzzAnimation1, zzzAnimation2];
export const cryAnimation = createSpriteAnimation(sprites.emote_cry2, 12, 0, 13, 0);
@@ -24,8 +24,8 @@ export const holdPoofAnimation = createSpriteAnimation(sprites.hold_poof, 12, 0,
export const magicAnimation = createSpriteAnimation(sprites.magic2, 8, 2, 6, 0, true);
function createSpriteAnimation(
{ frames, palette }: AnimatedRenderable, fps: number, start: number, middle: number, end: number, loop = true,
flipFrames?: Sprite[],
{ frames, palette }: AnimatedRenderable, fps: number, start: number, middle: number, end: number, loop = true,
flipFrames?: Sprite[],
): SpriteAnimation {
return { start, middle, end, fps, palette, frames, loop, flipFrames };
return { start, middle, end, fps, palette, frames, loop, flipFrames };
}
+34 -34
View File
@@ -6,76 +6,76 @@ import { getUrl } from './rev';
import { createFonts } from './fonts';
export function createSprite(x: number, y: number, w: number, h: number, ox: number, oy: number, type: number): Sprite {
return { x, y, w, h, ox, oy, type };
return { x, y, w, h, ox, oy, type };
}
export function addTitles(sprites: ColorExtraSets, titles: string[]): ColorExtraSets {
return sprites && sprites.map((ns, i) =>
ns && ns.map(s => s && { color: s.color, colors: s.colors, title: titles[i], label: titles[i] }));
return sprites && sprites.map((ns, i) =>
ns && ns.map(s => s && { color: s.color, colors: s.colors, title: titles[i], label: titles[i] }));
}
export function addLabels(sprites: ColorExtraSets, labels: string[]) {
sprites && sprites.forEach((s, i) => s && s[0] ? s[0]!.label = labels[i] : undefined);
return sprites;
sprites && sprites.forEach((s, i) => s && s[0] ? s[0]!.label = labels[i] : undefined);
return sprites;
}
export function createEyeSprite(eye: PonyEye | undefined, iris: number, defaultPalette: Uint32Array): ColorExtra | undefined {
return eye && { color: eye.irises[iris]!, colors: 2, extra: eye.base, palettes: [defaultPalette] };
return eye && { color: eye.irises[iris]!, colors: 2, extra: eye.base, palettes: [defaultPalette] };
}
export function getColorCount(sprite: ColorExtra | undefined): number {
return sprite && sprite.colors ? Math.floor((sprite.colors - 1) / 2) : 0;
return sprite && sprite.colors ? Math.floor((sprite.colors - 1) / 2) : 0;
}
export function createSpriteUtils() {
createFonts();
createFonts();
}
type LoadImage = (src: string) => Promise<HTMLImageElement | ImageBitmap>;
function getImageData(img: HTMLImageElement | ImageBitmap) {
const canvas = createCanvas(img.width, img.height);
const context = canvas.getContext('2d')!;
context.drawImage(img, 0, 0);
return context.getImageData(0, 0, img.width, img.height);
const canvas = createCanvas(img.width, img.height);
const context = canvas.getContext('2d')!;
context.drawImage(img, 0, 0);
return context.getImageData(0, 0, img.width, img.height);
}
function loadSpriteSheet(sheet: SpriteSheet, loadImage: LoadImage) {
return Promise.all([
loadImage(sheet.src!),
sheet.srcA ? loadImage(sheet.srcA) : Promise.resolve(undefined)
])
.then(([img, imgA]) => {
sheet.data = getImageData(img);
return Promise.all([
loadImage(sheet.src!),
sheet.srcA ? loadImage(sheet.srcA) : Promise.resolve(undefined)
])
.then(([img, imgA]) => {
sheet.data = getImageData(img);
if (imgA) {
const alpha = getImageData(imgA);
const alphaData = alpha.data;
const sheedData = sheet.data.data;
if (imgA) {
const alpha = getImageData(imgA);
const alphaData = alpha.data;
const sheedData = sheet.data.data;
for (let i = 0; i < sheedData.length; i += 4) {
sheedData[i + 3] = alphaData[i];
}
}
});
for (let i = 0; i < sheedData.length; i += 4) {
sheedData[i + 3] = alphaData[i];
}
}
});
}
export function loadSpriteSheets(sheets: SpriteSheet[], loadImage: LoadImage) {
return Promise.all(sheets.map(s => loadSpriteSheet(s, loadImage))).then(noop);
return Promise.all(sheets.map(s => loadSpriteSheet(s, loadImage))).then(noop);
}
export let spriteSheetsLoaded = false;
export function loadAndInitSheets(sheets: SpriteSheet[], loadImage: LoadImage) {
return loadSpriteSheets(sheets, loadImage)
.then(createSpriteUtils)
.then(() => true)
.catch(e => (console.error(e), false))
.then(loaded => spriteSheetsLoaded = loaded);
return loadSpriteSheets(sheets, loadImage)
.then(createSpriteUtils)
.then(() => true)
.catch(e => (console.error(e), false))
.then(loaded => spriteSheetsLoaded = loaded);
}
export function loadImageFromUrl(url: string) {
return loadImage(getUrl(url));
return loadImage(getUrl(url));
}
export const loadAndInitSpriteSheets = once(() => loadAndInitSheets(spriteSheets, loadImageFromUrl));
+451 -451
View File
File diff suppressed because it is too large Load Diff
+71 -71
View File
@@ -1,15 +1,15 @@
interface TimingEntry {
time: number;
name?: string;
time: number;
name?: string;
}
interface TimingResult {
name: string;
count: number;
selfTime: number;
totalTime: number;
selfPercent: number;
totalPercent: number;
name: string;
count: number;
selfTime: number;
totalTime: number;
selfPercent: number;
totalPercent: number;
}
const ENABLED = false;
@@ -19,89 +19,89 @@ const entries: TimingEntry[] = [];
let entriesCount = 0;
if (TIMING && ENABLED) {
for (let i = 0; i < ENTRIES_LIMIT; i++) {
entries.push({ time: 0, name: undefined });
}
for (let i = 0; i < ENTRIES_LIMIT; i++) {
entries.push({ time: 0, name: undefined });
}
}
export function timeStart(name: string) {
if (TIMING && ENABLED) {
if (entriesCount < ENTRIES_LIMIT) {
const entry = entries[entriesCount];
entry.time = performance.now();
entry.name = name;
entriesCount++;
} else {
console.warn(`exceeded timing entry limit`);
}
}
if (TIMING && ENABLED) {
if (entriesCount < ENTRIES_LIMIT) {
const entry = entries[entriesCount];
entry.time = performance.now();
entry.name = name;
entriesCount++;
} else {
console.warn(`exceeded timing entry limit`);
}
}
}
export function timeEnd() {
if (TIMING && ENABLED) {
if (entriesCount < ENTRIES_LIMIT) {
const entry = entries[entriesCount];
entry.time = performance.now();
entry.name = undefined;
entriesCount++;
} else {
console.warn(`exceeded timing entry limit`);
}
}
if (TIMING && ENABLED) {
if (entriesCount < ENTRIES_LIMIT) {
const entry = entries[entriesCount];
entry.time = performance.now();
entry.name = undefined;
entriesCount++;
} else {
console.warn(`exceeded timing entry limit`);
}
}
}
export function timeReset() {
if (TIMING && ENABLED) {
entriesCount = 0;
}
if (TIMING && ENABLED) {
entriesCount = 0;
}
}
export function timingCollate(): TimingResult[] {
if (TIMING && ENABLED && entriesCount > 0) {
interface Entry extends TimingEntry {
excludedTime: number;
}
if (TIMING && ENABLED && entriesCount > 0) {
interface Entry extends TimingEntry {
excludedTime: number;
}
const listings: TimingResult[] = [];
const startStack: Entry[] = [];
const listings: TimingResult[] = [];
const startStack: Entry[] = [];
for (let i = 0; i < entriesCount; i++) {
const entry = entries[i];
for (let i = 0; i < entriesCount; i++) {
const entry = entries[i];
if (entry.name !== undefined) {
startStack.push({ ...entry, excludedTime: 0 });
} else {
const start = startStack.pop()!;
const name = start.name!;
const time = entry.time - start.time;
let listing = listings.find(l => l.name === name);
if (entry.name !== undefined) {
startStack.push({ ...entry, excludedTime: 0 });
} else {
const start = startStack.pop()!;
const name = start.name!;
const time = entry.time - start.time;
let listing = listings.find(l => l.name === name);
if (!listing) {
listing = { name, selfTime: 0, totalTime: 0, selfPercent: 0, totalPercent: 0, count: 0 };
listings.push(listing);
}
if (!listing) {
listing = { name, selfTime: 0, totalTime: 0, selfPercent: 0, totalPercent: 0, count: 0 };
listings.push(listing);
}
listing.count++;
listing.selfTime += (time - start.excludedTime);
listing.totalTime += time;
listing.count++;
listing.selfTime += (time - start.excludedTime);
listing.totalTime += time;
if (startStack.length) {
startStack[startStack.length - 1].excludedTime += time;
}
}
}
if (startStack.length) {
startStack[startStack.length - 1].excludedTime += time;
}
}
}
const firstTime = entries[0].time;
const lastTime = entries[entriesCount - 1].time;
const totalTime = lastTime - firstTime;
const firstTime = entries[0].time;
const lastTime = entries[entriesCount - 1].time;
const totalTime = lastTime - firstTime;
for (const listing of listings) {
listing.selfPercent = 100 * listing.selfTime / totalTime;
listing.totalPercent = 100 * listing.totalTime / totalTime;
}
for (const listing of listings) {
listing.selfPercent = 100 * listing.selfTime / totalTime;
listing.totalPercent = 100 * listing.totalTime / totalTime;
}
return listings.sort((a, b) => b.selfTime - a.selfTime);
}
return listings.sort((a, b) => b.selfTime - a.selfTime);
}
return [];
return [];
}
+86 -86
View File
@@ -11,17 +11,17 @@ import { SpriteBatch } from '../graphics/spriteBatch';
import { BATCH_SIZE_MAX } from '../common/constants';
export interface WebGL {
gl: WebGLRenderingContext;
frameBuffer: FrameBuffer | undefined;
frameBufferSheet: SpriteSheet;
spriteShader: Shader;
lightShader: Shader;
paletteShader: Shader;
spriteBatch: SpriteBatch;
paletteBatch: PaletteSpriteBatch;
palettes: CommonPalettes;
failedFBO: boolean;
renderer: string;
gl: WebGLRenderingContext;
frameBuffer: FrameBuffer | undefined;
frameBufferSheet: SpriteSheet;
spriteShader: Shader;
lightShader: Shader;
paletteShader: Shader;
spriteBatch: SpriteBatch;
paletteBatch: PaletteSpriteBatch;
palettes: CommonPalettes;
failedFBO: boolean;
renderer: string;
}
const spriteShaderSource = spriteShader;
@@ -29,108 +29,108 @@ const paletteShaderSource = paletteLayersShader;
const lightShaderSource = lightShader;
function createIndices(capacity: number) {
const numIndices = (capacity * 6) | 0;
const indices = new Uint16Array(numIndices);
const numIndices = (capacity * 6) | 0;
const indices = new Uint16Array(numIndices);
for (let i = 0, j = 0; i < numIndices; j = (j + 4) | 0) {
indices[i++] = (j + 0) | 0;
indices[i++] = (j + 1) | 0;
indices[i++] = (j + 2) | 0;
indices[i++] = (j + 0) | 0;
indices[i++] = (j + 2) | 0;
indices[i++] = (j + 3) | 0;
}
for (let i = 0, j = 0; i < numIndices; j = (j + 4) | 0) {
indices[i++] = (j + 0) | 0;
indices[i++] = (j + 1) | 0;
indices[i++] = (j + 2) | 0;
indices[i++] = (j + 0) | 0;
indices[i++] = (j + 2) | 0;
indices[i++] = (j + 3) | 0;
}
return indices;
return indices;
}
export function initWebGL(canvas: HTMLCanvasElement, paletteManager: PaletteManager, camera: Camera): WebGL {
const gl = getWebGLContext(canvas);
return initWebGLResources(gl, paletteManager, camera);
const gl = getWebGLContext(canvas);
return initWebGLResources(gl, paletteManager, camera);
}
export function initWebGLResources(gl: WebGLRenderingContext, paletteManager: PaletteManager, camera: Camera): WebGL {
let renderer = '';
let failedFBO = false;
let frameBuffer: FrameBuffer | undefined;
let frameBufferSheet: SpriteSheet = { texture: undefined, sprites: [], palette: false };
let renderer = '';
let failedFBO = false;
let frameBuffer: FrameBuffer | undefined;
let frameBufferSheet: SpriteSheet = { texture: undefined, sprites: [], palette: false };
try {
const size = getRenderTargetSize(camera.w, camera.h);
frameBuffer = createFrameBuffer(gl, size, size);
frameBufferSheet.texture = frameBuffer.texture;
} catch (e) {
DEVELOPMENT && console.warn(e);
failedFBO = true;
}
try {
const size = getRenderTargetSize(camera.w, camera.h);
frameBuffer = createFrameBuffer(gl, size, size);
frameBufferSheet.texture = frameBuffer.texture;
} catch (e) {
DEVELOPMENT && console.warn(e);
failedFBO = true;
}
createTexturesForSpriteSheets(gl, sprites.spriteSheets);
const palettes = createCommonPalettes(paletteManager);
createTexturesForSpriteSheets(gl, sprites.spriteSheets);
const palettes = createCommonPalettes(paletteManager);
const paletteShader = createShader(gl, paletteShaderSource);
const spriteShader = createShader(gl, spriteShaderSource);
const lightShader = createShader(gl, lightShaderSource);
const paletteShader = createShader(gl, paletteShaderSource);
const spriteShader = createShader(gl, spriteShaderSource);
const lightShader = createShader(gl, lightShaderSource);
const VERTICES_PER_SPRITE = 4;
const buffer = new ArrayBuffer(BATCH_SIZE_MAX * VERTICES_PER_SPRITE * PALETTE_BATCH_BYTES_PER_VERTEX);
const vertexBuffer = gl.createBuffer();
const VERTICES_PER_SPRITE = 4;
const buffer = new ArrayBuffer(BATCH_SIZE_MAX * VERTICES_PER_SPRITE * PALETTE_BATCH_BYTES_PER_VERTEX);
const vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
throw new Error(`Failed to allocate vertex buffer`);
}
if (!vertexBuffer) {
throw new Error(`Failed to allocate vertex buffer`);
}
const indexBuffer = gl.createBuffer();
const indexBuffer = gl.createBuffer();
if (!indexBuffer) {
throw new Error(`Failed to allocate index buffer`);
}
if (!indexBuffer) {
throw new Error(`Failed to allocate index buffer`);
}
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, createIndices(BATCH_SIZE_MAX), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, createIndices(BATCH_SIZE_MAX), gl.STATIC_DRAW);
const vertexBuffer2 = gl.createBuffer();
const vertexBuffer2 = gl.createBuffer();
if (!vertexBuffer2) {
throw new Error(`Failed to allocate vertex buffer (2)`);
}
if (!vertexBuffer2) {
throw new Error(`Failed to allocate vertex buffer (2)`);
}
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer2);
gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer2);
gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW);
const spriteBatch = new SpriteBatch(gl, BATCH_SIZE_MAX, buffer, vertexBuffer2, indexBuffer);
const paletteBatch = new PaletteSpriteBatch(gl, BATCH_SIZE_MAX, buffer, vertexBuffer, indexBuffer);
spriteBatch.rectSprite = sprites.pixel;
paletteBatch.rectSprite = sprites.pixel2;
paletteBatch.defaultPalette = palettes.defaultPalette;
const spriteBatch = new SpriteBatch(gl, BATCH_SIZE_MAX, buffer, vertexBuffer2, indexBuffer);
const paletteBatch = new PaletteSpriteBatch(gl, BATCH_SIZE_MAX, buffer, vertexBuffer, indexBuffer);
spriteBatch.rectSprite = sprites.pixel;
paletteBatch.rectSprite = sprites.pixel2;
paletteBatch.defaultPalette = palettes.defaultPalette;
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
paletteManager.init(gl);
paletteManager.init(gl);
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
if (debugInfo) {
renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
}
if (debugInfo) {
renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
}
return {
gl, paletteShader, spriteShader, lightShader, spriteBatch, paletteBatch,
frameBuffer, frameBufferSheet, palettes, failedFBO, renderer,
};
return {
gl, paletteShader, spriteShader, lightShader, spriteBatch, paletteBatch,
frameBuffer, frameBufferSheet, palettes, failedFBO, renderer,
};
}
export function disposeWebGL(webgl: WebGL) {
const { gl } = webgl;
const { gl } = webgl;
unbindAllTexturesAndBuffers(gl);
disposeTexturesForSpriteSheets(gl, sprites.spriteSheets);
disposeFrameBuffer(gl, webgl.frameBuffer);
disposeShader(gl, webgl.lightShader);
disposeShader(gl, webgl.spriteShader);
disposeShader(gl, webgl.paletteShader);
webgl.spriteBatch.dispose();
webgl.paletteBatch.dispose();
unbindAllTexturesAndBuffers(gl);
disposeTexturesForSpriteSheets(gl, sprites.spriteSheets);
disposeFrameBuffer(gl, webgl.frameBuffer);
disposeShader(gl, webgl.lightShader);
disposeShader(gl, webgl.spriteShader);
disposeShader(gl, webgl.paletteShader);
webgl.spriteBatch.dispose();
webgl.paletteBatch.dispose();
}