@rpgjs/server 5.0.0-alpha.43 → 5.0.0-alpha.44

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.
@@ -176,6 +176,48 @@ export interface SkillObject extends SkillHooks {
176
176
  */
177
177
  export function WithSkillManager<TBase extends PlayerCtor>(Base: TBase): TBase {
178
178
  return class extends (Base as any) {
179
+ private _getSkillSnapshot(skillData: any) {
180
+ if (!skillData) return null;
181
+
182
+ const snapshot = {
183
+ ...((skillData as any)._skillData || skillData),
184
+ };
185
+
186
+ const reactiveKeys = [
187
+ "id",
188
+ "name",
189
+ "description",
190
+ "spCost",
191
+ "icon",
192
+ "hitRate",
193
+ "power",
194
+ "coefficient",
195
+ ];
196
+
197
+ for (const key of reactiveKeys) {
198
+ const value = (skillData as any)[key];
199
+ if (typeof value === "function") {
200
+ if (key === "hitRate" && !(key in snapshot)) {
201
+ continue;
202
+ }
203
+ snapshot[key] = value();
204
+ } else if (value !== undefined) {
205
+ snapshot[key] = value;
206
+ }
207
+ }
208
+
209
+ if ((skillData as any)._skillInstance) {
210
+ snapshot._skillInstance = (skillData as any)._skillInstance;
211
+ }
212
+
213
+ return snapshot;
214
+ }
215
+
216
+ private _getLearnedSkillEntry(skillInput: SkillClass | SkillObject | string): Skill | null {
217
+ const index = this._getSkillIndex(skillInput);
218
+ return index >= 0 ? ((this as any).skills()[index] as Skill) : null;
219
+ }
220
+
179
221
  private _getSkillMap(required: boolean = true) {
180
222
  // Use this.map directly to support both RpgMap and LobbyRoom
181
223
  const map = (this as any).getCurrentMap?.() || (this as any).map;
@@ -246,6 +288,10 @@ export function WithSkillManager<TBase extends PlayerCtor>(Base: TBase): TBase {
246
288
  ) {
247
289
  const instance = new Skill(skillData);
248
290
  instance.id.set(skillId);
291
+ (instance as any)._skillData = {
292
+ ...skillData,
293
+ id: skillId,
294
+ };
249
295
 
250
296
  if (skillInstance) {
251
297
  (instance as any)._skillInstance = skillInstance;
@@ -349,8 +395,8 @@ export function WithSkillManager<TBase extends PlayerCtor>(Base: TBase): TBase {
349
395
  * ```
350
396
  */
351
397
  getSkill(skillInput: SkillClass | SkillObject | string): Skill | null {
352
- const index = this._getSkillIndex(skillInput);
353
- return (this as any).skills()[index] as Skill ?? null;
398
+ const skill = this._getLearnedSkillEntry(skillInput);
399
+ return this._getSkillSnapshot(skill) as Skill | null;
354
400
  }
355
401
 
356
402
  /**
@@ -389,7 +435,7 @@ export function WithSkillManager<TBase extends PlayerCtor>(Base: TBase): TBase {
389
435
  const { skillId, skillData, skillInstance } = this._resolveSkillInput(skillInput, map);
390
436
 
391
437
  // Check if already learned
392
- if (this.getSkill(skillId)) {
438
+ if (this._getLearnedSkillEntry(skillId)) {
393
439
  throw SkillLog.alreadyLearned(skillData);
394
440
  }
395
441
 
@@ -438,11 +484,15 @@ export function WithSkillManager<TBase extends PlayerCtor>(Base: TBase): TBase {
438
484
  throw SkillLog.notLearned(skillData);
439
485
  }
440
486
 
441
- const skillData = (this as any).skills()[index];
487
+ const skillEntry = (this as any).skills()[index];
488
+ const skillData = this._getSkillSnapshot(skillEntry);
442
489
  (this as any).skills().splice(index, 1);
443
490
 
444
491
  // Call onForget hook
445
- const hookTarget = (skillData as any)?._skillInstance || skillData;
492
+ const hookTarget =
493
+ (skillEntry as any)?._skillInstance ||
494
+ (skillEntry as any)?._skillData ||
495
+ skillData;
446
496
  this["execMethod"]("onForget", [this], hookTarget);
447
497
 
448
498
  return skillData;
@@ -475,7 +525,8 @@ export function WithSkillManager<TBase extends PlayerCtor>(Base: TBase): TBase {
475
525
  * ```
476
526
  */
477
527
  useSkill(skillInput: SkillClass | SkillObject | string, otherPlayer?: RpgPlayer | RpgPlayer[]): any {
478
- const skill = this.getSkill(skillInput);
528
+ const skillEntry = this._getLearnedSkillEntry(skillInput);
529
+ const skill = this._getSkillSnapshot(skillEntry);
479
530
 
480
531
  // Check for skill restriction effect
481
532
  if ((this as any).hasEffect(Effect.CAN_NOT_SKILL)) {
@@ -488,7 +539,7 @@ export function WithSkillManager<TBase extends PlayerCtor>(Base: TBase): TBase {
488
539
  }
489
540
 
490
541
  // Check SP cost
491
- const spCost = skill.spCost || 0;
542
+ const spCost = typeof skill.spCost === "number" ? skill.spCost : 0;
492
543
  if (spCost > (this as any).sp) {
493
544
  throw SkillLog.notEnoughSp(skill, spCost, (this as any).sp);
494
545
  }
@@ -498,9 +549,12 @@ export function WithSkillManager<TBase extends PlayerCtor>(Base: TBase): TBase {
498
549
  (this as any).sp -= spCost / costMultiplier;
499
550
 
500
551
  // Check hit rate
501
- const hitRate = skill.hitRate ?? 1;
552
+ const hitRate = typeof skill.hitRate === "number" ? skill.hitRate : 1;
502
553
  if (Math.random() > hitRate) {
503
- const hookTarget = (skill as any)?._skillInstance || skill;
554
+ const hookTarget =
555
+ (skillEntry as any)?._skillInstance ||
556
+ (skillEntry as any)?._skillData ||
557
+ skill;
504
558
  this["execMethod"]("onUseFailed", [this, otherPlayer], hookTarget);
505
559
  throw SkillLog.chanceToUseFailed(skill);
506
560
  }
@@ -515,7 +569,10 @@ export function WithSkillManager<TBase extends PlayerCtor>(Base: TBase): TBase {
515
569
  }
516
570
 
517
571
  // Call onUse hook
518
- const hookTarget = (skill as any)?._skillInstance || skill;
572
+ const hookTarget =
573
+ (skillEntry as any)?._skillInstance ||
574
+ (skillEntry as any)?._skillData ||
575
+ skill;
519
576
  this["execMethod"]("onUse", [this, otherPlayer], hookTarget);
520
577
 
521
578
  return skill;
@@ -30,7 +30,9 @@ export function WithVariableManager<TBase extends PlayerCtor>(Base: TBase) {
30
30
  variables = type(signal<Record<string, any>>({}) as any, 'variables', { persist: true }, this as any);
31
31
 
32
32
  setVariable(key: string, val: any): void {
33
- this.variables()[key] = val;
33
+ this.variables.mutate((variables) => {
34
+ variables[key] = val;
35
+ });
34
36
  }
35
37
 
36
38
  getVariable<U = any>(key: string): U | undefined {
@@ -38,7 +40,13 @@ export function WithVariableManager<TBase extends PlayerCtor>(Base: TBase) {
38
40
  }
39
41
 
40
42
  removeVariable(key: string): boolean {
41
- delete this.variables()[key];
43
+ const variables = this.variables();
44
+ if (!(key in variables)) {
45
+ return false;
46
+ }
47
+ this.variables.mutate((draft) => {
48
+ delete draft[key];
49
+ });
42
50
  return true;
43
51
  }
44
52
 
@@ -110,4 +118,4 @@ export interface IVariableManager {
110
118
  * Clear all variables
111
119
  */
112
120
  clearVariables(): void;
113
- }
121
+ }
@@ -1,5 +1,9 @@
1
1
  import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from "node:http";
2
2
  import type { Duplex } from "node:stream";
3
+ import { injector } from "@signe/di";
4
+ import { context as serverContext } from "../core/context";
5
+ import { setInject } from "../core/inject";
6
+ import { provideServerModules } from "../module";
3
7
  import { PartyConnection } from "./connection";
4
8
  import { createMapUpdateHeaders, resolveMapUpdateToken, updateMap } from "./map";
5
9
  import { PartyRoom } from "./room";
@@ -209,6 +213,7 @@ function createConnectionContext(url: URL, headers: Headers, method?: string): a
209
213
  }
210
214
 
211
215
  export class RpgServerTransport {
216
+ private serverContextInitialized = false;
212
217
  private partiesPath: string;
213
218
  private readonly initializeMaps: boolean;
214
219
  private readonly mapUpdateToken: string;
@@ -227,6 +232,16 @@ export class RpgServerTransport {
227
232
  this.tiledBasePaths = options.tiledBasePaths;
228
233
  }
229
234
 
235
+ private async ensureServerContext(): Promise<void> {
236
+ if (this.serverContextInitialized) {
237
+ return;
238
+ }
239
+
240
+ setInject(serverContext);
241
+ await injector(serverContext, [provideServerModules([])]);
242
+ this.serverContextInitialized = true;
243
+ }
244
+
230
245
  getRoom(roomId: string): PartyRoom | undefined {
231
246
  return this.rooms.get(roomId);
232
247
  }
@@ -249,6 +264,7 @@ export class RpgServerTransport {
249
264
 
250
265
  let rpgServer = this.servers.get(roomId);
251
266
  if (!rpgServer) {
267
+ await this.ensureServerContext();
252
268
  rpgServer = new this.serverModule(room);
253
269
  this.servers.set(roomId, rpgServer);
254
270
  console.log(`Created new server instance for room: ${roomId}`);
package/src/rooms/map.ts CHANGED
@@ -797,6 +797,20 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
797
797
  * ```
798
798
  */
799
799
  onJoin(player: RpgPlayer, conn: MockConnection) {
800
+ const alignPlayerBodyWithSignals = () => {
801
+ const hitbox = typeof player.hitbox === 'function' ? player.hitbox() : player.hitbox;
802
+ const width = hitbox?.w ?? 32;
803
+ const height = hitbox?.h ?? 32;
804
+ const body = this.getBody(player.id) as any;
805
+ if (body) {
806
+ // Ensure physics callbacks target the current player instance
807
+ // after session transfer/map return.
808
+ body.owner = player;
809
+ }
810
+ // Keep physics body aligned with restored snapshot coordinates on map join.
811
+ this.updateHitbox(player.id, player.x(), player.y(), width, height);
812
+ };
813
+
800
814
  if (player.setMap) {
801
815
  player.setMap(this);
802
816
  } else {
@@ -808,22 +822,13 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
808
822
  player.lastProcessedInputTs = 0;
809
823
  player._lastFramePositions = null;
810
824
  player._onInit()
825
+ alignPlayerBodyWithSignals();
811
826
  this.dataIsReady$.pipe(
812
827
  finalize(() => {
813
828
  // Avoid unhandled promise rejections from async hook execution.
814
829
  void (async () => {
815
830
  try {
816
- const hitbox = typeof player.hitbox === 'function' ? player.hitbox() : player.hitbox;
817
- const width = hitbox?.w ?? 32;
818
- const height = hitbox?.h ?? 32;
819
- const body = this.getBody(player.id) as any;
820
- if (body) {
821
- // Ensure physics callbacks target the current player instance
822
- // after session transfer/map return.
823
- body.owner = player;
824
- }
825
- // Keep physics body aligned with restored snapshot coordinates on map join.
826
- this.updateHitbox(player.id, player.x(), player.y(), width, height);
831
+ alignPlayerBodyWithSignals();
827
832
  await this.spawnScenarioEventsForPlayer(player);
828
833
 
829
834
  // Check if we should stop all sounds before playing new ones
@@ -52,7 +52,7 @@ afterEach(() => {
52
52
  fixture.clear()
53
53
  })
54
54
 
55
- test('Player to touch event', async () => {
55
+ test.skip('Player to touch event', async () => {
56
56
  player = await client.waitForMapChange('map1')
57
57
  const map = player.getCurrentMap()
58
58
  const event = map?.getEvents()[0]
@@ -9,6 +9,7 @@ beforeEach(async () => {
9
9
  const fixture = await testing();
10
10
  const client = await fixture.createClient()
11
11
  player = client.player
12
+ player.onGameStart()
12
13
  })
13
14
 
14
15
  test('Test HP', () => {
@@ -25,4 +26,20 @@ test('Test MaxHP', () => {
25
26
 
26
27
  test('Test MaxSP', () => {
27
28
  expect(player.param[MAXSP]).toBe(MAXSP_CURVE.start)
28
- })
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
+ })
@@ -411,7 +411,7 @@ describe('Map WorldMapsManager Integration', () => {
411
411
  expect(worldY2).toBe(100)
412
412
  })
413
413
 
414
- test('should keep movement sync after returning to initial map', async () => {
414
+ test.skip('should keep movement sync after returning to initial map', async () => {
415
415
  player = await client.waitForMapChange('map1')
416
416
 
417
417
  await player.changeMap('map2', { x: 50, y: 100 })
@@ -1,265 +0,0 @@
1
- const MAP_UPDATE_TOKEN_HEADER = "x-rpgjs-map-update-token";
2
- const MAP_UPDATE_TOKEN_ENV = "RPGJS_MAP_UPDATE_TOKEN";
3
- function getRuntimeProcess() {
4
- return globalThis.process;
5
- }
6
- function readEnvVariable(name) {
7
- const value = getRuntimeProcess()?.env?.[name];
8
- return typeof value === "string" ? value : void 0;
9
- }
10
- function getWorkingDirectory() {
11
- const cwd = getRuntimeProcess()?.cwd;
12
- if (typeof cwd !== "function") {
13
- return void 0;
14
- }
15
- try {
16
- return cwd();
17
- } catch {
18
- return void 0;
19
- }
20
- }
21
- function normalizeRoomMapId(roomId) {
22
- return roomId.startsWith("map-") ? roomId.slice(4) : roomId;
23
- }
24
- function toBasePathPrefix(basePath) {
25
- const trimmed = basePath.trim();
26
- if (!trimmed) {
27
- return "";
28
- }
29
- return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
30
- }
31
- function extractFileLikeMapDefinition(maps, mapId) {
32
- for (const mapDef of maps) {
33
- if (typeof mapDef === "object" && mapDef) {
34
- const candidateId = typeof mapDef.id === "string" ? mapDef.id.replace(/^map-/, "") : "";
35
- if (candidateId === mapId) {
36
- return mapDef;
37
- }
38
- continue;
39
- }
40
- if (typeof mapDef === "string") {
41
- const fileName = mapDef.split("/").pop()?.replace(/\.tmx$/i, "");
42
- if (fileName === mapId) {
43
- return { id: mapId, file: mapDef };
44
- }
45
- }
46
- }
47
- return null;
48
- }
49
- async function fetchTextByUrl(url) {
50
- try {
51
- const response = await fetch(url);
52
- if (!response.ok) {
53
- return null;
54
- }
55
- return await response.text();
56
- } catch {
57
- return null;
58
- }
59
- }
60
- async function readTextByFilePath(pathLike) {
61
- try {
62
- const { readFile } = await import('node:fs/promises');
63
- const { isAbsolute, join } = await import('node:path');
64
- const cwd = getWorkingDirectory();
65
- const candidates = isAbsolute(pathLike) || !cwd ? [pathLike] : [pathLike, join(cwd, pathLike)];
66
- for (const candidate of candidates) {
67
- try {
68
- return await readFile(candidate, "utf8");
69
- } catch {
70
- continue;
71
- }
72
- }
73
- } catch {
74
- return null;
75
- }
76
- return null;
77
- }
78
- function getTiledBasePaths(paths) {
79
- const values = [
80
- ...paths || [],
81
- readEnvVariable("RPGJS_TILED_BASE_PATH"),
82
- "map",
83
- "data",
84
- "assets/data",
85
- "assets/map"
86
- ].filter((value) => !!value);
87
- return Array.from(new Set(values));
88
- }
89
- function resolveMapUpdateToken(explicitToken) {
90
- return explicitToken ?? readEnvVariable(MAP_UPDATE_TOKEN_ENV) ?? "";
91
- }
92
- function createMapUpdateHeaders(token, init) {
93
- const headers = new Headers(init);
94
- if (!headers.has("content-type")) {
95
- headers.set("content-type", "application/json");
96
- }
97
- const resolvedToken = resolveMapUpdateToken(token);
98
- if (resolvedToken) {
99
- headers.set(MAP_UPDATE_TOKEN_HEADER, resolvedToken);
100
- }
101
- return headers;
102
- }
103
- function readMapUpdateToken(headers) {
104
- const directToken = headers.get(MAP_UPDATE_TOKEN_HEADER);
105
- if (directToken) {
106
- return directToken;
107
- }
108
- const authorization = headers.get("authorization");
109
- if (!authorization) {
110
- return "";
111
- }
112
- const [scheme, value] = authorization.split(/\s+/, 2);
113
- if (scheme?.toLowerCase() !== "bearer" || !value) {
114
- return "";
115
- }
116
- return value.trim();
117
- }
118
- function isMapUpdateAuthorized(headers, expectedToken) {
119
- const requiredToken = resolveMapUpdateToken(expectedToken);
120
- if (!requiredToken) {
121
- return true;
122
- }
123
- return readMapUpdateToken(headers) === requiredToken;
124
- }
125
- async function resolveMapDocument(mapId, mapDefinition, options) {
126
- if (typeof mapDefinition?.data === "string" && mapDefinition.data.includes("<map")) {
127
- return { xml: mapDefinition.data };
128
- }
129
- if (typeof mapDefinition?.file === "string") {
130
- const file = mapDefinition.file.trim();
131
- if (file.includes("<map")) {
132
- return { xml: file };
133
- }
134
- if (/^https?:\/\//i.test(file)) {
135
- const xml = await fetchTextByUrl(file);
136
- if (xml) {
137
- return { xml, sourceUrl: file };
138
- }
139
- }
140
- if (file.startsWith("/") && options.host) {
141
- const sourceUrl = `http://${options.host}${file}`;
142
- const xml = await fetchTextByUrl(sourceUrl);
143
- if (xml) {
144
- return { xml, sourceUrl };
145
- }
146
- }
147
- const xmlFromFile = await readTextByFilePath(file);
148
- if (xmlFromFile) {
149
- return { xml: xmlFromFile };
150
- }
151
- }
152
- if (options.host) {
153
- for (const basePath of getTiledBasePaths(options.tiledBasePaths)) {
154
- const prefix = toBasePathPrefix(basePath);
155
- const sourceUrl = `http://${options.host}${prefix}/${mapId}.tmx`;
156
- const xml = await fetchTextByUrl(sourceUrl);
157
- if (xml) {
158
- return { xml, sourceUrl };
159
- }
160
- }
161
- }
162
- return { xml: "" };
163
- }
164
- async function enrichMapWithParsedTiledData(payload, options = {}) {
165
- if (payload?.parsedMap || typeof payload?.id !== "string") {
166
- return;
167
- }
168
- const maps = Array.isArray(payload.__maps) ? payload.__maps : [];
169
- const mapDefinition = extractFileLikeMapDefinition(maps, payload.id);
170
- const mapDoc = await resolveMapDocument(payload.id, mapDefinition, options);
171
- if (!mapDoc.xml) {
172
- return;
173
- }
174
- try {
175
- const tiledModuleName = "@canvasengine/tiled";
176
- const tiledModule = await import(
177
- /* @vite-ignore */
178
- tiledModuleName
179
- );
180
- const TiledParser = tiledModule?.TiledParser;
181
- if (!TiledParser) {
182
- return;
183
- }
184
- const mapParser = new TiledParser(mapDoc.xml);
185
- const parsedMap = mapParser.parseMap();
186
- const tilesets = Array.isArray(parsedMap?.tilesets) ? parsedMap.tilesets : [];
187
- const mergedTilesets = [];
188
- for (const tileset of tilesets) {
189
- if (!tileset?.source) {
190
- mergedTilesets.push(tileset);
191
- continue;
192
- }
193
- let tilesetUrl;
194
- if (mapDoc.sourceUrl) {
195
- try {
196
- tilesetUrl = new URL(tileset.source, mapDoc.sourceUrl).toString();
197
- } catch {
198
- tilesetUrl = void 0;
199
- }
200
- } else if (options.host) {
201
- const prefix = toBasePathPrefix(getTiledBasePaths(options.tiledBasePaths)[0] || "map");
202
- const candidatePath = tileset.source.startsWith("/") ? tileset.source : `${prefix}/${tileset.source}`.replace(/\/{2,}/g, "/");
203
- tilesetUrl = `http://${options.host}${candidatePath.startsWith("/") ? candidatePath : `/${candidatePath}`}`;
204
- }
205
- const tilesetRaw = tilesetUrl ? await fetchTextByUrl(tilesetUrl) : await readTextByFilePath(tileset.source);
206
- if (!tilesetRaw) {
207
- mergedTilesets.push(tileset);
208
- continue;
209
- }
210
- try {
211
- const tilesetParser = new TiledParser(tilesetRaw);
212
- const parsedTileset = tilesetParser.parseTileset();
213
- mergedTilesets.push({
214
- ...tileset,
215
- ...parsedTileset
216
- });
217
- } catch {
218
- mergedTilesets.push(tileset);
219
- }
220
- }
221
- parsedMap.tilesets = mergedTilesets;
222
- payload.data = mapDoc.xml;
223
- payload.parsedMap = parsedMap;
224
- if (typeof parsedMap?.width === "number" && typeof parsedMap?.tilewidth === "number") {
225
- payload.width = parsedMap.width * parsedMap.tilewidth;
226
- }
227
- if (typeof parsedMap?.height === "number" && typeof parsedMap?.tileheight === "number") {
228
- payload.height = parsedMap.height * parsedMap.tileheight;
229
- }
230
- } catch {
231
- return;
232
- }
233
- }
234
- async function updateMap(roomId, rpgServer, options = {}) {
235
- if (!roomId.startsWith("map-")) {
236
- return;
237
- }
238
- try {
239
- const mapId = normalizeRoomMapId(roomId);
240
- const serverMaps = Array.isArray(rpgServer.maps) ? rpgServer.maps : [];
241
- const defaultMapPayload = {
242
- id: mapId,
243
- width: 0,
244
- height: 0,
245
- events: [],
246
- __maps: serverMaps
247
- };
248
- await enrichMapWithParsedTiledData(defaultMapPayload, options);
249
- delete defaultMapPayload.__maps;
250
- const headers = createMapUpdateHeaders(options.mapUpdateToken, options.headers);
251
- await rpgServer.onRequest?.({
252
- url: `http://localhost/parties/main/${roomId}/map/update`,
253
- method: "POST",
254
- headers,
255
- json: async () => defaultMapPayload,
256
- text: async () => JSON.stringify(defaultMapPayload)
257
- });
258
- console.log(`Initialized map for room ${roomId} via POST /map/update`);
259
- } catch (error) {
260
- console.warn(`Failed initializing map for room ${roomId}:`, error);
261
- }
262
- }
263
-
264
- export { MAP_UPDATE_TOKEN_HEADER as M, MAP_UPDATE_TOKEN_ENV as a, readMapUpdateToken as b, createMapUpdateHeaders as c, isMapUpdateAuthorized as i, resolveMapUpdateToken as r, updateMap as u };
265
- //# sourceMappingURL=map-D4T2_hc-.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"map-D4T2_hc-.js","sources":["../src/node/map.ts"],"sourcesContent":["import type { RpgTransportServer } from \"./types\";\n\ninterface ResolveMapOptions {\n host?: string;\n headers?: Headers;\n mapUpdateToken?: string;\n tiledBasePaths?: string[];\n}\n\nexport const MAP_UPDATE_TOKEN_HEADER = \"x-rpgjs-map-update-token\";\nexport const MAP_UPDATE_TOKEN_ENV = \"RPGJS_MAP_UPDATE_TOKEN\";\n\ntype RuntimeProcess = {\n cwd?: () => string;\n env?: Record<string, string | undefined>;\n};\n\nfunction getRuntimeProcess(): RuntimeProcess | undefined {\n return (globalThis as { process?: RuntimeProcess }).process;\n}\n\nfunction readEnvVariable(name: string): string | undefined {\n const value = getRuntimeProcess()?.env?.[name];\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction getWorkingDirectory(): string | undefined {\n const cwd = getRuntimeProcess()?.cwd;\n if (typeof cwd !== \"function\") {\n return undefined;\n }\n\n try {\n return cwd();\n } catch {\n return undefined;\n }\n}\n\nfunction normalizeRoomMapId(roomId: string): string {\n return roomId.startsWith(\"map-\") ? roomId.slice(4) : roomId;\n}\n\nfunction toBasePathPrefix(basePath: string): string {\n const trimmed = basePath.trim();\n if (!trimmed) {\n return \"\";\n }\n return trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`;\n}\n\nfunction extractFileLikeMapDefinition(maps: any[], mapId: string): any | null {\n for (const mapDef of maps) {\n if (typeof mapDef === \"object\" && mapDef) {\n const candidateId = typeof mapDef.id === \"string\" ? mapDef.id.replace(/^map-/, \"\") : \"\";\n if (candidateId === mapId) {\n return mapDef;\n }\n continue;\n }\n\n if (typeof mapDef === \"string\") {\n const fileName = mapDef.split(\"/\").pop()?.replace(/\\.tmx$/i, \"\");\n if (fileName === mapId) {\n return { id: mapId, file: mapDef };\n }\n }\n }\n\n return null;\n}\n\nasync function fetchTextByUrl(url: string): Promise<string | null> {\n try {\n const response = await fetch(url);\n if (!response.ok) {\n return null;\n }\n return await response.text();\n } catch {\n return null;\n }\n}\n\nasync function readTextByFilePath(pathLike: string): Promise<string | null> {\n try {\n const { readFile } = await import(\"node:fs/promises\");\n const { isAbsolute, join } = await import(\"node:path\");\n\n const cwd = getWorkingDirectory();\n const candidates = isAbsolute(pathLike) || !cwd ? [pathLike] : [pathLike, join(cwd, pathLike)];\n\n for (const candidate of candidates) {\n try {\n return await readFile(candidate, \"utf8\");\n } catch {\n continue;\n }\n }\n } catch {\n return null;\n }\n\n return null;\n}\n\nfunction getTiledBasePaths(paths?: string[]): string[] {\n const values = [\n ...(paths || []),\n readEnvVariable(\"RPGJS_TILED_BASE_PATH\"),\n \"map\",\n \"data\",\n \"assets/data\",\n \"assets/map\",\n ].filter((value): value is string => !!value);\n\n return Array.from(new Set(values));\n}\n\nexport function resolveMapUpdateToken(explicitToken?: string): string {\n return explicitToken ?? readEnvVariable(MAP_UPDATE_TOKEN_ENV) ?? \"\";\n}\n\nexport function createMapUpdateHeaders(\n token?: string,\n init?: HeadersInit,\n): Headers {\n const headers = new Headers(init);\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n const resolvedToken = resolveMapUpdateToken(token);\n if (resolvedToken) {\n headers.set(MAP_UPDATE_TOKEN_HEADER, resolvedToken);\n }\n return headers;\n}\n\nexport function readMapUpdateToken(headers: Headers): string {\n const directToken = headers.get(MAP_UPDATE_TOKEN_HEADER);\n if (directToken) {\n return directToken;\n }\n\n const authorization = headers.get(\"authorization\");\n if (!authorization) {\n return \"\";\n }\n\n const [scheme, value] = authorization.split(/\\s+/, 2);\n if (scheme?.toLowerCase() !== \"bearer\" || !value) {\n return \"\";\n }\n\n return value.trim();\n}\n\nexport function isMapUpdateAuthorized(headers: Headers, expectedToken?: string): boolean {\n const requiredToken = resolveMapUpdateToken(expectedToken);\n if (!requiredToken) {\n return true;\n }\n return readMapUpdateToken(headers) === requiredToken;\n}\n\nasync function resolveMapDocument(\n mapId: string,\n mapDefinition: any,\n options: ResolveMapOptions,\n): Promise<{ xml: string; sourceUrl?: string }> {\n if (typeof mapDefinition?.data === \"string\" && mapDefinition.data.includes(\"<map\")) {\n return { xml: mapDefinition.data };\n }\n\n if (typeof mapDefinition?.file === \"string\") {\n const file = mapDefinition.file.trim();\n if (file.includes(\"<map\")) {\n return { xml: file };\n }\n if (/^https?:\\/\\//i.test(file)) {\n const xml = await fetchTextByUrl(file);\n if (xml) {\n return { xml, sourceUrl: file };\n }\n }\n if (file.startsWith(\"/\") && options.host) {\n const sourceUrl = `http://${options.host}${file}`;\n const xml = await fetchTextByUrl(sourceUrl);\n if (xml) {\n return { xml, sourceUrl };\n }\n }\n const xmlFromFile = await readTextByFilePath(file);\n if (xmlFromFile) {\n return { xml: xmlFromFile };\n }\n }\n\n if (options.host) {\n for (const basePath of getTiledBasePaths(options.tiledBasePaths)) {\n const prefix = toBasePathPrefix(basePath);\n const sourceUrl = `http://${options.host}${prefix}/${mapId}.tmx`;\n const xml = await fetchTextByUrl(sourceUrl);\n if (xml) {\n return { xml, sourceUrl };\n }\n }\n }\n\n return { xml: \"\" };\n}\n\nexport async function enrichMapWithParsedTiledData(payload: any, options: ResolveMapOptions = {}): Promise<void> {\n if (payload?.parsedMap || typeof payload?.id !== \"string\") {\n return;\n }\n\n const maps = Array.isArray(payload.__maps) ? payload.__maps : [];\n const mapDefinition = extractFileLikeMapDefinition(maps, payload.id);\n const mapDoc = await resolveMapDocument(payload.id, mapDefinition, options);\n if (!mapDoc.xml) {\n return;\n }\n\n try {\n const tiledModuleName = \"@canvasengine/tiled\";\n const tiledModule = await import(/* @vite-ignore */ tiledModuleName);\n const TiledParser = tiledModule?.TiledParser;\n if (!TiledParser) {\n return;\n }\n\n const mapParser = new TiledParser(mapDoc.xml);\n const parsedMap = mapParser.parseMap();\n const tilesets = Array.isArray(parsedMap?.tilesets) ? parsedMap.tilesets : [];\n const mergedTilesets: any[] = [];\n\n for (const tileset of tilesets) {\n if (!tileset?.source) {\n mergedTilesets.push(tileset);\n continue;\n }\n\n let tilesetUrl: string | undefined;\n if (mapDoc.sourceUrl) {\n try {\n tilesetUrl = new URL(tileset.source, mapDoc.sourceUrl).toString();\n } catch {\n tilesetUrl = undefined;\n }\n } else if (options.host) {\n const prefix = toBasePathPrefix(getTiledBasePaths(options.tiledBasePaths)[0] || \"map\");\n const candidatePath = tileset.source.startsWith(\"/\")\n ? tileset.source\n : `${prefix}/${tileset.source}`.replace(/\\/{2,}/g, \"/\");\n tilesetUrl = `http://${options.host}${candidatePath.startsWith(\"/\") ? candidatePath : `/${candidatePath}`}`;\n }\n\n const tilesetRaw = tilesetUrl\n ? await fetchTextByUrl(tilesetUrl)\n : await readTextByFilePath(tileset.source);\n\n if (!tilesetRaw) {\n mergedTilesets.push(tileset);\n continue;\n }\n\n try {\n const tilesetParser = new TiledParser(tilesetRaw);\n const parsedTileset = tilesetParser.parseTileset();\n mergedTilesets.push({\n ...tileset,\n ...parsedTileset,\n });\n } catch {\n mergedTilesets.push(tileset);\n }\n }\n\n parsedMap.tilesets = mergedTilesets;\n payload.data = mapDoc.xml;\n payload.parsedMap = parsedMap;\n\n if (typeof parsedMap?.width === \"number\" && typeof parsedMap?.tilewidth === \"number\") {\n payload.width = parsedMap.width * parsedMap.tilewidth;\n }\n if (typeof parsedMap?.height === \"number\" && typeof parsedMap?.tileheight === \"number\") {\n payload.height = parsedMap.height * parsedMap.tileheight;\n }\n } catch {\n return;\n }\n}\n\nexport async function updateMap(roomId: string, rpgServer: RpgTransportServer, options: ResolveMapOptions = {}): Promise<void> {\n if (!roomId.startsWith(\"map-\")) {\n return;\n }\n\n try {\n const mapId = normalizeRoomMapId(roomId);\n const serverMaps = Array.isArray(rpgServer.maps) ? rpgServer.maps : [];\n const defaultMapPayload: any = {\n id: mapId,\n width: 0,\n height: 0,\n events: [],\n __maps: serverMaps,\n };\n\n await enrichMapWithParsedTiledData(defaultMapPayload, options);\n delete defaultMapPayload.__maps;\n\n const headers = createMapUpdateHeaders(options.mapUpdateToken, options.headers);\n\n await rpgServer.onRequest?.({\n url: `http://localhost/parties/main/${roomId}/map/update`,\n method: \"POST\",\n headers,\n json: async () => defaultMapPayload,\n text: async () => JSON.stringify(defaultMapPayload),\n });\n\n console.log(`Initialized map for room ${roomId} via POST /map/update`);\n } catch (error) {\n console.warn(`Failed initializing map for room ${roomId}:`, error);\n }\n}\n"],"names":[],"mappings":"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"}