mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-24 21:55:52 +02:00
Merge branch 'archive' into archive-update-v0.55.2
This commit is contained in:
@@ -342,6 +342,13 @@ Read more about it [here](https://stackoverflow.com/questions/1139762/ignore-fil
|
||||
|
||||
## Changelog
|
||||
|
||||
#### Pony.Town v0.55.2
|
||||
- Optimized server performance
|
||||
- Fixed various animation issues
|
||||
- Fixed issues with kiss animation while turning head
|
||||
- Added additional server performance stats to the admin panel
|
||||
- Changed the button on admin panel that removes old events from 10min to 1day
|
||||
|
||||
### Pony.Town v0.55.0
|
||||
- Added Kiss action
|
||||
- Added being able to extinguish torches by sneezing
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pony-town",
|
||||
"version": "0.55.0",
|
||||
"version": "0.55.2",
|
||||
"description": "A game of ponies building a town",
|
||||
"main": "pony-town.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -39,7 +39,7 @@ const ICON_SIZE = 16;
|
||||
const headX = -26;
|
||||
const headY = -30;
|
||||
const headlessBoopFrame = { ...cloneDeep(boop.frames[7]), head: 0 };
|
||||
const headlessBoop: BodyAnimation = { name: '', loop: false, fps: 1, frames: [headlessBoopFrame] };
|
||||
const headlessBoop: BodyAnimation = { name: '', loop: false, fps: 1, frames: [headlessBoopFrame], disableHeadTurnFrames: 0 };
|
||||
|
||||
function createPony(coatColor: string, wings = false, horn = false) {
|
||||
const info = createDefaultPony();
|
||||
|
||||
@@ -139,7 +139,21 @@ export function boopAction(game: PonyTownGame) {
|
||||
}
|
||||
|
||||
export function turnHeadAction(game: PonyTownGame) {
|
||||
if (game.player && game.send(server => server.action(Action.TurnHead))) {
|
||||
if (!game.player) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (game.player.animator.state) {
|
||||
const disabledFrames = game.player.animator.state.animation.disableHeadTurnFrames;
|
||||
if (disabledFrames > 0) {
|
||||
const disabledTime = disabledFrames / game.player.animator.state.animation.fps;
|
||||
if (disabledTime >= game.player.animator.time) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (game.send(server => server.action(Action.TurnHead))) {
|
||||
game.player.state = game.player.state ^ EntityState.HeadTurned;
|
||||
game.headTurnedOverride = hasFlag(game.player.state, EntityState.HeadTurned);
|
||||
game.onActionsUpdate.next();
|
||||
|
||||
@@ -20,7 +20,7 @@ export function createBodyFrame([
|
||||
}
|
||||
|
||||
export function createBodyAnimation(
|
||||
name: string, fps: number, loop: boolean, frames: number[][], shadowOffsets?: number[][]
|
||||
name: string, fps: number, loop: boolean, frames: number[][], shadowOffsets?: number[][], disableHeadTurnFrames?: number
|
||||
): Readonly<BodyAnimation> {
|
||||
if (shadowOffsets && shadowOffsets.length !== frames.length) {
|
||||
throw new Error(`Incorrect frame count for shadowOffsets for ${name}, animation frames ${frames.length}, shadow frames ${shadowOffsets.length}`);
|
||||
@@ -28,7 +28,7 @@ export function createBodyAnimation(
|
||||
|
||||
const shadow = shadowOffsets && shadowOffsets.map<BodyShadow>(([frame, offset]) => ({ frame, offset }));
|
||||
|
||||
return { name, loop, fps, frames: frames.map(createBodyFrame), shadow };
|
||||
return { name, loop, fps, frames: frames.map(createBodyFrame), shadow, disableHeadTurnFrames: disableHeadTurnFrames || 0 };
|
||||
}
|
||||
|
||||
export const stand = createBodyAnimation('stand', 24, true, [
|
||||
@@ -468,7 +468,7 @@ export const kissBody = createBodyAnimation('kiss-body', 24, false, [
|
||||
...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])
|
||||
]);
|
||||
], undefined, 12);
|
||||
|
||||
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]),
|
||||
@@ -479,7 +479,7 @@ export const kissLiftHoofBody = createBodyAnimation('kiss-lift-hoof-body', 24, f
|
||||
...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])
|
||||
]);
|
||||
], undefined, 12);
|
||||
|
||||
export const kissFlyBody = createBodyAnimation('kiss-fly-body', 16, false, [
|
||||
[1, 1, 3, 0, 8, 10, 6, 5, 0, -16],
|
||||
@@ -514,7 +514,7 @@ export const kissFlyBody = createBodyAnimation('kiss-fly-body', 16, false, [
|
||||
[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]
|
||||
]);
|
||||
], undefined, 12);
|
||||
|
||||
export const kissFlyBugBody = createBodyAnimation('kiss-fly-bug-body', 24, false, [
|
||||
[1, 1, 3, 0, 8, 10, 6, 5, 0, -16],
|
||||
@@ -557,7 +557,7 @@ export const kissFlyBugBody = createBodyAnimation('kiss-fly-bug-body', 24, false
|
||||
[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]
|
||||
]);
|
||||
], undefined, 12);
|
||||
|
||||
export const kissLieBody = createBodyAnimation('kiss-lie-body', 24, false, [
|
||||
...repeat(3, [14, 1, 0, 2, 38, 38, 26, 26]),
|
||||
@@ -569,7 +569,7 @@ export const kissLieBody = createBodyAnimation('kiss-lie-body', 24, false, [
|
||||
[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])]);
|
||||
], [...repeat(72, [3, 3])], 70);
|
||||
|
||||
export const kissSitBody = createBodyAnimation('kiss-sit-body', 24, false, [
|
||||
...repeat(3, [8, 1, 2, 2, 34, 34, 25, 25]),
|
||||
@@ -579,7 +579,7 @@ export const kissSitBody = createBodyAnimation('kiss-sit-body', 24, false, [
|
||||
...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])]);
|
||||
], [...repeat(72, [0, 6])], 68);
|
||||
|
||||
export const kissSwimBody = createBodyAnimation('kiss-swim-body', 8, false, [
|
||||
...repeat(2, [1, 1, 0, 0, 8, 10, 6, 5, 0, 14]),
|
||||
@@ -596,7 +596,7 @@ export const kissSwimBody = createBodyAnimation('kiss-swim-body', 8, false, [
|
||||
...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]
|
||||
]);
|
||||
], undefined, 12);
|
||||
|
||||
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],
|
||||
@@ -626,6 +626,7 @@ export function mergeAnimations(name: string, fps: number, loop: boolean, animat
|
||||
name,
|
||||
fps,
|
||||
loop,
|
||||
disableHeadTurnFrames: Math.min(...animations.map(a => a.disableHeadTurnFrames)),
|
||||
frames: flatten(animations.map(a => a.frames)),
|
||||
shadow: flatten(animations.map(a => a.shadow || a.frames.map(() => ({ frame: 0, offset: 0 })))),
|
||||
};
|
||||
|
||||
@@ -127,20 +127,19 @@ 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(hovering, kissingFlying, { exitAfter: 0, onlyDirectTo: kissingFlying });
|
||||
|
||||
transition(kissing, kissingToTrotting, { exitAfter: 0, onlyDirectTo: trotting });
|
||||
transition(kissingToTrotting, trotting);
|
||||
transition(kissingToTrotting, standing);
|
||||
|
||||
transition(kissingSitting, sittingToTrotting, { exitAfter: 0, onlyDirectTo: trotting });
|
||||
transition(sittingToTrotting, standing);
|
||||
transition(kissingSitting, sitting);
|
||||
transition(sitting, kissingSitting, { exitAfter: 0, onlyDirectTo: kissingSitting });
|
||||
|
||||
transition(kissingLying, lyingToTrotting, { exitAfter: 0, onlyDirectTo: trotting });
|
||||
transition(lyingToTrotting, standing);
|
||||
transition(kissingLying, lying);
|
||||
transition(lying, kissingLying, { exitAfter: 0, onlyDirectTo: kissingLying });
|
||||
|
||||
transition(anyState, trotting, { exitAfter: 0, keepTime: true });
|
||||
transition(anyState, flying, { exitAfter: 0, keepTime: true });
|
||||
|
||||
@@ -132,6 +132,8 @@ export interface InternalApi extends InternalCommonApi {
|
||||
cancelUpdate(): Promise<void>;
|
||||
shutdownServer(value: boolean): Promise<void>;
|
||||
getTimings(): Promise<any[]>;
|
||||
setTimingEnabled(isEnabled: boolean): Promise<void>;
|
||||
getWorldPerfStats(): Promise<any>;
|
||||
teleportTo(adminAccountId: string, targetAccountId: string): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -822,6 +824,35 @@ export interface TimingEntry {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface WorldPerfStats {
|
||||
lastUpdateTime: number;
|
||||
maxUpdateTime: number;
|
||||
minUpdateTime: number;
|
||||
avgUpdateTime: number;
|
||||
isSamplingEnabled: boolean;
|
||||
clients: number;
|
||||
movingEntities: number;
|
||||
regionsCount: number;
|
||||
mapsCount: number;
|
||||
clientQueue: number;
|
||||
controllersCount: number;
|
||||
clientsWithAdds: number;
|
||||
clientsWithUpdates: number;
|
||||
clientsWithSays: number;
|
||||
totalSays: number;
|
||||
sent: number;
|
||||
received: number;
|
||||
sentPackets: number;
|
||||
receivedPackets: number;
|
||||
}
|
||||
|
||||
export function defaultWorldPerfStats() {
|
||||
return { lastUpdateTime: 0, maxUpdateTime: 0, minUpdateTime: 0, avgUpdateTime: 0, isSamplingEnabled: false,
|
||||
clients: 0, movingEntities: 0, regionsCount: 0, mapsCount: 0, clientQueue: 0, controllersCount: 0,
|
||||
clientsWithAdds: 0, clientsWithUpdates: 0, clientsWithSays: 0, totalSays: 0, sent: 0, received: 0,
|
||||
sentPackets: 0, receivedPackets: 0 };
|
||||
}
|
||||
|
||||
export type ModelTypes =
|
||||
'accounts' | 'auths' | 'origins' | 'ponies' | 'accountAuths' | 'accountOrigins' | 'accountPonies';
|
||||
|
||||
@@ -917,5 +948,7 @@ export interface IAdminServerActions {
|
||||
clearSessions(accountId: string): Promise<void>;
|
||||
// other
|
||||
getTimings(serverId: string): Promise<any[]>;
|
||||
setTimingEnabled(serverId: string, isEnabled: boolean): Promise<void>;
|
||||
getWorldPerfStats(serverId: string): Promise<any>;
|
||||
teleportTo(accountId: string): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -5,16 +5,6 @@ import { getRegionGlobal, isInWaterAt } from './worldMap';
|
||||
import { tileWidth, tileHeight, PONY_TYPE, REGION_SIZE, REGION_WIDTH, REGION_HEIGHT } from './constants';
|
||||
import { isInTheAir, isFlying } from './entityUtils';
|
||||
|
||||
let isCollidingCount = 0;
|
||||
let isCollidingObjectCount = 0;
|
||||
|
||||
export function getCollisionStats() {
|
||||
const stats = { isCollidingCount, isCollidingObjectCount };
|
||||
isCollidingCount = 0;
|
||||
isCollidingObjectCount = 0;
|
||||
return stats;
|
||||
}
|
||||
|
||||
export function isOutsideMap<T>(x: number, y: number, map: IMap<T>): boolean {
|
||||
return x < 0 || y < 0 || x >= map.width || y >= map.height;
|
||||
}
|
||||
|
||||
@@ -1380,6 +1380,7 @@ export interface BodyAnimation {
|
||||
fps: number;
|
||||
frames: BodyAnimationFrame[];
|
||||
shadow?: BodyShadow[];
|
||||
disableHeadTurnFrames: number;
|
||||
}
|
||||
|
||||
export interface HeadAnimationFrame {
|
||||
@@ -1855,3 +1856,17 @@ export const enum UpdateFlags {
|
||||
SwitchRegion = 2048,
|
||||
// max 32768
|
||||
}
|
||||
|
||||
export let counterNow: () => number;
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
counterNow = performance.now;
|
||||
} else {
|
||||
const hrtime = process.hrtime;
|
||||
const getNanoSeconds = () => {
|
||||
const hr = hrtime();
|
||||
return hr[0] * 1e9 + hr[1];
|
||||
};
|
||||
const nodeLoadTime = getNanoSeconds() - process.uptime() * 1e9;
|
||||
counterNow = () => (getNanoSeconds() - nodeLoadTime) / 1e6;
|
||||
}
|
||||
|
||||
@@ -839,6 +839,7 @@ function state(
|
||||
name: '',
|
||||
loop: false,
|
||||
fps: 24,
|
||||
disableHeadTurnFrames: 0,
|
||||
frames: times(frames, i => ({
|
||||
...createBodyFrame([]),
|
||||
head: (head || ones)[i],
|
||||
|
||||
@@ -12,9 +12,9 @@ ng-template(#duplicatesPopover)
|
||||
button.btn.btn-sm.btn-default.ml-1((click)="cleanupDeleted()" title="Cleanup deleted events")
|
||||
fa-icon.mr-1([icon]="syncIcon" size="lg")
|
||||
| clear
|
||||
button.btn.btn-sm.btn-default.ml-1((click)="removeEvents(1000 * 60 * 10)" title="Delete all older than 10 minutes")
|
||||
button.btn.btn-sm.btn-default.ml-1((click)="removeEvents(1000 * 60 * 60 * 24)" title="Delete all older than 1 day")
|
||||
fa-icon.mr-1([icon]="clockIcon" size="lg")
|
||||
| 10min
|
||||
| 1day
|
||||
button.btn.btn-sm.btn-default.ml-1((click)="removeEvents(1000 * 3)" title="Delete all events")
|
||||
fa-icon.mr-1([icon]="trashIcon" size="lg")
|
||||
| all
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
div(style="position: relative;")
|
||||
div(style="position: absolute; top: -50px; right: 0;")
|
||||
b.p-2 {{server}}
|
||||
.btn-group(*ngIf="server")
|
||||
.btn-group(*ngIf="loaded")
|
||||
button.btn.btn-default((click)="resetZoom()")
|
||||
| Reset zoom
|
||||
button.btn.btn-default((click)="fitZoom()")
|
||||
| Fit zoom
|
||||
button.btn.btn-default((click)="fullFrameZoom()")
|
||||
| Full frame
|
||||
button.btn.btn-default.ml-1(*ngIf="server" (click)="load(server)")
|
||||
button.btn.btn-default.ml-1(*ngIf="loaded" (click)="load(server)")
|
||||
| Refresh
|
||||
button.btn.btn-default.ml-1(*ngIf="loaded" (click)="disable()")
|
||||
| Disable
|
||||
button.btn.btn-default.ml-1(*ngIf="!loaded && server" (click)="enable()")
|
||||
| Enable
|
||||
.btn-group.dropdown.ml-1(dropdown)
|
||||
button.btn.btn-default.dropdown-toggle(dropdownToggle)
|
||||
| Load
|
||||
| Select
|
||||
.dropdown-menu.dropdown-menu-right(*dropdownMenu)
|
||||
button.dropdown-item(*ngFor="let s of servers" (click)="load(s)")
|
||||
| {{s}}
|
||||
@@ -20,11 +24,88 @@ div(style="position: relative;")
|
||||
div(#container style="position: relative; overflow: hidden;")
|
||||
canvas(#canvas (mousemove)="mouseMove($event)" (wheel)="wheel($event)" (agDrag)="drag($event)")
|
||||
div(#tooltip style="display: none; position: absolute; left: 0; top: 0; padding: 2px 6px; background: #111; border-radius: 2px; font-size: 12px; white-space: pre; pointer-events: none;")
|
||||
|
||||
|
||||
p
|
||||
.float-right.text-muted
|
||||
div total samples: {{timings.length}}
|
||||
div samples: {{timings.length}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div sampling: {{worldPerfStats.isSamplingEnabled}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div avg update: {{worldPerfStats.avgUpdateTime.toFixed(2)}}
|
||||
|
||||
table.table.table-sm(style="font-size: small; width: 1000px;")
|
||||
br
|
||||
.float-right.text-muted
|
||||
div last update: {{worldPerfStats.lastUpdateTime.toFixed(2)}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div max update: {{worldPerfStats.maxUpdateTime.toFixed(2)}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div min update: {{worldPerfStats.minUpdateTime.toFixed(2)}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div moving entities: {{worldPerfStats.movingEntities}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div controllers: {{worldPerfStats.controllersCount}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div regions: {{worldPerfStats.regionsCount}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div maps: {{worldPerfStats.mapsCount}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div clients: {{worldPerfStats.clients}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div clients with adds: {{worldPerfStats.clientsWithAdds}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div clients with updates: {{worldPerfStats.clientsWithUpdates}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div clients with says: {{worldPerfStats.clientsWithSays}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div total says: {{worldPerfStats.totalSays}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div client queue: {{worldPerfStats.clientQueue}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div client sent: {{(worldPerfStats.sent / 1024).toFixed(2) + 'kb'}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div client received: {{(worldPerfStats.received / 1024).toFixed(2) + 'kb'}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div client sent packets: {{worldPerfStats.sentPackets}}
|
||||
|
||||
br
|
||||
.float-right.text-muted
|
||||
div client received packets: {{worldPerfStats.receivedPackets}}
|
||||
|
||||
table.table.table-sm(style="position: relative; bottom : 410px; font-size: small; width: 1000px;")
|
||||
thead
|
||||
tr
|
||||
th.text-right Self Time
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
import { AdminModel } from '../../../services/adminModel';
|
||||
import { TimingEntry, TimingEntryType } from '../../../../common/adminInterfaces';
|
||||
import { TimingEntry, TimingEntryType, WorldPerfStats, defaultWorldPerfStats } from '../../../../common/adminInterfaces';
|
||||
import { findById, pointInRect, clamp } from '../../../../common/utils';
|
||||
import { SERVER_FPS } from '../../../../common/constants';
|
||||
import { AgDragEvent } from '../../../shared/directives/agDrag';
|
||||
@@ -39,6 +39,7 @@ export class AdminReportsPerf {
|
||||
endTime = 0;
|
||||
listing: ListingEntry[] = [];
|
||||
timings: TimingEntry[] = [];
|
||||
worldPerfStats = defaultWorldPerfStats();
|
||||
private tooltips: Tooltip[] = [];
|
||||
private startTimeFrom = 0;
|
||||
private endTimeFrom = 0;
|
||||
@@ -53,10 +54,23 @@ export class AdminReportsPerf {
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
setInterval(this.update, frameTime, this);
|
||||
}
|
||||
get servers() {
|
||||
return this.model.state.gameServers.map(s => s.id);
|
||||
}
|
||||
disable() {
|
||||
this.loaded = false;
|
||||
this.timings = [];
|
||||
this.listing = [];
|
||||
this.model.setTimingEnabled(this.server, false);
|
||||
this.redraw();
|
||||
}
|
||||
async enable() {
|
||||
this.loaded = true;
|
||||
await this.load(this.server);
|
||||
}
|
||||
async load(server: string) {
|
||||
this.server = server;
|
||||
this.timings = [];
|
||||
@@ -72,6 +86,17 @@ export class AdminReportsPerf {
|
||||
|
||||
this.redraw();
|
||||
}
|
||||
async update(self: AdminReportsPerf) {
|
||||
if (self.server) {
|
||||
const result = await self.model.getWorldPerfStats(self.server);
|
||||
if (result) {
|
||||
self.worldPerfStats = result as WorldPerfStats;
|
||||
}
|
||||
}
|
||||
else {
|
||||
self.worldPerfStats = defaultWorldPerfStats();
|
||||
}
|
||||
}
|
||||
mouseMove(e: MouseEvent) {
|
||||
const rect = this.container.nativeElement.getBoundingClientRect() as DOMRect;
|
||||
const x = e.pageX - rect.left;
|
||||
@@ -117,6 +142,10 @@ export class AdminReportsPerf {
|
||||
}
|
||||
}
|
||||
resetZoom() {
|
||||
if (!this.timings.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstTime = this.timings[0].time;
|
||||
const lastTime = this.timings[this.timings.length - 1].time;
|
||||
this.startTime = firstTime - timePadding;
|
||||
@@ -125,6 +154,10 @@ export class AdminReportsPerf {
|
||||
this.lastZoom = 0;
|
||||
}
|
||||
fitZoom() {
|
||||
if (!this.timings.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstTime = this.timings[0].time;
|
||||
const lastTime = this.timings[this.timings.length - 1].time;
|
||||
const totalTime = lastTime - firstTime;
|
||||
@@ -134,6 +167,10 @@ export class AdminReportsPerf {
|
||||
this.lastZoom = 1;
|
||||
}
|
||||
fullFrameZoom() {
|
||||
if (!this.timings.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstTime = this.timings[0].time;
|
||||
const lastTime = firstTime + frameTime;
|
||||
this.startTime = firstTime - 2;
|
||||
@@ -245,6 +282,10 @@ export class AdminReportsPerf {
|
||||
}
|
||||
}
|
||||
recalcListing() {
|
||||
if (!this.timings.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
interface Entry extends TimingEntry {
|
||||
excludedTime: number;
|
||||
}
|
||||
|
||||
@@ -507,6 +507,14 @@ export class AdminModel {
|
||||
return this.server.getTimings(server)
|
||||
.catch(this.handleError);
|
||||
}
|
||||
setTimingEnabled(server: string, isEnabled: boolean) {
|
||||
return this.server.setTimingEnabled(server, isEnabled)
|
||||
.catch(this.handleError);
|
||||
}
|
||||
getWorldPerfStats(server: string) {
|
||||
return this.server.getWorldPerfStats(server)
|
||||
.catch(this.handleError);
|
||||
}
|
||||
teleportTo(accountId: string) {
|
||||
return this.server.teleportTo(accountId)
|
||||
.catch(this.handleError);
|
||||
|
||||
@@ -685,6 +685,7 @@ function toBodyAnimation({ name, loop, fps, frames }: BodyAnimation, full: boole
|
||||
loop,
|
||||
fps,
|
||||
shadow,
|
||||
disableHeadTurnFrames: 0,
|
||||
frames: flatMap(frames, f => repeat(full ? f.duration : 1, {
|
||||
body: f.body,
|
||||
head: f.head,
|
||||
@@ -706,7 +707,7 @@ function toBodyAnimation({ name, loop, fps, frames }: BodyAnimation, full: boole
|
||||
backLegY: switchFarClose ? f.backFarLegY : f.backLegY,
|
||||
backFarLegX: switchFarClose ? f.backLegX : f.backFarLegX,
|
||||
backFarLegY: switchFarClose ? f.backLegY : f.backFarLegY,
|
||||
})),
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -547,6 +547,16 @@ export class AdminServerActions implements IAdminServerActions, SocketServer {
|
||||
return server.api.getTimings();
|
||||
}
|
||||
@Method({ promise: true })
|
||||
async setTimingEnabled(serverId: string, isEnabled: boolean) {
|
||||
const server = getServer(serverId);
|
||||
server.api.setTimingEnabled(isEnabled);
|
||||
}
|
||||
@Method({ promise: true })
|
||||
async getWorldPerfStats(serverId: string) {
|
||||
const server = getServer(serverId);
|
||||
return server.api.getWorldPerfStats();
|
||||
}
|
||||
@Method({ promise: true })
|
||||
async teleportTo(accountId: string) {
|
||||
const adminAccountId = this.account._id.toString();
|
||||
await forAllGameServers(server => server.api.teleportTo(adminAccountId, accountId));
|
||||
|
||||
@@ -15,10 +15,11 @@ import { createReloadSettings } from './internal-common';
|
||||
import { UserError } from '../userError';
|
||||
import { liveSettings } from '../liveSettings';
|
||||
import { formatDuration, invalidEnum } from '../../common/utils';
|
||||
import { timingEntries } from '../timing';
|
||||
import { timingEntries, setTimingEnabled } from '../timing';
|
||||
import { toPairs, groupBy } from 'lodash';
|
||||
import { getSizeOfMap } from '../serverMap';
|
||||
import { teleportTo } from '../playerUtils';
|
||||
import { getWorldPerfStats } from '../worldPerfStats';
|
||||
|
||||
export const createAccountChanged =
|
||||
(world: World, tokens: TokenService, findAccount: FindAccountSafe) =>
|
||||
@@ -269,6 +270,8 @@ export function createInternalApi(
|
||||
shutdownServer: createShutdownServer(world, live),
|
||||
accountHidden: createHiddenStats(hiding),
|
||||
getTimings: async () => timingEntries(),
|
||||
setTimingEnabled: async (isEnabled: boolean) => setTimingEnabled(isEnabled),
|
||||
getWorldPerfStats: async () => getWorldPerfStats(),
|
||||
teleportTo: createTeleportTo(world),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ import {
|
||||
import { replaceEmojis } from '../client/emoji';
|
||||
import { expression, parseExpression } from '../common/expressionUtils';
|
||||
import {
|
||||
canBoopOrKiss, isPonySitting, isPonyStanding, getBoopRect, canStand, isPonyFlying, setPonyState, canSit, canLie, getkissRect, getSneezeRect
|
||||
canBoopOrKiss, isPonySitting, isPonyStanding, getBoopRect, canStand, isPonyFlying, setPonyState, canSit,
|
||||
canLie, getkissRect, getSneezeRect
|
||||
} from '../common/entityUtils';
|
||||
import { withBorder } from '../common/rect';
|
||||
import { isOnlineFriend } from './services/friends';
|
||||
@@ -407,8 +408,10 @@ export function useHeldItem(client: IClient) {
|
||||
}
|
||||
}
|
||||
|
||||
export function canPerformAction(client: IClient) {
|
||||
const now = Date.now();
|
||||
export function canPerformAction(client: IClient, now?: number) {
|
||||
if (!now) {
|
||||
now = Date.now();
|
||||
}
|
||||
return client.lastExpressionAction < now && client.lastBoopOrKissAction < now;
|
||||
}
|
||||
|
||||
@@ -420,9 +423,7 @@ export function updateEntityPlayerState(client: IClient, entity: ServerEntity) {
|
||||
// actions
|
||||
|
||||
export function turnHead(client: IClient) {
|
||||
if (canPerformAction(client)) {
|
||||
updateEntityState(client.pony, client.pony.state ^ EntityState.HeadTurned);
|
||||
}
|
||||
updateEntityState(client.pony, client.pony.state ^ EntityState.HeadTurned);
|
||||
}
|
||||
|
||||
const purpleGrapeTypes = entities.grapesPurple.map(x => x.type);
|
||||
@@ -471,7 +472,7 @@ function boopEntity(client: IClient, rect: Rect, isOnlyBooping: boolean) {
|
||||
}
|
||||
|
||||
export function boop(client: IClient, now: number) {
|
||||
if (canPerformAction(client) && canBoopOrKiss(client.pony)) {
|
||||
if (canPerformAction(client, now) && canBoopOrKiss(client.pony)) {
|
||||
cancelEntityExpression(client.pony);
|
||||
sendAction(client.pony, Action.Boop);
|
||||
boopEntity(client, getBoopRect(client.pony), false);
|
||||
@@ -480,20 +481,21 @@ export function boop(client: IClient, now: number) {
|
||||
}
|
||||
|
||||
export function kiss(client: IClient, now: number) {
|
||||
if (canPerformAction(client) && canBoopOrKiss(client.pony)) {
|
||||
if (canPerformAction(client, now) && canBoopOrKiss(client.pony)) {
|
||||
cancelEntityExpression(client.pony);
|
||||
sendAction(client.pony, Action.Kiss);
|
||||
boopEntity(client, getkissRect(client.pony), false);
|
||||
client.lastBoopOrKissAction = now + 3350;
|
||||
client.lastBoopOrKissAction = now + 3400;
|
||||
}
|
||||
}
|
||||
|
||||
export function sneeze(client: IClient) {
|
||||
if (canPerformAction(client)) {
|
||||
const now = Date.now();
|
||||
if (canPerformAction(client, now)) {
|
||||
cancelEntityExpression(client.pony);
|
||||
sendAction(client.pony, Action.Sneeze);
|
||||
boopEntity(client, getSneezeRect(client.pony), true);
|
||||
client.lastExpressionAction = Date.now() + 750;
|
||||
client.lastExpressionAction = now + 750;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,10 +559,11 @@ export function fly(client: IClient) {
|
||||
}
|
||||
|
||||
export function expressionAction(client: IClient, action: Action) {
|
||||
if (canPerformAction(client) && isExpressionAction(action)) {
|
||||
const now = Date.now();
|
||||
if (canPerformAction(client, now) && isExpressionAction(action)) {
|
||||
cancelEntityExpression(client.pony);
|
||||
sendAction(client.pony, action);
|
||||
client.lastExpressionAction = Date.now() + 750;
|
||||
client.lastExpressionAction = now + 750;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -836,7 +839,6 @@ 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);
|
||||
// console.log('isMobile ' + client.isMobile);
|
||||
const text = (client.isMobile && tool.textMobile) ? tool.textMobile : tool.text;
|
||||
saySystem(client, text);
|
||||
}
|
||||
|
||||
@@ -320,6 +320,7 @@ export class ServerActions implements IServerActions, SocketServer {
|
||||
|
||||
let totalEditableEntities = 0;
|
||||
|
||||
// TODO: optimize
|
||||
for (const region of this.map.regions) {
|
||||
for (const entity of region.entities) {
|
||||
if (hasFlag(entity.state, EntityState.Editable)) {
|
||||
|
||||
+40
-25
@@ -1,37 +1,41 @@
|
||||
import { TimingEntry, TimingEntryType } from '../common/adminInterfaces';
|
||||
import { MINUTE } from '../common/constants';
|
||||
import { counterNow } from '../common/interfaces';
|
||||
|
||||
const ENABLED = true;
|
||||
const ENTRIES_LIMIT = 50000;
|
||||
const ENTRIES_LIMIT = 20000;
|
||||
|
||||
const entries: TimingEntry[] = [];
|
||||
let entries: TimingEntry[] = [];
|
||||
let entriesCount = 0;
|
||||
let isProfilingEnabled = false;
|
||||
let lastFetchTime = Date.now();
|
||||
|
||||
let now: () => number;
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
now = performance.now;
|
||||
} else {
|
||||
const hrtime = process.hrtime;
|
||||
const getNanoSeconds = () => {
|
||||
const hr = hrtime();
|
||||
return hr[0] * 1e9 + hr[1];
|
||||
};
|
||||
const nodeLoadTime = getNanoSeconds() - process.uptime() * 1e9;
|
||||
now = () => (getNanoSeconds() - nodeLoadTime) / 1e6;
|
||||
export function getTimingEnabled() {
|
||||
return isProfilingEnabled;
|
||||
}
|
||||
|
||||
if (ENABLED) {
|
||||
for (let i = 0; i < ENTRIES_LIMIT; i++) {
|
||||
entries.push({ type: 0, time: 0, name: undefined });
|
||||
export function setTimingEnabled(isEnabled: boolean) {
|
||||
if (isProfilingEnabled === isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
isProfilingEnabled = isEnabled;
|
||||
|
||||
if (isEnabled) {
|
||||
for (let i = 0; i < ENTRIES_LIMIT; i++) {
|
||||
entries.push({ type: 0, time: 0, name: undefined });
|
||||
}
|
||||
}
|
||||
else {
|
||||
entries = [];
|
||||
}
|
||||
}
|
||||
|
||||
export function timingStart(name: string) {
|
||||
if (ENABLED) {
|
||||
if (isProfilingEnabled) {
|
||||
if (entriesCount < ENTRIES_LIMIT) {
|
||||
const entry = entries[entriesCount];
|
||||
entry.type = TimingEntryType.Start;
|
||||
entry.time = now();
|
||||
entry.time = counterNow();
|
||||
entry.name = name;
|
||||
entriesCount++;
|
||||
} else {
|
||||
@@ -41,11 +45,11 @@ export function timingStart(name: string) {
|
||||
}
|
||||
|
||||
export function timingEnd() {
|
||||
if (ENABLED) {
|
||||
if (isProfilingEnabled) {
|
||||
if (entriesCount < ENTRIES_LIMIT) {
|
||||
const entry = entries[entriesCount];
|
||||
entry.type = TimingEntryType.End;
|
||||
entry.time = now();
|
||||
entry.time = counterNow();
|
||||
entry.name = undefined;
|
||||
entriesCount++;
|
||||
} else {
|
||||
@@ -55,11 +59,22 @@ export function timingEnd() {
|
||||
}
|
||||
|
||||
export function timingReset() {
|
||||
if (ENABLED) {
|
||||
entriesCount = 0;
|
||||
}
|
||||
entriesCount = 0;
|
||||
}
|
||||
|
||||
export function timingEntries() {
|
||||
lastFetchTime = Date.now();
|
||||
if (!isProfilingEnabled) {
|
||||
setTimingEnabled(true);
|
||||
return [];
|
||||
}
|
||||
return entries.slice(0, entriesCount);
|
||||
}
|
||||
|
||||
export function timingUpdate() {
|
||||
if (isProfilingEnabled) {
|
||||
if (Date.now() - lastFetchTime > MINUTE) {
|
||||
setTimingEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-17
@@ -1,7 +1,7 @@
|
||||
import { getWriterBuffer } from 'ag-sockets';
|
||||
import { remove, compact } from 'lodash';
|
||||
import {
|
||||
TileType, WorldState, Season, Holiday, WorldStateFlags, LeaveReason, NotificationFlags, UpdateFlags, Action,
|
||||
TileType, WorldState, Season, Holiday, WorldStateFlags, LeaveReason, NotificationFlags, UpdateFlags, Action, counterNow,
|
||||
} from '../common/interfaces';
|
||||
import { removeItem, distance, clamp, randomPoint, includes, fromNow } from '../common/utils';
|
||||
import { HOUR_LENGTH, DAY_LENGTH } from '../common/timeUtils';
|
||||
@@ -31,12 +31,12 @@ import {
|
||||
import { roundPosition, roundPositionXMidPixel, roundPositionYMidPixel } from '../common/positionUtils';
|
||||
import { logger } from './logger';
|
||||
import { updateCamera, centerCameraOn } from '../common/camera';
|
||||
import { timingStart, timingEnd } from './timing';
|
||||
import { timingStart, timingEnd, timingUpdate } from './timing';
|
||||
import { getRegionGlobal, getTile } from '../common/worldMap';
|
||||
import { getEntityTypeName } from '../common/entities';
|
||||
import { toFriendOnline, toFriendOffline, FriendsService } from './services/friends';
|
||||
// import { Pool, createPool } from './pool';
|
||||
import { isStaticCollision, getCollisionStats, fixCollision, updatePosition } from '../common/collision';
|
||||
import { isStaticCollision, fixCollision, updatePosition } from '../common/collision';
|
||||
import { HidingService } from './services/hiding';
|
||||
import { generateRegionCollider } from '../common/region';
|
||||
import { updateTileIndices } from '../client/tileUtils';
|
||||
@@ -44,6 +44,7 @@ import { removeEntityFromRegion } from './serverRegion';
|
||||
import { createIslandMap } from './maps/islandMap';
|
||||
import { createHouseMap } from './maps/houseMap';
|
||||
import { updateMainMapSeason } from './maps/mainMap';
|
||||
import { updateWorldPerfStats } from './worldPerfStats';
|
||||
|
||||
interface MapSwitch {
|
||||
map: ServerMap;
|
||||
@@ -287,8 +288,16 @@ export class World {
|
||||
}
|
||||
}
|
||||
update(delta: number, now: number) {
|
||||
const statsStart = counterNow();
|
||||
let movingEntities = 0;
|
||||
let regionsCount = 0;
|
||||
let mapsCount = 0;
|
||||
let controllersCount = 0;
|
||||
|
||||
const started = Date.now();
|
||||
|
||||
timingUpdate();
|
||||
|
||||
timingStart('world.update()');
|
||||
|
||||
resetEncodeUpdate();
|
||||
@@ -315,7 +324,10 @@ export class World {
|
||||
|
||||
timingStart('update positions');
|
||||
for (const map of this.maps) {
|
||||
++mapsCount;
|
||||
for (const region of map.regions) {
|
||||
++regionsCount;
|
||||
|
||||
// TODO: update only moving entities, separate list of movingEntities
|
||||
for (const entity of region.movables) {
|
||||
// TODO: make sure timestamp is initialized if entity is moving
|
||||
@@ -323,6 +335,7 @@ export class World {
|
||||
|
||||
if (delta > 0) {
|
||||
if (entity.vx !== 0 || entity.vy !== 0) {
|
||||
++movingEntities;
|
||||
updatePosition(entity, delta, map);
|
||||
}
|
||||
|
||||
@@ -349,6 +362,7 @@ export class World {
|
||||
|
||||
for (const map of this.maps) {
|
||||
for (const controller of map.controllers) {
|
||||
++controllersCount;
|
||||
controller.update(deltaSeconds, nowSeconds);
|
||||
}
|
||||
}
|
||||
@@ -408,9 +422,7 @@ export class World {
|
||||
totalSays += saysQueue.length;
|
||||
|
||||
setupTiming(client);
|
||||
timingStart('client.update()');
|
||||
client.update(unsubscribes, subscribes, updateBuffer, regionUpdates, saysQueue);
|
||||
timingEnd();
|
||||
clearTiming(client);
|
||||
|
||||
resetClientUpdates(client);
|
||||
@@ -426,17 +438,16 @@ export class World {
|
||||
}
|
||||
timingEnd();
|
||||
|
||||
const { isCollidingCount, isCollidingObjectCount } = getCollisionStats();
|
||||
|
||||
timingStart(`adds [${clientsWithAdds}]\n` +
|
||||
`updates [${clientsWithUpdates}]\n` +
|
||||
`says [${totalSays} / ${clientsWithSays}]\n` +
|
||||
`sockets [${this.socketStatsText()}]\n` +
|
||||
`collisions [${isCollidingObjectCount} / ${isCollidingCount}]`);
|
||||
timingStart(`cleanupOfflineClients`);
|
||||
this.cleanupOfflineClients();
|
||||
timingEnd();
|
||||
|
||||
timingEnd();
|
||||
|
||||
const { sent, received, sentPackets, receivedPackets } = this.socketStats.stats();
|
||||
updateWorldPerfStats(statsStart, this.clients.length, movingEntities, regionsCount, mapsCount, this.joinQueue.length,
|
||||
controllersCount, clientsWithAdds, clientsWithUpdates, clientsWithSays, totalSays,
|
||||
sent, received, sentPackets, receivedPackets);
|
||||
}
|
||||
sparseUpdate(now: number) {
|
||||
timingStart('world.sparseUpdate()');
|
||||
@@ -536,11 +547,6 @@ export class World {
|
||||
|
||||
timingEnd();
|
||||
}
|
||||
private socketStatsText() {
|
||||
const { sent, received, sentPackets, receivedPackets } = this.socketStats.stats();
|
||||
return `sent: ${(sent / 1024).toFixed(2)} kb (${sentPackets}), ` +
|
||||
`recv: ${(received / 1024).toFixed(2)} kb (${receivedPackets})`;
|
||||
}
|
||||
updatesStats() {
|
||||
timingStart('updatesStats()');
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { defaultWorldPerfStats } from '../common/adminInterfaces';
|
||||
import { counterNow } from '../common/interfaces';
|
||||
import { getTimingEnabled } from './timing';
|
||||
|
||||
const FRAME_HISTORY_SIZE = 25;
|
||||
|
||||
let stats = defaultWorldPerfStats();
|
||||
const frameHistory: number[] = [];
|
||||
frameHistory.length = FRAME_HISTORY_SIZE;
|
||||
frameHistory.fill(-1);
|
||||
let frameHistoryPtr = 0;
|
||||
|
||||
function addFrameToHistory(time: number) {
|
||||
frameHistory[frameHistoryPtr++] = time;
|
||||
if (frameHistoryPtr === FRAME_HISTORY_SIZE) {
|
||||
frameHistoryPtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function updateWorldPerfStats(
|
||||
frameStart: number, clients: number, movingEntities: number, regionsCount: number, mapsCount: number, clientQueue: number,
|
||||
controllersCount: number, clientsWithAdds: number, clientsWithUpdates: number, clientsWithSays: number, totalSays: number,
|
||||
sent: number, received: number, sentPackets: number, receivedPackets: number) {
|
||||
const time = counterNow() - frameStart;
|
||||
stats.lastUpdateTime = time;
|
||||
addFrameToHistory(time);
|
||||
stats.isSamplingEnabled = getTimingEnabled();
|
||||
stats.clients = clients;
|
||||
stats.movingEntities = movingEntities;
|
||||
stats.regionsCount = regionsCount;
|
||||
stats.mapsCount = mapsCount;
|
||||
stats.clientQueue = clientQueue;
|
||||
stats.controllersCount = controllersCount;
|
||||
stats.clientsWithAdds = clientsWithAdds;
|
||||
stats.clientsWithUpdates = clientsWithUpdates;
|
||||
stats.clientsWithSays = clientsWithSays;
|
||||
stats.totalSays = totalSays;
|
||||
stats.sent = sent;
|
||||
stats.received = received;
|
||||
stats.sentPackets = sentPackets;
|
||||
stats.receivedPackets = receivedPackets;
|
||||
|
||||
let avg = 0, nonZero = 0;
|
||||
stats.maxUpdateTime = Number.MIN_VALUE;
|
||||
stats.minUpdateTime = Number.MAX_VALUE;
|
||||
|
||||
for (const f of frameHistory) {
|
||||
if (f < 0) {
|
||||
continue;
|
||||
}
|
||||
if (f < stats.minUpdateTime) {
|
||||
stats.minUpdateTime = f;
|
||||
}
|
||||
if (f > stats.maxUpdateTime) {
|
||||
stats.maxUpdateTime = f;
|
||||
}
|
||||
avg += f;
|
||||
++nonZero;
|
||||
}
|
||||
|
||||
stats.avgUpdateTime = avg / nonZero;
|
||||
}
|
||||
|
||||
export function getWorldPerfStats() {
|
||||
return stats;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ const sets: [keyof typeof sprites, number, number, (number[] | null)[]][] = [
|
||||
['hornsBehind', -1, 1, [null, null, null, null, [3, 5], [3, 7], [3, 9, 9], [3, 9, 13], [3, 9, 13], [3, 7], [3, 11, 5, 5], [3, 9, 5, 5], [3, 9, 5], [3, 7], []]],
|
||||
['ears', -1, 1, [[3, 7], [3, 9], [3, 9], [3, 5], [3, 7, 7], [3, 7, 9]]],
|
||||
['earsFar', -1, 1, [[3, 7], [3, 9], [3, 9], [3, 5], [3, 7, 7], [3, 7, 9]]],
|
||||
['frontLegHooves', 1, 39, [null, [3], [3], [5], [5], [5], [3]]],
|
||||
['frontLegHooves', 1, 40, [null, [3], [3], [5], [5], [5], [3]]],
|
||||
['backLegHooves', 1, 27, [null, [3], [3], [5], [3]]],
|
||||
['wings', 1, 13, [null, [3, 5, 9], [5], [3, 5, 9], [3]]],
|
||||
['tails', 0, 3, [null, [3, 11, 13, 7, 5, 5], [3, 13, 9, 7, 5, 5], [3, 11, 9, 7, 5, 5], [3, 13, 11, 7, 5, 5], [3, 9, 13, 7, 5, 5], [3, 11, 13, 7, 5, 5, 5], [5, 13, 11, 7, 7], [3, 11, 13, 5, 7, 5], [3, 11, 11, 7, 5, 5, 11], [3, 11, 13, 5], [3, 11, 11, 7, 5, 5], [3, 9, 9, 7, 5], [3, 5, 9, 5], [5, 11, 9, 7, 7], [5, 7], [3, 5, 7], [5, 11, 9, 7, 7], [7, 13, 11, 9, 9], [5, 7], [3, 11, 13, 7, 5, 5], [3, 9, 11, 7, 5, 5], [3, 11, 13, 7, 5, 5], [3, 9, 11, 7, 5, 5], [3, 13, 7], [3, 5, 13, 13, 11, 11], [3, 9, 13, 13, 13, 11], [3, 7, 5, 13, 5], [5, 13, 9, 9, 7, 7], [5, 13, 9, 9, 7, 7], [3, 5, 7]]],
|
||||
@@ -29,13 +29,13 @@ const sets: [keyof typeof sprites, number, number, (number[] | null)[]][] = [
|
||||
['facialHairBehind', -1, 1, [null, [3], [3, 7], null, [3, 7, 9], [3, 5], [3, 7, 9], [3, 7, 17], [3, 17, 7], [3, 13, 7, 7], [3, 13, 7, 9], [3, 13, 7, 9], [3, 13, 7, 9], [5, 13, 9, 13], [5, 13, 9, 13], [3, 17, 7, 11]]],
|
||||
['headAccessoriesBehind', -1, 1, [null, [3, 5], [3, 9, 9, 9], [3, 5, 7, 9, 7], [3, 5], [3, 7], [3, 7, 13], [3, 11, 11], [3, 11, 13], [3, 11, 7, 9, 9], [3, 9, 13], [5, 11, 13], [3, 7], [3, 9, 13], [3, 9], [3, 9], [3, 7], [3, 7], [3, 7, 5, 5], [3, 7, 11]]],
|
||||
['neckAccessories', 1, 16, [null, [3, 5], [3, 5, 9, 5], [3, 5, 7, 13, 7], [3, 5], [3, 7, 7, 5, 5], [3, 5, 7], [3, 5], [3], [3], [3, 5, 9, 5], [3, 5, 9], [3, 9], [3], [3]]],
|
||||
['frontLegAccessories', 1, 39, [null, [3, 13, 13, 13, 13]]],
|
||||
['frontLegAccessories', 1, 40, [null, [3, 13, 13, 13, 13]]],
|
||||
['backLegAccessories', 1, 27, [null, [3, 13, 13, 13, 13]]],
|
||||
['chestAccessories', 1, 16, [null, [3, 5, 9], [3, 7, 5, 5, 7, 5, 7, 5, 7, 11, 9, 5, 11], [5, 7, 11], [5, 7]]],
|
||||
['backAccessories', 1, 16, [null, [3], [3, 7, 13, 5], [3, 7, 13, 5], [3, 9, 13, 5], [3, 3]]],
|
||||
['waistAccessories', 1, 17, [null, [9], [9], [9]]],
|
||||
['earAccessories', -1, 1, [null, [3], [3], [3], [3], [5], [3, 11], [3], [3], [3], [3, 7, 7, 7], [3, 11, 11, 11], [3]]],
|
||||
['earAccessoriesBehind', -1, 1, [null, null, null, null, null, null, null, null, [3], null, [3, 7, 7, 7], [3, 11, 11, 11], []]],
|
||||
['earAccessories', -1, 1, [null, [3], [3], [3], [3], [5], [3, 11], [3], [3], [3], [3, 7, 7, 7], [3, 11, 11, 11], [3], []]],
|
||||
['earAccessoriesBehind', -1, 1, [null, null, null, null, null, null, null, null, [3], null, [3, 7, 7, 7], [3, 11, 11, 11], [], []]],
|
||||
['extraAccessories', -1, 1, [[11], [5], [5], [7], [9], [13], [5], [11], [9], [11, 11], [9], [9, 9], [7], [9], null, null, [13], [13]]],
|
||||
['extraAccessoriesBehind', -1, 1, [[11], [5], null, [7], [9], null, null, null, null, null, null, null, [7], [9], [5], [5], [13], []]],
|
||||
];
|
||||
|
||||
@@ -114,6 +114,7 @@ describe('playerUtils', () => {
|
||||
account,
|
||||
character,
|
||||
ip: '',
|
||||
isMobile: false,
|
||||
map,
|
||||
isSwitchingMap: false,
|
||||
pony,
|
||||
@@ -132,7 +133,7 @@ describe('playerUtils', () => {
|
||||
safeX: 10,
|
||||
safeY: 20,
|
||||
lastPacket: 123,
|
||||
lastBoopAction: 0,
|
||||
lastBoopOrKissAction: 0,
|
||||
lastExpressionAction: 0,
|
||||
lastX: 10,
|
||||
lastY: 20,
|
||||
@@ -591,7 +592,7 @@ describe('playerUtils', () => {
|
||||
|
||||
boop(client, 100);
|
||||
|
||||
expect(client.lastBoopOrKissAction).equal(100 + 500);
|
||||
expect(client.lastBoopOrKissAction).equal(100 + 850);
|
||||
});
|
||||
|
||||
it('executes boop on found entity', () => {
|
||||
@@ -615,6 +616,7 @@ describe('playerUtils', () => {
|
||||
boop(client, 0);
|
||||
|
||||
assert.notCalled(stubBoop);
|
||||
expect(client.pony.region!.entityUpdates.length).eql(0);
|
||||
});
|
||||
|
||||
it('does nothing if cannot perform action', () => {
|
||||
@@ -622,7 +624,7 @@ describe('playerUtils', () => {
|
||||
|
||||
boop(client, 0);
|
||||
|
||||
expect(client.pony.region!.entityUpdates).eql([]);
|
||||
expect(client.pony.region!.entityUpdates.length).eql(0);
|
||||
});
|
||||
|
||||
it('does nothing if moving', () => {
|
||||
@@ -630,7 +632,7 @@ describe('playerUtils', () => {
|
||||
|
||||
boop(client, 0);
|
||||
|
||||
expect(client.pony.region!.entityUpdates).eql([]);
|
||||
expect(client.pony.region!.entityUpdates.length).eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -663,6 +665,8 @@ describe('playerUtils', () => {
|
||||
|
||||
it('updates entity flag to standing', () => {
|
||||
client.pony.state = EntityState.PonySitting;
|
||||
client.lastBoopOrKissAction = 0;
|
||||
client.lastExpressionAction = 0;
|
||||
|
||||
stand(client);
|
||||
|
||||
@@ -882,6 +886,8 @@ describe('playerUtils', () => {
|
||||
});
|
||||
|
||||
it('sends given action', () => {
|
||||
client.lastExpressionAction = 0;
|
||||
client.lastBoopOrKissAction = 0;
|
||||
expressionAction(client, Action.Yawn);
|
||||
|
||||
expect(client.pony.region!.entityUpdates).eql([
|
||||
@@ -896,7 +902,7 @@ describe('playerUtils', () => {
|
||||
});
|
||||
|
||||
it('does nothing if cannot perform action', () => {
|
||||
client.lastExpressionAction = Date.now() + 1000;
|
||||
client.lastExpressionAction = Date.now() + 2500;
|
||||
|
||||
expressionAction(client, Action.Yawn);
|
||||
|
||||
@@ -913,6 +919,7 @@ describe('playerUtils', () => {
|
||||
|
||||
it('updates last expression action', () => {
|
||||
client.lastExpressionAction = 0;
|
||||
client.lastBoopOrKissAction = 0;
|
||||
|
||||
expressionAction(client, Action.Yawn);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user