@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.
- package/dist/Player/ParameterManager.d.ts +14 -0
- package/dist/index.js +468 -3971
- package/dist/index.js.map +1 -1
- package/dist/module-CuaepWlN.js +3965 -0
- package/dist/module-CuaepWlN.js.map +1 -0
- package/dist/node/index.js +12 -2
- package/dist/node/index.js.map +1 -1
- package/dist/node/transport.d.ts +2 -0
- package/package.json +4 -4
- package/src/Player/MoveManager.ts +25 -4
- package/src/Player/ParameterManager.ts +103 -5
- package/src/Player/SkillManager.ts +67 -10
- package/src/Player/VariableManager.ts +11 -3
- package/src/node/transport.ts +16 -0
- package/src/rooms/map.ts +16 -11
- package/tests/event.spec.ts +1 -1
- package/tests/player-param.spec.ts +18 -1
- package/tests/world-maps.spec.ts +1 -1
- package/dist/map-D4T2_hc-.js +0 -265
- package/dist/map-D4T2_hc-.js.map +0 -1
|
@@ -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
|
|
353
|
-
return
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
552
|
+
const hitRate = typeof skill.hitRate === "number" ? skill.hitRate : 1;
|
|
502
553
|
if (Math.random() > hitRate) {
|
|
503
|
-
const hookTarget =
|
|
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 =
|
|
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()
|
|
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
|
-
|
|
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
|
+
}
|
package/src/node/transport.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
package/tests/event.spec.ts
CHANGED
|
@@ -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
|
+
})
|
package/tests/world-maps.spec.ts
CHANGED
|
@@ -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 })
|
package/dist/map-D4T2_hc-.js
DELETED
|
@@ -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
|
package/dist/map-D4T2_hc-.js.map
DELETED
|
@@ -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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"}
|