@rpgjs/server 5.0.0-alpha.35 → 5.0.0-alpha.39
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/index.js +278 -36
- package/dist/index.js.map +1 -1
- package/dist/rooms/map.d.ts +34 -3
- package/package.json +4 -4
- package/src/Player/MoveManager.ts +12 -2
- package/src/Player/Player.ts +6 -3
- package/src/rooms/map.ts +280 -22
package/dist/rooms/map.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Hooks, RpgCommonMap, RpgShape, WorldMapsManager, WeatherState, WorldMap
|
|
|
3
3
|
import { RpgPlayer, RpgEvent } from '../Player/Player';
|
|
4
4
|
import { BehaviorSubject } from 'rxjs';
|
|
5
5
|
import { MapOptions } from '../decorators/map';
|
|
6
|
+
import { EventMode } from '../decorators/event';
|
|
6
7
|
/**
|
|
7
8
|
* Interface for input controls configuration
|
|
8
9
|
*
|
|
@@ -50,9 +51,13 @@ export type EventPosOption = {
|
|
|
50
51
|
/** ID of the event */
|
|
51
52
|
id?: string;
|
|
52
53
|
/** X position of the event on the map */
|
|
53
|
-
x
|
|
54
|
+
x?: number;
|
|
54
55
|
/** Y position of the event on the map */
|
|
55
|
-
y
|
|
56
|
+
y?: number;
|
|
57
|
+
/** Event mode override */
|
|
58
|
+
mode?: EventMode | "shared" | "scenario";
|
|
59
|
+
/** Owner player id when mode is scenario */
|
|
60
|
+
scenarioOwnerId?: string;
|
|
56
61
|
/**
|
|
57
62
|
* Event definition - can be either:
|
|
58
63
|
* - A class that extends RpgPlayer
|
|
@@ -60,6 +65,10 @@ export type EventPosOption = {
|
|
|
60
65
|
*/
|
|
61
66
|
event: EventConstructor | (EventHooks & Record<string, any>);
|
|
62
67
|
};
|
|
68
|
+
type CreateDynamicEventOptions = {
|
|
69
|
+
mode?: EventMode | "shared" | "scenario";
|
|
70
|
+
scenarioOwnerId?: string;
|
|
71
|
+
};
|
|
63
72
|
interface WeatherSetOptions {
|
|
64
73
|
sync?: boolean;
|
|
65
74
|
}
|
|
@@ -172,11 +181,32 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
|
|
|
172
181
|
private _inputLoopSubscription?;
|
|
173
182
|
/** Enable/disable automatic tick processing (useful for unit tests) */
|
|
174
183
|
private _autoTickEnabled;
|
|
184
|
+
/** Runtime templates for scenario events to instantiate per player */
|
|
185
|
+
private _scenarioEventTemplates;
|
|
186
|
+
/** Runtime registry of event mode by id */
|
|
187
|
+
private _eventModeById;
|
|
188
|
+
/** Runtime registry of scenario owner by event id */
|
|
189
|
+
private _eventOwnerById;
|
|
190
|
+
/** Runtime registry of spawned scenario event ids by player id */
|
|
191
|
+
private _scenarioEventIdsByPlayer;
|
|
175
192
|
autoSync: boolean;
|
|
176
193
|
constructor(room: any);
|
|
177
194
|
onStart(): Promise<void>;
|
|
178
195
|
private isPositiveNumber;
|
|
179
196
|
private resolveTrustedMapDimensions;
|
|
197
|
+
private normalizeEventMode;
|
|
198
|
+
private resolveEventMode;
|
|
199
|
+
private resolveScenarioOwnerId;
|
|
200
|
+
private normalizeEventObject;
|
|
201
|
+
private cloneEventTemplate;
|
|
202
|
+
private buildRuntimeEventId;
|
|
203
|
+
private setEventRuntimeMetadata;
|
|
204
|
+
private clearEventRuntimeMetadata;
|
|
205
|
+
private getEventModeById;
|
|
206
|
+
private getScenarioOwnerIdByEventId;
|
|
207
|
+
isEventVisibleForPlayer(eventOrId: string | RpgEvent, playerOrId: string | RpgPlayer): boolean;
|
|
208
|
+
private spawnScenarioEventsForPlayer;
|
|
209
|
+
private removeScenarioEventsForPlayer;
|
|
180
210
|
/**
|
|
181
211
|
* Setup collision detection between players, events, and shapes
|
|
182
212
|
*
|
|
@@ -724,7 +754,7 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
|
|
|
724
754
|
* }
|
|
725
755
|
* });
|
|
726
756
|
*/
|
|
727
|
-
createDynamicEvent(eventObj: EventPosOption): Promise<
|
|
757
|
+
createDynamicEvent(eventObj: EventPosOption, options?: CreateDynamicEventOptions): Promise<string | undefined>;
|
|
728
758
|
/**
|
|
729
759
|
* Get an event by its ID
|
|
730
760
|
*
|
|
@@ -801,6 +831,7 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
|
|
|
801
831
|
* ```
|
|
802
832
|
*/
|
|
803
833
|
getEvents(): RpgEvent[];
|
|
834
|
+
getEventsForPlayer(playerOrId: string | RpgPlayer): RpgEvent[];
|
|
804
835
|
/**
|
|
805
836
|
* Get the first event that matches a condition
|
|
806
837
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpgjs/server",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.39",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"publishConfig": {
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"description": "",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@rpgjs/common": "5.0.0-alpha.
|
|
15
|
-
"@rpgjs/physic": "5.0.0-alpha.
|
|
16
|
-
"@rpgjs/testing": "5.0.0-alpha.
|
|
14
|
+
"@rpgjs/common": "5.0.0-alpha.39",
|
|
15
|
+
"@rpgjs/physic": "5.0.0-alpha.39",
|
|
16
|
+
"@rpgjs/testing": "5.0.0-alpha.39",
|
|
17
17
|
"@rpgjs/database": "^4.3.0",
|
|
18
18
|
"@signe/di": "^2.8.3",
|
|
19
19
|
"@signe/reactive": "^2.8.3",
|
|
@@ -820,8 +820,18 @@ export function WithMoveManager<TBase extends PlayerCtor>(Base: TBase) {
|
|
|
820
820
|
const map = (this as unknown as PlayerWithMixins).getCurrentMap() as any;
|
|
821
821
|
if (!map) return;
|
|
822
822
|
|
|
823
|
-
|
|
824
|
-
|
|
823
|
+
let strategies: MovementStrategy[] = [];
|
|
824
|
+
try {
|
|
825
|
+
strategies = this.getActiveMovements();
|
|
826
|
+
}
|
|
827
|
+
catch (error) {
|
|
828
|
+
// Teardown race: entity can be removed while AI still clears movements.
|
|
829
|
+
const message = (error as Error | undefined)?.message ?? "";
|
|
830
|
+
if (message.includes("unable to resolve entity")) {
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
throw error;
|
|
834
|
+
}
|
|
825
835
|
const toRemove = strategies.filter(s => s instanceof SeekAvoid || s instanceof LinearRepulsion);
|
|
826
836
|
|
|
827
837
|
if (toRemove.length > 0) {
|
package/src/Player/Player.ts
CHANGED
|
@@ -657,9 +657,12 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
657
657
|
const map = this.getCurrentMap();
|
|
658
658
|
if (!map) return;
|
|
659
659
|
const { events } = map;
|
|
660
|
+
const visibleMapEvents = Object.values(events?.() ?? {}).filter((event: any) =>
|
|
661
|
+
map.isEventVisibleForPlayer?.(event, this) ?? true
|
|
662
|
+
);
|
|
660
663
|
const arrayEvents: any[] = [
|
|
661
664
|
...Object.values(this.events()),
|
|
662
|
-
...
|
|
665
|
+
...visibleMapEvents,
|
|
663
666
|
];
|
|
664
667
|
for (let event of arrayEvents) {
|
|
665
668
|
if (event.onChanges) event.onChanges(this);
|
|
@@ -810,7 +813,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
810
813
|
const event = map.getEvent<RpgEvent>(entity.uuid);
|
|
811
814
|
const player = map.getPlayer(entity.uuid);
|
|
812
815
|
|
|
813
|
-
if (event) {
|
|
816
|
+
if (event && (!map.isEventVisibleForPlayer || map.isEventVisibleForPlayer(event, this))) {
|
|
814
817
|
event.execMethod("onInShape", [shape, this]);
|
|
815
818
|
// Track that this event is in the shape
|
|
816
819
|
if ((event as any)._inShapes) {
|
|
@@ -831,7 +834,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
831
834
|
const event = map.getEvent<RpgEvent>(entity.uuid);
|
|
832
835
|
const player = map.getPlayer(entity.uuid);
|
|
833
836
|
|
|
834
|
-
if (event) {
|
|
837
|
+
if (event && (!map.isEventVisibleForPlayer || map.isEventVisibleForPlayer(event, this))) {
|
|
835
838
|
event.execMethod("onOutShape", [shape, this]);
|
|
836
839
|
// Remove from tracking
|
|
837
840
|
if ((event as any)._inShapes) {
|
package/src/rooms/map.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { COEFFICIENT_ELEMENTS, DAMAGE_CRITICAL, DAMAGE_PHYSIC, DAMAGE_SKILL } fr
|
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { EntityState } from "@rpgjs/physic";
|
|
15
15
|
import { MapOptions } from "../decorators/map";
|
|
16
|
+
import { EventMode } from "../decorators/event";
|
|
16
17
|
import { BaseRoom } from "./BaseRoom";
|
|
17
18
|
import { buildSaveSlotMeta, resolveSaveStorageStrategy } from "../services/save";
|
|
18
19
|
import { Log } from "../logs/log";
|
|
@@ -98,9 +99,13 @@ export type EventPosOption = {
|
|
|
98
99
|
id?: string,
|
|
99
100
|
|
|
100
101
|
/** X position of the event on the map */
|
|
101
|
-
x
|
|
102
|
+
x?: number,
|
|
102
103
|
/** Y position of the event on the map */
|
|
103
|
-
y
|
|
104
|
+
y?: number,
|
|
105
|
+
/** Event mode override */
|
|
106
|
+
mode?: EventMode | "shared" | "scenario",
|
|
107
|
+
/** Owner player id when mode is scenario */
|
|
108
|
+
scenarioOwnerId?: string,
|
|
104
109
|
/**
|
|
105
110
|
* Event definition - can be either:
|
|
106
111
|
* - A class that extends RpgPlayer
|
|
@@ -109,6 +114,11 @@ export type EventPosOption = {
|
|
|
109
114
|
event: EventConstructor | (EventHooks & Record<string, any>)
|
|
110
115
|
}
|
|
111
116
|
|
|
117
|
+
type CreateDynamicEventOptions = {
|
|
118
|
+
mode?: EventMode | "shared" | "scenario";
|
|
119
|
+
scenarioOwnerId?: string;
|
|
120
|
+
};
|
|
121
|
+
|
|
112
122
|
interface WeatherSetOptions {
|
|
113
123
|
sync?: boolean;
|
|
114
124
|
}
|
|
@@ -232,6 +242,14 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
232
242
|
private _inputLoopSubscription?: any;
|
|
233
243
|
/** Enable/disable automatic tick processing (useful for unit tests) */
|
|
234
244
|
private _autoTickEnabled: boolean = true;
|
|
245
|
+
/** Runtime templates for scenario events to instantiate per player */
|
|
246
|
+
private _scenarioEventTemplates: EventPosOption[] = [];
|
|
247
|
+
/** Runtime registry of event mode by id */
|
|
248
|
+
private _eventModeById: Map<string, EventMode> = new Map();
|
|
249
|
+
/** Runtime registry of scenario owner by event id */
|
|
250
|
+
private _eventOwnerById: Map<string, string> = new Map();
|
|
251
|
+
/** Runtime registry of spawned scenario event ids by player id */
|
|
252
|
+
private _scenarioEventIdsByPlayer: Map<string, Set<string>> = new Map();
|
|
235
253
|
|
|
236
254
|
autoSync: boolean = true;
|
|
237
255
|
|
|
@@ -286,6 +304,172 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
286
304
|
}
|
|
287
305
|
}
|
|
288
306
|
|
|
307
|
+
private normalizeEventMode(mode: unknown): EventMode {
|
|
308
|
+
return mode === EventMode.Scenario || mode === "scenario"
|
|
309
|
+
? EventMode.Scenario
|
|
310
|
+
: EventMode.Shared;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private resolveEventMode(eventObj: any): EventMode {
|
|
314
|
+
if (!eventObj) return EventMode.Shared;
|
|
315
|
+
|
|
316
|
+
if (eventObj.mode !== undefined) {
|
|
317
|
+
return this.normalizeEventMode(eventObj.mode);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const eventDef = eventObj.event ?? eventObj;
|
|
321
|
+
if (eventDef?.mode !== undefined) {
|
|
322
|
+
return this.normalizeEventMode(eventDef.mode);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (typeof eventDef === "function") {
|
|
326
|
+
const staticMode = (eventDef as any).mode;
|
|
327
|
+
const prototypeMode = (eventDef as any).prototype?.mode;
|
|
328
|
+
if (staticMode !== undefined) {
|
|
329
|
+
return this.normalizeEventMode(staticMode);
|
|
330
|
+
}
|
|
331
|
+
if (prototypeMode !== undefined) {
|
|
332
|
+
return this.normalizeEventMode(prototypeMode);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return EventMode.Shared;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private resolveScenarioOwnerId(eventObj: any): string | undefined {
|
|
340
|
+
if (!eventObj) return undefined;
|
|
341
|
+
const ownerId = eventObj.scenarioOwnerId
|
|
342
|
+
?? eventObj._scenarioOwnerId
|
|
343
|
+
?? eventObj.event?.scenarioOwnerId
|
|
344
|
+
?? eventObj.event?._scenarioOwnerId;
|
|
345
|
+
return typeof ownerId === "string" && ownerId.length > 0 ? ownerId : undefined;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private normalizeEventObject(eventObj: EventPosOption | any): EventPosOption {
|
|
349
|
+
if (eventObj && typeof eventObj === "object" && "event" in eventObj) {
|
|
350
|
+
return eventObj as EventPosOption;
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
event: eventObj as any,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private cloneEventTemplate(eventObj: EventPosOption): EventPosOption {
|
|
358
|
+
const clone: EventPosOption = { ...eventObj };
|
|
359
|
+
if (clone.event && typeof clone.event === "object") {
|
|
360
|
+
clone.event = { ...(clone.event as Record<string, any>) } as any;
|
|
361
|
+
}
|
|
362
|
+
return clone;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private buildRuntimeEventId(baseId: string | undefined, mode: EventMode, scenarioOwnerId?: string): string {
|
|
366
|
+
const fallbackId = baseId || generateShortUUID();
|
|
367
|
+
if (mode !== EventMode.Scenario || !scenarioOwnerId) {
|
|
368
|
+
return fallbackId;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const scopedId = `${fallbackId}::${scenarioOwnerId}`;
|
|
372
|
+
if (!this.events()[scopedId]) {
|
|
373
|
+
return scopedId;
|
|
374
|
+
}
|
|
375
|
+
return `${scopedId}::${generateShortUUID()}`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private setEventRuntimeMetadata(eventId: string, mode: EventMode, scenarioOwnerId?: string): void {
|
|
379
|
+
this._eventModeById.set(eventId, mode);
|
|
380
|
+
if (mode === EventMode.Scenario && scenarioOwnerId) {
|
|
381
|
+
this._eventOwnerById.set(eventId, scenarioOwnerId);
|
|
382
|
+
const ids = this._scenarioEventIdsByPlayer.get(scenarioOwnerId) ?? new Set<string>();
|
|
383
|
+
ids.add(eventId);
|
|
384
|
+
this._scenarioEventIdsByPlayer.set(scenarioOwnerId, ids);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
this._eventOwnerById.delete(eventId);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private clearEventRuntimeMetadata(eventId: string): void {
|
|
391
|
+
this._eventModeById.delete(eventId);
|
|
392
|
+
const ownerId = this._eventOwnerById.get(eventId);
|
|
393
|
+
if (ownerId) {
|
|
394
|
+
const ids = this._scenarioEventIdsByPlayer.get(ownerId);
|
|
395
|
+
if (ids) {
|
|
396
|
+
ids.delete(eventId);
|
|
397
|
+
if (ids.size === 0) {
|
|
398
|
+
this._scenarioEventIdsByPlayer.delete(ownerId);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
this._eventOwnerById.delete(eventId);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private getEventModeById(eventId: string): EventMode {
|
|
406
|
+
const runtimeMode = this._eventModeById.get(eventId);
|
|
407
|
+
if (runtimeMode) {
|
|
408
|
+
return runtimeMode;
|
|
409
|
+
}
|
|
410
|
+
const event = this.getEvent(eventId) as any;
|
|
411
|
+
return this.normalizeEventMode(event?.mode);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private getScenarioOwnerIdByEventId(eventId: string): string | undefined {
|
|
415
|
+
const runtimeOwnerId = this._eventOwnerById.get(eventId);
|
|
416
|
+
if (runtimeOwnerId) {
|
|
417
|
+
return runtimeOwnerId;
|
|
418
|
+
}
|
|
419
|
+
const event = this.getEvent(eventId) as any;
|
|
420
|
+
const ownerId = event?._scenarioOwnerId ?? event?.scenarioOwnerId;
|
|
421
|
+
return typeof ownerId === "string" && ownerId.length > 0 ? ownerId : undefined;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
isEventVisibleForPlayer(eventOrId: string | RpgEvent, playerOrId: string | RpgPlayer): boolean {
|
|
425
|
+
const playerId = typeof playerOrId === "string" ? playerOrId : playerOrId?.id;
|
|
426
|
+
if (!playerId) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
const eventId = typeof eventOrId === "string" ? eventOrId : eventOrId?.id;
|
|
430
|
+
if (!eventId) {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
const mode = this.getEventModeById(eventId);
|
|
434
|
+
if (mode === EventMode.Shared) {
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
const ownerId = this.getScenarioOwnerIdByEventId(eventId);
|
|
438
|
+
return ownerId === playerId;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private async spawnScenarioEventsForPlayer(player: RpgPlayer): Promise<void> {
|
|
442
|
+
if (!player?.id || this._scenarioEventTemplates.length === 0) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
this.removeScenarioEventsForPlayer(player.id);
|
|
446
|
+
for (const template of this._scenarioEventTemplates) {
|
|
447
|
+
const clone = this.cloneEventTemplate(template);
|
|
448
|
+
await this.createDynamicEvent(clone, { mode: EventMode.Scenario, scenarioOwnerId: player.id });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private removeScenarioEventsForPlayer(playerId: string): void {
|
|
453
|
+
const ids = this._scenarioEventIdsByPlayer.get(playerId);
|
|
454
|
+
if (!ids || ids.size === 0) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
for (const eventId of [...ids]) {
|
|
458
|
+
const event = this.getEvent(eventId) as any;
|
|
459
|
+
if (event && typeof event.remove === "function") {
|
|
460
|
+
try {
|
|
461
|
+
event.remove();
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
// Fallback to direct map removal when the event lifecycle is already partially torn down.
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
this.removeEvent(eventId);
|
|
469
|
+
}
|
|
470
|
+
this._scenarioEventIdsByPlayer.delete(playerId);
|
|
471
|
+
}
|
|
472
|
+
|
|
289
473
|
/**
|
|
290
474
|
* Setup collision detection between players, events, and shapes
|
|
291
475
|
*
|
|
@@ -399,7 +583,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
399
583
|
const eventId = player.id === entityA.uuid ? entityB.uuid : entityA.uuid;
|
|
400
584
|
const event = this.getEvent(eventId);
|
|
401
585
|
|
|
402
|
-
if (event) {
|
|
586
|
+
if (event && this.isEventVisibleForPlayer(eventId, player)) {
|
|
403
587
|
// Mark this collision as processed
|
|
404
588
|
activeCollisions.add(collisionKey);
|
|
405
589
|
|
|
@@ -485,6 +669,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
485
669
|
*/
|
|
486
670
|
interceptorPacket(player: RpgPlayer, packet: any, conn: MockConnection) {
|
|
487
671
|
let obj: any = {}
|
|
672
|
+
let packetValue = packet?.value;
|
|
488
673
|
|
|
489
674
|
if (!player) {
|
|
490
675
|
return null
|
|
@@ -517,6 +702,20 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
517
702
|
}
|
|
518
703
|
}
|
|
519
704
|
|
|
705
|
+
if (packetValue && typeof packetValue === "object" && packetValue.events && typeof packetValue.events === "object") {
|
|
706
|
+
const eventEntries = Object.entries(packetValue.events);
|
|
707
|
+
const filteredEntries = eventEntries.filter(([eventId]) => this.isEventVisibleForPlayer(eventId, player));
|
|
708
|
+
if (filteredEntries.length !== eventEntries.length) {
|
|
709
|
+
packetValue = { ...packetValue };
|
|
710
|
+
if (filteredEntries.length === 0) {
|
|
711
|
+
delete (packetValue as any).events;
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
(packetValue as any).events = Object.fromEntries(filteredEntries);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
520
719
|
if (typeof packet.value == 'string') {
|
|
521
720
|
return packet
|
|
522
721
|
}
|
|
@@ -524,7 +723,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
524
723
|
return {
|
|
525
724
|
...packet,
|
|
526
725
|
value: {
|
|
527
|
-
...
|
|
726
|
+
...packetValue,
|
|
528
727
|
...obj
|
|
529
728
|
}
|
|
530
729
|
};
|
|
@@ -585,6 +784,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
585
784
|
}
|
|
586
785
|
// Keep physics body aligned with restored snapshot coordinates on map join.
|
|
587
786
|
this.updateHitbox(player.id, player.x(), player.y(), width, height);
|
|
787
|
+
await this.spawnScenarioEventsForPlayer(player);
|
|
588
788
|
|
|
589
789
|
// Check if we should stop all sounds before playing new ones
|
|
590
790
|
if ((this as any).stopAllSoundsBeforeJoin) {
|
|
@@ -651,6 +851,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
651
851
|
|
|
652
852
|
// Execute player hooks
|
|
653
853
|
await lastValueFrom(this.hooks.callHooks("server-player-onLeaveMap", player, this));
|
|
854
|
+
this.removeScenarioEventsForPlayer(player.id);
|
|
654
855
|
player.pendingInputs = [];
|
|
655
856
|
player.lastProcessedInputTs = 0;
|
|
656
857
|
player._lastFramePositions = null;
|
|
@@ -748,10 +949,12 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
748
949
|
onAction(player: RpgPlayer, action: any) {
|
|
749
950
|
// Get collisions using the helper method from RpgCommonMap
|
|
750
951
|
const collisions = (this as any).getCollisions(player.id);
|
|
751
|
-
const events
|
|
952
|
+
const events = collisions
|
|
953
|
+
.map(id => this.getEvent(id))
|
|
954
|
+
.filter((event): event is RpgEvent => !!event && this.isEventVisibleForPlayer(event, player));
|
|
752
955
|
if (events.length > 0) {
|
|
753
956
|
events.forEach(event => {
|
|
754
|
-
event
|
|
957
|
+
event.execMethod('onAction', [player, action]);
|
|
755
958
|
});
|
|
756
959
|
}
|
|
757
960
|
player.execMethod('onInput', [action]);
|
|
@@ -969,10 +1172,25 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
969
1172
|
|
|
970
1173
|
await lastValueFrom(this.hooks.callHooks("server-map-onBeforeUpdate", map, this))
|
|
971
1174
|
|
|
1175
|
+
this._scenarioEventTemplates = [];
|
|
1176
|
+
this._eventModeById.clear();
|
|
1177
|
+
this._eventOwnerById.clear();
|
|
1178
|
+
this._scenarioEventIdsByPlayer.clear();
|
|
1179
|
+
|
|
972
1180
|
this.loadPhysic()
|
|
973
1181
|
|
|
974
1182
|
for (let event of map.events ?? []) {
|
|
975
|
-
|
|
1183
|
+
const normalizedEvent = this.normalizeEventObject(event);
|
|
1184
|
+
const mode = this.resolveEventMode(normalizedEvent);
|
|
1185
|
+
if (mode === EventMode.Scenario) {
|
|
1186
|
+
this._scenarioEventTemplates.push(this.cloneEventTemplate(normalizedEvent));
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
await this.createDynamicEvent(normalizedEvent, { mode: EventMode.Shared });
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
for (const player of this.getPlayers()) {
|
|
1193
|
+
await this.spawnScenarioEventsForPlayer(player);
|
|
976
1194
|
}
|
|
977
1195
|
|
|
978
1196
|
this.dataIsReady$.complete()
|
|
@@ -1482,28 +1700,36 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
1482
1700
|
* }
|
|
1483
1701
|
* });
|
|
1484
1702
|
*/
|
|
1485
|
-
async createDynamicEvent(eventObj: EventPosOption) {
|
|
1486
|
-
|
|
1487
|
-
if (!eventObj.event) {
|
|
1488
|
-
// @ts-ignore
|
|
1489
|
-
eventObj = {
|
|
1490
|
-
event: eventObj
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1703
|
+
async createDynamicEvent(eventObj: EventPosOption, options: CreateDynamicEventOptions = {}): Promise<string | undefined> {
|
|
1704
|
+
eventObj = this.normalizeEventObject(eventObj);
|
|
1493
1705
|
|
|
1494
1706
|
const value = await lastValueFrom(this.hooks.callHooks("server-event-onBeforeCreated", eventObj, this));
|
|
1495
1707
|
value.filter(v => v).forEach(v => {
|
|
1496
|
-
eventObj = v
|
|
1497
|
-
})
|
|
1708
|
+
eventObj = v;
|
|
1709
|
+
});
|
|
1710
|
+
|
|
1711
|
+
const event = eventObj.event;
|
|
1712
|
+
const x = typeof eventObj.x === "number" ? eventObj.x : 0;
|
|
1713
|
+
const y = typeof eventObj.y === "number" ? eventObj.y : 0;
|
|
1498
1714
|
|
|
1499
|
-
const
|
|
1715
|
+
const requestedMode = options.mode ?? this.resolveEventMode(eventObj);
|
|
1716
|
+
const mode = this.normalizeEventMode(requestedMode);
|
|
1717
|
+
const ownerFromData = options.scenarioOwnerId ?? this.resolveScenarioOwnerId(eventObj);
|
|
1718
|
+
const scenarioOwnerId = mode === EventMode.Scenario ? ownerFromData : undefined;
|
|
1719
|
+
const effectiveMode = mode === EventMode.Scenario && scenarioOwnerId
|
|
1720
|
+
? EventMode.Scenario
|
|
1721
|
+
: EventMode.Shared;
|
|
1500
1722
|
|
|
1501
|
-
|
|
1723
|
+
if (mode === EventMode.Scenario && !scenarioOwnerId) {
|
|
1724
|
+
console.warn("Scenario event created without owner id. Falling back to shared mode.");
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
const id = this.buildRuntimeEventId(eventObj.id, effectiveMode, scenarioOwnerId);
|
|
1502
1728
|
let eventInstance: RpgPlayer;
|
|
1503
1729
|
|
|
1504
1730
|
if (this.events()[id]) {
|
|
1505
1731
|
console.warn(`Event ${id} already exists on map`);
|
|
1506
|
-
return;
|
|
1732
|
+
return undefined;
|
|
1507
1733
|
}
|
|
1508
1734
|
|
|
1509
1735
|
// Check if event is a constructor function (class)
|
|
@@ -1541,7 +1767,17 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
1541
1767
|
}
|
|
1542
1768
|
|
|
1543
1769
|
eventInstance = new DynamicEvent();
|
|
1544
|
-
if (event.name) eventInstance.name.set(event.name);
|
|
1770
|
+
if ((event as any).name) eventInstance.name.set((event as any).name);
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
(eventInstance as any).mode = effectiveMode;
|
|
1774
|
+
if (effectiveMode === EventMode.Scenario && scenarioOwnerId) {
|
|
1775
|
+
(eventInstance as any)._scenarioOwnerId = scenarioOwnerId;
|
|
1776
|
+
(eventInstance as any).scenarioOwnerId = scenarioOwnerId;
|
|
1777
|
+
}
|
|
1778
|
+
else {
|
|
1779
|
+
delete (eventInstance as any)._scenarioOwnerId;
|
|
1780
|
+
delete (eventInstance as any).scenarioOwnerId;
|
|
1545
1781
|
}
|
|
1546
1782
|
|
|
1547
1783
|
eventInstance.map = this;
|
|
@@ -1550,8 +1786,10 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
1550
1786
|
await eventInstance.teleport({ x, y });
|
|
1551
1787
|
|
|
1552
1788
|
this.events()[id] = eventInstance;
|
|
1789
|
+
this.setEventRuntimeMetadata(id, effectiveMode, scenarioOwnerId);
|
|
1553
1790
|
|
|
1554
|
-
await eventInstance.execMethod('onInit')
|
|
1791
|
+
await eventInstance.execMethod('onInit');
|
|
1792
|
+
return id;
|
|
1555
1793
|
}
|
|
1556
1794
|
|
|
1557
1795
|
/**
|
|
@@ -1642,6 +1880,10 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
1642
1880
|
return Object.values(this.events())
|
|
1643
1881
|
}
|
|
1644
1882
|
|
|
1883
|
+
getEventsForPlayer(playerOrId: string | RpgPlayer): RpgEvent[] {
|
|
1884
|
+
return this.getEvents().filter(event => this.isEventVisibleForPlayer(event, playerOrId));
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1645
1887
|
/**
|
|
1646
1888
|
* Get the first event that matches a condition
|
|
1647
1889
|
*
|
|
@@ -1714,6 +1956,22 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
1714
1956
|
* ```
|
|
1715
1957
|
*/
|
|
1716
1958
|
removeEvent(eventId: string) {
|
|
1959
|
+
const event = this.getEvent(eventId) as any;
|
|
1960
|
+
if (event) {
|
|
1961
|
+
try {
|
|
1962
|
+
event.stopMoveTo?.();
|
|
1963
|
+
}
|
|
1964
|
+
catch {
|
|
1965
|
+
// Ignore teardown race: the physics entity may already be gone.
|
|
1966
|
+
}
|
|
1967
|
+
try {
|
|
1968
|
+
event.breakRoutes?.(true);
|
|
1969
|
+
}
|
|
1970
|
+
catch {
|
|
1971
|
+
// Ignore teardown race in route manager.
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
this.clearEventRuntimeMetadata(eventId);
|
|
1717
1975
|
delete this.events()[eventId]
|
|
1718
1976
|
}
|
|
1719
1977
|
|