@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.
@@ -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
+ });