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
+1 -1
View File
@@ -30,7 +30,7 @@ creator(s):
SC (https://0somecunt0.tumblr.com/tagged/sfw) SC (https://0somecunt0.tumblr.com/tagged/sfw)
SailorDolpin (https://vk.com/id324582699) SailorDolpin (https://vk.com/id324582699)
Deeraw (https://www.deviantart.com/deerdraw, https://twitter.com/TheOnlyDeeraw) Deeraw (https://www.deviantart.com/deerdraw, https://twitter.com/TheOnlyDeeraw)
Aviivix Aviivix
SnowFl8keAnge1 SnowFl8keAnge1
+20
View File
@@ -342,6 +342,26 @@ Read more about it [here](https://stackoverflow.com/questions/1139762/ignore-fil
## Changelog ## Changelog
### Pony.Town v0.55.0
- Added Kiss action
- Added being able to extinguish torches by sneezing
- Fixed house tool instructions saying "use mouse wheel" on mobile
- Fixed being able to open two actions menus in some cases
- Fixed actions menu button getting disabled by closing actions menu with the Escape key
- Fixed Yawn and Sneeze animations glitching with some eye styles
- Fixed :thinking: expression being inconsistent with /thinking command
- Fixed flying entities slowing down when flying over water
- Fixed jack-o'-lantern not turning off during the day
- Fixed some UI issues
- Added combining of head and body animations in the Animation tool
- Added flag to not allow closing mouths in Animation tool
- Fixed issue with gif preview in Animation tool
- Added a front leg frame
- Added Save button in account settings automatically redirecting to Home for new accounts
- Updated Help page to provide more workarounds for users who experience game showing black screen
- Updated contributor list
### Pony.Town v0.54.0 ### Pony.Town v0.54.0
- Added a separate logo file for Visit Pony Town button - Added a separate logo file for Visit Pony Town button
- Fixed users not being able to close actions menu in some cases - Fixed users not being able to close actions menu in some cases
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "pony-town", "name": "pony-town",
"version": "0.54.0", "version": "0.55.0",
"description": "A game of ponies building a town", "description": "A game of ponies building a town",
"main": "pony-town.js", "main": "pony-town.js",
"scripts": { "scripts": {
+10 -7
View File
@@ -2,7 +2,7 @@ import { compact } from 'lodash';
import { import {
Expression, ExpressionButtonAction, CommandButtonAction, ActionButtonAction, ItemButtonAction, Expression, ExpressionButtonAction, CommandButtonAction, ActionButtonAction, ItemButtonAction,
ColorShadow, Eye, Muzzle, Iris, ButtonActionSlot, ExpressionExtra, ButtonAction, ChatType, BodyAnimation, ColorShadow, Eye, Muzzle, Iris, ButtonActionSlot, ExpressionExtra, ButtonAction, ChatType, BodyAnimation,
Action, isPartyChat, EntityButtonAction, defaultDrawOptions Action, isPartyChat, EntityButtonAction, defaultDrawOptions, HeadAnimationProperties
} from '../common/interfaces'; } from '../common/interfaces';
import * as sprites from '../generated/sprites'; import * as sprites from '../generated/sprites';
import { createExpression } from './clientUtils'; import { createExpression } from './clientUtils';
@@ -11,7 +11,7 @@ import { PonyTownGame } from './game';
import { boopAction, upAction, downAction, turnHeadAction } from './playerActions'; import { boopAction, upAction, downAction, turnHeadAction } from './playerActions';
import { ACTIONS_LIMIT, COMMAND_ACTION_TIME_DELAY } from '../common/constants'; import { ACTIONS_LIMIT, COMMAND_ACTION_TIME_DELAY } from '../common/constants';
import { cloneDeep, hasFlag } from '../common/utils'; 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 { createDefaultPony, syncLockedPonyInfo, toPalette, mockPaletteManager } from '../common/ponyInfo';
import { import {
ACTION_EXPRESSION_EYE_COLOR, ACTION_EXPRESSION_BG, ACTION_ACTION_COAT_COLOR, WHITE, HEARTS_COLOR, 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', 'Drop item', Action.Drop),
actionButtonAction('drop-toy', 'Drop toy', Action.DropToy), actionButtonAction('drop-toy', 'Drop toy', Action.DropToy),
actionButtonAction('magic', 'Magic', Action.Magic), actionButtonAction('magic', 'Magic', Action.Magic),
actionButtonAction('switch-tool', 'Switch tool', Action.SwitchTool), actionButtonAction('kiss', 'Kiss', Action.Kiss),
actionButtonAction('switch-entity', 'Switch item to place'),
actionButtonAction('switch-entity-rev', 'Switch item to place (reverse)'),
actionButtonAction('switch-tile', 'Switch tile to place'),
]; ];
const commandActions = [ const commandActions = [
@@ -395,7 +392,8 @@ export function drawAction(canvas: HTMLCanvasElement, action: ButtonAction | und
const buffer = drawCanvas(bufferSize, bufferSize, sprites.paletteSpriteSheet, undefined, batch => { const buffer = drawCanvas(bufferSize, bufferSize, sprites.paletteSpriteSheet, undefined, batch => {
const state = { ...createState(), expression: action.expression }; const state = { ...createState(), expression: action.expression };
const options = { ...defaultDrawPonyOptions(), noEars: true }; 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) { if (action.expression) {
const extra = action.expression.extra; 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); batch.drawSprite(sprites.magic_icon, WHITE, defaultPalette, 4, 2);
break; break;
} }
case 'kiss': {
const state = { ...createState(), headAnimation: kiss, headAnimationFrame: 9 };
drawPony(batch, actionPony, state, 17, 40, defaultDrawPonyOptions());
break;
}
case 'switch-tool': { case 'switch-tool': {
const palette = mockPaletteManager.addArray(sprites.tools_icon.palettes![0]); const palette = mockPaletteManager.addArray(sprites.tools_icon.palettes![0]);
batch.drawSprite(sprites.tools_icon.color, WHITE, palette, 0, 2); batch.drawSprite(sprites.tools_icon.color, WHITE, palette, 0, 2);
+2
View File
@@ -91,6 +91,7 @@ export const CONTRIBUTORS: Contributors[] = [
group: 'Programmers', group: 'Programmers',
contributors: [ contributors: [
{ name: 'Industrialice' }, { 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: 'Deeraw', links: ['https://www.deviantart.com/deerdraw', 'https://twitter.com/TheOnlyDeeraw'] },
{ name: 'Aviivix' }, { name: 'Aviivix' },
{ name: 'SnowFl8keAnge1' }, { 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 { import {
isPony, createPony, setPonyExpression, updatePonyInfo, updatePonyHold, doPonyAction, hasHeadAnimation, isPony, createPony, setPonyExpression, updatePonyInfo, updatePonyHold, doPonyAction, hasHeadAnimation,
setHeadAnimation, setHeadAnimation,
doBoopPonyAction doBoopPonyAction,
isPonyBug
} from '../common/pony'; } from '../common/pony';
import { PonyTownGame } from './game'; import { PonyTownGame } from './game';
import { setupPlayer, savePlayerPosition } from './sec'; import { setupPlayer, savePlayerPosition } from './sec';
@@ -25,7 +26,7 @@ import { decodeUpdate, readOneUpdate } from '../common/encoders/updateDecoder';
import { updateEntityVelocity } from '../common/entityUtils'; import { updateEntityVelocity } from '../common/entityUtils';
import { decodePonyInfo } from '../common/compressPony'; import { decodePonyInfo } from '../common/compressPony';
import { mockPaletteManager } from '../common/ponyInfo'; import { mockPaletteManager } from '../common/ponyInfo';
import { yawn, laugh, sneeze, excite } from './ponyAnimations'; import { yawn, laugh, sneeze, kiss, kissFly, kissFlyBug, excite } from './ponyAnimations';
import { import {
findEntityById, getRegionGlobal, setTile, removeEntity, addEntity, removeEntityDirectly, setRegion, findEntityById, getRegionGlobal, setTile, removeEntity, addEntity, removeEntityDirectly, setRegion,
addEntityToMapRegion, switchEntityRegion, getRegionUnsafe, addOrRemoveFromEntityList, addEntityToMapRegion, switchEntityRegion, getRegionUnsafe, addOrRemoveFromEntityList,
@@ -420,6 +421,22 @@ export function handleAction(game: PonyTownGame, id: number, action: Action) {
setHeadAnimation(pony, excite); setHeadAnimation(pony, excite);
} }
break; 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: default:
log(`handleAction: Invalid action: ${action}`); 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'; import { repeat, flatten } from '../common/utils';
// body animations // body animations
@@ -23,7 +23,7 @@ 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> { ): Readonly<BodyAnimation> {
if (shadowOffsets && shadowOffsets.length !== frames.length) { 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 })); 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]), ...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 flyUpAnims = [undefined, flyUp, flyUp, flyUp, flyUpBug];
export const flyDownAnims = [undefined, flyDown, flyDown, flyDown, flyDownBug]; export const flyDownAnims = [undefined, flyDown, flyDown, flyDown, flyDownBug];
export const animations = [ export const animations = [
stand, trot, boop, boopSit, boopLie, boopSwim, boopFly, boopFlyBug, sit, sitDown, standUp, lie, lieDown, sitUp, stand, trot, boop, boopSit, boopLie, boopSwim, boopFly, boopFlyBug, sit, sitDown, standUp, lie, lieDown, sitUp,
fly, flyBug, flyUp, flyUpBug, flyDown, flyDownBug, sitToTrot, lieToTrot, flyToTrot, flyToTrotBug, 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]); 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 }; return { headX, headY, left, right, mouth };
} }
export function createHeadAnimation(name: string, fps: number, loop: boolean, frames: number[][]): HeadAnimation { export function createHeadAnimation(name: string, fps: number, loop: boolean, frames: number[][],
return { name, fps, loop, frames: frames.map(createHeadFrame) }; properties?: HeadAnimationProperties): HeadAnimation {
if (!properties) {
properties = HeadAnimationProperties.None;
}
return { name, fps, loop, properties, frames: frames.map(createHeadFrame) };
} }
export const smile = createHeadAnimation('smile', 24, true, [ 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(18, [1, -1, 12, 12, 16]),
...repeat(8, [0, 0, 12, 12, 12]), ...repeat(8, [0, 0, 12, 12, 12]),
[0, 0, 18, 18, 2], [0, 0, 18, 18, 2],
]); ], HeadAnimationProperties.DontIncreaseEyeOpenness);
export const surprise = createHeadAnimation('surprise', 8, false, [ export const surprise = createHeadAnimation('surprise', 8, false, [
[0, 1, 6, 6, 1], [0, 1, 6, 6, 1],
@@ -538,7 +691,7 @@ export const sneeze = createHeadAnimation('sneeze', 12, false, [
...repeat(2, [1, -1, 18, 18, 16]), ...repeat(2, [1, -1, 18, 18, 16]),
...repeat(8, [-1, 1, 23, 23, 13]), ...repeat(8, [-1, 1, 23, 23, 13]),
...repeat(4, [0, 0, 18, 18, 7]), ...repeat(4, [0, 0, 18, 18, 7]),
]); ], HeadAnimationProperties.DontIncreaseEyeOpenness);
export const happy_tongue = createHeadAnimation('happy_tongue', 12, false, [ export const happy_tongue = createHeadAnimation('happy_tongue', 12, false, [
...repeat(3, [0, 0, 1, 1, 0]), ...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]), ...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 = [ export const headAnimations = [
smile, nom, laugh, yawn, surprise, surpriseSad, sneeze, excite, smile, nom, laugh, yawn, surprise, surpriseSad, sneeze, excite, kiss, kissFly, kissFlyBug
]; ];
// default animations // default animations
+44 -17
View File
@@ -1,7 +1,8 @@
import { import {
PonyEye, PonyState, PalettePonyInfo, PaletteSpriteSet, Palette, HeadAnimationFrame, PonyEye, PonyState, PalettePonyInfo, PaletteSpriteSet, Palette, HeadAnimationFrame,
Eye, Iris, ColorExtraSets, ExpressionExtra, BodyAnimationFrame, DrawPonyOptions, Muzzle, BodyShadow, DrawOptions, 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'; } from '../common/interfaces';
import { WHITE, SHINES_COLOR, FAR_COLOR, TRANSPARENT, fillToOutlineColor } from '../common/colors'; import { WHITE, SHINES_COLOR, FAR_COLOR, TRANSPARENT, fillToOutlineColor } from '../common/colors';
import { toInt, hasFlag, repeat, flatten, point } from '../common/utils'; 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) { export function drawPony(batch: Batch, info: Info, state: State, ponyX: number, ponyY: number, options: Options) {
const frame = getPonyAnimationFrame(state.animation, state.animationFrame, defaultBodyFrame); const frame = getPonyAnimationFrame(state.animation, state.animationFrame, defaultBodyFrame);
const headFrame = getPonyAnimationFrame(state.headAnimation || defaultHeadAnimation, state.headAnimationFrame, defaultHeadFrame); const headFrame = getPonyAnimationFrame(state.headAnimation || defaultHeadAnimation, state.headAnimationFrame, defaultHeadFrame);
const headAnimationProperties = (state.headAnimation || defaultHeadAnimation).properties;
const baseX = ponyX - PONY_WIDTH / 2; const baseX = ponyX - PONY_WIDTH / 2;
const baseY = ponyY - PONY_HEIGHT; const baseY = ponyY - PONY_HEIGHT;
const x = baseX + frame.bodyX; 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); 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); drawSet(batch, sprites.headAccessories, info.headAccessory, hatOffset.x, hatOffset.y + hatOffsetY, WHITE);
batch.restore(); batch.restore();
@@ -514,7 +516,7 @@ export function drawPony(batch: Batch, info: Info, state: State, ponyX: number,
export function drawHead( export function drawHead(
batch: Batch, info: Info, x: number, y: number, headSprites: ColorExtraSets, headFrame: HeadAnimationFrame, 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, options: Options, flip: boolean, maneOffsetY: number,
) { ) {
const extraOffset = at(EXTRA_ACCESSORY_OFFSETS, info.mane && info.mane.type) || pointZero; 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 // make sure eyes are closed if sleeping
if (hasFlag(expression.extra, ExpressionExtra.Zzz)) { if (hasFlag(expression.extra, ExpressionExtra.Zzz)) {
if (!isEyeSleeping(eyeLeftBase)) { if (getEyeOpenness(eyeLeftBase) !== 0) {
eyeLeftBase = Eye.Closed; eyeLeftBase = Eye.Closed;
} }
if (!isEyeSleeping(eyeRightBase)) { if (getEyeOpenness(eyeRightBase) !== 0) {
eyeRightBase = Eye.Closed; eyeRightBase = Eye.Closed;
} }
} }
} }
const eyeRight = getEyeFrame(info.eyeOpennessRight || 1, eyeRightBase, headFrame.right, blinkFrame); const eyeRight = getEyeFrame(info.eyeOpennessRight || 1, eyeRightBase, headFrame.right, animationProperties, blinkFrame);
const eyeLeft = getEyeFrame(info.eyeOpennessLeft || 1, eyeLeftBase, headFrame.left, blinkFrame); const eyeLeft = getEyeFrame(info.eyeOpennessLeft || 1, eyeLeftBase, headFrame.left, animationProperties, blinkFrame);
const eyeFrameLeft = flip ? eyeRight : eyeLeft; const eyeFrameLeft = flip ? eyeRight : eyeLeft;
const eyeFrameRight = flip ? eyeLeft : eyeRight; const eyeFrameRight = flip ? eyeLeft : eyeRight;
const eyeColorLeft = flip ? info.eyeColorRight : info.eyeColorLeft; const eyeColorLeft = flip ? info.eyeColorRight : info.eyeColorLeft;
@@ -613,12 +615,7 @@ export function drawHead(
} }
if (draw(options, NoDraw.Body) && draw(options, NoDraw.Nose)) { if (draw(options, NoDraw.Body) && draw(options, NoDraw.Nose)) {
const muzzle = holding ? const muzzle = getMouthFrame(!!holding, expression, headFrame.mouth, info.muzzle, animationProperties);
Muzzle.Smile :
headFrame.mouth === -1 ?
(expression ? expression.muzzle : info.muzzle) :
headFrame.mouth;
const noses = at(sprites.noses, muzzle); const noses = at(sprites.noses, muzzle);
const nose = att(noses, info.nose && info.nose.type)![0]; 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) { function getEyeFrame(base: Eye, expression: Eye, anim: Eye, animationProperties: HeadAnimationProperties, blinkFrame: number) {
if (anim !== -1)
return anim;
const frame = expression === -1 ? base : expression; 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]; const blink = blinkFrames[frame];
if (blinkFrame > 1 && blink) { if (blinkFrame > 1 && blink) {
@@ -741,6 +745,29 @@ function getEyeFrame(base: Eye, expression: Eye, anim: Eye, blinkFrame: number)
return frame; 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( function drawEye(
batch: Batch, eye: PonyEye | undefined, iris: Iris, info: Info, palette: Palette | undefined, eyePalette: Palette, batch: Batch, eye: PonyEye | undefined, iris: Iris, info: Info, palette: Palette | undefined, eyePalette: Palette,
x: number, y: number x: number, y: number
+46 -5
View File
@@ -2,7 +2,8 @@ import { animatorState as state, animatorTransition as transition, anyState, Ani
import { import {
stand, sit, sitDown, standUp, lie, lieDown, sitUp, flyBug, fly, flyUp, flyDown, flyUpBug, flyDownBug, stand, sit, sitDown, standUp, lie, lieDown, sitUp, flyBug, fly, flyUp, flyDown, flyUpBug, flyDownBug,
trot, boop, boopSit, swim, sitToTrot, lieToTrot, boopLie, trotToFly, trotToFlyBug, boopFly, boopFlyBug, 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'; } from './ponyAnimations';
import { BodyAnimation } from '../common/interfaces'; 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 boopingFlying = state(n('booping-flying'), boopFly, { bug: boopFlyBug });
export const boopingSwimming = state(n('booping-swimming'), boopSwim); 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 sitting = state(n('sitting'), sit);
export const sittingDown = state(n('sitting-down'), sitDown); export const sittingDown = state(n('sitting-down'), sitDown);
export const standingUp = state(n('standing-up'), standUp); export const standingUp = state(n('standing-up'), standUp);
@@ -51,7 +60,8 @@ export const ponyStates = [
sitting, sittingDown, standingUp, sittingToTrotting, sitting, sittingDown, standingUp, sittingToTrotting,
lying, lyingDown, sittingUp, lyingToTrotting, lying, lyingDown, sittingUp, lyingToTrotting,
hovering, flying, flyingUp, flyingDown, trottingToFlying, flyingToTrotting, 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 }); transition(hovering, flyingDown, { exitAfter: 0 });
@@ -101,7 +111,6 @@ transition(flying, hovering, { exitAfter: 0, keepTime: true });
transition(boopingSwimming, swimming); transition(boopingSwimming, swimming);
transition(swimming, boopingSwimming, { exitAfter: 0 }); transition(swimming, boopingSwimming, { exitAfter: 0 });
transition(booping, standing); transition(booping, standing);
transition(standing, booping, { exitAfter: 0 }); transition(standing, booping, { exitAfter: 0 });
transition(boopingSitting, sitting); transition(boopingSitting, sitting);
@@ -111,7 +120,27 @@ transition(lying, boopingLying, { exitAfter: 0 });
transition(boopingFlying, hovering, { enterTime: 1.1 / 10 }); transition(boopingFlying, hovering, { enterTime: 1.1 / 10 });
transition(hovering, boopingFlying, { exitAfter: 0 }); 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, trotting, { exitAfter: 0, keepTime: true });
transition(anyState, flying, { 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) { export function isSwimmingState(state: AnimatorState<BodyAnimation> | undefined) {
return state === swimming || state === trottingToSwimming || state === swimmingToTrotting || 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) { export function isFlyingUpOrDown(state: AnimatorState<BodyAnimation> | undefined) {
@@ -153,3 +183,14 @@ export function toBoopState(state: AnimatorState<BodyAnimation>) {
default: return undefined; 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 // 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 pumpkinCollider = mixColliderRounded(-11, -6, 22, 12, 5, false);
const pumpkinPickable = mixPickable(26, 50); const pumpkinPickable = mixPickable(26, 50);
const pumpkinParts = [pumpkinCollider, pumpkinPickable]; const pumpkinParts = [pumpkinCollider, pumpkinPickable];
const pumpkinDX = 11; const pumpkinDX = 11;
const pumpkinDY = 15; const pumpkinDY = 15;
const pumpkinAnimOff = [0];
const pumpkinAnimOn = [1];
export const pumpkin = doodad(n('pumpkin'), sprites.pumpkin_default, pumpkinDX, pumpkinDY, 0, export const pumpkin = doodad(n('pumpkin'), sprites.pumpkin_default, pumpkinDX, pumpkinDY, 0,
...pumpkinParts); ...pumpkinParts);
@@ -1193,11 +1199,20 @@ export const jackoOn = doodad(n('jacko-on'), sprites.pumpkin_on, pumpkinDX, pump
mixLight(jackoLightColor, 0, 0, 256, 192), mixLight(jackoLightColor, 0, 0, 256, 192),
mixLightSprite(sprites.pumpkin_light, WHITE, pumpkinDX, pumpkinDY)); 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, ...pumpkinParts,
mixLight(jackoLightColor, 0, 0, 256, 192), mixLight(jackoLightColor, 0, 0, 256, 192),
mixLightSprite(sprites.pumpkin_light, WHITE, pumpkinDX, pumpkinDY), mixLightSprite(sprites.pumpkin_light, WHITE, pumpkinDX, pumpkinDY),
mixFlags(EntityFlags.OnOff)); mixFlags(EntityFlags.OnOff));*/
// tombstones // tombstones
@@ -2358,10 +2373,14 @@ export const fruits = [
]; ];
export const tools = [ export const tools = [
{ type: saw.type, text: 'Saw: place & remove walls' }, { type: saw.type, text: 'Saw: place & remove walls', textMobile: undefined },
{ type: broom.type, text: 'Broom: remove furniture' }, { type: broom.type, text: 'Broom: remove furniture', textMobile: undefined },
{ type: hammer.type, text: 'Hammer: place furniture\nuse [mouse wheel] to switch item' }, { type: hammer.type,
{ type: shovel.type, text: 'Shovel: change floor\nuse [mouse wheel] to switch floor 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); 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); 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) { export function isMoving(entity: Entity) {
return entity.vx !== 0 || entity.vy !== 0; return entity.vx !== 0 || entity.vy !== 0;
} }
@@ -126,7 +138,7 @@ export function canBoop(pony: Pony) {
return isIdle(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)); 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.X, Eye.X, Muzzle.SmileOpen),
'😟': () => expression(Eye.Sad, Eye.Sad, Muzzle.Neutral), '😟': () => expression(Eye.Sad, Eye.Sad, Muzzle.Neutral),
'😠': () => expression(Eye.Angry, Eye.Angry, Muzzle.Smile), '😠': () => 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.Smile, Iris.Up, Iris.Forward),
'👿': () => expression(Eye.Angry, Eye.Angry, Muzzle.SmileTeeth), '👿': () => 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.X, Eye.X, Muzzle.SmileOpen]],
['😟', [Eye.Sad, Eye.Sad, Muzzle.Neutral]], ['😟', [Eye.Sad, Eye.Sad, Muzzle.Neutral]],
['😠', [Eye.Angry, Eye.Angry, Muzzle.Smile]], ['😠', [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.Smile, Iris.Up, Iris.Forward]],
['👿', [Eye.Angry, Eye.Angry, Muzzle.SmileTeeth]], ['👿', [Eye.Angry, Eye.Angry, Muzzle.SmileTeeth]],
// unsafe faces // unsafe faces
+85 -8
View File
@@ -566,6 +566,7 @@ export const enum DoAction {
Boop, Boop,
Swing, Swing,
HoldPoof, HoldPoof,
Kiss,
} }
export interface Pony extends Entity { export interface Pony extends Entity {
@@ -1098,6 +1099,7 @@ export const enum Action {
RequestEntityInfo, RequestEntityInfo,
ACL, ACL,
Magic, Magic,
Kiss,
RemoveEntity, RemoveEntity,
PlaceEntity, PlaceEntity,
SwitchTool, SwitchTool,
@@ -1221,6 +1223,7 @@ export interface SpriteSet<T> extends SpriteSetBase {
} }
export interface PonyInfoBase<T, SET> { export interface PonyInfoBase<T, SET> {
//body: SET | undefined;
head: SET | undefined; head: SET | undefined;
nose: SET | undefined; nose: SET | undefined;
ears: SET | undefined; ears: SET | undefined;
@@ -1387,10 +1390,17 @@ export interface HeadAnimationFrame {
mouth: Muzzle; mouth: Muzzle;
} }
export const enum HeadAnimationProperties {
None = 0,
DontIncreaseEyeOpenness = 1,
DontDecreaseMouthOpenness = 2
}
export interface HeadAnimation { export interface HeadAnimation {
name: string; name: string;
loop: boolean; loop: boolean;
fps: number; fps: number;
properties: HeadAnimationProperties;
frames: HeadAnimationFrame[]; frames: HeadAnimationFrame[];
} }
@@ -1548,10 +1558,44 @@ export const enum Muzzle {
// max: 31 // max: 31
} }
export const CLOSED_MUZZLES = [ export function getMuzzleOpenness(muzzle: Muzzle) {
Muzzle.Smile, Muzzle.Frown, Muzzle.Neutral, Muzzle.Scrunch, Muzzle.Flat, Muzzle.Concerned, switch (muzzle) {
Muzzle.Kiss, Muzzle.Kiss2, 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 { export const enum Eye {
None = 0, None = 0,
@@ -1582,10 +1626,43 @@ export const enum Eye {
// max: 31 // max: 31
} }
export function isEyeSleeping(eye: Eye) { export function getEyeOpenness(eye: Eye) {
return eye === Eye.Closed || switch (eye) {
(eye >= Eye.Lines && eye <= Eye.ClosedHappy) || case Eye.None:
(eye >= Eye.Peaceful && eye <= Eye.X2); 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 { 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 { stand, sneeze, defaultHeadAnimation, defaultBodyFrame, defaultHeadFrame } from '../client/ponyAnimations';
import { import {
PaletteSpriteBatch, Pony, BodyAnimation, EntityState, SpriteBatch, ExpressionExtra, HeadAnimation, Palette, 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, Iris, EntityPlayerState,
} from './interfaces'; } from './interfaces';
import { hasFlag, setFlag } from './utils'; import { hasFlag, setFlag } from './utils';
@@ -26,7 +26,7 @@ import {
} from './animator'; } from './animator';
import { import {
trotting, flying, hovering, toBoopState, isFlyingUpOrDown, isFlyingDown, isSittingDown, isSittingUp, swinging, 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'; } from '../client/ponyStates';
import { decodePonyInfo } from './compressPony'; import { decodePonyInfo } from './compressPony';
import { defaultPonyState, defaultDrawPonyOptions, isStateEqual } from '../client/ponyHelpers'; 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) { export function ensurePonyInfoDecoded(pony: Pony) {
if (pony.info !== undefined && pony.palettePonyInfo === undefined) { if (pony.info !== undefined && pony.palettePonyInfo === undefined) {
pony.palettePonyInfo = decodePonyInfo(pony.info, pony.paletteManager); pony.palettePonyInfo = decodePonyInfo(pony.info, pony.paletteManager);
@@ -445,6 +453,9 @@ export function updatePonyEntity(pony: Pony, delta: number, gameTime: number, sa
case DoAction.HoldPoof: case DoAction.HoldPoof:
playAnimation(pony.holdPoofEffect, holdPoofAnimation); playAnimation(pony.holdPoofEffect, holdPoofAnimation);
break; break;
case DoAction.Kiss:
setAnimatorState(pony.animator, toKissState(animationState) || animationState);
break;
default: default:
if (DEVELOPMENT) { if (DEVELOPMENT) {
console.error(`Invalid DoAction: ${pony.doAction}`); console.error(`Invalid DoAction: ${pony.doAction}`);
@@ -587,8 +598,8 @@ function filterExpression(expression: Expression) {
blush || blush ||
hasFlag(extra, ExpressionExtra.Hearts) || hasFlag(extra, ExpressionExtra.Hearts) ||
hasFlag(extra, ExpressionExtra.Cry) || hasFlag(extra, ExpressionExtra.Cry) ||
isEyeSleeping(expression.left) || (getEyeOpenness(expression.left) === 0) ||
isEyeSleeping(expression.right) (getEyeOpenness(expression.right) === 0)
) { ) {
if (expression.muzzle === Muzzle.SmilePant || expression.muzzle === Muzzle.NeutralPant) { if (expression.muzzle === Muzzle.SmilePant || expression.muzzle === Muzzle.NeutralPant) {
expression.muzzle = Muzzle.Neutral; expression.muzzle = Muzzle.Neutral;
+1 -1
View File
@@ -142,7 +142,7 @@ const exampleCM = [
BLUE, BLUE, BLUE, BLUE, BLUE, BLUE, BLUE, BLUE, BLUE, BLUE,
]; ];
const frontLegsCount = 39; const frontLegsCount = 40;
const backLegsCount = 27; const backLegsCount = 27;
const frontLegsSheet = { const frontLegsSheet = {
+1 -1
View File
@@ -26,7 +26,7 @@ h1(focusTitle) Account settings
| Changes saved | Changes saved
.form-group.text-right .form-group.text-right
button.btn.btn-default(type="submit" [disabled]="!canSubmit" style="min-width: 100px;") button.btn.btn-default(type="submit" [disabled]="!canSubmit" style="min-width: 100px;")
| Save | {{saveButtonText}}
div(style="margin-bottom: 100px;") div(style="margin-bottom: 100px;")
.form-group .form-group
+10 -1
View File
@@ -8,6 +8,7 @@ import { oauthProviders } from '../../../client/data';
import { Model } from '../../services/model'; import { Model } from '../../services/model';
import { getProviderIcon } from '../../shared/sign-in-box/sign-in-box'; import { getProviderIcon } from '../../shared/sign-in-box/sign-in-box';
import { faStar, faExclamationCircle, faSync } from '../../../client/icons'; import { faStar, faExclamationCircle, faSync } from '../../../client/icons';
import { Router } from '@angular/router';
@Component({ @Component({
selector: 'account', selector: 'account',
@@ -33,9 +34,10 @@ export class Account implements OnInit, OnDestroy {
removedAccount?: boolean; removedAccount?: boolean;
accountError?: string; accountError?: string;
accountSaved = false; accountSaved = false;
isNewAccount = false;
hides: HiddenPlayer[] | undefined = undefined; hides: HiddenPlayer[] | undefined = undefined;
page = 0; page = 0;
constructor(private model: Model) { constructor(private model: Model, private router: Router) {
} }
ngOnInit() { ngOnInit() {
const account = this.account!; const account = this.account!;
@@ -44,6 +46,7 @@ export class Account implements OnInit, OnDestroy {
name: account.name, name: account.name,
birthdate: account.birthdate, birthdate: account.birthdate,
}; };
this.isNewAccount = account.birthdate === '';
this.pageChanged(); this.pageChanged();
} }
@@ -88,6 +91,9 @@ export class Account implements OnInit, OnDestroy {
get showAccountAlert() { get showAccountAlert() {
return this.model.missingBirthdate; return this.model.missingBirthdate;
} }
get saveButtonText() {
return this.isNewAccount ? 'Save and continue' : 'Save';
}
icon(id: string) { icon(id: string) {
return getProviderIcon(id); return getProviderIcon(id);
} }
@@ -98,6 +104,9 @@ export class Account implements OnInit, OnDestroy {
this.model.updateAccount(this.data) this.model.updateAccount(this.data)
.catch((e: Error) => this.accountError = e.message) .catch((e: Error) => this.accountError = e.message)
.then(() => this.accountSaved = true); .then(() => this.accountSaved = true);
if (this.isNewAccount) {
this.router.navigate(['home']);
}
} }
} }
removeSite(site: SocialSiteInfo) { removeSite(site: SocialSiteInfo) {
+4 -2
View File
@@ -205,13 +205,15 @@ h1(focusTitle) Help
p.text-fading. p.text-fading.
This is usually caused by outdated graphics drivers or browser. 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 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. p.text-fading.
If your browser is already up to date, try installing different browser. If your browser is already up to date, try installing different browser.
We recommend using #[a(href="https://www.google.com/chrome/") Chrome] We recommend using #[a(href="https://www.google.com/chrome/") Chrome]
or #[a(href="https://www.mozilla.org/en-US/firefox/new/") Firefox]. or #[a(href="https://www.mozilla.org/en-US/firefox/new/") Firefox].
p.text-fading. 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 section
h4 Graphical glitches, disappearing objects h4 Graphical glitches, disappearing objects
@@ -83,6 +83,13 @@ export class ActionBar {
this.closeActions(); this.closeActions();
} }
else { 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.isWaitingForActionsModal = true;
this.modalRef = this.modalService.show(this.actionsModal, { ignoreBackdropClick: true }); this.modalRef = this.modalService.show(this.actionsModal, { ignoreBackdropClick: true });
} }
@@ -94,6 +94,7 @@ export class ActionsModal implements OnInit, OnDestroy {
this.game.editingActions = false; this.game.editingActions = false;
clearInterval(this.interval); clearInterval(this.interval);
this.subscription && this.subscription.unsubscribe(); 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(); this.notify.emit();
} }
ok() { ok() {
@@ -102,8 +102,8 @@
.form-group .form-group
custom-checkbox( custom-checkbox(
[(checked)]="account.filterCyrillic" [(checked)]="account.filterCyrillic"
help="Hide all messages containing russian alphabet") help="Hide all messages containing Russian alphabet")
| Filter russian chat | Filter Russian chat
.form-group .form-group
custom-checkbox( custom-checkbox(
[(checked)]="account.filterSwearWords" [(checked)]="account.filterSwearWords"
@@ -112,6 +112,12 @@ ng-template(#shareLinkPopover)
.form-group .form-group
button.btn.btn-sm.ml-1([(ngModel)]="animation.loop" btnCheckbox [btnHighlight]="animation.loop") button.btn.btn-sm.ml-1([(ngModel)]="animation.loop" btnCheckbox [btnHighlight]="animation.loop")
| 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) .btn-group.ml-1(dropdown)
button.btn.btn-sm.btn-default.dropdown-toggle(dropdownToggle title="Before animation") button.btn.btn-sm.btn-default.dropdown-toggle(dropdownToggle title="Before animation")
| before: {{beforeAnimation?.name || 'none'}} | before: {{beforeAnimation?.name || 'none'}}
@@ -7,9 +7,9 @@ import {
BodyAnimationFrame as IBodyAnimationFrame, BodyAnimationFrame as IBodyAnimationFrame,
HeadAnimation as IHeadAnimation, HeadAnimation as IHeadAnimation,
HeadAnimationFrame as IHeadAnimationFrame, HeadAnimationFrame as IHeadAnimationFrame,
ColorExtraSet, PonyInfo, PonyObject, BodyShadow, PonyEye ColorExtraSet, PonyInfo, PonyObject, BodyShadow, PonyEye, HeadAnimationProperties
} from '../../../common/interfaces'; } 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 { toPalette, createDefaultPony, syncLockedPonyInfo } from '../../../common/ponyInfo';
import { Key } from '../../../client/input/input'; import { Key } from '../../../client/input/input';
import { defaultPonyState, defaultDrawPonyOptions } from '../../../client/ponyHelpers'; import { defaultPonyState, defaultDrawPonyOptions } from '../../../client/ponyHelpers';
@@ -63,6 +63,8 @@ interface BodyAnimation extends BaseAnimation {
interface HeadAnimation extends BaseAnimation { interface HeadAnimation extends BaseAnimation {
lockEyes?: boolean; lockEyes?: boolean;
frames: HeadAnimationFrame[]; frames: HeadAnimationFrame[];
dontOpenEyes?: boolean;
dontCloseMouth?: boolean;
} }
interface AnimationsData { interface AnimationsData {
@@ -201,6 +203,13 @@ export class ToolsAnimation implements OnInit, OnDestroy {
if (!this.playing) { if (!this.playing) {
if (this.mode === 'body') { if (this.mode === 'body') {
this.state.animationFrame = this.frame; 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 { } else {
this.state.headAnimationFrame = this.frame; this.state.headAnimationFrame = this.frame;
} }
@@ -459,9 +468,10 @@ export class ToolsAnimation implements OnInit, OnDestroy {
console.log(`shadow: [${shadow.map(x => `[${x.join(', ')}]`).join(', ')}]`); console.log(`shadow: [${shadow.map(x => `[${x.join(', ')}]`).join(', ')}]`);
} }
} else { } else {
const animation = toHeadAnimation(this.headAnimation, true); const frames = this.headAnimation.frames
const compressed = animation.frames.map(compressHeadFrame); .map(f => [f.duration, '[' + compressHeadFrame(f).join(', ') + ']'])
console.log(JSON.stringify(compressed)); .map(([repeat, frame]) => repeat > 1 ? `...repeat(${repeat}, ${frame})` : frame);
console.log(`frames: [\n${frames.map(x => `\t${x}`).join(',\n')}\n]`);
} }
} }
png(scale = 1) { png(scale = 1) {
@@ -531,9 +541,11 @@ export class ToolsAnimation implements OnInit, OnDestroy {
if (this.playing) { if (this.playing) {
this.time += delta; this.time += delta;
if (this.mode === 'body') { const isBodyMode = this.mode === 'body';
if (isBodyMode) {
if (this.state.animation) { 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) { if (frame > this.state.animation.frames.length && !this.state.animation.loop) {
this.bodyAnimationPlaying = (this.bodyAnimationPlaying + 1) % this.bodyAnimationsToPlay.length; this.bodyAnimationPlaying = (this.bodyAnimationPlaying + 1) % this.bodyAnimationsToPlay.length;
@@ -541,14 +553,15 @@ export class ToolsAnimation implements OnInit, OnDestroy {
this.state.animationFrame = 0; this.state.animationFrame = 0;
this.time = 0; this.time = 0;
} else { } 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); if (this.state.headAnimation) {
this.state.headAnimationFrame = frame % this.state.headAnimation.frames.length; 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', {}); return this.storage.getJSON<AnimationsData>('tools-animations', {});
} }
private createAnimationSprites(scale: number) { private createAnimationSprites(scale: number) {
const isBodyMode = this.mode === 'body';
const animation = toBodyAnimation(this.bodyAnimation, true, this.switch); const animation = toBodyAnimation(this.bodyAnimation, true, this.switch);
const headAnimation = toHeadAnimation(this.headAnimation, true); 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 buffer = createCanvas(ponyWidth, ponyHeight);
const batch = new ContextSpriteBatch(buffer); const batch = new ContextSpriteBatch(buffer);
const info = toPalette(this.pony.info); const info = toPalette(this.pony.info);
@@ -582,14 +596,23 @@ export class ToolsAnimation implements OnInit, OnDestroy {
const x = i % cols; const x = i % cols;
const y = Math.floor(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); batch.start(sprites.paletteSpriteSheet, 0);
drawPony(batch, info, { drawPony(batch, info, {
...defaultPonyState(), ...defaultPonyState(),
animation, animation,
animationFrame: this.mode === 'body' ? i : 0, animationFrame: isBodyMode ? i : 0,
headAnimation: this.mode === 'head' ? headAnimation : undefined, headAnimation: headAnimation,
headAnimationFrame: this.mode === 'head' ? i : 0, headAnimationFrame: isBodyMode ? headFrame : i,
blinkFrame: 1, blinkFrame: 1,
}, ponyWidth / 2, ponyHeight - 10, options); }, ponyWidth / 2, ponyHeight - 10, options);
@@ -696,7 +719,7 @@ function compressBodyFrame(f: BodyAnimationFrame): number[] {
], x => !x); ], 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[] = []; const fs: HeadAnimationFrame[] = [];
frames.forEach(f => { frames.forEach(f => {
@@ -713,18 +736,30 @@ function fromHeadAnimation({ name, fps, loop, frames }: IHeadAnimation, index: n
builtin: true, builtin: true,
fps, fps,
loop, loop,
dontOpenEyes: hasFlag(properties, HeadAnimationProperties.DontIncreaseEyeOpenness),
dontCloseMouth: hasFlag(properties, HeadAnimationProperties.DontDecreaseMouthOpenness),
name: `# builtin ${index.toString().padStart(2, '0')} # ${name}`, name: `# builtin ${index.toString().padStart(2, '0')} # ${name}`,
frames: fs, 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; 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 { return {
name, name,
fps, fps,
loop, loop,
properties,
frames: flatMap(fs, f => repeat(full ? f.duration : 1, f)), frames: flatMap(fs, f => repeat(full ? f.duration : 1, f)),
}; };
} }
@@ -788,6 +823,7 @@ function fixHeadAnimation(a: HeadAnimation): HeadAnimation {
fps: a.fps || 24, fps: a.fps || 24,
loop: a.loop || false, loop: a.loop || false,
lockEyes: a.lockEyes || false, lockEyes: a.lockEyes || false,
dontOpenEyes: a.dontOpenEyes || false,
frames: (a.frames || []).map(fixHeadFrame), frames: (a.frames || []).map(fixHeadFrame),
}; };
} }
@@ -17,7 +17,7 @@ import { createCamera } from '../../../common/camera';
import { mockPaletteManager } from '../../../common/ponyInfo'; import { mockPaletteManager } from '../../../common/ponyInfo';
import { isCritter } from '../../../common/entityUtils'; import { isCritter } from '../../../common/entityUtils';
import { 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'; } from '../../../common/entities';
import { drawMap } from '../../../client/draw'; import { drawMap } from '../../../client/draw';
import { includes, observableToPromise, hasFlag } from '../../../common/utils'; import { includes, observableToPromise, hasFlag } from '../../../common/utils';
@@ -142,7 +142,7 @@ function drawTheMap(canvas: HTMLCanvasElement, map: WorldMap, info: ToolsMapOthe
}; };
const ignoreTypes = [ 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); ].map(e => e.type);
const shouldDraw = (e: Entity) => { 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(['sneeze', 'achoo'], Action.Sneeze),
action(['excite', 'tada'], Action.Excite), action(['excite', 'tada'], Action.Excite),
action(['magic'], Action.Magic), action(['magic'], Action.Magic),
action(['kiss'], Action.Kiss),
// house // house
command(['savehouse'], '/savehouse - saves current house setup', '', async ({ }, client) => { command(['savehouse'], '/savehouse - saves current house setup', '', async ({ }, client) => {
@@ -325,18 +326,22 @@ export function createCommands(world: World): Command[] {
client.reporter.systemLog(`House unlocked`); client.reporter.systemLog(`House unlocked`);
}), }),
command(['removetoolbox'], '/removetoolbox - removes toolbox from the house', '', ({ world }, client) => { 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; return;
client.lastMapLoadOrSave = Date.now();
removeToolbox(world, client.map); removeToolbox(world, client.map);
saySystem(client, 'Toolbox removed'); saySystem(client, 'Toolbox removed');
client.reporter.systemLog(`Toolbox removed`); client.reporter.systemLog(`Toolbox removed`);
}), }),
command(['restoretoolbox'], '/restoretoolbox - restores toolbox to the house', '', ({ }, client) => { command(['restoretoolbox'], '/restoretoolbox - restores toolbox to the house', '', ({ }, client) => {
if (!isValidMapForEditing(client.map, client, false, true)) if (!isValidMapForEditing(client.map, client, true, true))
return; return;
client.lastMapLoadOrSave = Date.now();
restoreToolbox(world, client.map); restoreToolbox(world, client.map);
saySystem(client, 'Toolbox restored'); saySystem(client, 'Toolbox restored');
+81 -42
View File
@@ -10,8 +10,8 @@ import {
} from '../common/utils'; } from '../common/utils';
import { CharacterState, ServerConfig, AccountState, CharacterStateFlags, GameServerSettings } from '../common/adminInterfaces'; import { CharacterState, ServerConfig, AccountState, CharacterStateFlags, GameServerSettings } from '../common/adminInterfaces';
import { import {
EntityState, Expression, PonyOptions, Action, ExpressionExtra, Eye, Muzzle, CLOSED_MUZZLES, EntityState, Expression, PonyOptions, Action, ExpressionExtra, Eye, Muzzle, getMuzzleOpenness,
isExpressionAction, EntityPlayerState, UpdateFlags, InteractAction isExpressionAction, EntityPlayerState, UpdateFlags, InteractAction, Rect
} from '../common/interfaces'; } from '../common/interfaces';
import { encodeExpression, EMPTY_EXPRESSION, decodeExpression } from '../common/encoders/expressionEncoder'; import { encodeExpression, EMPTY_EXPRESSION, decodeExpression } from '../common/encoders/expressionEncoder';
import { EXPRESSION_TIMEOUT, DAY, FLY_DELAY, SECOND, PONY_TYPE } from '../common/constants'; import { EXPRESSION_TIMEOUT, DAY, FLY_DELAY, SECOND, PONY_TYPE } from '../common/constants';
@@ -31,7 +31,7 @@ import {
import { replaceEmojis } from '../client/emoji'; import { replaceEmojis } from '../client/emoji';
import { expression, parseExpression } from '../common/expressionUtils'; import { expression, parseExpression } from '../common/expressionUtils';
import { import {
canBoop2, isPonySitting, isPonyStanding, getBoopRect, canStand, isPonyFlying, setPonyState, canSit, canLie canBoopOrKiss, isPonySitting, isPonyStanding, getBoopRect, canStand, isPonyFlying, setPonyState, canSit, canLie, getkissRect, getSneezeRect
} from '../common/entityUtils'; } from '../common/entityUtils';
import { withBorder } from '../common/rect'; import { withBorder } from '../common/rect';
import { isOnlineFriend } from './services/friends'; import { isOnlineFriend } from './services/friends';
@@ -75,6 +75,17 @@ export function updateClientCharacter(client: IClient, character: ICharacter) {
client.characterName = replaceEmojis(client.character.name); 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( export function createClient(
client: IClient, account: IAccount, friends: string[], hides: string[], character: ICharacter, pony: ServerEntity, client: IClient, account: IAccount, friends: string[], hides: string[], character: ICharacter, pony: ServerEntity,
defaultMap: ServerMap, reporter: Reporter, origin: IOriginInfo | undefined defaultMap: ServerMap, reporter: Reporter, origin: IOriginInfo | undefined
@@ -85,6 +96,7 @@ export function createClient(
client.country = origin && origin.country || '??'; client.country = origin && origin.country || '??';
client.userAgent = client.originalRequest && client.originalRequest.headers['user-agent']; client.userAgent = client.originalRequest && client.originalRequest.headers['user-agent'];
client.isMobile = isMobileUserAgent(client.userAgent);
client.accountId = account._id.toString(); client.accountId = account._id.toString();
client.accountName = account.name; client.accountName = account.name;
client.ignores = new Set(account.ignores); client.ignores = new Set(account.ignores);
@@ -117,8 +129,7 @@ export function createClient(
client.safeY = pony.y; client.safeY = pony.y;
client.lastPacket = Date.now(); client.lastPacket = Date.now();
client.lastAction = 0; client.lastBoopOrKissAction = 0;
client.lastBoopAction = 0;
client.lastExpressionAction = 0; client.lastExpressionAction = 0;
client.lastSays = []; client.lastSays = [];
client.lastX = pony.x; client.lastX = pony.x;
@@ -307,7 +318,7 @@ export function parseOrCurrentExpression(pony: ServerEntity, message: string) {
export function playerSleep(pony: ServerEntity, args = '') { export function playerSleep(pony: ServerEntity, args = '') {
if (pony.vx === 0 && pony.vy === 0) { if (pony.vx === 0 && pony.vy === 0) {
const base = parseOrCurrentExpression(pony, args) || expression(Eye.Closed, Eye.Closed, Muzzle.Neutral); 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 }; const expr = { ...base, muzzle, left: Eye.Closed, right: Eye.Closed, extra: ExpressionExtra.Zzz };
setEntityExpression(pony, expr, 0, true); setEntityExpression(pony, expr, 0, true);
} }
@@ -397,7 +408,8 @@ export function useHeldItem(client: IClient) {
} }
export function canPerformAction(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) { 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 purpleGrapeTypes = entities.grapesPurple.map(x => x.type);
const greenGrapeTypes = entities.grapesGreen.map(x => x.type); const greenGrapeTypes = entities.grapesGreen.map(x => x.type);
export function boop(client: IClient, now: number) { function boopEntity(client: IClient, rect: Rect, isOnlyBooping: boolean) {
if (canPerformAction(client) && canBoop2(client.pony) && client.lastBoopAction < now) { if (!client.shadowed && (isPonySitting(client.pony) || isPonyStanding(client.pony))) {
cancelEntityExpression(client.pony); const boopBounds = withBorder(rect, 1);
sendAction(client.pony, Action.Boop); const entities = findEntitiesInBounds(client.map, boopBounds);
const entity = entities.find(e => canBoopEntity(e, rect));
if (!client.shadowed && (isPonySitting(client.pony) || isPonyStanding(client.pony))) { if (entity) {
const boopRect = getBoopRect(client.pony); if (entity.boop) {
const boopBounds = withBorder(boopRect, 1); entity.boop(client);
const entities = findEntitiesInBounds(client.map, boopBounds); } else if (!isOnlyBooping && entity.type === PONY_TYPE) {
const entity = entities.find(e => canBoopEntity(e, boopRect)); const clientHold = client.pony.options!.hold || 0;
if (entity) { if (isHoldingGrapes(entity) && clientHold !== grapeGreen.type && clientHold !== grapePurple.type) {
if (entity.boop) { let index = purpleGrapeTypes.indexOf(entity.options!.hold || 0);
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) { if (index !== -1) {
let index = purpleGrapeTypes.indexOf(entity.options!.hold || 0); 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) { 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); unholdItem(entity);
} else { } else {
holdItem(entity, purpleGrapeTypes[index + 1]); holdItem(entity, greenGrapeTypes[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]);
}
} }
} }
} }
} }
} }
} }
}
}
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) { export function expressionAction(client: IClient, action: Action) {
if (canPerformAction(client) && isExpressionAction(action) && client.lastExpressionAction < Date.now()) { if (canPerformAction(client) && isExpressionAction(action)) {
cancelEntityExpression(client.pony); cancelEntityExpression(client.pony);
sendAction(client.pony, action); 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: case Action.TurnHead:
turnHead(client); turnHead(client);
break; break;
case Action.Sneeze:
sneeze(client);
break;
case Action.Stand: case Action.Stand:
stand(client); stand(client);
break; break;
@@ -766,6 +800,9 @@ export function execAction(client: IClient, action: Action, settings: GameServer
updateEntityState(client.pony, setFlag(client.pony.state, EntityState.Magic, !has)); updateEntityState(client.pony, setFlag(client.pony.state, EntityState.Magic, !has));
} }
break; break;
case Action.Kiss:
kiss(client, Date.now());
break;
case Action.SwitchTool: case Action.SwitchTool:
switchTool(client, false); switchTool(client, false);
break; 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 newIndex = reverse ? (index === -1 ? tools.length - 1 : index - 1) : ((index + 1) % tools.length);
const tool = tools[newIndex]; const tool = tools[newIndex];
holdItem(client.pony, tool.type); 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 header = 'data:image/gif;base64,';
const buffer = Buffer.from(image.substr(header.length), 'base64'); const buffer = Buffer.from(image.substr(header.length), 'base64');
const magick = /^win/.test(process.platform) ? 'magick' : 'convert'; 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')}"`; + `+repage${repeat(' +delete', remove)} "${filePath.replace(/png$/, 'gif')}"`;
console.log('executing ' + command);
fs.writeFileAsync(filePath, buffer) fs.writeFileAsync(filePath, buffer)
.then(() => execAsync(command)) .then(() => execAsync(command))
+3 -2
View File
@@ -156,6 +156,7 @@ export interface IClient extends ClientActions, ClientExtensions {
characterName: string; characterName: string;
character: ICharacter; character: ICharacter;
isMobile: boolean;
isMod: boolean; isMod: boolean;
shadowed: boolean; shadowed: boolean;
supporterLevel: number; supporterLevel: number;
@@ -181,8 +182,7 @@ export interface IClient extends ClientActions, ClientExtensions {
safeX: number; safeX: number;
safeY: number; safeY: number;
lastPacket: number; lastPacket: number;
lastAction: number; lastBoopOrKissAction: number;
lastBoopAction: number;
lastExpressionAction: number; lastExpressionAction: number;
lastSays: LastSay[]; lastSays: LastSay[];
lastX: number; lastX: number;
@@ -278,6 +278,7 @@ export interface Controller {
update(delta: number, now: number): void; update(delta: number, now: number): void;
sparseUpdate?(): void; sparseUpdate?(): void;
toggleWall?(x: number, y: number, type: TileType): void; toggleWall?(x: number, y: number, type: TileType): void;
removeWalls?(): void;
} }
export interface AccountService { 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 { getState(): WorldState {
return { return {
time: this.time, time: this.time,
@@ -291,22 +298,13 @@ export class World {
const nowSeconds = now / 1000; const nowSeconds = now / 1000;
const deltaSeconds = delta / 1000; const deltaSeconds = delta / 1000;
timingStart('update tiles'); timingStart('update tiles and colliders');
for (const map of this.maps) { for (const map of this.maps) {
if (!map.dontUpdateTilesAndColliders) { if (!map.dontUpdateTilesAndColliders) {
for (const region of map.regions) { for (const region of map.regions) {
if (region.tilesDirty) { if (region.tilesDirty) {
updateTileIndices(region, map); 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) { if (region.colliderDirty) {
generateRegionCollider(region, map); generateRegionCollider(region, map);
} }
@@ -325,9 +323,7 @@ export class World {
if (delta > 0) { if (delta > 0) {
if (entity.vx !== 0 || entity.vy !== 0) { if (entity.vx !== 0 || entity.vy !== 0) {
timingStart('updatePosition()');
updatePosition(entity, delta, map); updatePosition(entity, delta, map);
timingEnd();
} }
entity.timestamp = nowSeconds; entity.timestamp = nowSeconds;
@@ -370,15 +366,15 @@ export class World {
timingEnd(); timingEnd();
timingStart('timeoutEntityExpression + inTheAirDelay'); timingStart('timeoutEntityExpression + inTheAirDelay');
for (const { pony } of this.clients) { for (const client of this.clients) {
// timeout expressions // timeout expressions
if (pony.exprTimeout && pony.exprTimeout < now) { if (client.pony.exprTimeout && client.pony.exprTimeout < now) {
setEntityExpression(pony, undefined); // NOTE: creates updates setEntityExpression(client.pony, undefined); // NOTE: creates updates
} }
// count down in-the-air delay // count down in-the-air delay
if (pony.inTheAirDelay !== undefined && pony.inTheAirDelay > 0) { if (client.pony.inTheAirDelay !== undefined && client.pony.inTheAirDelay > 0) {
pony.inTheAirDelay -= deltaSeconds; client.pony.inTheAirDelay -= deltaSeconds;
} }
} }
timingEnd(); timingEnd();
-1
View File
@@ -94,7 +94,6 @@ export function mockClient(fields: any = {}): IClient {
subscribes: [], subscribes: [],
saysQueue: [], saysQueue: [],
lastSays: [], lastSays: [],
lastAction: 0,
lastBoopAction: 0, lastBoopAction: 0,
lastExpressionAction: 0, lastExpressionAction: 0,
viewWidth: 3, viewWidth: 3,
+15 -15
View File
@@ -132,7 +132,6 @@ describe('playerUtils', () => {
safeX: 10, safeX: 10,
safeY: 20, safeY: 20,
lastPacket: 123, lastPacket: 123,
lastAction: 0,
lastBoopAction: 0, lastBoopAction: 0,
lastExpressionAction: 0, lastExpressionAction: 0,
lastX: 10, lastX: 10,
@@ -516,11 +515,11 @@ describe('playerUtils', () => {
describe('canPerformAction()', () => { describe('canPerformAction()', () => {
it('returns true if last action date is below current time', () => { 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', () => { 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 = mockClient();
client.map = createServerMap('foo', 0, 1, 1); client.map = createServerMap('foo', 0, 1, 1);
client.pony.region = client.map.regions[0]; client.pony.region = client.map.regions[0];
client.lastAction = 0; client.lastExpressionAction = 0;
client.lastBoopOrKissAction = 0;
}); });
it('sends boop action', () => { it('sends boop action', () => {
@@ -586,12 +586,12 @@ describe('playerUtils', () => {
expect(client.pony.options!.expr).equal(EMPTY_EXPRESSION); expect(client.pony.options!.expr).equal(EMPTY_EXPRESSION);
}); });
it('updates last boop action', () => { it('updates last boop/kiss action', () => {
client.lastBoopAction = 0; client.lastBoopOrKissAction = 0;
boop(client, 100); boop(client, 100);
expect(client.lastBoopAction).equal(100 + 500); expect(client.lastBoopOrKissAction).equal(100 + 500);
}); });
it('executes boop on found entity', () => { it('executes boop on found entity', () => {
@@ -618,7 +618,7 @@ describe('playerUtils', () => {
}); });
it('does nothing if cannot perform action', () => { it('does nothing if cannot perform action', () => {
client.lastAction = 1000; client.lastBoopOrKissAction = 1000;
boop(client, 0); boop(client, 0);
@@ -646,7 +646,7 @@ describe('playerUtils', () => {
it('does not update flags if cannot perform action', () => { it('does not update flags if cannot perform action', () => {
const client = mockClient(); const client = mockClient();
client.lastAction = Date.now() + 1000; client.lastBoopOrKissAction = Date.now() + 1000;
client.pony.state = 0; client.pony.state = 0;
turnHead(client); turnHead(client);
@@ -703,7 +703,7 @@ describe('playerUtils', () => {
}); });
it('does nothing if cannot perform action', () => { it('does nothing if cannot perform action', () => {
client.lastAction = Date.now() + 1000; client.lastBoopOrKissAction = Date.now() + 1000;
client.pony.state = 0; client.pony.state = 0;
stand(client); stand(client);
@@ -752,7 +752,7 @@ describe('playerUtils', () => {
}); });
it('does nothing if cannot perform action', () => { it('does nothing if cannot perform action', () => {
client.lastAction = Date.now() + 1000; client.lastBoopOrKissAction = Date.now() + 1000;
client.pony.state = 0; client.pony.state = 0;
sit(client, {}); sit(client, {});
@@ -796,7 +796,7 @@ describe('playerUtils', () => {
}); });
it('does nothing if cannot perform action', () => { it('does nothing if cannot perform action', () => {
client.lastAction = Date.now() + 1000; client.lastBoopOrKissAction = Date.now() + 1000;
client.pony.state = 0; client.pony.state = 0;
lie(client); lie(client);
@@ -844,7 +844,7 @@ describe('playerUtils', () => {
}); });
it('does nothing if already flying', () => { it('does nothing if already flying', () => {
client.lastAction = Date.now() + 1000; client.lastBoopOrKissAction = Date.now() + 1000;
client.pony.state = EntityState.PonyFlying; client.pony.state = EntityState.PonyFlying;
fly(client); fly(client);
@@ -854,7 +854,7 @@ describe('playerUtils', () => {
}); });
it('does nothing if cannot perform action', () => { it('does nothing if cannot perform action', () => {
client.lastAction = Date.now() + 1000; client.lastBoopOrKissAction = Date.now() + 1000;
client.pony.state = 0; client.pony.state = 0;
fly(client); fly(client);
@@ -896,7 +896,7 @@ describe('playerUtils', () => {
}); });
it('does nothing if cannot perform action', () => { it('does nothing if cannot perform action', () => {
client.lastAction = Date.now() + 1000; client.lastExpressionAction = Date.now() + 1000;
expressionAction(client, Action.Yawn); expressionAction(client, Action.Yawn);