@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.
Files changed (114) 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 +95 -32
  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 +87 -4
  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 -29711
  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 +394 -32
  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 +125 -14
  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
package/dist/module.d.ts CHANGED
@@ -1,2 +1,44 @@
1
1
  import { FactoryProvider } from '@signe/di';
2
- export declare function provideServerModules(modules: any[]): FactoryProvider;
2
+ import { RpgServer } from './RpgServer';
3
+ /**
4
+ * Type for server modules that can be either:
5
+ * - An object implementing RpgServer interface
6
+ * - A class decorated with @RpgModule decorator
7
+ */
8
+ export type RpgServerModule = RpgServer | (new () => any);
9
+ /**
10
+ * Provides server modules configuration to Angular Dependency Injection
11
+ *
12
+ * This function accepts an array of server modules that can be either:
13
+ * - Objects implementing the RpgServer interface
14
+ * - Classes decorated with the @RpgModule decorator (which will be instantiated)
15
+ *
16
+ * @param modules - Array of server modules (objects or classes)
17
+ * @returns FactoryProvider configuration for Angular DI
18
+ * @example
19
+ * ```ts
20
+ * // Using an object
21
+ * provideServerModules([
22
+ * {
23
+ * player: {
24
+ * onConnected(player) {
25
+ * console.log('Player connected')
26
+ * }
27
+ * }
28
+ * }
29
+ * ])
30
+ *
31
+ * // Using a decorated class
32
+ * @RpgModule<RpgServer>({
33
+ * engine: {
34
+ * onStart(server) {
35
+ * console.log('Server started')
36
+ * }
37
+ * }
38
+ * })
39
+ * class MyServerModule {}
40
+ *
41
+ * provideServerModules([MyServerModule])
42
+ * ```
43
+ */
44
+ export declare function provideServerModules(modules: RpgServerModule[]): FactoryProvider;
@@ -0,0 +1,51 @@
1
+ import { RpgWebSocketConnection } from './types';
2
+ export declare class PartyConnection {
3
+ private ws;
4
+ id: string;
5
+ uri: string;
6
+ private _state;
7
+ private messageQueue;
8
+ private isProcessingQueue;
9
+ private sequenceCounter;
10
+ private incomingQueue;
11
+ private isProcessingIncomingQueue;
12
+ static packetLossRate: number;
13
+ static packetLossEnabled: boolean;
14
+ static packetLossFilter: string;
15
+ static bandwidthEnabled: boolean;
16
+ static bandwidthKbps: number;
17
+ static bandwidthFilter: string;
18
+ static latencyEnabled: boolean;
19
+ static latencyMs: number;
20
+ static latencyFilter: string;
21
+ constructor(ws: RpgWebSocketConnection, id?: string, uri?: string);
22
+ private generateId;
23
+ send(data: any): Promise<void>;
24
+ private processMessageQueue;
25
+ private shouldApplyLatency;
26
+ private waitUntil;
27
+ close(): void;
28
+ setState(value: any): void;
29
+ get state(): any;
30
+ bufferIncoming(message: string, processor: (messages: string[]) => Promise<void>): void;
31
+ private processIncomingQueue;
32
+ static configurePacketLoss(enabled: boolean, rate: number, filter?: string): void;
33
+ static getPacketLossStatus(): {
34
+ enabled: boolean;
35
+ rate: number;
36
+ filter: string;
37
+ };
38
+ static configureBandwidth(enabled: boolean, kbps: number, filter?: string): void;
39
+ static getBandwidthStatus(): {
40
+ enabled: boolean;
41
+ kbps: number;
42
+ filter: string;
43
+ };
44
+ static configureLatency(enabled: boolean, ms: number, filter?: string): void;
45
+ static getLatencyStatus(): {
46
+ enabled: boolean;
47
+ ms: number;
48
+ filter: string;
49
+ };
50
+ }
51
+ export declare function logNetworkSimulationStatus(): void;
@@ -0,0 +1,5 @@
1
+ export { PartyConnection, logNetworkSimulationStatus } from './connection';
2
+ export { createMapUpdateHeaders, isMapUpdateAuthorized, MAP_UPDATE_TOKEN_ENV, MAP_UPDATE_TOKEN_HEADER, readMapUpdateToken, resolveMapUpdateToken, } from './map';
3
+ export { PartyRoom } from './room';
4
+ export { createRpgServerTransport, RpgServerTransport } from './transport';
5
+ export type { CreateRpgServerTransportOptions, HandleNodeRequestOptions, RpgTransportRequestLike, RpgTransportServer, RpgTransportServerConstructor, RpgWebSocketConnection, RpgWebSocketRequestLike, RpgWebSocketServer, SendMapUpdateOptions, } from './types';
@@ -0,0 +1,551 @@
1
+ import { a as isMapUpdateAuthorized, c as updateMap, h as injector, i as createMapUpdateHeaders, l as context, n as MAP_UPDATE_TOKEN_ENV, o as readMapUpdateToken, p as setInject, r as MAP_UPDATE_TOKEN_HEADER, s as resolveMapUpdateToken, t as provideServerModules } from "../module-CaCW1SDh.js";
2
+ //#region src/node/connection.ts
3
+ function readEnvVariable(name) {
4
+ const value = globalThis.process?.env?.[name];
5
+ return typeof value === "string" ? value : void 0;
6
+ }
7
+ var PartyConnection = class PartyConnection {
8
+ static {
9
+ this.packetLossRate = parseFloat(readEnvVariable("RPGJS_PACKET_LOSS_RATE") || "0.1");
10
+ }
11
+ static {
12
+ this.packetLossEnabled = readEnvVariable("RPGJS_ENABLE_PACKET_LOSS") === "true";
13
+ }
14
+ static {
15
+ this.packetLossFilter = readEnvVariable("RPGJS_PACKET_LOSS_FILTER") || "";
16
+ }
17
+ static {
18
+ this.bandwidthEnabled = readEnvVariable("RPGJS_ENABLE_BANDWIDTH") === "true";
19
+ }
20
+ static {
21
+ this.bandwidthKbps = parseInt(readEnvVariable("RPGJS_BANDWIDTH_KBPS") || "100");
22
+ }
23
+ static {
24
+ this.bandwidthFilter = readEnvVariable("RPGJS_BANDWIDTH_FILTER") || "";
25
+ }
26
+ static {
27
+ this.latencyEnabled = readEnvVariable("RPGJS_ENABLE_LATENCY") === "true";
28
+ }
29
+ static {
30
+ this.latencyMs = parseInt(readEnvVariable("RPGJS_LATENCY_MS") || "50");
31
+ }
32
+ static {
33
+ this.latencyFilter = readEnvVariable("RPGJS_LATENCY_FILTER") || "";
34
+ }
35
+ constructor(ws, id, uri) {
36
+ this.ws = ws;
37
+ this._state = {};
38
+ this.messageQueue = [];
39
+ this.isProcessingQueue = false;
40
+ this.sequenceCounter = 0;
41
+ this.incomingQueue = [];
42
+ this.isProcessingIncomingQueue = false;
43
+ this.id = id || this.generateId();
44
+ this.uri = uri || "";
45
+ }
46
+ generateId() {
47
+ return `conn_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
48
+ }
49
+ async send(data) {
50
+ if (this.ws.readyState !== 1) return;
51
+ const message = typeof data === "string" ? data : JSON.stringify(data);
52
+ const timestamp = Date.now();
53
+ const sequence = ++this.sequenceCounter;
54
+ this.messageQueue.push({
55
+ message,
56
+ timestamp,
57
+ sequence
58
+ });
59
+ if (!this.isProcessingQueue) this.processMessageQueue();
60
+ }
61
+ async processMessageQueue() {
62
+ if (this.isProcessingQueue) return;
63
+ this.isProcessingQueue = true;
64
+ while (this.messageQueue.length > 0) {
65
+ const queueItem = this.messageQueue.shift();
66
+ if (this.shouldApplyLatency(queueItem.message)) await this.waitUntil(queueItem.timestamp + PartyConnection.latencyMs);
67
+ if (PartyConnection.bandwidthEnabled && PartyConnection.bandwidthKbps > 0) {
68
+ if (!PartyConnection.bandwidthFilter || queueItem.message.includes(PartyConnection.bandwidthFilter)) {
69
+ const transmissionTimeMs = queueItem.message.length * 8 / (PartyConnection.bandwidthKbps * 1e3) * 1e3;
70
+ const bandwidthDelayMs = Math.max(transmissionTimeMs, 10);
71
+ console.log(`\x1b[34m[BANDWIDTH SIMULATION]\x1b[0m Connection ${this.id}: Message #${queueItem.sequence} transmission time: ${bandwidthDelayMs.toFixed(1)}ms`);
72
+ await new Promise((resolve) => setTimeout(resolve, bandwidthDelayMs));
73
+ }
74
+ }
75
+ this.ws.send(queueItem.message);
76
+ }
77
+ this.isProcessingQueue = false;
78
+ }
79
+ shouldApplyLatency(message) {
80
+ if (!PartyConnection.latencyEnabled || PartyConnection.latencyMs <= 0) return false;
81
+ if (!PartyConnection.latencyFilter) return true;
82
+ return message.includes(PartyConnection.latencyFilter);
83
+ }
84
+ async waitUntil(targetTimestamp) {
85
+ const delayMs = targetTimestamp - Date.now();
86
+ if (delayMs <= 0) return;
87
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
88
+ }
89
+ close() {
90
+ if (this.ws.readyState === 1) this.ws.close();
91
+ }
92
+ setState(value) {
93
+ this._state = value;
94
+ }
95
+ get state() {
96
+ return this._state;
97
+ }
98
+ bufferIncoming(message, processor) {
99
+ this.incomingQueue.push({
100
+ message,
101
+ timestamp: Date.now(),
102
+ processor
103
+ });
104
+ if (!this.isProcessingIncomingQueue) this.processIncomingQueue();
105
+ }
106
+ async processIncomingQueue() {
107
+ if (this.isProcessingIncomingQueue) return;
108
+ this.isProcessingIncomingQueue = true;
109
+ while (this.incomingQueue.length > 0) {
110
+ const item = this.incomingQueue.shift();
111
+ if (this.shouldApplyLatency(item.message)) await this.waitUntil(item.timestamp + PartyConnection.latencyMs);
112
+ try {
113
+ await item.processor([item.message]);
114
+ } catch (err) {
115
+ console.error("Error processing incoming message:", err);
116
+ }
117
+ }
118
+ this.isProcessingIncomingQueue = false;
119
+ }
120
+ static configurePacketLoss(enabled, rate, filter) {
121
+ PartyConnection.packetLossEnabled = enabled;
122
+ PartyConnection.packetLossRate = Math.max(0, Math.min(1, rate));
123
+ PartyConnection.packetLossFilter = filter || "";
124
+ if (enabled && rate > 0) {
125
+ const filterInfo = filter ? ` (filtered: "${filter}")` : "";
126
+ console.log(`\x1b[35m[PACKET LOSS SIMULATION]\x1b[0m Enabled with ${(rate * 100).toFixed(1)}% loss rate${filterInfo}`);
127
+ } else if (enabled) console.log("\x1B[35m[PACKET LOSS SIMULATION]\x1B[0m Enabled but rate is 0% (no messages will be dropped)");
128
+ else console.log("\x1B[35m[PACKET LOSS SIMULATION]\x1B[0m Disabled");
129
+ }
130
+ static getPacketLossStatus() {
131
+ return {
132
+ enabled: PartyConnection.packetLossEnabled,
133
+ rate: PartyConnection.packetLossRate,
134
+ filter: PartyConnection.packetLossFilter
135
+ };
136
+ }
137
+ static configureBandwidth(enabled, kbps, filter) {
138
+ PartyConnection.bandwidthEnabled = enabled;
139
+ PartyConnection.bandwidthKbps = Math.max(1, kbps);
140
+ PartyConnection.bandwidthFilter = filter || "";
141
+ if (enabled && kbps > 0) {
142
+ const filterInfo = filter ? ` (filtered: "${filter}")` : "";
143
+ console.log(`\x1b[35m[BANDWIDTH SIMULATION]\x1b[0m Enabled with ${kbps} kbps bandwidth${filterInfo}`);
144
+ } else if (enabled) console.log("\x1B[35m[BANDWIDTH SIMULATION]\x1B[0m Enabled but bandwidth is 0 kbps (no delay will be applied)");
145
+ else console.log("\x1B[35m[BANDWIDTH SIMULATION]\x1B[0m Disabled");
146
+ }
147
+ static getBandwidthStatus() {
148
+ return {
149
+ enabled: PartyConnection.bandwidthEnabled,
150
+ kbps: PartyConnection.bandwidthKbps,
151
+ filter: PartyConnection.bandwidthFilter
152
+ };
153
+ }
154
+ static configureLatency(enabled, ms, filter) {
155
+ PartyConnection.latencyEnabled = enabled;
156
+ PartyConnection.latencyMs = Math.max(0, ms);
157
+ PartyConnection.latencyFilter = filter || "";
158
+ if (enabled && ms > 0) {
159
+ const filterInfo = filter ? ` (filtered: "${filter}")` : "";
160
+ console.log(`\x1b[35m[LATENCY SIMULATION]\x1b[0m Enabled with ${ms}ms fixed latency${filterInfo}`);
161
+ } else if (enabled) console.log("\x1B[35m[LATENCY SIMULATION]\x1B[0m Enabled but latency is 0ms (no delay will be applied)");
162
+ else console.log("\x1B[35m[LATENCY SIMULATION]\x1B[0m Disabled");
163
+ }
164
+ static getLatencyStatus() {
165
+ return {
166
+ enabled: PartyConnection.latencyEnabled,
167
+ ms: PartyConnection.latencyMs,
168
+ filter: PartyConnection.latencyFilter
169
+ };
170
+ }
171
+ };
172
+ function logNetworkSimulationStatus() {
173
+ const packetLossStatus = PartyConnection.getPacketLossStatus();
174
+ const bandwidthStatus = PartyConnection.getBandwidthStatus();
175
+ const latencyStatus = PartyConnection.getLatencyStatus();
176
+ if (packetLossStatus.enabled) {
177
+ const filterInfo = packetLossStatus.filter ? ` (filter: "${packetLossStatus.filter}")` : "";
178
+ console.log(`\x1b[36m[NETWORK SIMULATION]\x1b[0m Packet loss simulation: ${(packetLossStatus.rate * 100).toFixed(1)}% loss rate${filterInfo}`);
179
+ } else console.log("\x1B[36m[NETWORK SIMULATION]\x1B[0m Packet loss simulation: disabled");
180
+ if (bandwidthStatus.enabled) {
181
+ const filterInfo = bandwidthStatus.filter ? ` (filter: "${bandwidthStatus.filter}")` : "";
182
+ console.log(`\x1b[36m[NETWORK SIMULATION]\x1b[0m Bandwidth simulation: ${bandwidthStatus.kbps} kbps${filterInfo}`);
183
+ } else console.log("\x1B[36m[NETWORK SIMULATION]\x1B[0m Bandwidth simulation: disabled");
184
+ if (latencyStatus.enabled) {
185
+ const filterInfo = latencyStatus.filter ? ` (filter: "${latencyStatus.filter}")` : "";
186
+ console.log(`\x1b[36m[NETWORK SIMULATION]\x1b[0m Latency simulation: ${latencyStatus.ms}ms ping${filterInfo}`);
187
+ } else console.log("\x1B[36m[NETWORK SIMULATION]\x1B[0m Latency simulation: disabled");
188
+ }
189
+ //#endregion
190
+ //#region src/node/room.ts
191
+ var PartyRoom = class {
192
+ constructor(id) {
193
+ this.env = {};
194
+ this.context = {};
195
+ this.connections = /* @__PURE__ */ new Map();
196
+ this.storageData = /* @__PURE__ */ new Map();
197
+ this.id = id;
198
+ this.internalID = `internal_${id}_${Date.now()}`;
199
+ }
200
+ async broadcast(message, except = []) {
201
+ const data = typeof message === "string" ? message : JSON.stringify(message);
202
+ const sendPromises = [];
203
+ for (const [connectionId, connection] of this.connections) if (!except.includes(connectionId)) sendPromises.push(connection.send(data));
204
+ await Promise.all(sendPromises);
205
+ }
206
+ getConnection(id) {
207
+ return this.connections.get(id);
208
+ }
209
+ getConnections(tag) {
210
+ return this.connections.values();
211
+ }
212
+ addConnection(connection) {
213
+ this.connections.set(connection.id, connection);
214
+ }
215
+ removeConnection(connectionId) {
216
+ this.connections.delete(connectionId);
217
+ }
218
+ get storage() {
219
+ return {
220
+ put: async (key, value) => {
221
+ this.storageData.set(key, value);
222
+ },
223
+ get: async (key) => {
224
+ return this.storageData.get(key);
225
+ },
226
+ delete: async (key) => {
227
+ this.storageData.delete(key);
228
+ },
229
+ list: async () => {
230
+ return Array.from(this.storageData.entries());
231
+ }
232
+ };
233
+ }
234
+ };
235
+ //#endregion
236
+ //#region src/node/transport.ts
237
+ function normalizePathPrefix(path, fallback) {
238
+ const trimmed = (path || fallback).trim();
239
+ if (!trimmed) return fallback;
240
+ const prefixed = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
241
+ return prefixed !== "/" ? prefixed.replace(/\/+$/, "") : prefixed;
242
+ }
243
+ function hasPathPrefix(pathname, prefix) {
244
+ return pathname === prefix || pathname.startsWith(`${prefix}/`);
245
+ }
246
+ function prependMountedPath(pathname, mountedPath) {
247
+ if (!mountedPath) return pathname;
248
+ const normalizedMountedPath = normalizePathPrefix(mountedPath, "/");
249
+ if (hasPathPrefix(pathname, normalizedMountedPath)) return pathname;
250
+ if (pathname === "/") return normalizedMountedPath;
251
+ return `${normalizedMountedPath}${pathname.startsWith("/") ? pathname : `/${pathname}`}`.replace(/\/{2,}/g, "/");
252
+ }
253
+ function parseHttpRoute(pathname, partiesPath) {
254
+ if (!hasPathPrefix(pathname, partiesPath)) return null;
255
+ const segments = pathname.slice(partiesPath.length).split("/").filter(Boolean);
256
+ if (segments.length < 2) return null;
257
+ return {
258
+ roomId: segments[0],
259
+ requestPath: `/${segments.slice(1).join("/")}`
260
+ };
261
+ }
262
+ function parseSocketRoute(pathname, partiesPath) {
263
+ if (!hasPathPrefix(pathname, partiesPath)) return null;
264
+ const segments = pathname.slice(partiesPath.length).split("/").filter(Boolean);
265
+ if (segments.length < 1) return null;
266
+ return { roomId: segments[0] };
267
+ }
268
+ function toHeaders(input) {
269
+ if (!input) return new Headers();
270
+ if (input instanceof Headers) return new Headers(input);
271
+ if (Array.isArray(input)) return new Headers(input);
272
+ if (input instanceof Map) {
273
+ const headers = new Headers();
274
+ for (const [key, value] of input) if (typeof value !== "undefined") headers.set(key, value);
275
+ return headers;
276
+ }
277
+ const headers = new Headers();
278
+ Object.entries(input).forEach(([key, value]) => {
279
+ if (Array.isArray(value)) {
280
+ if (typeof value[0] !== "undefined") headers.set(key, value[0]);
281
+ return;
282
+ }
283
+ if (typeof value !== "undefined") headers.set(key, String(value));
284
+ });
285
+ return headers;
286
+ }
287
+ function createRequestLike(url, method, headers, bodyText) {
288
+ return {
289
+ url,
290
+ method,
291
+ headers,
292
+ json: async () => {
293
+ if (!bodyText) return;
294
+ return JSON.parse(bodyText);
295
+ },
296
+ text: async () => bodyText
297
+ };
298
+ }
299
+ async function normalizeEngineResponse(result) {
300
+ if (result instanceof Response) return result;
301
+ if (typeof result === "string") return new Response(result, {
302
+ status: 200,
303
+ headers: { "Content-Type": "text/plain" }
304
+ });
305
+ return new Response(JSON.stringify(result ?? {}), {
306
+ status: 200,
307
+ headers: { "Content-Type": "application/json" }
308
+ });
309
+ }
310
+ async function sendNodeResponse(res, response) {
311
+ res.statusCode = response.status;
312
+ response.headers.forEach((value, key) => {
313
+ res.setHeader(key, value);
314
+ });
315
+ res.end(await response.text());
316
+ }
317
+ async function readNodeBody(req) {
318
+ return await new Promise((resolve, reject) => {
319
+ const chunks = [];
320
+ req.on("data", (chunk) => {
321
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
322
+ });
323
+ req.on("end", () => {
324
+ resolve(Buffer.concat(chunks).toString("utf8"));
325
+ });
326
+ req.on("error", reject);
327
+ });
328
+ }
329
+ function resolveUrlFromSocketRequest(request) {
330
+ const headers = toHeaders(request.headers);
331
+ const host = headers.get("host") || "localhost";
332
+ const rawUrl = request.url || "/";
333
+ const url = new URL(rawUrl, `http://${host}`);
334
+ return {
335
+ headers,
336
+ method: request.method,
337
+ rawUrl,
338
+ url
339
+ };
340
+ }
341
+ function createConnectionContext(url, headers, method) {
342
+ const normalizedHeaders = /* @__PURE__ */ new Map();
343
+ headers.forEach((value, key) => {
344
+ normalizedHeaders.set(key.toLowerCase(), value);
345
+ });
346
+ return {
347
+ request: {
348
+ headers: {
349
+ has: (name) => normalizedHeaders.has(name.toLowerCase()),
350
+ get: (name) => normalizedHeaders.get(name.toLowerCase()),
351
+ entries: () => normalizedHeaders.entries(),
352
+ keys: () => normalizedHeaders.keys(),
353
+ values: () => normalizedHeaders.values()
354
+ },
355
+ method,
356
+ url: url.toString()
357
+ },
358
+ url
359
+ };
360
+ }
361
+ var RpgServerTransport = class {
362
+ constructor(serverModule, options = {}) {
363
+ this.serverModule = serverModule;
364
+ this.serverContextInitialized = false;
365
+ this.rooms = /* @__PURE__ */ new Map();
366
+ this.servers = /* @__PURE__ */ new Map();
367
+ this.lastKnownHost = "";
368
+ this.initializeMaps = options.initializeMaps ?? true;
369
+ this.mapUpdateToken = resolveMapUpdateToken(options.mapUpdateToken);
370
+ this.partiesPath = normalizePathPrefix(options.partiesPath || "/parties/main", "/parties/main");
371
+ this.tiledBasePaths = options.tiledBasePaths;
372
+ }
373
+ async ensureServerContext() {
374
+ if (this.serverContextInitialized) return;
375
+ setInject(context);
376
+ await injector(context, [provideServerModules([])]);
377
+ this.serverContextInitialized = true;
378
+ }
379
+ getRoom(roomId) {
380
+ return this.rooms.get(roomId);
381
+ }
382
+ getServer(roomId) {
383
+ return this.servers.get(roomId);
384
+ }
385
+ async ensureRoomAndServer(roomId, host) {
386
+ if (host) this.lastKnownHost = host;
387
+ let room = this.rooms.get(roomId);
388
+ if (!room) {
389
+ room = new PartyRoom(roomId);
390
+ this.rooms.set(roomId, room);
391
+ console.log(`Created new room: ${roomId}`);
392
+ }
393
+ let rpgServer = this.servers.get(roomId);
394
+ if (!rpgServer) {
395
+ await this.ensureServerContext();
396
+ rpgServer = new this.serverModule(room);
397
+ this.servers.set(roomId, rpgServer);
398
+ console.log(`Created new server instance for room: ${roomId}`);
399
+ if (typeof rpgServer.onStart === "function") try {
400
+ await rpgServer.onStart();
401
+ console.log(`Server started for room: ${roomId}`);
402
+ } catch (error) {
403
+ console.error(`Error starting server for room ${roomId}:`, error);
404
+ }
405
+ if (this.initializeMaps) await updateMap(roomId, rpgServer, {
406
+ host: host || this.lastKnownHost,
407
+ mapUpdateToken: this.mapUpdateToken,
408
+ tiledBasePaths: this.tiledBasePaths
409
+ });
410
+ }
411
+ room.context.parties = this.buildPartiesContext();
412
+ return {
413
+ room,
414
+ rpgServer
415
+ };
416
+ }
417
+ buildPartiesContext() {
418
+ return { main: { get: async (targetRoomId) => {
419
+ return { fetch: async (path, init) => {
420
+ const method = (init?.method || "GET").toUpperCase();
421
+ const headers = toHeaders(init?.headers);
422
+ const requestPath = path.startsWith("/") ? path : `/${path}`;
423
+ let bodyText = "";
424
+ if (typeof init?.body === "string") bodyText = init.body;
425
+ else if (typeof init?.body !== "undefined") bodyText = JSON.stringify(init.body);
426
+ return this.dispatchRoomRequest(targetRoomId, createRequestLike(`http://localhost${this.partiesPath}/${targetRoomId}${requestPath}`, method, headers, bodyText), this.lastKnownHost);
427
+ } };
428
+ } } };
429
+ }
430
+ async dispatchRoomRequest(roomId, requestLike, host) {
431
+ const { room, rpgServer } = await this.ensureRoomAndServer(roomId, host);
432
+ room.context.parties = this.buildPartiesContext();
433
+ return normalizeEngineResponse(await rpgServer.onRequest?.(requestLike));
434
+ }
435
+ async fetch(request, init) {
436
+ const webRequest = request instanceof Request ? request : new Request(String(request), init);
437
+ const url = new URL(webRequest.url);
438
+ const route = parseHttpRoute(url.pathname, this.partiesPath);
439
+ if (!route) return new Response(JSON.stringify({ error: "Not found" }), {
440
+ status: 404,
441
+ headers: { "Content-Type": "application/json" }
442
+ });
443
+ const bodyText = await webRequest.text();
444
+ return this.dispatchRoomRequest(route.roomId, createRequestLike(webRequest.url, webRequest.method.toUpperCase(), toHeaders(webRequest.headers), bodyText), url.host);
445
+ }
446
+ async updateMap(mapId, payload, options = {}) {
447
+ const roomId = mapId.startsWith("map-") ? mapId : `map-${mapId}`;
448
+ const headers = createMapUpdateHeaders(this.mapUpdateToken, options.headers);
449
+ if (!headers.has("content-type")) headers.set("content-type", "application/json");
450
+ return this.dispatchRoomRequest(roomId, createRequestLike(`http://localhost${this.partiesPath}/${roomId}/map/update`, "POST", headers, JSON.stringify(payload)), options.host ?? this.lastKnownHost);
451
+ }
452
+ async handleNodeRequest(req, res, next, options = {}) {
453
+ try {
454
+ const headers = toHeaders(req.headers);
455
+ const host = headers.get("host") || "localhost";
456
+ const url = new URL(req.url || "/", `http://${host}`);
457
+ const normalizedPathname = prependMountedPath(url.pathname, options.mountedPath);
458
+ const normalizedUrl = new URL(url.toString());
459
+ normalizedUrl.pathname = normalizedPathname;
460
+ const route = parseHttpRoute(normalizedUrl.pathname, this.partiesPath);
461
+ if (!route) {
462
+ next?.();
463
+ return false;
464
+ }
465
+ const bodyText = await readNodeBody(req);
466
+ await sendNodeResponse(res, await this.dispatchRoomRequest(route.roomId, createRequestLike(normalizedUrl.toString(), (req.method || "GET").toUpperCase(), headers, bodyText), host));
467
+ return true;
468
+ } catch (error) {
469
+ console.error("Error handling RPG-JS request:", error);
470
+ res.statusCode = 500;
471
+ res.setHeader("Content-Type", "application/json");
472
+ res.end(JSON.stringify({ error: "Internal server error" }));
473
+ return true;
474
+ }
475
+ }
476
+ async acceptWebSocket(ws, request) {
477
+ const normalizedRequest = resolveUrlFromSocketRequest(request);
478
+ const route = parseSocketRoute(normalizedRequest.url.pathname, this.partiesPath);
479
+ if (!route) return false;
480
+ try {
481
+ console.log(`WebSocket upgrade request: ${normalizedRequest.url.pathname}`);
482
+ const queryParams = Object.fromEntries(normalizedRequest.url.searchParams.entries());
483
+ console.log(`Room: ${route.roomId}, Query params:`, queryParams);
484
+ const { room, rpgServer } = await this.ensureRoomAndServer(route.roomId, normalizedRequest.url.host);
485
+ room.context.parties = this.buildPartiesContext();
486
+ const connection = new PartyConnection(ws, queryParams._pk, normalizedRequest.rawUrl);
487
+ room.addConnection(connection);
488
+ console.log(`WebSocket connection established: ${connection.id} in room: ${route.roomId}`);
489
+ let isClosed = false;
490
+ const cleanup = async (logMessage, error) => {
491
+ if (isClosed) return;
492
+ isClosed = true;
493
+ if (logMessage) console.log(logMessage);
494
+ if (error) console.error("WebSocket error:", error);
495
+ room.removeConnection(connection.id);
496
+ await rpgServer.onClose?.(connection);
497
+ };
498
+ ws.on("message", async (data) => {
499
+ try {
500
+ const rawMessage = typeof data === "string" ? data : data.toString();
501
+ if (PartyConnection.packetLossEnabled && PartyConnection.packetLossRate > 0) {
502
+ if (!PartyConnection.packetLossFilter || rawMessage.includes(PartyConnection.packetLossFilter)) {
503
+ if (Math.random() < PartyConnection.packetLossRate) {
504
+ console.log(`\x1b[31m[PACKET LOSS]\x1b[0m Connection ${connection.id}: Server dropped an incoming packet (${(PartyConnection.packetLossRate * 100).toFixed(1)}% loss rate)`);
505
+ console.log(`\x1b[33m[PACKET DATA]\x1b[0m ${rawMessage.slice(0, 100)}${rawMessage.length > 100 ? "..." : ""}`);
506
+ return;
507
+ }
508
+ }
509
+ }
510
+ connection.bufferIncoming(rawMessage, async (batch) => {
511
+ for (const message of batch) await rpgServer.onMessage?.(message, connection);
512
+ });
513
+ } catch (error) {
514
+ console.error("Error processing WebSocket message:", error);
515
+ }
516
+ });
517
+ ws.on("close", () => {
518
+ cleanup(`WebSocket connection closed: ${connection.id} from room: ${route.roomId}`);
519
+ });
520
+ ws.on("error", (error) => {
521
+ cleanup(void 0, error);
522
+ });
523
+ if (typeof rpgServer.onConnect === "function") await rpgServer.onConnect(connection, createConnectionContext(normalizedRequest.url, normalizedRequest.headers, normalizedRequest.method));
524
+ await connection.send({
525
+ type: "connected",
526
+ id: connection.id,
527
+ message: "Connected to RPG-JS server"
528
+ });
529
+ return true;
530
+ } catch (error) {
531
+ console.error("Error establishing WebSocket connection:", error);
532
+ ws.close();
533
+ return true;
534
+ }
535
+ }
536
+ async handleUpgrade(wsServer, request, socket, head) {
537
+ const host = toHeaders(request.headers).get("host") || "localhost";
538
+ if (!parseSocketRoute(new URL(request.url || "/", `http://${host}`).pathname, this.partiesPath)) return false;
539
+ wsServer.handleUpgrade(request, socket, head, (ws) => {
540
+ this.acceptWebSocket(ws, request);
541
+ });
542
+ return true;
543
+ }
544
+ };
545
+ function createRpgServerTransport(serverModule, options) {
546
+ return new RpgServerTransport(serverModule, options);
547
+ }
548
+ //#endregion
549
+ export { MAP_UPDATE_TOKEN_ENV, MAP_UPDATE_TOKEN_HEADER, PartyConnection, PartyRoom, RpgServerTransport, createMapUpdateHeaders, createRpgServerTransport, isMapUpdateAuthorized, logNetworkSimulationStatus, readMapUpdateToken, resolveMapUpdateToken };
550
+
551
+ //# sourceMappingURL=index.js.map