@rpgjs/server 5.0.0-alpha.9 → 5.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Gui/DialogGui.d.ts +5 -0
- package/dist/Gui/GameoverGui.d.ts +23 -0
- package/dist/Gui/Gui.d.ts +6 -0
- package/dist/Gui/MenuGui.d.ts +22 -3
- package/dist/Gui/NotificationGui.d.ts +1 -2
- package/dist/Gui/SaveLoadGui.d.ts +13 -0
- package/dist/Gui/ShopGui.d.ts +28 -3
- package/dist/Gui/TitleGui.d.ts +23 -0
- package/dist/Gui/index.d.ts +10 -1
- package/dist/Player/BattleManager.d.ts +44 -32
- package/dist/Player/ClassManager.d.ts +24 -4
- package/dist/Player/ComponentManager.d.ts +95 -32
- package/dist/Player/Components.d.ts +345 -0
- package/dist/Player/EffectManager.d.ts +50 -4
- package/dist/Player/ElementManager.d.ts +77 -4
- package/dist/Player/GoldManager.d.ts +1 -1
- package/dist/Player/GuiManager.d.ts +87 -4
- package/dist/Player/ItemFixture.d.ts +1 -1
- package/dist/Player/ItemManager.d.ts +431 -4
- package/dist/Player/MoveManager.d.ts +301 -34
- package/dist/Player/ParameterManager.d.ts +364 -28
- package/dist/Player/Player.d.ts +558 -14
- package/dist/Player/SkillManager.d.ts +187 -13
- package/dist/Player/StateManager.d.ts +75 -4
- package/dist/Player/VariableManager.d.ts +62 -4
- package/dist/RpgServer.d.ts +278 -63
- package/dist/RpgServerEngine.d.ts +2 -1
- package/dist/decorators/event.d.ts +46 -0
- package/dist/decorators/map.d.ts +299 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +17920 -29711
- package/dist/index.js.map +1 -1
- package/dist/logs/log.d.ts +2 -3
- package/dist/module-CaCW1SDh.js +11018 -0
- package/dist/module-CaCW1SDh.js.map +1 -0
- package/dist/module.d.ts +43 -1
- package/dist/node/connection.d.ts +51 -0
- package/dist/node/index.d.ts +5 -0
- package/dist/node/index.js +551 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/map.d.ts +16 -0
- package/dist/node/room.d.ts +21 -0
- package/dist/node/transport.d.ts +28 -0
- package/dist/node/types.d.ts +47 -0
- package/dist/presets/index.d.ts +0 -9
- package/dist/rooms/BaseRoom.d.ts +132 -0
- package/dist/rooms/lobby.d.ts +10 -2
- package/dist/rooms/map.d.ts +1359 -32
- package/dist/services/save.d.ts +43 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/localStorage.d.ts +23 -0
- package/package.json +25 -10
- package/src/Gui/DialogGui.ts +19 -4
- package/src/Gui/GameoverGui.ts +39 -0
- package/src/Gui/Gui.ts +23 -1
- package/src/Gui/MenuGui.ts +155 -6
- package/src/Gui/NotificationGui.ts +1 -2
- package/src/Gui/SaveLoadGui.ts +60 -0
- package/src/Gui/ShopGui.ts +146 -16
- package/src/Gui/TitleGui.ts +39 -0
- package/src/Gui/index.ts +15 -2
- package/src/Player/BattleManager.ts +39 -56
- package/src/Player/ClassManager.ts +82 -74
- package/src/Player/ComponentManager.ts +394 -32
- package/src/Player/Components.ts +380 -0
- package/src/Player/EffectManager.ts +50 -96
- package/src/Player/ElementManager.ts +74 -152
- package/src/Player/GuiManager.ts +125 -14
- package/src/Player/ItemManager.ts +747 -341
- package/src/Player/MoveManager.ts +1532 -750
- package/src/Player/ParameterManager.ts +636 -106
- package/src/Player/Player.ts +1273 -79
- package/src/Player/SkillManager.ts +558 -197
- package/src/Player/StateManager.ts +131 -258
- package/src/Player/VariableManager.ts +85 -157
- package/src/RpgServer.ts +293 -62
- package/src/decorators/event.ts +61 -0
- package/src/decorators/map.ts +343 -0
- package/src/index.ts +11 -1
- package/src/logs/log.ts +10 -3
- package/src/module.ts +126 -3
- package/src/node/connection.ts +254 -0
- package/src/node/index.ts +22 -0
- package/src/node/map.ts +328 -0
- package/src/node/room.ts +63 -0
- package/src/node/transport.ts +532 -0
- package/src/node/types.ts +61 -0
- package/src/presets/index.ts +1 -10
- package/src/rooms/BaseRoom.ts +232 -0
- package/src/rooms/lobby.ts +25 -7
- package/src/rooms/map.ts +2682 -206
- package/src/services/save.ts +147 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/localStorage.ts +76 -0
- package/tests/battle.spec.ts +375 -0
- package/tests/change-map.spec.ts +72 -0
- package/tests/class.spec.ts +274 -0
- package/tests/custom-websocket.spec.ts +127 -0
- package/tests/effect.spec.ts +219 -0
- package/tests/element.spec.ts +221 -0
- package/tests/event.spec.ts +80 -0
- package/tests/gold.spec.ts +99 -0
- package/tests/item.spec.ts +609 -0
- package/tests/module.spec.ts +38 -0
- package/tests/move.spec.ts +601 -0
- package/tests/node-transport.spec.ts +223 -0
- package/tests/player-param.spec.ts +45 -0
- package/tests/prediction-reconciliation.spec.ts +182 -0
- package/tests/random-move.spec.ts +65 -0
- package/tests/skill.spec.ts +658 -0
- package/tests/state.spec.ts +467 -0
- package/tests/variable.spec.ts +185 -0
- package/tests/world-maps.spec.ts +896 -0
- package/vite.config.ts +36 -3
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { RpgServerEngine } from "../src/RpgServerEngine";
|
|
3
|
+
import {
|
|
4
|
+
MAP_UPDATE_TOKEN_ENV,
|
|
5
|
+
PartyConnection,
|
|
6
|
+
createRpgServerTransport,
|
|
7
|
+
} from "../src/node";
|
|
8
|
+
|
|
9
|
+
function wait(ms = 0): Promise<void> {
|
|
10
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class MockWebSocket {
|
|
14
|
+
readyState = 1;
|
|
15
|
+
sent: string[] = [];
|
|
16
|
+
private handlers = new Map<string, Array<(...args: any[]) => void>>();
|
|
17
|
+
|
|
18
|
+
send(data: string): void {
|
|
19
|
+
this.sent.push(data);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
close(): void {
|
|
23
|
+
this.readyState = 3;
|
|
24
|
+
this.emit("close");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
on(event: string, callback: (...args: any[]) => void): void {
|
|
28
|
+
const listeners = this.handlers.get(event) || [];
|
|
29
|
+
listeners.push(callback);
|
|
30
|
+
this.handlers.set(event, listeners);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
emit(event: string, ...args: any[]): void {
|
|
34
|
+
for (const callback of this.handlers.get(event) || []) {
|
|
35
|
+
callback(...args);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class MockServer extends RpgServerEngine {
|
|
41
|
+
requests: Array<{ method: string; roomId: string; url: string; body: any }> = [];
|
|
42
|
+
messages: Array<{ connectionId: string; message: string }> = [];
|
|
43
|
+
closedConnections: string[] = [];
|
|
44
|
+
connectedContexts: Array<{ connectionId: string; url: string }> = [];
|
|
45
|
+
|
|
46
|
+
constructor(public room: any) {
|
|
47
|
+
super();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async onRequest(req: any) {
|
|
51
|
+
let body: any;
|
|
52
|
+
try {
|
|
53
|
+
body = await req.json();
|
|
54
|
+
} catch {
|
|
55
|
+
body = await req.text();
|
|
56
|
+
}
|
|
57
|
+
this.requests.push({
|
|
58
|
+
method: req.method,
|
|
59
|
+
roomId: this.room.id,
|
|
60
|
+
url: req.url,
|
|
61
|
+
body,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
method: req.method,
|
|
66
|
+
roomId: this.room.id,
|
|
67
|
+
url: req.url,
|
|
68
|
+
body,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async onMessage(message: string, connection: any) {
|
|
73
|
+
this.messages.push({
|
|
74
|
+
connectionId: connection.id,
|
|
75
|
+
message,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async onConnect(connection: any, context: any) {
|
|
80
|
+
this.connectedContexts.push({
|
|
81
|
+
connectionId: connection.id,
|
|
82
|
+
url: context.request.url,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async onClose(connection: any) {
|
|
87
|
+
this.closedConnections.push(connection.id);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class RealServer extends RpgServerEngine {}
|
|
92
|
+
|
|
93
|
+
describe("createRpgServerTransport", () => {
|
|
94
|
+
const originalMapUpdateToken = process.env[MAP_UPDATE_TOKEN_ENV];
|
|
95
|
+
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
PartyConnection.configurePacketLoss(false, 0);
|
|
98
|
+
PartyConnection.configureBandwidth(false, 100);
|
|
99
|
+
PartyConnection.configureLatency(false, 0);
|
|
100
|
+
delete process.env[MAP_UPDATE_TOKEN_ENV];
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
afterEach(() => {
|
|
104
|
+
if (typeof originalMapUpdateToken === "string") {
|
|
105
|
+
process.env[MAP_UPDATE_TOKEN_ENV] = originalMapUpdateToken;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
delete process.env[MAP_UPDATE_TOKEN_ENV];
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("handles fetch-style HTTP requests without Vite", async () => {
|
|
112
|
+
const transport = createRpgServerTransport(MockServer as any, {
|
|
113
|
+
initializeMaps: false,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const response = await transport.fetch("http://localhost/parties/main/lobby/echo?foo=bar", {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: {
|
|
119
|
+
"content-type": "application/json",
|
|
120
|
+
},
|
|
121
|
+
body: JSON.stringify({ hello: "world" }),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(response.status).toBe(200);
|
|
125
|
+
expect(await response.json()).toEqual({
|
|
126
|
+
method: "POST",
|
|
127
|
+
roomId: "lobby",
|
|
128
|
+
url: "http://localhost/parties/main/lobby/echo?foo=bar",
|
|
129
|
+
body: { hello: "world" },
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("handles websocket connections without the Vite wrapper", async () => {
|
|
134
|
+
const transport = createRpgServerTransport(MockServer as any, {
|
|
135
|
+
initializeMaps: false,
|
|
136
|
+
});
|
|
137
|
+
const ws = new MockWebSocket();
|
|
138
|
+
|
|
139
|
+
const handled = await transport.acceptWebSocket(ws as any, {
|
|
140
|
+
url: "http://localhost/parties/main/lobby?_pk=player-1",
|
|
141
|
+
method: "GET",
|
|
142
|
+
headers: {
|
|
143
|
+
host: "localhost",
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(handled).toBe(true);
|
|
148
|
+
|
|
149
|
+
await wait(10);
|
|
150
|
+
|
|
151
|
+
const server = transport.getServer("lobby") as MockServer;
|
|
152
|
+
expect(server.connectedContexts).toEqual([
|
|
153
|
+
{
|
|
154
|
+
connectionId: "player-1",
|
|
155
|
+
url: "http://localhost/parties/main/lobby?_pk=player-1",
|
|
156
|
+
},
|
|
157
|
+
]);
|
|
158
|
+
expect(JSON.parse(ws.sent[0])).toMatchObject({
|
|
159
|
+
type: "connected",
|
|
160
|
+
id: "player-1",
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
ws.emit("message", Buffer.from("ping"));
|
|
164
|
+
await wait(10);
|
|
165
|
+
|
|
166
|
+
expect(server.messages).toEqual([
|
|
167
|
+
{
|
|
168
|
+
connectionId: "player-1",
|
|
169
|
+
message: "ping",
|
|
170
|
+
},
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
ws.emit("close");
|
|
174
|
+
await wait(10);
|
|
175
|
+
|
|
176
|
+
expect(server.closedConnections).toEqual(["player-1"]);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("rejects map/update in production when the token is missing", async () => {
|
|
180
|
+
process.env[MAP_UPDATE_TOKEN_ENV] = "prod-secret";
|
|
181
|
+
|
|
182
|
+
const transport = createRpgServerTransport(RealServer as any, {
|
|
183
|
+
initializeMaps: false,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const response = await transport.fetch("http://localhost/parties/main/map-town/map/update", {
|
|
187
|
+
method: "POST",
|
|
188
|
+
headers: {
|
|
189
|
+
"content-type": "application/json",
|
|
190
|
+
},
|
|
191
|
+
body: JSON.stringify({
|
|
192
|
+
id: "town",
|
|
193
|
+
width: 320,
|
|
194
|
+
height: 240,
|
|
195
|
+
events: [],
|
|
196
|
+
}),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
expect(response.status).toBe(401);
|
|
200
|
+
expect(await response.json()).toMatchObject({
|
|
201
|
+
error: "Unauthorized map update",
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("sends the configured token when transport.updateMap() is used", async () => {
|
|
206
|
+
process.env[MAP_UPDATE_TOKEN_ENV] = "prod-secret";
|
|
207
|
+
|
|
208
|
+
const transport = createRpgServerTransport(RealServer as any, {
|
|
209
|
+
initializeMaps: false,
|
|
210
|
+
mapUpdateToken: "prod-secret",
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const response = await transport.updateMap("town", {
|
|
214
|
+
id: "town",
|
|
215
|
+
width: 320,
|
|
216
|
+
height: 240,
|
|
217
|
+
events: [],
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
expect(response.status).toBe(200);
|
|
221
|
+
expect(response.headers.get("content-type")).toContain("application/json");
|
|
222
|
+
});
|
|
223
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
import { testing } from '@rpgjs/testing'
|
|
3
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
|
4
|
+
import { ATK, MAXHP, MAXHP_CURVE, MAXSP, MAXSP_CURVE, RpgPlayer } from '../src'
|
|
5
|
+
|
|
6
|
+
let player: RpgPlayer
|
|
7
|
+
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
const fixture = await testing();
|
|
10
|
+
const client = await fixture.createClient()
|
|
11
|
+
player = client.player
|
|
12
|
+
player.initializeDefaultStats()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('Test HP', () => {
|
|
16
|
+
expect(player.hp).toBe(MAXHP_CURVE.start)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('Test SP', () => {
|
|
20
|
+
expect(player.sp).toBe(MAXSP_CURVE.start)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('Test MaxHP', () => {
|
|
24
|
+
expect(player.param[MAXHP]).toBe(MAXHP_CURVE.start)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('Test MaxSP', () => {
|
|
28
|
+
expect(player.param[MAXSP]).toBe(MAXSP_CURVE.start)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('Set fixed parameter value', () => {
|
|
32
|
+
player.setParameter(MAXHP, 1000)
|
|
33
|
+
player.setParameter(MAXSP, 250)
|
|
34
|
+
|
|
35
|
+
expect(player.param[MAXHP]).toBe(1000)
|
|
36
|
+
expect(player.param[MAXSP]).toBe(250)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('Allow direct param assignment for fixed values', () => {
|
|
40
|
+
player.param[MAXHP] = 900
|
|
41
|
+
player.param[MAXSP] = 120
|
|
42
|
+
|
|
43
|
+
expect(player.param[MAXHP]).toBe(900)
|
|
44
|
+
expect(player.param[MAXSP]).toBe(120)
|
|
45
|
+
})
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { testing, type TestingFixture } from "@rpgjs/testing";
|
|
3
|
+
import { createModule, defineModule, Direction } from "@rpgjs/common";
|
|
4
|
+
import { RpgPlayer, RpgServer } from "../src";
|
|
5
|
+
|
|
6
|
+
const serverModule = defineModule<RpgServer>({
|
|
7
|
+
maps: [
|
|
8
|
+
{
|
|
9
|
+
id: "test-map",
|
|
10
|
+
file: "",
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
player: {
|
|
14
|
+
async onConnected(player) {
|
|
15
|
+
await player.changeMap("test-map", { x: 100, y: 100 });
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
let fixture: TestingFixture;
|
|
21
|
+
let client: any;
|
|
22
|
+
let player: RpgPlayer;
|
|
23
|
+
let serverMap: any;
|
|
24
|
+
|
|
25
|
+
beforeEach(async () => {
|
|
26
|
+
const module = createModule("PredictionServerModule", [
|
|
27
|
+
{
|
|
28
|
+
server: serverModule,
|
|
29
|
+
},
|
|
30
|
+
]);
|
|
31
|
+
fixture = await testing(module);
|
|
32
|
+
client = await fixture.createClient();
|
|
33
|
+
player = await client.waitForMapChange("test-map");
|
|
34
|
+
serverMap = fixture.server.subRoom as any;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(async () => {
|
|
38
|
+
await fixture.clear();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("Prediction + Reconciliation Server Protocol", () => {
|
|
42
|
+
test("should reply pong with server tick", () => {
|
|
43
|
+
const emitSpy = vi.spyOn(player, "emit");
|
|
44
|
+
const payload = {
|
|
45
|
+
clientTime: 1234,
|
|
46
|
+
clientFrame: 42,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
serverMap.onPing(player, payload);
|
|
50
|
+
|
|
51
|
+
expect(emitSpy).toHaveBeenCalledWith(
|
|
52
|
+
"pong",
|
|
53
|
+
expect.objectContaining({
|
|
54
|
+
clientTime: payload.clientTime,
|
|
55
|
+
clientFrame: payload.clientFrame,
|
|
56
|
+
serverTick: serverMap.getTick(),
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("should include authoritative ack metadata in sync interceptor", async () => {
|
|
62
|
+
const frame = 7;
|
|
63
|
+
await serverMap.onInput(player, {
|
|
64
|
+
input: Direction.Right,
|
|
65
|
+
frame,
|
|
66
|
+
tick: 0,
|
|
67
|
+
timestamp: Date.now(),
|
|
68
|
+
});
|
|
69
|
+
await serverMap.processInput(player.id);
|
|
70
|
+
|
|
71
|
+
const intercepted = serverMap.interceptorPacket(
|
|
72
|
+
player,
|
|
73
|
+
{ type: "sync", value: {} },
|
|
74
|
+
player.conn,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(intercepted?.value?.ack).toEqual(
|
|
78
|
+
expect.objectContaining({
|
|
79
|
+
frame,
|
|
80
|
+
serverTick: expect.any(Number),
|
|
81
|
+
x: expect.any(Number),
|
|
82
|
+
y: expect.any(Number),
|
|
83
|
+
direction: expect.any(String),
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("should align ack position with the synced local player payload when available", () => {
|
|
89
|
+
player._lastFramePositions = {
|
|
90
|
+
frame: 21,
|
|
91
|
+
position: {
|
|
92
|
+
x: 0,
|
|
93
|
+
y: 0,
|
|
94
|
+
direction: Direction.Down,
|
|
95
|
+
},
|
|
96
|
+
serverTick: 1,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const intercepted = serverMap.interceptorPacket(
|
|
100
|
+
player,
|
|
101
|
+
{
|
|
102
|
+
type: "sync",
|
|
103
|
+
value: {
|
|
104
|
+
players: {
|
|
105
|
+
[player.id]: {
|
|
106
|
+
x: 321,
|
|
107
|
+
y: 45,
|
|
108
|
+
direction: Direction.Left,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
player.conn,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
expect(intercepted?.value?.ack).toEqual(
|
|
117
|
+
expect.objectContaining({
|
|
118
|
+
frame: 21,
|
|
119
|
+
x: 321,
|
|
120
|
+
y: 45,
|
|
121
|
+
direction: Direction.Left,
|
|
122
|
+
serverTick: serverMap.getTick(),
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("should queue trajectory frames and replay them progressively on server ticks", async () => {
|
|
128
|
+
const baseTs = Date.now();
|
|
129
|
+
await serverMap.onInput(player, {
|
|
130
|
+
input: Direction.Right,
|
|
131
|
+
frame: 3,
|
|
132
|
+
tick: 3,
|
|
133
|
+
timestamp: baseTs + 32,
|
|
134
|
+
trajectory: [
|
|
135
|
+
{
|
|
136
|
+
input: Direction.Right,
|
|
137
|
+
frame: 1,
|
|
138
|
+
tick: 1,
|
|
139
|
+
timestamp: baseTs,
|
|
140
|
+
x: 101,
|
|
141
|
+
y: 100,
|
|
142
|
+
direction: Direction.Right,
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
input: Direction.Right,
|
|
146
|
+
frame: 2,
|
|
147
|
+
tick: 2,
|
|
148
|
+
timestamp: baseTs + 16,
|
|
149
|
+
x: 102,
|
|
150
|
+
y: 100,
|
|
151
|
+
direction: Direction.Right,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
input: Direction.Right,
|
|
155
|
+
frame: 3,
|
|
156
|
+
tick: 3,
|
|
157
|
+
timestamp: baseTs + 32,
|
|
158
|
+
x: 103,
|
|
159
|
+
y: 100,
|
|
160
|
+
direction: Direction.Right,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(player.pendingInputs.map((entry: any) => entry.frame)).toEqual([1, 2, 3]);
|
|
166
|
+
|
|
167
|
+
await serverMap.processInput(player.id);
|
|
168
|
+
expect(player._lastFramePositions?.frame).toBe(1);
|
|
169
|
+
expect(player._lastFramePositions?.position?.x).toBe(101);
|
|
170
|
+
expect(player.pendingInputs.map((entry: any) => entry.frame)).toEqual([2, 3]);
|
|
171
|
+
|
|
172
|
+
await serverMap.processInput(player.id);
|
|
173
|
+
expect(player._lastFramePositions?.frame).toBe(2);
|
|
174
|
+
expect(player._lastFramePositions?.position?.x).toBe(102);
|
|
175
|
+
expect(player.pendingInputs.map((entry: any) => entry.frame)).toEqual([3]);
|
|
176
|
+
|
|
177
|
+
await serverMap.processInput(player.id);
|
|
178
|
+
expect(player._lastFramePositions?.frame).toBe(3);
|
|
179
|
+
expect(player._lastFramePositions?.position?.x).toBe(103);
|
|
180
|
+
expect(player.pendingInputs).toHaveLength(0);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
|
|
2
|
+
import { test, expect, describe, vi } from "vitest";
|
|
3
|
+
import { Direction } from "@rpgjs/common";
|
|
4
|
+
import { Move } from "../src/Player/MoveManager";
|
|
5
|
+
|
|
6
|
+
describe("Improved Random Movement Unit Tests", () => {
|
|
7
|
+
test("Move.random(repeat) should return array of identical directions", () => {
|
|
8
|
+
// Mock Math.random to return a fixed value
|
|
9
|
+
const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.1); // Should map to index 0 (Right)
|
|
10
|
+
|
|
11
|
+
const directions = Move.random(5);
|
|
12
|
+
|
|
13
|
+
expect(directions).toHaveLength(5);
|
|
14
|
+
expect(directions.every(d => d === Direction.Right)).toBe(true);
|
|
15
|
+
|
|
16
|
+
randomSpy.mockRestore();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("Move.random(repeat) should produce different directions with different random values", () => {
|
|
20
|
+
const randomSpy = vi.spyOn(Math, 'random');
|
|
21
|
+
|
|
22
|
+
randomSpy.mockReturnValue(0.1); // Right
|
|
23
|
+
const rightDirs = Move.random(1);
|
|
24
|
+
expect(rightDirs[0]).toBe(Direction.Right);
|
|
25
|
+
|
|
26
|
+
randomSpy.mockReturnValue(0.3); // Left
|
|
27
|
+
const leftDirs = Move.random(1);
|
|
28
|
+
expect(leftDirs[0]).toBe(Direction.Left);
|
|
29
|
+
|
|
30
|
+
randomSpy.mockReturnValue(0.6); // Up
|
|
31
|
+
const upDirs = Move.random(1);
|
|
32
|
+
expect(upDirs[0]).toBe(Direction.Up);
|
|
33
|
+
|
|
34
|
+
randomSpy.mockReturnValue(0.9); // Down
|
|
35
|
+
const downDirs = Move.random(1);
|
|
36
|
+
expect(downDirs[0]).toBe(Direction.Down);
|
|
37
|
+
|
|
38
|
+
randomSpy.mockRestore();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("Move.tileRandom(repeat) should return consistent direction for tiles", () => {
|
|
42
|
+
// Mock Math.random to return a fixed value
|
|
43
|
+
const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.1); // Should map to index 0 (Right)
|
|
44
|
+
|
|
45
|
+
const mockPlayer = {
|
|
46
|
+
speed: 3
|
|
47
|
+
} as any;
|
|
48
|
+
|
|
49
|
+
const mockMap = {
|
|
50
|
+
tileWidth: 32,
|
|
51
|
+
tileHeight: 32
|
|
52
|
+
} as any;
|
|
53
|
+
|
|
54
|
+
const callback = Move.tileRandom(3);
|
|
55
|
+
const directions = callback(mockPlayer, mockMap);
|
|
56
|
+
|
|
57
|
+
// repeatTile = Math.floor(32 / 3) * 3 = 10 * 3 = 30
|
|
58
|
+
// So we expect 30 directions, all Right
|
|
59
|
+
|
|
60
|
+
expect(directions.length).toBe(30);
|
|
61
|
+
expect(directions.every(d => d === Direction.Right)).toBe(true);
|
|
62
|
+
|
|
63
|
+
randomSpy.mockRestore();
|
|
64
|
+
});
|
|
65
|
+
});
|