Merge remote-tracking branch 'upstream/archive' into archive-update-v0.55.0

This commit is contained in:
Eliot Partridge
2019-10-01 18:04:54 -05:00
39 changed files with 2998 additions and 2486 deletions
+10 -7
View File
@@ -2,7 +2,7 @@ import { compact } from 'lodash';
import {
Expression, ExpressionButtonAction, CommandButtonAction, ActionButtonAction, ItemButtonAction,
ColorShadow, Eye, Muzzle, Iris, ButtonActionSlot, ExpressionExtra, ButtonAction, ChatType, BodyAnimation,
Action, isPartyChat, EntityButtonAction, defaultDrawOptions
Action, isPartyChat, EntityButtonAction, defaultDrawOptions, HeadAnimationProperties
} from '../common/interfaces';
import * as sprites from '../generated/sprites';
import { createExpression } from './clientUtils';
@@ -11,7 +11,7 @@ import { PonyTownGame } from './game';
import { boopAction, upAction, downAction, turnHeadAction } from './playerActions';
import { ACTIONS_LIMIT, COMMAND_ACTION_TIME_DELAY } from '../common/constants';
import { cloneDeep, hasFlag } from '../common/utils';
import { boop, defaultHeadFrame, stand, sneeze, yawn, lie, sit, fly, laugh, excite } from './ponyAnimations';
import { boop, defaultHeadFrame, stand, sneeze, yawn, lie, sit, fly, laugh, kiss, excite } from './ponyAnimations';
import { createDefaultPony, syncLockedPonyInfo, toPalette, mockPaletteManager } from '../common/ponyInfo';
import {
ACTION_EXPRESSION_EYE_COLOR, ACTION_EXPRESSION_BG, ACTION_ACTION_COAT_COLOR, WHITE, HEARTS_COLOR,
@@ -118,10 +118,7 @@ const actionActions = [
actionButtonAction('drop', 'Drop item', Action.Drop),
actionButtonAction('drop-toy', 'Drop toy', Action.DropToy),
actionButtonAction('magic', 'Magic', Action.Magic),
actionButtonAction('switch-tool', 'Switch tool', Action.SwitchTool),
actionButtonAction('switch-entity', 'Switch item to place'),
actionButtonAction('switch-entity-rev', 'Switch item to place (reverse)'),
actionButtonAction('switch-tile', 'Switch tile to place'),
actionButtonAction('kiss', 'Kiss', Action.Kiss),
];
const commandActions = [
@@ -395,7 +392,8 @@ export function drawAction(canvas: HTMLCanvasElement, action: ButtonAction | und
const buffer = drawCanvas(bufferSize, bufferSize, sprites.paletteSpriteSheet, undefined, batch => {
const state = { ...createState(), expression: action.expression };
const options = { ...defaultDrawPonyOptions(), noEars: true };
drawHead(batch, expressionPony, headX, headY, undefined, defaultHeadFrame, state, options, false, 0);
drawHead(batch, expressionPony, headX, headY, undefined,
defaultHeadFrame, HeadAnimationProperties.None, state, options, false, 0);
if (action.expression) {
const extra = action.expression.extra;
@@ -525,6 +523,11 @@ export function drawAction(canvas: HTMLCanvasElement, action: ButtonAction | und
batch.drawSprite(sprites.magic_icon, WHITE, defaultPalette, 4, 2);
break;
}
case 'kiss': {
const state = { ...createState(), headAnimation: kiss, headAnimationFrame: 9 };
drawPony(batch, actionPony, state, 17, 40, defaultDrawPonyOptions());
break;
}
case 'switch-tool': {
const palette = mockPaletteManager.addArray(sprites.tools_icon.palettes![0]);
batch.drawSprite(sprites.tools_icon.color, WHITE, palette, 0, 2);
+2
View File
@@ -91,6 +91,7 @@ export const CONTRIBUTORS: Contributors[] = [
group: 'Programmers',
contributors: [
{ name: 'Industrialice' },
{ name: 'Stubenhocker', links: ['https://twitter.com/Stubenhocker13'] },
],
},
{
@@ -129,6 +130,7 @@ export const CONTRIBUTORS: Contributors[] = [
{ name: 'Deeraw', links: ['https://www.deviantart.com/deerdraw', 'https://twitter.com/TheOnlyDeeraw'] },
{ name: 'Aviivix' },
{ name: 'SnowFl8keAnge1' },
{ name: '3aHo3a', links: ['http://twitter.com/imnuclearimwild'] },
],
},
];
+19 -2
View File
@@ -14,7 +14,8 @@ import { getPonyState, setPonyState, isPonyFlying, addChatBubble, isHidden } fro
import {
isPony, createPony, setPonyExpression, updatePonyInfo, updatePonyHold, doPonyAction, hasHeadAnimation,
setHeadAnimation,
doBoopPonyAction
doBoopPonyAction,
isPonyBug
} from '../common/pony';
import { PonyTownGame } from './game';
import { setupPlayer, savePlayerPosition } from './sec';
@@ -25,7 +26,7 @@ import { decodeUpdate, readOneUpdate } from '../common/encoders/updateDecoder';
import { updateEntityVelocity } from '../common/entityUtils';
import { decodePonyInfo } from '../common/compressPony';
import { mockPaletteManager } from '../common/ponyInfo';
import { yawn, laugh, sneeze, excite } from './ponyAnimations';
import { yawn, laugh, sneeze, kiss, kissFly, kissFlyBug, excite } from './ponyAnimations';
import {
findEntityById, getRegionGlobal, setTile, removeEntity, addEntity, removeEntityDirectly, setRegion,
addEntityToMapRegion, switchEntityRegion, getRegionUnsafe, addOrRemoveFromEntityList,
@@ -420,6 +421,22 @@ export function handleAction(game: PonyTownGame, id: number, action: Action) {
setHeadAnimation(pony, excite);
}
break;
case Action.Kiss:
if (!pony.ponyState.headTurned) {
doPonyAction(pony, DoAction.Kiss);
}
if (isPonyFlying(pony)) {
if (isPonyBug(pony)) {
setHeadAnimation(pony, kissFlyBug);
}
else {
setHeadAnimation(pony, kissFly);
}
}
else {
setHeadAnimation(pony, kiss);
}
break;
default:
log(`handleAction: Invalid action: ${action}`);
}
+186 -9
View File
@@ -1,4 +1,4 @@
import { BodyAnimation, BodyAnimationFrame, HeadAnimation, HeadAnimationFrame, BodyShadow } from '../common/interfaces';
import { BodyAnimation, BodyAnimationFrame, HeadAnimation, HeadAnimationFrame, BodyShadow, HeadAnimationProperties } from '../common/interfaces';
import { repeat, flatten } from '../common/utils';
// body animations
@@ -23,7 +23,7 @@ export function createBodyAnimation(
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}`);
throw new Error(`Incorrect frame count for shadowOffsets for ${name}, animation frames ${frames.length}, shadow frames ${shadowOffsets.length}`);
}
const shadow = shadowOffsets && shadowOffsets.map<BodyShadow>(([frame, offset]) => ({ frame, offset }));
@@ -459,14 +459,163 @@ export const swing = createBodyAnimation('swing', 12, false, [
...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];
export const kissBody = createBodyAnimation('kiss-body', 24, false, [
...repeat(3, [2, 1, 0, 0, 1, 1, 1, 1, -1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1]),
[2, 1, 1, 0, 28, 28, 18, 18, -2, 0, -1, 1, 1, 0, 1, 0, 1, 0, 1],
[2, 1, 1, 0, 26, 26, 19, 19, -3, 0, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1],
...repeat(2, [5, 1, 1, 0, 26, 26, 19, 19, -10, -6, 1, 0, 1, 1, 1, 1, 1, -1, 1, -1]),
...repeat(60, [5, 1, 1, 0, 27, 27, 20, 20, -11, -6, 1, 0, 1, 1, 1, 1, 1, -1, 1, -1]),
...repeat(2, [5, 1, 1, 0, 26, 26, 19, 19, -10, -6, 1, 0, 1, 1, 1, 1, 1, -1, 1, -1]),
[5, 1, 1, 0, 28, 28, 18, 18, -9, -5, 1, 0, 1, 1, 1, 1, 1, -1, 1, -1],
...repeat(2, [2, 1, 1, 0, 1, 1, 1, 1, -1, 0, -1, 1, 1, 0, 1, 0, 1, 0, 1])
]);
export const kissLiftHoofBody = createBodyAnimation('kiss-lift-hoof-body', 24, false, [
...repeat(3, [2, 1, 0, 0, 1, 1, 1, 1, -1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1]),
[2, 1, 1, 0, 28, 28, 18, 18, -2, 0, -1, 1, 1, 0, 1, 0, 1, 0, 1],
[2, 1, 1, 0, 27, 26, 19, 19, -3, 0, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1],
...repeat(2, [5, 1, 1, 0, 8, 26, 19, 19, -10, -6, 1, 0, 1, 2, 1, 1, 1, -1, 1, -1]),
...repeat(60, [5, 1, 1, 0, 8, 27, 20, 20, -11, -6, 1, 0, 1, 1, 1, 1, 1, -1, 1, -1]),
...repeat(2, [5, 1, 1, 0, 8, 26, 19, 19, -10, -6, 1, 0, 1, 2, 1, 1, 1, -1, 1, -1]),
[5, 1, 1, 0, 28, 28, 18, 18, -9, -5, 1, 0, 1, 1, 1, 1, 1, -1, 1, -1],
...repeat(2, [2, 1, 1, 0, 1, 1, 1, 1, -1, 0, -1, 1, 1, 0, 1, 0, 1, 0, 1])
]);
export const kissFlyBody = createBodyAnimation('kiss-fly-body', 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, 8, 10, 6, 5, 0, -14],
[1, 1, 6, 0, 8, 10, 6, 5, 0, -14],
[1, 1, 7, 0, 8, 10, 6, 5, -1, -15],
[1, 1, 8, 0, 9, 10, 5, 5, -1, -17, -1],
[1, 1, 9, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 10, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 11, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 12, 0, 9, 10, 4, 4, -1, -17, -1, 0, 0, 0, 0, -1],
[1, 1, 3, 0, 9, 10, 4, 4, -1, -16, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -15, -1, 0, 0, 0, 0, -1],
[1, 1, 5, 0, 9, 10, 4, 4, -1, -14, -1, 0, 0, 0, 0, -1],
[1, 1, 6, 0, 9, 10, 4, 4, -1, -14, -1, 0, 0, 0, 0, -1],
[1, 1, 7, 0, 9, 10, 4, 4, -1, -15, -1, 0, 0, 0, 0, -1],
[1, 1, 8, 0, 9, 10, 4, 4, -1, -17, -1, 0, 0, 0, 0, -1],
[1, 1, 9, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 10, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 11, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 12, 0, 9, 10, 4, 4, -1, -17, -1, 0, 0, 0, 0, -1],
[1, 1, 3, 0, 9, 10, 4, 4, -1, -16, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -15, -1, 0, 0, 0, 0, -1],
[1, 1, 5, 0, 9, 10, 4, 4, -1, -14, -1, 0, 0, 0, 0, -1],
[1, 1, 6, 0, 9, 10, 4, 4, -1, -14, -1, 0, 0, 0, 0, -1],
[1, 1, 7, 0, 9, 10, 4, 4, -1, -15, -1, 0, 0, 0, 0, -1],
[1, 1, 8, 0, 9, 10, 4, 4, -1, -17, -1, 0, 0, 0, 0, -1],
[1, 1, 9, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 10, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 11, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 12, 0, 9, 10, 4, 4, 0, -17, -1, 0, 0, 0, 0, -1],
[1, 1, 3, 0, 8, 10, 5, 4, 0, -16, 0, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15]
]);
export const kissFlyBugBody = createBodyAnimation('kiss-fly-bug-body', 24, false, [
[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, -1, -14],
[1, 1, 4, 0, 9, 10, 5, 5, -1, -14],
[1, 1, 5, 0, 9, 10, 4, 4, -1, -14, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -14, -1, 0, 0, 0, 0, -1],
[1, 1, 3, 0, 9, 10, 4, 4, -1, -15, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -15, -1, 0, 0, 0, 0, -1],
[1, 1, 5, 0, 9, 10, 4, 4, -1, -16, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -17, -1, 0, 0, 0, 0, -1],
[1, 1, 3, 0, 9, 10, 4, 4, -1, -17, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 5, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 3, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -17, -1, 0, 0, 0, 0, -1],
[1, 1, 5, 0, 9, 10, 4, 4, -1, -17, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -17, -1, 0, 0, 0, 0, -1],
[1, 1, 3, 0, 9, 10, 4, 4, -1, -16, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -16, -1, 0, 0, 0, 0, -1],
[1, 1, 5, 0, 9, 10, 4, 4, -1, -15, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -15, -1, 0, 0, 0, 0, -1],
[1, 1, 3, 0, 9, 10, 4, 4, -1, -14, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -14, -1, 0, 0, 0, 0, -1],
[1, 1, 5, 0, 9, 10, 4, 4, -1, -14, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -14, -1, 0, 0, 0, 0, -1],
[1, 1, 3, 0, 9, 10, 4, 4, -1, -15, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -15, -1, 0, 0, 0, 0, -1],
[1, 1, 5, 0, 9, 10, 4, 4, -1, -16, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -17, -1, 0, 0, 0, 0, -1],
[1, 1, 3, 0, 9, 10, 4, 4, -1, -17, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 5, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 3, 0, 9, 10, 4, 4, -1, -18, -1, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 9, 10, 4, 4, 0, -17, -1, 0, 0, 0, 0, -1],
[1, 1, 5, 0, 8, 10, 5, 4, 0, -17, 0, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -17]
]);
export const kissLieBody = createBodyAnimation('kiss-lie-body', 24, false, [
...repeat(3, [14, 1, 0, 2, 38, 38, 26, 26]),
...repeat(2, [15, 1, 0, 2, 38, 38, 26, 26]),
[13, 1, 0, 2, 38, 38, 26, 26, 0, 0, -1],
...repeat(2, [13, 1, 0, 2, 38, 38, 26, 26, -1, 0, -1, 0, 1, 0, 1, 0, 1, 0, 1]),
...repeat(57, [13, 1, 0, 2, 38, 38, 26, 26, -1, 0, -2, 0, 1, 0, 1, 0, 1, 0, 1]),
...repeat(2, [13, 1, 0, 2, 38, 38, 26, 26, 0, 0, -2]),
[13, 1, 0, 2, 38, 38, 26, 26, 0, 0, -1],
...repeat(2, [14, 1, 0, 2, 38, 38, 26, 26, 0, 0, -1]),
...repeat(2, [14, 1, 0, 2, 38, 38, 26, 26])
], [...repeat(72, [3, 3])]);
export const kissSitBody = createBodyAnimation('kiss-sit-body', 24, false, [
...repeat(3, [8, 1, 2, 2, 34, 34, 25, 25]),
...repeat(2, [9, 1, 2, 2, 34, 34, 26, 26]),
[9, 1, 2, 2, 34, 34, 26, 26, 0, -1, -1, 0, 0, 1, 0, 1, 0, 1, 0, 1],
...repeat(2, [9, 1, 2, 2, 39, 39, 26, 26, -1, -1, -1, 0, 0, 1, 0, 1, 1, 1, 1, 1]),
...repeat(60, [9, 1, 2, 2, 39, 39, 26, 26, -1, -1, -2, 0, 0, 1, 0, 1, 1, 1, 1, 1]),
...repeat(2, [9, 1, 2, 2, 34, 34, 26, 26, 0, -1, -2, 0, 0, 1, 0, 1, 0, 1, 0, 1]),
...repeat(2, [9, 1, 2, 2, 34, 34, 26, 26, 0, 0, -1])
], [...repeat(72, [0, 6])]);
export const kissSwimBody = createBodyAnimation('kiss-swim-body', 8, false, [
...repeat(2, [1, 1, 0, 0, 8, 10, 6, 5, 0, 14]),
[1, 1, 0, 0, 9, 10, 5, 5, -1, 13],
[1, 1, 0, 0, 9, 10, 4, 4, -1, 13, -1, 0, 0, 0, 0, -1],
...repeat(2, [1, 1, 0, 0, 9, 10, 4, 4, -1, 12, -1, 0, 0, 0, 0, -1]),
...repeat(2, [1, 1, 0, 0, 9, 10, 4, 4, -1, 13, -1, 0, 0, 0, 0, -1]),
...repeat(2, [1, 1, 0, 0, 9, 10, 4, 4, -1, 14, -1, 0, 0, 0, 0, -1]),
...repeat(2, [1, 1, 0, 0, 9, 10, 4, 4, -1, 13, -1, 0, 0, 0, 0, -1]),
...repeat(2, [1, 1, 0, 0, 9, 10, 4, 4, -1, 12, -1, 0, 0, 0, 0, -1]),
...repeat(2, [1, 1, 0, 0, 9, 10, 4, 4, -1, 13, -1, 0, 0, 0, 0, -1]),
...repeat(2, [1, 1, 0, 0, 9, 10, 4, 4, -1, 14, -1, 0, 0, 0, 0, -1]),
...repeat(2, [1, 1, 0, 0, 9, 10, 4, 4, -1, 13, -1, 0, 0, 0, 0, -1]),
...repeat(2, [1, 1, 0, 0, 9, 10, 4, 4, -1, 12, -1, 0, 0, 0, 0, -1]),
[1, 1, 0, 0, 8, 10, 4, 4, 0, 13, -1, 0, 0, 0, 0, -1],
[1, 1, 0, 0, 8, 10, 5, 5, 0, 13]
]);
export const kissToTrot = createBodyAnimation('kiss-to-trot-body', 24, false, [
[2, 1, 0, 0, 8, 4, 19, 4, -1, 0, -1, 1, 0, 0, 0, 0, 1, -1],
[1, 1, 0, 0, 12, 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 flyAnims = [undefined, fly, fly, fly, flyBug, kissFlyBody, kissFlyBugBody];
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,
swim, trotToSwim, swimToTrot, flyToSwim, swimToFly, kissBody, kissLiftHoofBody, kissFlyBody, kissFlyBugBody, kissLieBody,
kissSitBody, kissSwimBody, kissToTrot
];
export const sitDownUp = mergeAnimations('sit', 24, false, [...repeat(12, stand), sitDown, ...repeat(12, sit), standUp]);
@@ -488,8 +637,12 @@ export function createHeadFrame([headX = 0, headY = 0, left = 0, right = 0, mout
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) };
export function createHeadAnimation(name: string, fps: number, loop: boolean, frames: number[][],
properties?: HeadAnimationProperties): HeadAnimation {
if (!properties) {
properties = HeadAnimationProperties.None;
}
return { name, fps, loop, properties, frames: frames.map(createHeadFrame) };
}
export const smile = createHeadAnimation('smile', 24, true, [
@@ -510,7 +663,7 @@ export const yawn = createHeadAnimation('yawn', 12, false, [
...repeat(18, [1, -1, 12, 12, 16]),
...repeat(8, [0, 0, 12, 12, 12]),
[0, 0, 18, 18, 2],
]);
], HeadAnimationProperties.DontIncreaseEyeOpenness);
export const surprise = createHeadAnimation('surprise', 8, false, [
[0, 1, 6, 6, 1],
@@ -538,7 +691,7 @@ export const sneeze = createHeadAnimation('sneeze', 12, false, [
...repeat(2, [1, -1, 18, 18, 16]),
...repeat(8, [-1, 1, 23, 23, 13]),
...repeat(4, [0, 0, 18, 18, 7]),
]);
], HeadAnimationProperties.DontIncreaseEyeOpenness);
export const happy_tongue = createHeadAnimation('happy_tongue', 12, false, [
...repeat(3, [0, 0, 1, 1, 0]),
@@ -558,8 +711,32 @@ export const happy_tongue_meno_2 = createHeadAnimation('happy_tongue_meno_2', 12
...repeat(8, [0, 0, 11, 11, 4]),
]);
export const kiss = createHeadAnimation('kiss', 24, false, [
...repeat(2, [0, 0, 1, 1, 0]),
...repeat(2, [0, 0, 3, 3, 0]),
...repeat(63, [0, 0, 6, 6, 13]),
[0, 0, 3, 3, 17],
[0, 0, 1, 1, 0]
], HeadAnimationProperties.DontIncreaseEyeOpenness);
export const kissFly = createHeadAnimation('kiss-fly', 24, false, [
...repeat(2, [0, 0, 1, 1, 0]),
...repeat(2, [0, 0, 3, 3, 0]),
...repeat(38, [0, 0, 6, 6, 13]),
[0, 0, 3, 3, 17],
[0, 0, 1, 1, 0]
], HeadAnimationProperties.DontIncreaseEyeOpenness);
export const kissFlyBug = createHeadAnimation('kiss-fly-bug', 24, false, [
...repeat(2, [0, 0, 1, 1, 0]),
...repeat(2, [0, 0, 3, 3, 0]),
...repeat(35, [0, 0, 6, 6, 13]),
[0, 0, 3, 3, 17],
[0, 0, 1, 1, 0]
], HeadAnimationProperties.DontIncreaseEyeOpenness);
export const headAnimations = [
smile, nom, laugh, yawn, surprise, surpriseSad, sneeze, excite,
smile, nom, laugh, yawn, surprise, surpriseSad, sneeze, excite, kiss, kissFly, kissFlyBug
];
// default animations
+44 -17
View File
@@ -1,7 +1,8 @@
import {
PonyEye, PonyState, PalettePonyInfo, PaletteSpriteSet, Palette, HeadAnimationFrame,
Eye, Iris, ColorExtraSets, ExpressionExtra, BodyAnimationFrame, DrawPonyOptions, Muzzle, BodyShadow, DrawOptions,
NoDraw, PaletteSpriteBatch, defaultDrawOptions, PonyStateFlags, PaletteManager, isEyeSleeping, Matrix2D,
NoDraw, PaletteSpriteBatch, defaultDrawOptions, PonyStateFlags, PaletteManager, Matrix2D, HeadAnimationProperties,
getEyeOpenness, Expression, getMuzzleOpenness,
} from '../common/interfaces';
import { WHITE, SHINES_COLOR, FAR_COLOR, TRANSPARENT, fillToOutlineColor } from '../common/colors';
import { toInt, hasFlag, repeat, flatten, point } from '../common/utils';
@@ -232,6 +233,7 @@ function getWakeIndex(info: Info) {
export function drawPony(batch: Batch, info: Info, state: State, ponyX: number, ponyY: number, options: Options) {
const frame = getPonyAnimationFrame(state.animation, state.animationFrame, defaultBodyFrame);
const headFrame = getPonyAnimationFrame(state.headAnimation || defaultHeadAnimation, state.headAnimationFrame, defaultHeadFrame);
const headAnimationProperties = (state.headAnimation || defaultHeadAnimation).properties;
const baseX = ponyX - PONY_WIDTH / 2;
const baseY = ponyY - PONY_HEIGHT;
const x = baseX + frame.bodyX;
@@ -502,7 +504,7 @@ export function drawPony(batch: Batch, info: Info, state: State, ponyX: number,
drawSet(batch, sprites.behindManes, info.mane, 0, maneBehindOffsetY, WHITE);
}
drawHead(batch, info, 0, 0, headSprite, headFrame, state, options, headFlip, maneOffsetY);
drawHead(batch, info, 0, 0, headSprite, headFrame, headAnimationProperties, state, options, headFlip, maneOffsetY);
drawSet(batch, sprites.headAccessories, info.headAccessory, hatOffset.x, hatOffset.y + hatOffsetY, WHITE);
batch.restore();
@@ -514,7 +516,7 @@ export function drawPony(batch: Batch, info: Info, state: State, ponyX: number,
export function drawHead(
batch: Batch, info: Info, x: number, y: number, headSprites: ColorExtraSets, headFrame: HeadAnimationFrame,
{ blinkFrame, expression, holding, blushColor, drawFaceExtra }: State,
animationProperties: HeadAnimationProperties, { blinkFrame, expression, holding, blushColor, drawFaceExtra }: State,
options: Options, flip: boolean, maneOffsetY: number,
) {
const extraOffset = at(EXTRA_ACCESSORY_OFFSETS, info.mane && info.mane.type) || pointZero;
@@ -550,18 +552,18 @@ export function drawHead(
// make sure eyes are closed if sleeping
if (hasFlag(expression.extra, ExpressionExtra.Zzz)) {
if (!isEyeSleeping(eyeLeftBase)) {
if (getEyeOpenness(eyeLeftBase) !== 0) {
eyeLeftBase = Eye.Closed;
}
if (!isEyeSleeping(eyeRightBase)) {
if (getEyeOpenness(eyeRightBase) !== 0) {
eyeRightBase = Eye.Closed;
}
}
}
const eyeRight = getEyeFrame(info.eyeOpennessRight || 1, eyeRightBase, headFrame.right, blinkFrame);
const eyeLeft = getEyeFrame(info.eyeOpennessLeft || 1, eyeLeftBase, headFrame.left, blinkFrame);
const eyeRight = getEyeFrame(info.eyeOpennessRight || 1, eyeRightBase, headFrame.right, animationProperties, blinkFrame);
const eyeLeft = getEyeFrame(info.eyeOpennessLeft || 1, eyeLeftBase, headFrame.left, animationProperties, blinkFrame);
const eyeFrameLeft = flip ? eyeRight : eyeLeft;
const eyeFrameRight = flip ? eyeLeft : eyeRight;
const eyeColorLeft = flip ? info.eyeColorRight : info.eyeColorLeft;
@@ -613,12 +615,7 @@ export function drawHead(
}
if (draw(options, NoDraw.Body) && draw(options, NoDraw.Nose)) {
const muzzle = holding ?
Muzzle.Smile :
headFrame.mouth === -1 ?
(expression ? expression.muzzle : info.muzzle) :
headFrame.mouth;
const muzzle = getMouthFrame(!!holding, expression, headFrame.mouth, info.muzzle, animationProperties);
const noses = at(sprites.noses, muzzle);
const nose = att(noses, info.nose && info.nose.type)![0];
@@ -723,11 +720,18 @@ function drawLeg(
}
}
function getEyeFrame(base: Eye, expression: Eye, anim: Eye, blinkFrame: number) {
if (anim !== -1)
return anim;
function getEyeFrame(base: Eye, expression: Eye, anim: Eye, animationProperties: HeadAnimationProperties, blinkFrame: number) {
const frame = expression === -1 ? base : expression;
if (anim !== -1) {
if (hasFlag(animationProperties, HeadAnimationProperties.DontIncreaseEyeOpenness)) {
if (getEyeOpenness(anim) > getEyeOpenness(frame)) {
return frame;
}
}
return anim;
}
const blink = blinkFrames[frame];
if (blinkFrame > 1 && blink) {
@@ -741,6 +745,29 @@ function getEyeFrame(base: Eye, expression: Eye, anim: Eye, blinkFrame: number)
return frame;
}
function getMouthFrame(holding: boolean, expression: Expression | undefined, headFrameMuzzle: Muzzle,
currentMuzzle: Muzzle | undefined, properties: HeadAnimationProperties) {
if (holding) {
return Muzzle.Smile;
}
let applyMuzzle = currentMuzzle;
if (expression) {
applyMuzzle = expression.muzzle;
}
if (headFrameMuzzle !== -1) {
if (applyMuzzle && hasFlag(properties, HeadAnimationProperties.DontDecreaseMouthOpenness)) {
if (getMuzzleOpenness(headFrameMuzzle) < getMuzzleOpenness(applyMuzzle)) {
return applyMuzzle;
}
}
return headFrameMuzzle;
}
return applyMuzzle;
}
function drawEye(
batch: Batch, eye: PonyEye | undefined, iris: Iris, info: Info, palette: Palette | undefined, eyePalette: Palette,
x: number, y: number
+46 -5
View File
@@ -2,7 +2,8 @@ import { animatorState as state, animatorTransition as transition, anyState, Ani
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
flyToTrot, flyToTrotBug, swing, swimToTrot, trotToSwim, swimToFly, flyToSwim, boopSwim, swimToFlyBug, flyToSwimBug,
kissBody, kissLiftHoofBody, kissFlyBody, kissFlyBugBody, kissLieBody, kissSitBody, kissSwimBody, kissToTrot
} from './ponyAnimations';
import { BodyAnimation } from '../common/interfaces';
@@ -25,6 +26,14 @@ export const boopingLying = state(n('booping-lying'), boopLie);
export const boopingFlying = state(n('booping-flying'), boopFly, { bug: boopFlyBug });
export const boopingSwimming = state(n('booping-swimming'), boopSwim);
export const kissing = state(n('kissing'), kissBody);
export const kissingHoof = state(n('kissing'), kissLiftHoofBody);
export const kissingFlying = state(n('kissing-flying'), kissFlyBody, { bug: kissFlyBugBody });
export const kissingLying = state(n('kissing-lying'), kissLieBody);
export const kissingSitting = state(n('kissing-sitting'), kissSitBody);
export const kissingSwimming = state(n('kissing-swimming'), kissSwimBody);
export const kissingToTrotting = state(n('sitting-to-trotting'), kissToTrot);
export const sitting = state(n('sitting'), sit);
export const sittingDown = state(n('sitting-down'), sitDown);
export const standingUp = state(n('standing-up'), standUp);
@@ -51,7 +60,8 @@ export const ponyStates = [
sitting, sittingDown, standingUp, sittingToTrotting,
lying, lyingDown, sittingUp, lyingToTrotting,
hovering, flying, flyingUp, flyingDown, trottingToFlying, flyingToTrotting,
swinging, swimmingToFlying, flyingToSwimming, boopingSwimming,
swinging, swimmingToFlying, flyingToSwimming, boopingSwimming, kissing, kissingHoof,
kissingFlying, kissingLying, kissingSitting, kissingSwimming, kissingToTrotting
];
transition(hovering, flyingDown, { exitAfter: 0 });
@@ -101,7 +111,6 @@ transition(flying, hovering, { exitAfter: 0, keepTime: true });
transition(boopingSwimming, swimming);
transition(swimming, boopingSwimming, { exitAfter: 0 });
transition(booping, standing);
transition(standing, booping, { exitAfter: 0 });
transition(boopingSitting, sitting);
@@ -111,7 +120,27 @@ transition(lying, boopingLying, { exitAfter: 0 });
transition(boopingFlying, hovering, { enterTime: 1.1 / 10 });
transition(hovering, boopingFlying, { exitAfter: 0 });
// transition(anyState, trottingToSwimming, { exitAfter: 0 });
transition(kissingSwimming, swimming);
transition(swimming, kissingSwimming, { exitAfter: 0 });
transition(kissing, standing);
transition(standing, kissing, { exitAfter: 0 });
transition(kissingHoof, standing);
transition(standing, kissingHoof, { exitAfter: 0 });
transition(kissingFlying, hovering, { enterTime: 1.1 / 10 });
transition(hovering, kissingFlying, { exitAfter: 0 });
transition(kissingLying, lying);
transition(lying, kissingLying, { exitAfter: 0 });
transition(kissingSitting, sitting);
transition(sitting, kissingSitting, { exitAfter: 0 });
transition(kissing, kissingToTrotting, { exitAfter: 0, onlyDirectTo: trotting });
transition(kissingToTrotting, trotting);
transition(kissingSitting, sittingToTrotting, { exitAfter: 0, onlyDirectTo: trotting });
transition(sittingToTrotting, standing);
transition(kissingLying, lyingToTrotting, { exitAfter: 0, onlyDirectTo: trotting });
transition(lyingToTrotting, standing);
transition(anyState, trotting, { exitAfter: 0, keepTime: true });
transition(anyState, flying, { exitAfter: 0, keepTime: true });
@@ -129,7 +158,8 @@ export function isFlyingDown(state: AnimatorState<BodyAnimation> | undefined) {
export function isSwimmingState(state: AnimatorState<BodyAnimation> | undefined) {
return state === swimming || state === trottingToSwimming || state === swimmingToTrotting ||
state === flyingToSwimming || state === swimmingToFlying || state === boopingSwimming;
state === flyingToSwimming || state === swimmingToFlying || state === boopingSwimming ||
state === kissingSwimming;
}
export function isFlyingUpOrDown(state: AnimatorState<BodyAnimation> | undefined) {
@@ -153,3 +183,14 @@ export function toBoopState(state: AnimatorState<BodyAnimation>) {
default: return undefined;
}
}
export function toKissState(state: AnimatorState<BodyAnimation>) {
switch (state) {
case standing: return (Math.random() < 0.5 ? kissing : kissingHoof);
case sitting: return kissingSitting;
case lying: return kissingLying;
case hovering: return kissingFlying;
case swimming: return kissingSwimming;
default: return undefined;
}
}
+25 -6
View File
@@ -1176,11 +1176,17 @@ export const sandPileBig = doodad(n('sandpile-big'), sprites.snowpile_big, 43, 2
// pumpkins
const pumpkinOffOnSprites: AnimatedRenderable = {frames: [sprites.pumpkin_off.color, sprites.pumpkin_on.color],
palette: sprites.pumpkin_on.palettes![0],
shadow: sprites.pumpkin_on.shadow};
const pumpkinLightSprite: AnimatedRenderable1 = {frames: [undefined, sprites.pumpkin_light]};
const pumpkinCollider = mixColliderRounded(-11, -6, 22, 12, 5, false);
const pumpkinPickable = mixPickable(26, 50);
const pumpkinParts = [pumpkinCollider, pumpkinPickable];
const pumpkinDX = 11;
const pumpkinDY = 15;
const pumpkinAnimOff = [0];
const pumpkinAnimOn = [1];
export const pumpkin = doodad(n('pumpkin'), sprites.pumpkin_default, pumpkinDX, pumpkinDY, 0,
...pumpkinParts);
@@ -1193,11 +1199,20 @@ export const jackoOn = doodad(n('jacko-on'), sprites.pumpkin_on, pumpkinDX, pump
mixLight(jackoLightColor, 0, 0, 256, 192),
mixLightSprite(sprites.pumpkin_light, WHITE, pumpkinDX, pumpkinDY));
export const jacko = doodad(n('jacko'), sprites.pumpkin_on, pumpkinDX, pumpkinDY, 0,
export const jacko = registerMix(n('jacko'),
mixAnimation(pumpkinOffOnSprites, 8, pumpkinDX, pumpkinDY, {
lightSprite: pumpkinLightSprite,
animations: [pumpkinAnimOff, pumpkinAnimOn],
}),
...pumpkinParts,
mixLight(jackoLightColor, 0, 0, 256, 192),
mixFlags(EntityFlags.OnOff));
/*export const jacko = doodad(n('jacko'), sprites.pumpkin_on, pumpkinDX, pumpkinDY, 0,
...pumpkinParts,
mixLight(jackoLightColor, 0, 0, 256, 192),
mixLightSprite(sprites.pumpkin_light, WHITE, pumpkinDX, pumpkinDY),
mixFlags(EntityFlags.OnOff));
mixFlags(EntityFlags.OnOff));*/
// tombstones
@@ -2358,10 +2373,14 @@ export const fruits = [
];
export const tools = [
{ type: saw.type, text: 'Saw: place & remove walls' },
{ type: broom.type, text: 'Broom: remove furniture' },
{ type: hammer.type, text: 'Hammer: place furniture\nuse [mouse wheel] to switch item' },
{ type: shovel.type, text: 'Shovel: change floor\nuse [mouse wheel] to switch floor type' },
{ type: saw.type, text: 'Saw: place & remove walls', textMobile: undefined },
{ type: broom.type, text: 'Broom: remove furniture', textMobile: undefined },
{ type: hammer.type,
text: 'Hammer: place furniture\nuse [mouse wheel] to switch item',
textMobile: 'Hammer: place furniture\nuse [Switch item to place] action to switch item' },
{ type: shovel.type,
text: 'Shovel: change floor\nuse [mouse wheel] to switch floor type',
textMobile: 'Shovel: change floor\nuse [Switch tile to place] action to switch floor type' },
];
export const candies1Types = [candyCane1, candyCane2, cookie, cookiePony].map(e => e.type);
+13 -1
View File
@@ -60,6 +60,18 @@ export function getBoopRect(entity: Entity) {
return rect(entity.x + (right ? 0.6 : -0.9) * (sitting ? 0.6 : 1), entity.y - 0.2, 0.3, 0.4);
}
export function getkissRect(entity: Entity) {
const right = hasFlag(entity.state, EntityState.FacingRight);
const sitting = isPonySitting(entity);
return rect(entity.x + (right ? 0.4 : -0.8) * (sitting ? 0.6 : 1), entity.y - 0.225, 0.4, 0.45);
}
export function getSneezeRect(entity: Entity) {
const right = hasFlag(entity.state, EntityState.FacingRight);
const sitting = isPonySitting(entity);
return rect(entity.x + (right ? 0.45 : -0.95) * (sitting ? 0.6 : 1), entity.y - 0.25, 0.6, 0.5);
}
export function isMoving(entity: Entity) {
return entity.vx !== 0 || entity.vy !== 0;
}
@@ -126,7 +138,7 @@ export function canBoop(pony: Pony) {
return isIdle(pony);
}
export function canBoop2(entity: Entity) {
export function canBoopOrKiss(entity: Entity) {
return !isMoving(entity) && (isPonyStanding(entity) || isPonySitting(entity) || isPonyLying(entity) || isPonyFlying(entity));
}
+1 -1
View File
@@ -280,7 +280,7 @@ const constants = createPlainMap<() => Expression | undefined>({
'😆': () => expression(Eye.X, Eye.X, Muzzle.SmileOpen),
'😟': () => expression(Eye.Sad, Eye.Sad, Muzzle.Neutral),
'😠': () => expression(Eye.Angry, Eye.Angry, Muzzle.Smile),
'🤔': () => expression(Eye.Neutral, Eye.Frown2, Muzzle.Kiss),
'🤔': () => expression(Eye.Neutral, Eye.Frown2, Muzzle.Concerned),
'😈': () => expression(Eye.Angry, Eye.Angry, Muzzle.Smile, Iris.Up, Iris.Forward),
'👿': () => expression(Eye.Angry, Eye.Angry, Muzzle.SmileTeeth),
});
+1 -1
View File
@@ -283,7 +283,7 @@ export const expressions: [string, Result][] = [
['😆', [Eye.X, Eye.X, Muzzle.SmileOpen]],
['😟', [Eye.Sad, Eye.Sad, Muzzle.Neutral]],
['😠', [Eye.Angry, Eye.Angry, Muzzle.Smile]],
['🤔', [Eye.Neutral, Eye.Frown2, Muzzle.Kiss]],
['🤔', [Eye.Neutral, Eye.Frown2, Muzzle.Concerned]],
['😈', [Eye.Angry, Eye.Angry, Muzzle.Smile, Iris.Up, Iris.Forward]],
['👿', [Eye.Angry, Eye.Angry, Muzzle.SmileTeeth]],
// unsafe faces
+85 -8
View File
@@ -566,6 +566,7 @@ export const enum DoAction {
Boop,
Swing,
HoldPoof,
Kiss,
}
export interface Pony extends Entity {
@@ -1098,6 +1099,7 @@ export const enum Action {
RequestEntityInfo,
ACL,
Magic,
Kiss,
RemoveEntity,
PlaceEntity,
SwitchTool,
@@ -1221,6 +1223,7 @@ export interface SpriteSet<T> extends SpriteSetBase {
}
export interface PonyInfoBase<T, SET> {
//body: SET | undefined;
head: SET | undefined;
nose: SET | undefined;
ears: SET | undefined;
@@ -1387,10 +1390,17 @@ export interface HeadAnimationFrame {
mouth: Muzzle;
}
export const enum HeadAnimationProperties {
None = 0,
DontIncreaseEyeOpenness = 1,
DontDecreaseMouthOpenness = 2
}
export interface HeadAnimation {
name: string;
loop: boolean;
fps: number;
properties: HeadAnimationProperties;
frames: HeadAnimationFrame[];
}
@@ -1548,10 +1558,44 @@ export const enum Muzzle {
// max: 31
}
export const CLOSED_MUZZLES = [
Muzzle.Smile, Muzzle.Frown, Muzzle.Neutral, Muzzle.Scrunch, Muzzle.Flat, Muzzle.Concerned,
Muzzle.Kiss, Muzzle.Kiss2,
];
export function getMuzzleOpenness(muzzle: Muzzle) {
switch (muzzle) {
case Muzzle.Smile:
case Muzzle.Frown:
case Muzzle.Neutral:
case Muzzle.Scrunch:
case Muzzle.Blep:
case Muzzle.Flat:
case Muzzle.Concerned:
case Muzzle.Kiss:
case Muzzle.Kiss2:
case Muzzle.FlatBlep:
return 0;
case Muzzle.SmileOpen:
case Muzzle.ConcernedOpen:
case Muzzle.FrownOpen:
case Muzzle.NeutralOpen2:
case Muzzle.SmileTeeth:
case Muzzle.FrownTeeth:
case Muzzle.NeutralTeeth:
case Muzzle.ConcernedTeeth:
case Muzzle.Oh:
return 1;
case Muzzle.SmileOpen2:
return 2;
case Muzzle.ConcernedOpen2:
case Muzzle.SmileOpen3:
case Muzzle.NeutralOpen3:
case Muzzle.SmilePant:
case Muzzle.NeutralPant:
return 3;
case Muzzle.ConcernedOpen3:
return 4;
default:
console.error('unregistered muzzle in getMuzzleOpenness');
return 0;
}
}
export const enum Eye {
None = 0,
@@ -1582,10 +1626,43 @@ export const enum Eye {
// max: 31
}
export function isEyeSleeping(eye: Eye) {
return eye === Eye.Closed ||
(eye >= Eye.Lines && eye <= Eye.ClosedHappy) ||
(eye >= Eye.Peaceful && eye <= Eye.X2);
export function getEyeOpenness(eye: Eye) {
switch (eye) {
case Eye.None:
case Eye.Closed:
case Eye.ClosedHappy:
case Eye.ClosedHappy2:
case Eye.ClosedHappy3:
case Eye.Lines:
case Eye.Peaceful:
case Eye.Peaceful2:
case Eye.X:
case Eye.X2:
return 0;
case Eye.Neutral5:
case Eye.Frown4:
return 1;
case Eye.Neutral4:
case Eye.Frown3:
case Eye.Sad4:
return 2;
case Eye.Neutral3:
case Eye.Frown2:
case Eye.Sad3:
case Eye.Angry2:
return 3;
case Eye.Neutral2:
case Eye.Sad2:
case Eye.Angry:
case Eye.Frown:
return 4;
case Eye.Neutral:
case Eye.Sad:
return 5;
default:
console.error('unregistered eye in getEyeOpenness');
return 5;
}
}
export const enum Iris {
+15 -4
View File
@@ -2,7 +2,7 @@ import { PONY_WIDTH, PONY_HEIGHT, BLINK_FRAMES, canFly } from '../client/ponyUti
import { stand, sneeze, defaultHeadAnimation, defaultBodyFrame, defaultHeadFrame } from '../client/ponyAnimations';
import {
PaletteSpriteBatch, Pony, BodyAnimation, EntityState, SpriteBatch, ExpressionExtra, HeadAnimation, Palette,
PaletteManager, DrawOptions, Rect, EntityFlags, IMap, Entity, DoAction, Muzzle, Expression, isEyeSleeping,
PaletteManager, DrawOptions, Rect, EntityFlags, IMap, Entity, DoAction, Muzzle, Expression, getEyeOpenness,
Iris, EntityPlayerState,
} from './interfaces';
import { hasFlag, setFlag } from './utils';
@@ -26,7 +26,7 @@ import {
} from './animator';
import {
trotting, flying, hovering, toBoopState, isFlyingUpOrDown, isFlyingDown, isSittingDown, isSittingUp, swinging,
standing, sitting, lying, swimming, isSwimmingState, swimmingToFlying,
standing, sitting, lying, swimming, isSwimmingState, swimmingToFlying, toKissState,
} from '../client/ponyStates';
import { decodePonyInfo } from './compressPony';
import { defaultPonyState, defaultDrawPonyOptions, isStateEqual } from '../client/ponyHelpers';
@@ -199,6 +199,14 @@ export function updatePonyInfo(pony: Pony, info: string | Uint8Array, apply: ()
}
}
export function isPonyBug(pony: Pony) {
if (pony.info === undefined || pony.palettePonyInfo === undefined) {
return false;
}
const wingType = pony.palettePonyInfo.wings && pony.palettePonyInfo.wings.type || 0;
return wingType === 4;
}
export function ensurePonyInfoDecoded(pony: Pony) {
if (pony.info !== undefined && pony.palettePonyInfo === undefined) {
pony.palettePonyInfo = decodePonyInfo(pony.info, pony.paletteManager);
@@ -445,6 +453,9 @@ export function updatePonyEntity(pony: Pony, delta: number, gameTime: number, sa
case DoAction.HoldPoof:
playAnimation(pony.holdPoofEffect, holdPoofAnimation);
break;
case DoAction.Kiss:
setAnimatorState(pony.animator, toKissState(animationState) || animationState);
break;
default:
if (DEVELOPMENT) {
console.error(`Invalid DoAction: ${pony.doAction}`);
@@ -587,8 +598,8 @@ function filterExpression(expression: Expression) {
blush ||
hasFlag(extra, ExpressionExtra.Hearts) ||
hasFlag(extra, ExpressionExtra.Cry) ||
isEyeSleeping(expression.left) ||
isEyeSleeping(expression.right)
(getEyeOpenness(expression.left) === 0) ||
(getEyeOpenness(expression.right) === 0)
) {
if (expression.muzzle === Muzzle.SmilePant || expression.muzzle === Muzzle.NeutralPant) {
expression.muzzle = Muzzle.Neutral;
+1 -1
View File
@@ -142,7 +142,7 @@ const exampleCM = [
BLUE, BLUE, BLUE, BLUE, BLUE,
];
const frontLegsCount = 39;
const frontLegsCount = 40;
const backLegsCount = 27;
const frontLegsSheet = {
+1 -1
View File
@@ -26,7 +26,7 @@ h1(focusTitle) Account settings
| Changes saved
.form-group.text-right
button.btn.btn-default(type="submit" [disabled]="!canSubmit" style="min-width: 100px;")
| Save
| {{saveButtonText}}
div(style="margin-bottom: 100px;")
.form-group
+10 -1
View File
@@ -8,6 +8,7 @@ import { oauthProviders } from '../../../client/data';
import { Model } from '../../services/model';
import { getProviderIcon } from '../../shared/sign-in-box/sign-in-box';
import { faStar, faExclamationCircle, faSync } from '../../../client/icons';
import { Router } from '@angular/router';
@Component({
selector: 'account',
@@ -33,9 +34,10 @@ export class Account implements OnInit, OnDestroy {
removedAccount?: boolean;
accountError?: string;
accountSaved = false;
isNewAccount = false;
hides: HiddenPlayer[] | undefined = undefined;
page = 0;
constructor(private model: Model) {
constructor(private model: Model, private router: Router) {
}
ngOnInit() {
const account = this.account!;
@@ -44,6 +46,7 @@ export class Account implements OnInit, OnDestroy {
name: account.name,
birthdate: account.birthdate,
};
this.isNewAccount = account.birthdate === '';
this.pageChanged();
}
@@ -88,6 +91,9 @@ export class Account implements OnInit, OnDestroy {
get showAccountAlert() {
return this.model.missingBirthdate;
}
get saveButtonText() {
return this.isNewAccount ? 'Save and continue' : 'Save';
}
icon(id: string) {
return getProviderIcon(id);
}
@@ -98,6 +104,9 @@ export class Account implements OnInit, OnDestroy {
this.model.updateAccount(this.data)
.catch((e: Error) => this.accountError = e.message)
.then(() => this.accountSaved = true);
if (this.isNewAccount) {
this.router.navigate(['home']);
}
}
}
removeSite(site: SocialSiteInfo) {
+4 -2
View File
@@ -205,13 +205,15 @@ h1(focusTitle) Help
p.text-fading.
This is usually caused by outdated graphics drivers or browser.
Check if your browser is #[a(href="https://updatemybrowser.org/") up to date] and check
if there are new updates for your graphics card or device.
if there are new updates for your graphics driver or your device.
p.text-fading.
If your browser is already up to date, try installing different browser.
We recommend using #[a(href="https://www.google.com/chrome/") Chrome]
or #[a(href="https://www.mozilla.org/en-US/firefox/new/") Firefox].
p.text-fading.
Some older devices don't support necessary functions and will never work with the game.
If other workarounds didn't help, switching Graphics Quality to Low usually works.
You'll find that option in the game settings. After switching to Low, it
might take a minute for the screen to stop glitching.
section
h4 Graphical glitches, disappearing objects
@@ -83,6 +83,13 @@ export class ActionBar {
this.closeActions();
}
else {
// if the action menu was opened from the settings dropdown, just do nothing
// it means you can't close that menu by clicking on this button, but it's
// a minor issue
if (document.body.classList.contains('actions-modal-opened')) {
return;
}
this.isWaitingForActionsModal = true;
this.modalRef = this.modalService.show(this.actionsModal, { ignoreBackdropClick: true });
}
@@ -94,6 +94,7 @@ export class ActionsModal implements OnInit, OnDestroy {
this.game.editingActions = false;
clearInterval(this.interval);
this.subscription && this.subscription.unsubscribe();
this.close.emit(); // need to emit in case the menu wasn't closed with the Close button
this.notify.emit();
}
ok() {
@@ -102,8 +102,8 @@
.form-group
custom-checkbox(
[(checked)]="account.filterCyrillic"
help="Hide all messages containing russian alphabet")
| Filter russian chat
help="Hide all messages containing Russian alphabet")
| Filter Russian chat
.form-group
custom-checkbox(
[(checked)]="account.filterSwearWords"
@@ -112,6 +112,12 @@ ng-template(#shareLinkPopover)
.form-group
button.btn.btn-sm.ml-1([(ngModel)]="animation.loop" btnCheckbox [btnHighlight]="animation.loop")
| loop
.form-group(*ngIf="mode !== 'body'")
button.btn.btn-sm.ml-1([(ngModel)]="animation.dontOpenEyes" btnCheckbox [btnHighlight]="animation.dontOpenEyes")
| dontOpenEyes
.form-group(*ngIf="mode !== 'body'")
button.btn.btn-sm.ml-1([(ngModel)]="animation.dontCloseMouth" btnCheckbox [btnHighlight]="animation.dontCloseMouth")
| dontCloseMouth
.btn-group.ml-1(dropdown)
button.btn.btn-sm.btn-default.dropdown-toggle(dropdownToggle title="Before animation")
| before: {{beforeAnimation?.name || 'none'}}
@@ -7,9 +7,9 @@ import {
BodyAnimationFrame as IBodyAnimationFrame,
HeadAnimation as IHeadAnimation,
HeadAnimationFrame as IHeadAnimationFrame,
ColorExtraSet, PonyInfo, PonyObject, BodyShadow, PonyEye
ColorExtraSet, PonyInfo, PonyObject, BodyShadow, PonyEye, HeadAnimationProperties
} from '../../../common/interfaces';
import { removeItem, repeat, isKeyEventInvalid, cloneDeep, array } from '../../../common/utils';
import { removeItem, repeat, isKeyEventInvalid, cloneDeep, array, hasFlag } from '../../../common/utils';
import { toPalette, createDefaultPony, syncLockedPonyInfo } from '../../../common/ponyInfo';
import { Key } from '../../../client/input/input';
import { defaultPonyState, defaultDrawPonyOptions } from '../../../client/ponyHelpers';
@@ -63,6 +63,8 @@ interface BodyAnimation extends BaseAnimation {
interface HeadAnimation extends BaseAnimation {
lockEyes?: boolean;
frames: HeadAnimationFrame[];
dontOpenEyes?: boolean;
dontCloseMouth?: boolean;
}
interface AnimationsData {
@@ -201,6 +203,13 @@ export class ToolsAnimation implements OnInit, OnDestroy {
if (!this.playing) {
if (this.mode === 'body') {
this.state.animationFrame = this.frame;
if (this.state.headAnimation) {
const headFrames = this.state.headAnimation.frames.length;
let headFrame = Math.floor(this.frame / this.state.animation.frames.length * headFrames);
headFrame = Math.min(headFrame, headFrames - 1);
this.state.headAnimationFrame = headFrame;
}
} else {
this.state.headAnimationFrame = this.frame;
}
@@ -459,9 +468,10 @@ export class ToolsAnimation implements OnInit, OnDestroy {
console.log(`shadow: [${shadow.map(x => `[${x.join(', ')}]`).join(', ')}]`);
}
} else {
const animation = toHeadAnimation(this.headAnimation, true);
const compressed = animation.frames.map(compressHeadFrame);
console.log(JSON.stringify(compressed));
const frames = this.headAnimation.frames
.map(f => [f.duration, '[' + compressHeadFrame(f).join(', ') + ']'])
.map(([repeat, frame]) => repeat > 1 ? `...repeat(${repeat}, ${frame})` : frame);
console.log(`frames: [\n${frames.map(x => `\t${x}`).join(',\n')}\n]`);
}
}
png(scale = 1) {
@@ -531,9 +541,11 @@ export class ToolsAnimation implements OnInit, OnDestroy {
if (this.playing) {
this.time += delta;
if (this.mode === 'body') {
const isBodyMode = this.mode === 'body';
if (isBodyMode) {
if (this.state.animation) {
const frame = this.time * this.state.animation.fps;
const frame = Math.floor(this.time * this.state.animation.fps);
if (frame > this.state.animation.frames.length && !this.state.animation.loop) {
this.bodyAnimationPlaying = (this.bodyAnimationPlaying + 1) % this.bodyAnimationsToPlay.length;
@@ -541,14 +553,15 @@ export class ToolsAnimation implements OnInit, OnDestroy {
this.state.animationFrame = 0;
this.time = 0;
} else {
this.state.animationFrame = Math.floor(frame) % this.state.animation.frames.length;
this.state.animationFrame = frame % this.state.animation.frames.length;
}
}
} else {
if (this.state.headAnimation) {
const frame = Math.floor(this.time * this.state.headAnimation.fps);
this.state.headAnimationFrame = frame % this.state.headAnimation.frames.length;
}
}
if (this.state.headAnimation) {
const fps = this.state.headAnimation.fps;
const frame = Math.floor(this.time * fps) + (isBodyMode && !this.state.headAnimation.loop ? fps : 0);
this.state.headAnimationFrame = frame % this.state.headAnimation.frames.length;
}
}
}
@@ -564,9 +577,10 @@ export class ToolsAnimation implements OnInit, OnDestroy {
return this.storage.getJSON<AnimationsData>('tools-animations', {});
}
private createAnimationSprites(scale: number) {
const isBodyMode = this.mode === 'body';
const animation = toBodyAnimation(this.bodyAnimation, true, this.switch);
const headAnimation = toHeadAnimation(this.headAnimation, true);
const frames = this.mode === 'body' ? animation.frames.length : headAnimation.frames.length;
const frames = isBodyMode ? animation.frames.length : headAnimation.frames.length;
const buffer = createCanvas(ponyWidth, ponyHeight);
const batch = new ContextSpriteBatch(buffer);
const info = toPalette(this.pony.info);
@@ -582,14 +596,23 @@ export class ToolsAnimation implements OnInit, OnDestroy {
const x = i % cols;
const y = Math.floor(i / cols);
let headFrame = 0;
if (isBodyMode) {
let animTime = i / animation.fps;
if (!headAnimation.loop) {
animTime += 1;
}
headFrame = Math.floor(animTime * headAnimation.fps);
}
batch.start(sprites.paletteSpriteSheet, 0);
drawPony(batch, info, {
...defaultPonyState(),
animation,
animationFrame: this.mode === 'body' ? i : 0,
headAnimation: this.mode === 'head' ? headAnimation : undefined,
headAnimationFrame: this.mode === 'head' ? i : 0,
animationFrame: isBodyMode ? i : 0,
headAnimation: headAnimation,
headAnimationFrame: isBodyMode ? headFrame : i,
blinkFrame: 1,
}, ponyWidth / 2, ponyHeight - 10, options);
@@ -696,7 +719,7 @@ function compressBodyFrame(f: BodyAnimationFrame): number[] {
], x => !x);
}
function fromHeadAnimation({ name, fps, loop, frames }: IHeadAnimation, index: number): HeadAnimation {
function fromHeadAnimation({ name, fps, loop, properties, frames }: IHeadAnimation, index: number): HeadAnimation {
const fs: HeadAnimationFrame[] = [];
frames.forEach(f => {
@@ -713,18 +736,30 @@ function fromHeadAnimation({ name, fps, loop, frames }: IHeadAnimation, index: n
builtin: true,
fps,
loop,
dontOpenEyes: hasFlag(properties, HeadAnimationProperties.DontIncreaseEyeOpenness),
dontCloseMouth: hasFlag(properties, HeadAnimationProperties.DontDecreaseMouthOpenness),
name: `# builtin ${index.toString().padStart(2, '0')} # ${name}`,
frames: fs,
};
}
function toHeadAnimation({ name, frames, fps, loop }: HeadAnimation, full: boolean): IHeadAnimation {
function toHeadAnimation({ name, frames, fps, loop, dontOpenEyes, dontCloseMouth }: HeadAnimation, full: boolean):
IHeadAnimation {
const fs = (full && !loop) ? repeat(fps, createDefaultHeadFrame()).concat(frames) : frames;
let properties = HeadAnimationProperties.None;
if (dontOpenEyes) {
properties |= HeadAnimationProperties.DontIncreaseEyeOpenness;
}
if (dontCloseMouth) {
properties |= HeadAnimationProperties.DontDecreaseMouthOpenness;
}
return {
name,
fps,
loop,
properties,
frames: flatMap(fs, f => repeat(full ? f.duration : 1, f)),
};
}
@@ -788,6 +823,7 @@ function fixHeadAnimation(a: HeadAnimation): HeadAnimation {
fps: a.fps || 24,
loop: a.loop || false,
lockEyes: a.lockEyes || false,
dontOpenEyes: a.dontOpenEyes || false,
frames: (a.frames || []).map(fixHeadFrame),
};
}
@@ -17,7 +17,7 @@ import { createCamera } from '../../../common/camera';
import { mockPaletteManager } from '../../../common/ponyInfo';
import { isCritter } from '../../../common/entityUtils';
import {
createAnEntity, cloud, pony, apple, apple2, orange, orange2, candy, gift1, gift2, appleGreen, appleGreen2
createAnEntity, cloud, pony, apple, apple2, orange, orange2, candy, gift1, gift2, appleGreen, appleGreen2, bunny
} from '../../../common/entities';
import { drawMap } from '../../../client/draw';
import { includes, observableToPromise, hasFlag } from '../../../common/utils';
@@ -142,7 +142,7 @@ function drawTheMap(canvas: HTMLCanvasElement, map: WorldMap, info: ToolsMapOthe
};
const ignoreTypes = [
cloud, pony, apple, apple2, appleGreen, appleGreen2, orange, orange2, candy, gift1, gift2
cloud, pony, apple, apple2, appleGreen, appleGreen2, orange, orange2, candy, gift1, gift2, bunny
].map(e => e.type);
const shouldDraw = (e: Entity) => {
+2307 -2303
View File
File diff suppressed because one or more lines are too long
+7 -2
View File
@@ -258,6 +258,7 @@ export function createCommands(world: World): Command[] {
action(['sneeze', 'achoo'], Action.Sneeze),
action(['excite', 'tada'], Action.Excite),
action(['magic'], Action.Magic),
action(['kiss'], Action.Kiss),
// house
command(['savehouse'], '/savehouse - saves current house setup', '', async ({ }, client) => {
@@ -325,18 +326,22 @@ export function createCommands(world: World): Command[] {
client.reporter.systemLog(`House unlocked`);
}),
command(['removetoolbox'], '/removetoolbox - removes toolbox from the house', '', ({ world }, client) => {
if (!isValidMapForEditing(client.map, client, false, true))
if (!isValidMapForEditing(client.map, client, true, true))
return;
client.lastMapLoadOrSave = Date.now();
removeToolbox(world, client.map);
saySystem(client, 'Toolbox removed');
client.reporter.systemLog(`Toolbox removed`);
}),
command(['restoretoolbox'], '/restoretoolbox - restores toolbox to the house', '', ({ }, client) => {
if (!isValidMapForEditing(client.map, client, false, true))
if (!isValidMapForEditing(client.map, client, true, true))
return;
client.lastMapLoadOrSave = Date.now();
restoreToolbox(world, client.map);
saySystem(client, 'Toolbox restored');
+81 -42
View File
@@ -10,8 +10,8 @@ import {
} from '../common/utils';
import { CharacterState, ServerConfig, AccountState, CharacterStateFlags, GameServerSettings } from '../common/adminInterfaces';
import {
EntityState, Expression, PonyOptions, Action, ExpressionExtra, Eye, Muzzle, CLOSED_MUZZLES,
isExpressionAction, EntityPlayerState, UpdateFlags, InteractAction
EntityState, Expression, PonyOptions, Action, ExpressionExtra, Eye, Muzzle, getMuzzleOpenness,
isExpressionAction, EntityPlayerState, UpdateFlags, InteractAction, Rect
} from '../common/interfaces';
import { encodeExpression, EMPTY_EXPRESSION, decodeExpression } from '../common/encoders/expressionEncoder';
import { EXPRESSION_TIMEOUT, DAY, FLY_DELAY, SECOND, PONY_TYPE } from '../common/constants';
@@ -31,7 +31,7 @@ import {
import { replaceEmojis } from '../client/emoji';
import { expression, parseExpression } from '../common/expressionUtils';
import {
canBoop2, isPonySitting, isPonyStanding, getBoopRect, canStand, isPonyFlying, setPonyState, canSit, canLie
canBoopOrKiss, isPonySitting, isPonyStanding, getBoopRect, canStand, isPonyFlying, setPonyState, canSit, canLie, getkissRect, getSneezeRect
} from '../common/entityUtils';
import { withBorder } from '../common/rect';
import { isOnlineFriend } from './services/friends';
@@ -75,6 +75,17 @@ export function updateClientCharacter(client: IClient, character: ICharacter) {
client.characterName = replaceEmojis(client.character.name);
}
function isMobileUserAgent(userAgent?: string) {
if (!userAgent) {
return false;
}
return userAgent.includes('Android') ||
userAgent.includes('iPhone') ||
userAgent.includes('iPad') ||
userAgent.includes('iPod') ||
userAgent.includes('Windows Phone');
}
export function createClient(
client: IClient, account: IAccount, friends: string[], hides: string[], character: ICharacter, pony: ServerEntity,
defaultMap: ServerMap, reporter: Reporter, origin: IOriginInfo | undefined
@@ -85,6 +96,7 @@ export function createClient(
client.country = origin && origin.country || '??';
client.userAgent = client.originalRequest && client.originalRequest.headers['user-agent'];
client.isMobile = isMobileUserAgent(client.userAgent);
client.accountId = account._id.toString();
client.accountName = account.name;
client.ignores = new Set(account.ignores);
@@ -117,8 +129,7 @@ export function createClient(
client.safeY = pony.y;
client.lastPacket = Date.now();
client.lastAction = 0;
client.lastBoopAction = 0;
client.lastBoopOrKissAction = 0;
client.lastExpressionAction = 0;
client.lastSays = [];
client.lastX = pony.x;
@@ -307,7 +318,7 @@ export function parseOrCurrentExpression(pony: ServerEntity, message: string) {
export function playerSleep(pony: ServerEntity, args = '') {
if (pony.vx === 0 && pony.vy === 0) {
const base = parseOrCurrentExpression(pony, args) || expression(Eye.Closed, Eye.Closed, Muzzle.Neutral);
const muzzle = CLOSED_MUZZLES.indexOf(base.muzzle) !== -1 ? base.muzzle : Muzzle.Neutral;
const muzzle = getMuzzleOpenness(base.muzzle) === 0 ? base.muzzle : Muzzle.Neutral;
const expr = { ...base, muzzle, left: Eye.Closed, right: Eye.Closed, extra: ExpressionExtra.Zzz };
setEntityExpression(pony, expr, 0, true);
}
@@ -397,7 +408,8 @@ export function useHeldItem(client: IClient) {
}
export function canPerformAction(client: IClient) {
return client.lastAction < Date.now();
const now = Date.now();
return client.lastExpressionAction < now && client.lastBoopOrKissAction < now;
}
export function updateEntityPlayerState(client: IClient, entity: ServerEntity) {
@@ -416,53 +428,72 @@ export function turnHead(client: IClient) {
const purpleGrapeTypes = entities.grapesPurple.map(x => x.type);
const greenGrapeTypes = entities.grapesGreen.map(x => x.type);
export function boop(client: IClient, now: number) {
if (canPerformAction(client) && canBoop2(client.pony) && client.lastBoopAction < now) {
cancelEntityExpression(client.pony);
sendAction(client.pony, Action.Boop);
function boopEntity(client: IClient, rect: Rect, isOnlyBooping: boolean) {
if (!client.shadowed && (isPonySitting(client.pony) || isPonyStanding(client.pony))) {
const boopBounds = withBorder(rect, 1);
const entities = findEntitiesInBounds(client.map, boopBounds);
const entity = entities.find(e => canBoopEntity(e, rect));
if (!client.shadowed && (isPonySitting(client.pony) || isPonyStanding(client.pony))) {
const boopRect = getBoopRect(client.pony);
const boopBounds = withBorder(boopRect, 1);
const entities = findEntitiesInBounds(client.map, boopBounds);
const entity = entities.find(e => canBoopEntity(e, boopRect));
if (entity) {
if (entity.boop) {
entity.boop(client);
} else if (!isOnlyBooping && entity.type === PONY_TYPE) {
const clientHold = client.pony.options!.hold || 0;
if (entity) {
if (entity.boop) {
entity.boop(client);
} else if (entity.type === PONY_TYPE) {
const clientHold = client.pony.options!.hold || 0;
if (isHoldingGrapes(entity) && clientHold !== grapeGreen.type && clientHold !== grapePurple.type) {
let index = purpleGrapeTypes.indexOf(entity.options!.hold || 0);
if (isHoldingGrapes(entity) && clientHold !== grapeGreen.type && clientHold !== grapePurple.type) {
let index = purpleGrapeTypes.indexOf(entity.options!.hold || 0);
if (index !== -1) {
holdItem(client.pony, grapePurple.type);
if (index === (purpleGrapeTypes.length - 1)) {
unholdItem(entity);
} else {
holdItem(entity, purpleGrapeTypes[index + 1]);
}
} else {
let index = greenGrapeTypes.indexOf(entity.options!.hold || 0);
if (index !== -1) {
holdItem(client.pony, grapePurple.type);
holdItem(client.pony, grapeGreen.type);
if (index === (purpleGrapeTypes.length - 1)) {
if (index === (greenGrapeTypes.length - 1)) {
unholdItem(entity);
} else {
holdItem(entity, purpleGrapeTypes[index + 1]);
}
} else {
let index = greenGrapeTypes.indexOf(entity.options!.hold || 0);
if (index !== -1) {
holdItem(client.pony, grapeGreen.type);
if (index === (greenGrapeTypes.length - 1)) {
unholdItem(entity);
} else {
holdItem(entity, greenGrapeTypes[index + 1]);
}
holdItem(entity, greenGrapeTypes[index + 1]);
}
}
}
}
}
}
}
}
client.lastBoopAction = now + 500;
export function boop(client: IClient, now: number) {
if (canPerformAction(client) && canBoopOrKiss(client.pony)) {
cancelEntityExpression(client.pony);
sendAction(client.pony, Action.Boop);
boopEntity(client, getBoopRect(client.pony), false);
client.lastBoopOrKissAction = now + 850;
}
}
export function kiss(client: IClient, now: number) {
if (canPerformAction(client) && canBoopOrKiss(client.pony)) {
cancelEntityExpression(client.pony);
sendAction(client.pony, Action.Kiss);
boopEntity(client, getkissRect(client.pony), false);
client.lastBoopOrKissAction = now + 3350;
}
}
export function sneeze(client: IClient) {
if (canPerformAction(client)) {
cancelEntityExpression(client.pony);
sendAction(client.pony, Action.Sneeze);
boopEntity(client, getSneezeRect(client.pony), true);
client.lastExpressionAction = Date.now() + 750;
}
}
@@ -526,10 +557,10 @@ export function fly(client: IClient) {
}
export function expressionAction(client: IClient, action: Action) {
if (canPerformAction(client) && isExpressionAction(action) && client.lastExpressionAction < Date.now()) {
if (canPerformAction(client) && isExpressionAction(action)) {
cancelEntityExpression(client.pony);
sendAction(client.pony, action);
client.lastExpressionAction = Date.now() + 500;
client.lastExpressionAction = Date.now() + 750;
}
}
@@ -729,6 +760,9 @@ export function execAction(client: IClient, action: Action, settings: GameServer
case Action.TurnHead:
turnHead(client);
break;
case Action.Sneeze:
sneeze(client);
break;
case Action.Stand:
stand(client);
break;
@@ -766,6 +800,9 @@ export function execAction(client: IClient, action: Action, settings: GameServer
updateEntityState(client.pony, setFlag(client.pony.state, EntityState.Magic, !has));
}
break;
case Action.Kiss:
kiss(client, Date.now());
break;
case Action.SwitchTool:
switchTool(client, false);
break;
@@ -799,7 +836,9 @@ export function switchTool(client: IClient, reverse: boolean) {
const newIndex = reverse ? (index === -1 ? tools.length - 1 : index - 1) : ((index + 1) % tools.length);
const tool = tools[newIndex];
holdItem(client.pony, tool.type);
saySystem(client, tool.text);
console.log('isMobile ' + client.isMobile);
const text = (client.isMobile && tool.textMobile) ? tool.textMobile : tool.text;
saySystem(client, text);
}
}
+2 -1
View File
@@ -50,8 +50,9 @@ export default function (server: ServerConfig, settings: Settings, world: World
const header = 'data:image/gif;base64,';
const buffer = Buffer.from(image.substr(header.length), 'base64');
const magick = /^win/.test(process.platform) ? 'magick' : 'convert';
const command = `${magick} -dispose 3 -delay ${100 / fps} -loop 0 "${filePath}" -crop ${width}x${height} `
const command = `${magick} -dispose Background -delay ${100 / fps} -loop 0 "${filePath}" -crop ${width}x${height} `
+ `+repage${repeat(' +delete', remove)} "${filePath.replace(/png$/, 'gif')}"`;
console.log('executing ' + command);
fs.writeFileAsync(filePath, buffer)
.then(() => execAsync(command))
+3 -2
View File
@@ -156,6 +156,7 @@ export interface IClient extends ClientActions, ClientExtensions {
characterName: string;
character: ICharacter;
isMobile: boolean;
isMod: boolean;
shadowed: boolean;
supporterLevel: number;
@@ -181,8 +182,7 @@ export interface IClient extends ClientActions, ClientExtensions {
safeX: number;
safeY: number;
lastPacket: number;
lastAction: number;
lastBoopAction: number;
lastBoopOrKissAction: number;
lastExpressionAction: number;
lastSays: LastSay[];
lastX: number;
@@ -278,6 +278,7 @@ export interface Controller {
update(delta: number, now: number): void;
sparseUpdate?(): void;
toggleWall?(x: number, y: number, type: TileType): void;
removeWalls?(): void;
}
export interface AccountService {
+13 -17
View File
@@ -138,6 +138,13 @@ export class World {
}
}
}
removeWalls(map: ServerMap) {
for (const controller of map.controllers) {
if (controller.removeWalls) {
controller.removeWalls();
}
}
}
getState(): WorldState {
return {
time: this.time,
@@ -291,22 +298,13 @@ export class World {
const nowSeconds = now / 1000;
const deltaSeconds = delta / 1000;
timingStart('update tiles');
timingStart('update tiles and colliders');
for (const map of this.maps) {
if (!map.dontUpdateTilesAndColliders) {
for (const region of map.regions) {
if (region.tilesDirty) {
updateTileIndices(region, map);
}
}
}
}
timingEnd();
timingStart('update colliders');
for (const map of this.maps) {
if (!map.dontUpdateTilesAndColliders) {
for (const region of map.regions) {
if (region.colliderDirty) {
generateRegionCollider(region, map);
}
@@ -325,9 +323,7 @@ export class World {
if (delta > 0) {
if (entity.vx !== 0 || entity.vy !== 0) {
timingStart('updatePosition()');
updatePosition(entity, delta, map);
timingEnd();
}
entity.timestamp = nowSeconds;
@@ -370,15 +366,15 @@ export class World {
timingEnd();
timingStart('timeoutEntityExpression + inTheAirDelay');
for (const { pony } of this.clients) {
for (const client of this.clients) {
// timeout expressions
if (pony.exprTimeout && pony.exprTimeout < now) {
setEntityExpression(pony, undefined); // NOTE: creates updates
if (client.pony.exprTimeout && client.pony.exprTimeout < now) {
setEntityExpression(client.pony, undefined); // NOTE: creates updates
}
// count down in-the-air delay
if (pony.inTheAirDelay !== undefined && pony.inTheAirDelay > 0) {
pony.inTheAirDelay -= deltaSeconds;
if (client.pony.inTheAirDelay !== undefined && client.pony.inTheAirDelay > 0) {
client.pony.inTheAirDelay -= deltaSeconds;
}
}
timingEnd();
-1
View File
@@ -94,7 +94,6 @@ export function mockClient(fields: any = {}): IClient {
subscribes: [],
saysQueue: [],
lastSays: [],
lastAction: 0,
lastBoopAction: 0,
lastExpressionAction: 0,
viewWidth: 3,
+15 -15
View File
@@ -132,7 +132,6 @@ describe('playerUtils', () => {
safeX: 10,
safeY: 20,
lastPacket: 123,
lastAction: 0,
lastBoopAction: 0,
lastExpressionAction: 0,
lastX: 10,
@@ -516,11 +515,11 @@ describe('playerUtils', () => {
describe('canPerformAction()', () => {
it('returns true if last action date is below current time', () => {
expect(canPerformAction(mockClient({ lastAction: 1234 }))).true;
expect(canPerformAction(mockClient({ lastBoopOrKissAction: 1234 }))).true;
});
it('returns false if last action date is ahead or current time', () => {
expect(canPerformAction(mockClient({ lastAction: Date.now() + 1000 }))).false;
expect(canPerformAction(mockClient({ lastExpressionAction: Date.now() + 1000 }))).false;
});
});
@@ -563,7 +562,8 @@ describe('playerUtils', () => {
client = mockClient();
client.map = createServerMap('foo', 0, 1, 1);
client.pony.region = client.map.regions[0];
client.lastAction = 0;
client.lastExpressionAction = 0;
client.lastBoopOrKissAction = 0;
});
it('sends boop action', () => {
@@ -586,12 +586,12 @@ describe('playerUtils', () => {
expect(client.pony.options!.expr).equal(EMPTY_EXPRESSION);
});
it('updates last boop action', () => {
client.lastBoopAction = 0;
it('updates last boop/kiss action', () => {
client.lastBoopOrKissAction = 0;
boop(client, 100);
expect(client.lastBoopAction).equal(100 + 500);
expect(client.lastBoopOrKissAction).equal(100 + 500);
});
it('executes boop on found entity', () => {
@@ -618,7 +618,7 @@ describe('playerUtils', () => {
});
it('does nothing if cannot perform action', () => {
client.lastAction = 1000;
client.lastBoopOrKissAction = 1000;
boop(client, 0);
@@ -646,7 +646,7 @@ describe('playerUtils', () => {
it('does not update flags if cannot perform action', () => {
const client = mockClient();
client.lastAction = Date.now() + 1000;
client.lastBoopOrKissAction = Date.now() + 1000;
client.pony.state = 0;
turnHead(client);
@@ -703,7 +703,7 @@ describe('playerUtils', () => {
});
it('does nothing if cannot perform action', () => {
client.lastAction = Date.now() + 1000;
client.lastBoopOrKissAction = Date.now() + 1000;
client.pony.state = 0;
stand(client);
@@ -752,7 +752,7 @@ describe('playerUtils', () => {
});
it('does nothing if cannot perform action', () => {
client.lastAction = Date.now() + 1000;
client.lastBoopOrKissAction = Date.now() + 1000;
client.pony.state = 0;
sit(client, {});
@@ -796,7 +796,7 @@ describe('playerUtils', () => {
});
it('does nothing if cannot perform action', () => {
client.lastAction = Date.now() + 1000;
client.lastBoopOrKissAction = Date.now() + 1000;
client.pony.state = 0;
lie(client);
@@ -844,7 +844,7 @@ describe('playerUtils', () => {
});
it('does nothing if already flying', () => {
client.lastAction = Date.now() + 1000;
client.lastBoopOrKissAction = Date.now() + 1000;
client.pony.state = EntityState.PonyFlying;
fly(client);
@@ -854,7 +854,7 @@ describe('playerUtils', () => {
});
it('does nothing if cannot perform action', () => {
client.lastAction = Date.now() + 1000;
client.lastBoopOrKissAction = Date.now() + 1000;
client.pony.state = 0;
fly(client);
@@ -896,7 +896,7 @@ describe('playerUtils', () => {
});
it('does nothing if cannot perform action', () => {
client.lastAction = Date.now() + 1000;
client.lastExpressionAction = Date.now() + 1000;
expressionAction(client, Action.Yawn);