@rpgjs/server 5.0.0-alpha.8 → 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.
Files changed (116) hide show
  1. package/dist/Gui/DialogGui.d.ts +5 -0
  2. package/dist/Gui/GameoverGui.d.ts +23 -0
  3. package/dist/Gui/Gui.d.ts +6 -0
  4. package/dist/Gui/MenuGui.d.ts +22 -3
  5. package/dist/Gui/NotificationGui.d.ts +1 -2
  6. package/dist/Gui/SaveLoadGui.d.ts +13 -0
  7. package/dist/Gui/ShopGui.d.ts +28 -3
  8. package/dist/Gui/TitleGui.d.ts +23 -0
  9. package/dist/Gui/index.d.ts +10 -1
  10. package/dist/Player/BattleManager.d.ts +44 -32
  11. package/dist/Player/ClassManager.d.ts +24 -4
  12. package/dist/Player/ComponentManager.d.ts +100 -7
  13. package/dist/Player/Components.d.ts +345 -0
  14. package/dist/Player/EffectManager.d.ts +50 -4
  15. package/dist/Player/ElementManager.d.ts +77 -4
  16. package/dist/Player/GoldManager.d.ts +1 -1
  17. package/dist/Player/GuiManager.d.ts +233 -5
  18. package/dist/Player/ItemFixture.d.ts +1 -1
  19. package/dist/Player/ItemManager.d.ts +431 -4
  20. package/dist/Player/MoveManager.d.ts +301 -34
  21. package/dist/Player/ParameterManager.d.ts +364 -28
  22. package/dist/Player/Player.d.ts +558 -14
  23. package/dist/Player/SkillManager.d.ts +187 -13
  24. package/dist/Player/StateManager.d.ts +75 -4
  25. package/dist/Player/VariableManager.d.ts +62 -4
  26. package/dist/RpgServer.d.ts +278 -63
  27. package/dist/RpgServerEngine.d.ts +2 -1
  28. package/dist/decorators/event.d.ts +46 -0
  29. package/dist/decorators/map.d.ts +299 -0
  30. package/dist/index.d.ts +10 -0
  31. package/dist/index.js +17920 -29866
  32. package/dist/index.js.map +1 -1
  33. package/dist/logs/log.d.ts +2 -3
  34. package/dist/module-CaCW1SDh.js +11018 -0
  35. package/dist/module-CaCW1SDh.js.map +1 -0
  36. package/dist/module.d.ts +43 -1
  37. package/dist/node/connection.d.ts +51 -0
  38. package/dist/node/index.d.ts +5 -0
  39. package/dist/node/index.js +551 -0
  40. package/dist/node/index.js.map +1 -0
  41. package/dist/node/map.d.ts +16 -0
  42. package/dist/node/room.d.ts +21 -0
  43. package/dist/node/transport.d.ts +28 -0
  44. package/dist/node/types.d.ts +47 -0
  45. package/dist/presets/index.d.ts +0 -9
  46. package/dist/rooms/BaseRoom.d.ts +132 -0
  47. package/dist/rooms/lobby.d.ts +10 -2
  48. package/dist/rooms/map.d.ts +1359 -32
  49. package/dist/services/save.d.ts +43 -0
  50. package/dist/storage/index.d.ts +1 -0
  51. package/dist/storage/localStorage.d.ts +23 -0
  52. package/package.json +25 -10
  53. package/src/Gui/DialogGui.ts +19 -4
  54. package/src/Gui/GameoverGui.ts +39 -0
  55. package/src/Gui/Gui.ts +23 -1
  56. package/src/Gui/MenuGui.ts +155 -6
  57. package/src/Gui/NotificationGui.ts +1 -2
  58. package/src/Gui/SaveLoadGui.ts +60 -0
  59. package/src/Gui/ShopGui.ts +146 -16
  60. package/src/Gui/TitleGui.ts +39 -0
  61. package/src/Gui/index.ts +15 -2
  62. package/src/Player/BattleManager.ts +39 -56
  63. package/src/Player/ClassManager.ts +82 -74
  64. package/src/Player/ComponentManager.ts +401 -37
  65. package/src/Player/Components.ts +380 -0
  66. package/src/Player/EffectManager.ts +50 -96
  67. package/src/Player/ElementManager.ts +74 -152
  68. package/src/Player/GuiManager.ts +284 -149
  69. package/src/Player/ItemManager.ts +747 -341
  70. package/src/Player/MoveManager.ts +1532 -750
  71. package/src/Player/ParameterManager.ts +636 -106
  72. package/src/Player/Player.ts +1273 -79
  73. package/src/Player/SkillManager.ts +558 -197
  74. package/src/Player/StateManager.ts +131 -258
  75. package/src/Player/VariableManager.ts +85 -157
  76. package/src/RpgServer.ts +293 -62
  77. package/src/decorators/event.ts +61 -0
  78. package/src/decorators/map.ts +343 -0
  79. package/src/index.ts +11 -1
  80. package/src/logs/log.ts +10 -3
  81. package/src/module.ts +126 -3
  82. package/src/node/connection.ts +254 -0
  83. package/src/node/index.ts +22 -0
  84. package/src/node/map.ts +328 -0
  85. package/src/node/room.ts +63 -0
  86. package/src/node/transport.ts +532 -0
  87. package/src/node/types.ts +61 -0
  88. package/src/presets/index.ts +1 -10
  89. package/src/rooms/BaseRoom.ts +232 -0
  90. package/src/rooms/lobby.ts +25 -7
  91. package/src/rooms/map.ts +2682 -206
  92. package/src/services/save.ts +147 -0
  93. package/src/storage/index.ts +1 -0
  94. package/src/storage/localStorage.ts +76 -0
  95. package/tests/battle.spec.ts +375 -0
  96. package/tests/change-map.spec.ts +72 -0
  97. package/tests/class.spec.ts +274 -0
  98. package/tests/custom-websocket.spec.ts +127 -0
  99. package/tests/effect.spec.ts +219 -0
  100. package/tests/element.spec.ts +221 -0
  101. package/tests/event.spec.ts +80 -0
  102. package/tests/gold.spec.ts +99 -0
  103. package/tests/item.spec.ts +609 -0
  104. package/tests/module.spec.ts +38 -0
  105. package/tests/move.spec.ts +601 -0
  106. package/tests/node-transport.spec.ts +223 -0
  107. package/tests/player-param.spec.ts +45 -0
  108. package/tests/prediction-reconciliation.spec.ts +182 -0
  109. package/tests/random-move.spec.ts +65 -0
  110. package/tests/skill.spec.ts +658 -0
  111. package/tests/state.spec.ts +467 -0
  112. package/tests/variable.spec.ts +185 -0
  113. package/tests/world-maps.spec.ts +896 -0
  114. package/vite.config.ts +36 -3
  115. package/dist/Player/Event.d.ts +0 -0
  116. package/src/Player/Event.ts +0 -0
@@ -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
+ });