@rpgjs/server 5.0.0-alpha.25 → 5.0.0-alpha.27
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/Player/MoveManager.d.ts +62 -1
- package/dist/Player/Player.d.ts +33 -22
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1827 -365
- package/dist/index.js.map +1 -1
- package/dist/rooms/BaseRoom.d.ts +95 -0
- package/dist/rooms/lobby.d.ts +4 -1
- package/dist/rooms/map.d.ts +17 -75
- package/package.json +10 -10
- package/src/Player/ItemManager.ts +50 -15
- package/src/Player/MoveManager.ts +654 -112
- package/src/Player/Player.ts +179 -136
- package/src/index.ts +2 -1
- package/src/module.ts +13 -0
- package/src/rooms/BaseRoom.ts +120 -0
- package/src/rooms/lobby.ts +11 -1
- package/src/rooms/map.ts +70 -146
- package/tests/change-map.spec.ts +2 -2
- package/tests/event.spec.ts +80 -0
- package/tests/item.spec.ts +455 -441
- package/tests/move.spec.ts +601 -0
- package/tests/random-move.spec.ts +65 -0
- package/tests/world-maps.spec.ts +43 -81
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
import { beforeEach, test, expect, afterEach, describe } from "vitest";
|
|
2
|
+
import { testing, TestingFixture } from "@rpgjs/testing";
|
|
3
|
+
import { defineModule, createModule, Direction } from "@rpgjs/common";
|
|
4
|
+
import { RpgPlayer, RpgServer, Move } from "../src";
|
|
5
|
+
import { RpgClient } from "../../client/src";
|
|
6
|
+
|
|
7
|
+
// Define server module with test map
|
|
8
|
+
const serverModule = defineModule<RpgServer>({
|
|
9
|
+
maps: [
|
|
10
|
+
{
|
|
11
|
+
id: "test-map",
|
|
12
|
+
file: "",
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
player: {
|
|
16
|
+
async onConnected(player) {
|
|
17
|
+
await player.changeMap("test-map", { x: 100, y: 100 });
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Define client module
|
|
23
|
+
const clientModule = defineModule<RpgClient>({
|
|
24
|
+
// Client-side logic
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
let player: RpgPlayer;
|
|
28
|
+
let client: any;
|
|
29
|
+
let fixture: TestingFixture;
|
|
30
|
+
|
|
31
|
+
beforeEach(async () => {
|
|
32
|
+
const myModule = createModule("TestModule", [
|
|
33
|
+
{
|
|
34
|
+
server: serverModule,
|
|
35
|
+
client: clientModule,
|
|
36
|
+
},
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
fixture = await testing(myModule);
|
|
40
|
+
client = await fixture.createClient();
|
|
41
|
+
player = await client.waitForMapChange("test-map");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(async () => {
|
|
45
|
+
await fixture.clear();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
describe("Move Routes - Basic Movements", () => {
|
|
51
|
+
|
|
52
|
+
test("should move right using Direction enum", async () => {
|
|
53
|
+
const initialX = player.x();
|
|
54
|
+
const initialY = player.y();
|
|
55
|
+
|
|
56
|
+
await fixture.waitUntil(
|
|
57
|
+
player.moveRoutes([Direction.Right])
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
expect(player.x()).toBe(initialX + player.speed());
|
|
61
|
+
expect(player.y()).toBe(initialY);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("should move left using Direction enum", async () => {
|
|
65
|
+
const initialX = player.x();
|
|
66
|
+
const initialY = player.y();
|
|
67
|
+
|
|
68
|
+
await fixture.waitUntil(
|
|
69
|
+
player.moveRoutes([Direction.Left])
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(player.x()).toBe(initialX - player.speed());
|
|
73
|
+
expect(player.y()).toBe(initialY);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("should move up using Direction enum", async () => {
|
|
77
|
+
const initialX = player.x();
|
|
78
|
+
const initialY = player.y();
|
|
79
|
+
|
|
80
|
+
await fixture.waitUntil(
|
|
81
|
+
player.moveRoutes([Direction.Up])
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
expect(player.x()).toBe(initialX);
|
|
85
|
+
expect(player.y()).toBe(initialY - player.speed());
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("should move down using Direction enum", async () => {
|
|
89
|
+
const initialX = player.x();
|
|
90
|
+
const initialY = player.y();
|
|
91
|
+
|
|
92
|
+
await fixture.waitUntil(
|
|
93
|
+
player.moveRoutes([Direction.Down])
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(player.x()).toBe(initialX);
|
|
97
|
+
expect(player.y()).toBe(initialY + player.speed());
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("should execute multiple movements in sequence", async () => {
|
|
101
|
+
const initialX = player.x();
|
|
102
|
+
const initialY = player.y();
|
|
103
|
+
|
|
104
|
+
await fixture.waitUntil(
|
|
105
|
+
player.moveRoutes([
|
|
106
|
+
Direction.Right,
|
|
107
|
+
Direction.Down,
|
|
108
|
+
Direction.Left,
|
|
109
|
+
Direction.Up,
|
|
110
|
+
])
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Player should have moved in a square pattern
|
|
114
|
+
// Final position might not be exactly the same due to physics
|
|
115
|
+
expect(Math.abs(player.x() - initialX)).toBeLessThan(50);
|
|
116
|
+
expect(Math.abs(player.y() - initialY)).toBeLessThan(50);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("Move Routes - Move Helper Functions", () => {
|
|
121
|
+
test("should move right using Move.right()", async () => {
|
|
122
|
+
const initialX = player.x();
|
|
123
|
+
const initialY = player.y();
|
|
124
|
+
|
|
125
|
+
await fixture.waitUntil(
|
|
126
|
+
player.moveRoutes([Move.right()])
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
expect(player.x()).toBeGreaterThan(initialX);
|
|
130
|
+
expect(player.y()).toBe(initialY);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("should move left using Move.left()", async () => {
|
|
134
|
+
const initialX = player.x();
|
|
135
|
+
const initialY = player.y();
|
|
136
|
+
|
|
137
|
+
await fixture.waitUntil(
|
|
138
|
+
player.moveRoutes([Move.left()])
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(player.x()).toBeLessThan(initialX);
|
|
142
|
+
expect(player.y()).toBe(initialY);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("should move up using Move.up()", async () => {
|
|
146
|
+
const initialX = player.x();
|
|
147
|
+
const initialY = player.y();
|
|
148
|
+
|
|
149
|
+
await fixture.waitUntil(
|
|
150
|
+
player.moveRoutes([Move.up()])
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(player.x()).toBe(initialX);
|
|
154
|
+
expect(player.y()).toBeLessThan(initialY);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("should move down using Move.down()", async () => {
|
|
158
|
+
const initialX = player.x();
|
|
159
|
+
const initialY = player.y();
|
|
160
|
+
|
|
161
|
+
await fixture.waitUntil(
|
|
162
|
+
player.moveRoutes([Move.down()])
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
expect(player.x()).toBe(initialX);
|
|
166
|
+
expect(player.y()).toBeGreaterThan(initialY);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("should move multiple times with repeat parameter", async () => {
|
|
170
|
+
const initialX = player.x();
|
|
171
|
+
const initialY = player.y();
|
|
172
|
+
|
|
173
|
+
await fixture.waitUntil(
|
|
174
|
+
player.moveRoutes([Move.right(3)])
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
expect(player.x()).toBeGreaterThan(initialX);
|
|
178
|
+
expect(player.y()).toBe(initialY);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("Move Routes - Tile Movements", () => {
|
|
183
|
+
test("should move right by tiles using Move.tileRight()", async () => {
|
|
184
|
+
const initialX = player.x();
|
|
185
|
+
const initialY = player.y();
|
|
186
|
+
|
|
187
|
+
await fixture.waitUntil(
|
|
188
|
+
player.moveRoutes([Move.tileRight()])
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
expect(player.x()).toBeGreaterThan(initialX);
|
|
192
|
+
expect(player.y()).toBe(initialY);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("should move left by tiles using Move.tileLeft()", async () => {
|
|
196
|
+
const initialX = player.x();
|
|
197
|
+
const initialY = player.y();
|
|
198
|
+
|
|
199
|
+
await fixture.waitUntil(
|
|
200
|
+
player.moveRoutes([Move.tileLeft()])
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
expect(player.x()).toBeLessThan(initialX);
|
|
204
|
+
expect(player.y()).toBe(initialY);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("should move up by tiles using Move.tileUp()", async () => {
|
|
208
|
+
const initialX = player.x();
|
|
209
|
+
const initialY = player.y();
|
|
210
|
+
|
|
211
|
+
await fixture.waitUntil(
|
|
212
|
+
player.moveRoutes([Move.tileUp()])
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
expect(player.x()).toBe(initialX);
|
|
216
|
+
expect(player.y()).toBeLessThan(initialY);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("should move down by tiles using Move.tileDown()", async () => {
|
|
220
|
+
const initialX = player.x();
|
|
221
|
+
const initialY = player.y();
|
|
222
|
+
|
|
223
|
+
await fixture.waitUntil(
|
|
224
|
+
player.moveRoutes([Move.tileDown()])
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
expect(player.x()).toBe(initialX);
|
|
228
|
+
expect(player.y()).toBeGreaterThan(initialY);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("should move multiple tiles with repeat parameter", async () => {
|
|
232
|
+
const initialX = player.x();
|
|
233
|
+
const initialY = player.y();
|
|
234
|
+
|
|
235
|
+
await fixture.waitUntil(
|
|
236
|
+
player.moveRoutes([Move.tileRight(2)])
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
expect(player.x()).toBeGreaterThan(initialX);
|
|
240
|
+
expect(player.y()).toBe(initialY);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("Move Routes - Turn Commands", () => {
|
|
245
|
+
test("should turn right using Move.turnRight()", async () => {
|
|
246
|
+
const initialDirection = player.getDirection();
|
|
247
|
+
|
|
248
|
+
await fixture.waitUntil(
|
|
249
|
+
player.moveRoutes([Move.turnRight()])
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// Direction should have changed
|
|
253
|
+
expect(player.getDirection()).not.toBe(initialDirection);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("should turn left using Move.turnLeft()", async () => {
|
|
257
|
+
const initialDirection = player.getDirection();
|
|
258
|
+
|
|
259
|
+
await fixture.waitUntil(
|
|
260
|
+
player.moveRoutes([Move.turnLeft()])
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
expect(player.getDirection()).not.toBe(initialDirection);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("should turn up using Move.turnUp()", async () => {
|
|
267
|
+
await fixture.waitUntil(
|
|
268
|
+
player.moveRoutes([Move.turnUp()])
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
// turnUp() returns a string command, direction should be Up after execution
|
|
272
|
+
expect(player.getDirection()).toBe(Direction.Up);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("should turn down using Move.turnDown()", async () => {
|
|
276
|
+
await fixture.waitUntil(
|
|
277
|
+
player.moveRoutes([Move.turnDown()])
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
// turnDown() returns a string command, direction should be Down after execution
|
|
281
|
+
expect(player.getDirection()).toBe(Direction.Down);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("should combine turn and movement", async () => {
|
|
285
|
+
const initialX = player.x();
|
|
286
|
+
const initialY = player.y();
|
|
287
|
+
|
|
288
|
+
await fixture.waitUntil(
|
|
289
|
+
player.moveRoutes([
|
|
290
|
+
Move.turnRight(),
|
|
291
|
+
Move.right(),
|
|
292
|
+
Move.turnDown(),
|
|
293
|
+
Move.down(),
|
|
294
|
+
])
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
expect(player.x()).toBeGreaterThan(initialX);
|
|
298
|
+
expect(player.y()).toBeGreaterThan(initialY);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe("Move Routes - Callback Functions", () => {
|
|
303
|
+
test("should execute callback function routes", async () => {
|
|
304
|
+
const initialX = player.x();
|
|
305
|
+
const initialY = player.y();
|
|
306
|
+
|
|
307
|
+
const callbackRoute = (player: RpgPlayer, map: any) => {
|
|
308
|
+
return Move.right(2);
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
await fixture.waitUntil(
|
|
312
|
+
player.moveRoutes([callbackRoute])
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
expect(player.x()).toBeGreaterThan(initialX);
|
|
316
|
+
expect(player.y()).toBe(initialY);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test("should execute multiple callback functions", async () => {
|
|
320
|
+
const initialX = player.x();
|
|
321
|
+
const initialY = player.y();
|
|
322
|
+
|
|
323
|
+
const callback1 = (player: RpgPlayer, map: any) => Move.right();
|
|
324
|
+
const callback2 = (player: RpgPlayer, map: any) => Move.down();
|
|
325
|
+
|
|
326
|
+
await fixture.waitUntil(
|
|
327
|
+
player.moveRoutes([callback1, callback2])
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
expect(player.x()).toBeGreaterThan(initialX);
|
|
331
|
+
expect(player.y()).toBeGreaterThan(initialY);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe("Move Routes - Promises", () => {
|
|
336
|
+
test("should wait for promise before continuing", async () => {
|
|
337
|
+
const initialX = player.x();
|
|
338
|
+
const initialY = player.y();
|
|
339
|
+
|
|
340
|
+
const waitPromise = Move.wait(0.1); // 100ms wait
|
|
341
|
+
|
|
342
|
+
await fixture.waitUntil(
|
|
343
|
+
player.moveRoutes([
|
|
344
|
+
Move.right(),
|
|
345
|
+
waitPromise,
|
|
346
|
+
Move.down(),
|
|
347
|
+
])
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
expect(player.x()).toBeGreaterThan(initialX);
|
|
351
|
+
expect(player.y()).toBeGreaterThan(initialY);
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
describe("Move Routes - Nested Arrays", () => {
|
|
356
|
+
test("should flatten nested route arrays", async () => {
|
|
357
|
+
const initialX = player.x();
|
|
358
|
+
const initialY = player.y();
|
|
359
|
+
|
|
360
|
+
await fixture.waitUntil(
|
|
361
|
+
player.moveRoutes([
|
|
362
|
+
[Move.right(), Move.right()] as any,
|
|
363
|
+
[Move.down(), Move.down()] as any,
|
|
364
|
+
])
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
expect(player.x()).toBeGreaterThan(initialX);
|
|
368
|
+
expect(player.y()).toBeGreaterThan(initialY);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe("Move Routes - Infinite Routes", () => {
|
|
373
|
+
test("should start infinite route", async () => {
|
|
374
|
+
const initialX = player.x();
|
|
375
|
+
|
|
376
|
+
player.infiniteMoveRoute([Move.right()]);
|
|
377
|
+
|
|
378
|
+
// Let it run for a bit
|
|
379
|
+
await fixture.nextTickTimes(20);
|
|
380
|
+
await fixture.wait(400);
|
|
381
|
+
|
|
382
|
+
// Player should have moved
|
|
383
|
+
expect(player.x()).toBeGreaterThan(initialX);
|
|
384
|
+
|
|
385
|
+
// Stop the infinite route
|
|
386
|
+
player.breakRoutes();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test("should stop infinite route with breakRoutes()", async () => {
|
|
390
|
+
const initialX = player.x();
|
|
391
|
+
|
|
392
|
+
player.infiniteMoveRoute([Move.right()]);
|
|
393
|
+
|
|
394
|
+
// Let it run briefly
|
|
395
|
+
await fixture.nextTickTimes(5);
|
|
396
|
+
await fixture.wait(100);
|
|
397
|
+
|
|
398
|
+
const xBeforeBreak = player.x();
|
|
399
|
+
player.breakRoutes();
|
|
400
|
+
|
|
401
|
+
// Wait a bit more
|
|
402
|
+
await fixture.nextTickTimes(10);
|
|
403
|
+
await fixture.wait(200);
|
|
404
|
+
|
|
405
|
+
// Player should have stopped moving (or moved very little)
|
|
406
|
+
expect(Math.abs(player.x() - xBeforeBreak)).toBeLessThan(10);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("should force stop infinite route", async () => {
|
|
410
|
+
const initialX = player.x();
|
|
411
|
+
|
|
412
|
+
player.infiniteMoveRoute([Move.right()]);
|
|
413
|
+
|
|
414
|
+
// Let it run briefly
|
|
415
|
+
await fixture.nextTickTimes(5);
|
|
416
|
+
await fixture.wait(100);
|
|
417
|
+
|
|
418
|
+
const xBeforeBreak = player.x();
|
|
419
|
+
player.breakRoutes(true); // Force stop
|
|
420
|
+
|
|
421
|
+
// Wait a bit more
|
|
422
|
+
await fixture.nextTickTimes(10);
|
|
423
|
+
await fixture.wait(200);
|
|
424
|
+
|
|
425
|
+
// Player should have stopped moving
|
|
426
|
+
expect(Math.abs(player.x() - xBeforeBreak)).toBeLessThan(10);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test("should replay infinite route", async () => {
|
|
430
|
+
const initialX = player.x();
|
|
431
|
+
|
|
432
|
+
player.infiniteMoveRoute([Move.right()]);
|
|
433
|
+
|
|
434
|
+
// Let it run
|
|
435
|
+
await fixture.nextTickTimes(10);
|
|
436
|
+
await fixture.wait(200);
|
|
437
|
+
|
|
438
|
+
const xAfterStart = player.x();
|
|
439
|
+
player.breakRoutes();
|
|
440
|
+
|
|
441
|
+
// Wait a bit
|
|
442
|
+
await fixture.nextTickTimes(5);
|
|
443
|
+
await fixture.wait(100);
|
|
444
|
+
|
|
445
|
+
// Replay
|
|
446
|
+
player.replayRoutes();
|
|
447
|
+
|
|
448
|
+
// Let it run again
|
|
449
|
+
await fixture.nextTickTimes(10);
|
|
450
|
+
await fixture.wait(200);
|
|
451
|
+
|
|
452
|
+
// Player should have moved more
|
|
453
|
+
expect(player.x()).toBeGreaterThan(xAfterStart);
|
|
454
|
+
|
|
455
|
+
player.breakRoutes();
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
describe("Move Routes - Route Completion", () => {
|
|
460
|
+
test("should resolve promise when route completes", async () => {
|
|
461
|
+
let completed = false;
|
|
462
|
+
|
|
463
|
+
const promise = player.moveRoutes([Move.right()]);
|
|
464
|
+
|
|
465
|
+
promise.then(() => {
|
|
466
|
+
completed = true;
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
await fixture.waitUntil(promise);
|
|
470
|
+
expect(completed).toBe(true);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("should return true when route completes successfully", async () => {
|
|
474
|
+
const result = await fixture.waitUntil(
|
|
475
|
+
player.moveRoutes([Move.right()])
|
|
476
|
+
);
|
|
477
|
+
expect(result).toBe(true);
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
describe("Move Routes - Complex Scenarios", () => {
|
|
482
|
+
test("should execute complex route with multiple types", async () => {
|
|
483
|
+
const initialX = player.x();
|
|
484
|
+
const initialY = player.y();
|
|
485
|
+
|
|
486
|
+
await fixture.waitUntil(
|
|
487
|
+
player.moveRoutes([
|
|
488
|
+
Move.turnRight(),
|
|
489
|
+
Move.right(2),
|
|
490
|
+
Move.turnDown(),
|
|
491
|
+
Move.down(2),
|
|
492
|
+
Move.turnLeft(),
|
|
493
|
+
Move.left(2),
|
|
494
|
+
Move.turnUp(),
|
|
495
|
+
Move.up(2),
|
|
496
|
+
])
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
// Player should have moved in a square pattern
|
|
500
|
+
expect(Math.abs(player.x() - initialX)).toBeLessThan(100);
|
|
501
|
+
expect(Math.abs(player.y() - initialY)).toBeLessThan(100);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test("should clear movements when starting new route", async () => {
|
|
505
|
+
const initialX = player.x();
|
|
506
|
+
|
|
507
|
+
// Start first route
|
|
508
|
+
const promise1 = player.moveRoutes([Move.right()]);
|
|
509
|
+
|
|
510
|
+
// Start second route immediately (should clear first)
|
|
511
|
+
const promise2 = player.moveRoutes([Move.left()]);
|
|
512
|
+
|
|
513
|
+
await fixture.waitUntil(Promise.all([promise1, promise2]));
|
|
514
|
+
|
|
515
|
+
// Player should have moved left (second route)
|
|
516
|
+
expect(player.x()).toBeLessThan(initialX);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
describe("Move Routes - Stuck Detection", () => {
|
|
522
|
+
test("should call onStuck when player is blocked", async () => {
|
|
523
|
+
const initialX = player.x();
|
|
524
|
+
const initialY = player.y();
|
|
525
|
+
|
|
526
|
+
let stuckCalled = false;
|
|
527
|
+
let stuckTarget: { x: number; y: number } | null = null;
|
|
528
|
+
let stuckPosition: { x: number; y: number } | null = null;
|
|
529
|
+
|
|
530
|
+
// Create an obstacle by blocking tiles to the right
|
|
531
|
+
const map = player.getCurrentMap() as any;
|
|
532
|
+
const entity = map.physic.getEntityByUUID(player.id);
|
|
533
|
+
|
|
534
|
+
if (entity) {
|
|
535
|
+
// Block tiles to the right of the player
|
|
536
|
+
// Calculate tile coordinates based on player position
|
|
537
|
+
const playerTileX = Math.floor(initialX / 32);
|
|
538
|
+
|
|
539
|
+
entity.canEnterTile(({ x, y }) => {
|
|
540
|
+
// Block tiles starting 1-2 tiles to the right of player
|
|
541
|
+
const tileX = Math.floor(x);
|
|
542
|
+
if (tileX >= playerTileX + 1 && tileX <= playerTileX + 2) {
|
|
543
|
+
return false; // Block these tiles
|
|
544
|
+
}
|
|
545
|
+
return true;
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Try to move right - player should get stuck
|
|
550
|
+
const promise = player.moveRoutes([Move.right(5)], {
|
|
551
|
+
onStuck: (p, target, currentPos) => {
|
|
552
|
+
stuckCalled = true;
|
|
553
|
+
stuckTarget = target;
|
|
554
|
+
stuckPosition = currentPos;
|
|
555
|
+
return false; // Cancel the route
|
|
556
|
+
},
|
|
557
|
+
stuckTimeout: 400, // Timeout for stuck detection
|
|
558
|
+
stuckThreshold: 1
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// Wait for the promise to resolve (either completion or stuck cancellation)
|
|
562
|
+
await fixture.waitUntil(promise);
|
|
563
|
+
|
|
564
|
+
// Give some time for stuck detection to trigger
|
|
565
|
+
await fixture.nextTickTimes(30);
|
|
566
|
+
await fixture.wait(500);
|
|
567
|
+
|
|
568
|
+
// Verify onStuck was called
|
|
569
|
+
expect(stuckCalled).toBe(true);
|
|
570
|
+
expect(stuckTarget).not.toBeNull();
|
|
571
|
+
expect(stuckPosition).not.toBeNull();
|
|
572
|
+
|
|
573
|
+
// Player should have moved some distance but not reached the target
|
|
574
|
+
expect(player.x()).toBeGreaterThan(initialX);
|
|
575
|
+
expect(player.x()).toBeLessThan(initialX + 150); // Should be blocked before reaching far right
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
test("should not call onStuck when player moves normally", async () => {
|
|
579
|
+
const initialX = player.x();
|
|
580
|
+
|
|
581
|
+
let stuckCalled = false;
|
|
582
|
+
|
|
583
|
+
// Move without obstacles - should not trigger onStuck
|
|
584
|
+
await fixture.waitUntil(
|
|
585
|
+
player.moveRoutes([Move.right(2)], {
|
|
586
|
+
onStuck: () => {
|
|
587
|
+
stuckCalled = true;
|
|
588
|
+
return false;
|
|
589
|
+
},
|
|
590
|
+
stuckTimeout: 300,
|
|
591
|
+
stuckThreshold: 1
|
|
592
|
+
})
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
// Verify onStuck was NOT called
|
|
596
|
+
expect(stuckCalled).toBe(false);
|
|
597
|
+
|
|
598
|
+
// Player should have moved successfully
|
|
599
|
+
expect(player.x()).toBeGreaterThan(initialX);
|
|
600
|
+
});
|
|
601
|
+
});
|
|
@@ -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
|
+
});
|