@rpgjs/server 5.0.0-alpha.25 → 5.0.0-alpha.26

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/src/rooms/map.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Action, MockConnection, Request, Room, RoomOnJoin } from "@signe/room";
2
- import { Hooks, IceMovement, ModulesToken, ProjectileMovement, ProjectileType, RpgCommonMap, Direction, RpgCommonPlayer, RpgShape } from "@rpgjs/common";
1
+ import { Action, MockConnection, Request, Room, RoomMethods, RoomOnJoin } from "@signe/room";
2
+ import { Hooks, IceMovement, ModulesToken, ProjectileMovement, ProjectileType, RpgCommonMap, Direction, RpgCommonPlayer, RpgShape, findModules } from "@rpgjs/common";
3
3
  import { WorldMapsManager, type WorldMapConfig } from "@rpgjs/common";
4
4
  import { RpgPlayer, RpgEvent } from "../Player/Player";
5
5
  import { generateShortUUID, sync, type, users } from "@signe/sync";
@@ -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 { BaseRoom } from "./BaseRoom";
16
17
 
17
18
  /**
18
19
  * Interface for input controls configuration
@@ -113,7 +114,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
113
114
  * ```
114
115
  */
115
116
  @users(RpgPlayer) players = signal({});
116
-
117
+
117
118
  /**
118
119
  * Synchronized signal containing all events (NPCs, objects) on the map
119
120
  *
@@ -130,7 +131,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
130
131
  * ```
131
132
  */
132
133
  @sync(RpgPlayer) events = signal({});
133
-
134
+
134
135
  /**
135
136
  * Signal containing the map's database of items, classes, and other game data
136
137
  *
@@ -148,7 +149,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
148
149
  * ```
149
150
  */
150
151
  database = signal({});
151
-
152
+
152
153
  /**
153
154
  * Array of map configurations - can contain MapOptions objects or instances of map classes
154
155
  *
@@ -156,7 +157,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
156
157
  * It's populated when the map is loaded via `updateMap()`.
157
158
  */
158
159
  maps: (MapOptions | any)[] = []
159
-
160
+
160
161
  /**
161
162
  * Array of sound IDs to play when players join the map
162
163
  *
@@ -170,7 +171,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
170
171
  * ```
171
172
  */
172
173
  sounds: string[] = []
173
-
174
+
174
175
  /**
175
176
  * BehaviorSubject that completes when the map data is ready
176
177
  *
@@ -186,7 +187,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
186
187
  * ```
187
188
  */
188
189
  dataIsReady$ = new BehaviorSubject<void>(undefined);
189
-
190
+
190
191
  /**
191
192
  * Global configuration object for the map
192
193
  *
@@ -194,7 +195,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
194
195
  * It's populated from the map data when `updateMap()` is called.
195
196
  */
196
197
  globalConfig: any = {}
197
-
198
+
198
199
  /**
199
200
  * Damage formulas configuration for the map
200
201
  *
@@ -212,12 +213,23 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
212
213
  /** Enable/disable automatic tick processing (useful for unit tests) */
213
214
  private _autoTickEnabled: boolean = true;
214
215
 
215
- constructor() {
216
+ autoSync: boolean = true;
217
+
218
+ constructor(room) {
216
219
  super();
217
220
  this.hooks.callHooks("server-map-onStart", this).subscribe();
218
- this.throttleSync = this.isStandalone ? 0 : 50; // Reduced from 100ms to 50ms for better responsiveness
219
- this.throttleStorage = this.isStandalone ? 0 : 1000;
220
- this.sessionExpiryTime = 1000 * 60 * 5; //5 minutes
221
+ const isTest = room.env.TEST === 'true' ? true : false;
222
+ if (isTest) {
223
+ this.autoSync = false;
224
+ this.setAutoTick(false);
225
+ this.throttleSync = 0;
226
+ this.throttleStorage = 0;
227
+ }
228
+ else {
229
+ this.throttleSync = this.isStandalone ? 1 : 50
230
+ this.throttleStorage = this.isStandalone ? 1 : 50
231
+ };
232
+ this.sessionExpiryTime = 1000 * 60 * 5;
221
233
  this.setupCollisionDetection();
222
234
  if (this._autoTickEnabled) {
223
235
  this.loop();
@@ -291,7 +303,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
291
303
  // One of the entities is a shape
292
304
  const shape = shapeA || shapeB;
293
305
  const otherEntity = shapeA ? entityB : entityA;
294
-
306
+
295
307
  if (shape) {
296
308
  const shapeKey = `${otherEntity.uuid}-${shape.name}`;
297
309
  if (!activeShapeCollisions.has(shapeKey)) {
@@ -327,7 +339,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
327
339
  if (event) {
328
340
  // Mark this collision as processed
329
341
  activeCollisions.add(collisionKey);
330
-
342
+
331
343
  // Trigger the onPlayerTouch hook on the event
332
344
  event.execMethod('onPlayerTouch', [player]);
333
345
  }
@@ -350,7 +362,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
350
362
  // One of the entities is a shape
351
363
  const shape = shapeA || shapeB;
352
364
  const otherEntity = shapeA ? entityB : entityA;
353
-
365
+
354
366
  if (shape) {
355
367
  const shapeKey = `${otherEntity.uuid}-${shape.name}`;
356
368
  if (activeShapeCollisions.has(shapeKey)) {
@@ -467,7 +479,11 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
467
479
  * ```
468
480
  */
469
481
  onJoin(player: RpgPlayer, conn: MockConnection) {
470
- player.map = this;
482
+ if (player.setMap) {
483
+ player.setMap(this);
484
+ } else {
485
+ player.map = this;
486
+ }
471
487
  player.context = context;
472
488
  player.conn = conn;
473
489
  player._onInit()
@@ -477,17 +493,17 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
477
493
  if ((this as any).stopAllSoundsBeforeJoin) {
478
494
  player.stopAllSounds();
479
495
  }
480
-
481
- this.sounds.forEach(sound => player.playSound(sound,{ loop: true }));
482
-
496
+
497
+ this.sounds.forEach(sound => player.playSound(sound, { loop: true }));
498
+
483
499
  // Execute global map hooks (from RpgServer.map)
484
500
  await lastValueFrom(this.hooks.callHooks("server-map-onJoin", player, this));
485
-
501
+
486
502
  // // Execute map-specific hooks (from @MapData or MapOptions)
487
503
  if (typeof (this as any)._onJoin === 'function') {
488
504
  await (this as any)._onJoin(player);
489
505
  }
490
-
506
+
491
507
  // Execute player hooks
492
508
  await lastValueFrom(this.hooks.callHooks("server-player-onJoinMap", player, this));
493
509
  })
@@ -520,12 +536,12 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
520
536
  async onLeave(player: RpgPlayer, conn: MockConnection) {
521
537
  // Execute global map hooks (from RpgServer.map)
522
538
  await lastValueFrom(this.hooks.callHooks("server-map-onLeave", player, this));
523
-
539
+
524
540
  // Execute map-specific hooks (from @MapData or MapOptions)
525
541
  if (typeof (this as any)._onLeave === 'function') {
526
542
  await (this as any)._onLeave(player);
527
543
  }
528
-
544
+
529
545
  // Execute player hooks
530
546
  await lastValueFrom(this.hooks.callHooks("server-player-onLeaveMap", player, this));
531
547
  player.pendingInputs = [];
@@ -549,89 +565,6 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
549
565
  return inject<Hooks>(context, ModulesToken);
550
566
  }
551
567
 
552
- /**
553
- * Get the width of the map in pixels
554
- *
555
- * @returns The width of the map in pixels, or 0 if not loaded
556
- *
557
- * @example
558
- * ```ts
559
- * const width = map.widthPx;
560
- * console.log(`Map width: ${width}px`);
561
- * ```
562
- */
563
- get widthPx(): number {
564
- return this.data()?.width ?? 0
565
- }
566
-
567
- /**
568
- * Get the height of the map in pixels
569
- *
570
- * @returns The height of the map in pixels, or 0 if not loaded
571
- *
572
- * @example
573
- * ```ts
574
- * const height = map.heightPx;
575
- * console.log(`Map height: ${height}px`);
576
- * ```
577
- */
578
- get heightPx(): number {
579
- return this.data()?.height ?? 0
580
- }
581
-
582
- /**
583
- * Get the unique identifier of the map
584
- *
585
- * @returns The map ID, or empty string if not loaded
586
- *
587
- * @example
588
- * ```ts
589
- * const mapId = map.id;
590
- * console.log(`Current map: ${mapId}`);
591
- * ```
592
- */
593
- get id(): string {
594
- return this.data()?.id ?? ''
595
- }
596
-
597
- /**
598
- * Get the X position of this map in the world coordinate system
599
- *
600
- * This is used when maps are part of a larger world map. The world position
601
- * indicates where this map is located relative to other maps.
602
- *
603
- * @returns The X position in world coordinates, or 0 if not in a world
604
- *
605
- * @example
606
- * ```ts
607
- * const worldX = map.worldX;
608
- * console.log(`Map is at world position (${worldX}, ${map.worldY})`);
609
- * ```
610
- */
611
- get worldX(): number {
612
- const worldMaps = this.getWorldMapsManager?.();
613
- return worldMaps?.getMapInfo(this.id)?.worldX ?? 0
614
- }
615
-
616
- /**
617
- * Get the Y position of this map in the world coordinate system
618
- *
619
- * This is used when maps are part of a larger world map. The world position
620
- * indicates where this map is located relative to other maps.
621
- *
622
- * @returns The Y position in world coordinates, or 0 if not in a world
623
- *
624
- * @example
625
- * ```ts
626
- * const worldY = map.worldY;
627
- * console.log(`Map is at world position (${map.worldX}, ${worldY})`);
628
- * ```
629
- */
630
- get worldY(): number {
631
- const worldMaps = this.getWorldMapsManager?.();
632
- return worldMaps?.getMapInfo(this.id)?.worldY ?? 0
633
- }
634
-
635
568
  /**
636
569
  * Handle GUI interaction from a player
637
570
  *
@@ -649,7 +582,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
649
582
  */
650
583
  @Action('gui.interaction')
651
584
  guiInteraction(player: RpgPlayer, value) {
652
- //this.hooks.callHooks("server-player-guiInteraction", player, value);
585
+ this.hooks.callHooks("server-player-guiInteraction", player, value);
653
586
  player.syncChanges();
654
587
  }
655
588
 
@@ -791,6 +724,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
791
724
  }
792
725
  await lastValueFrom(this.hooks.callHooks("server-maps-load", this))
793
726
  await lastValueFrom(this.hooks.callHooks("server-worldMaps-load", this))
727
+ await lastValueFrom(this.hooks.callHooks("server-databaseHooks-load", this))
794
728
 
795
729
  map.events = map.events ?? []
796
730
 
@@ -811,7 +745,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
811
745
  else {
812
746
  this.sounds = map.sounds ?? []
813
747
  }
814
-
748
+
815
749
  // Attach map-specific hooks from MapOptions or @MapData
816
750
  if (mapFound?.onLoad) {
817
751
  (this as any)._onLoad = mapFound.onLoad;
@@ -836,15 +770,15 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
836
770
  }
837
771
 
838
772
  this.dataIsReady$.complete()
839
-
773
+
840
774
  // Execute global map hooks (from RpgServer.map)
841
775
  await lastValueFrom(this.hooks.callHooks("server-map-onLoad", this))
842
-
776
+
843
777
  // Execute map-specific hooks (from @MapData or MapOptions)
844
778
  if (typeof (this as any)._onLoad === 'function') {
845
779
  await (this as any)._onLoad();
846
780
  }
847
-
781
+
848
782
  // TODO: Update map
849
783
  }
850
784
 
@@ -1079,7 +1013,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
1079
1013
  if (this._inputLoopSubscription) {
1080
1014
  this._inputLoopSubscription.unsubscribe();
1081
1015
  }
1082
-
1016
+
1083
1017
  this._inputLoopSubscription = this.tick$.pipe(
1084
1018
  throttleTime(50) // Throttle to 50ms for input processing
1085
1019
  ).subscribe(async ({ timestamp }) => {
@@ -1232,8 +1166,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
1232
1166
  /**
1233
1167
  * Add data to the map's database
1234
1168
  *
1235
- * This method allows you to dynamically add items, classes, or any data to the map's database.
1236
- * By default, if an ID already exists, the operation is ignored to prevent overwriting existing data.
1169
+ * This method delegates to BaseRoom's implementation to avoid code duplication.
1237
1170
  *
1238
1171
  * @param id - Unique identifier for the data
1239
1172
  * @param data - The data to store (can be a class, object, or any value)
@@ -1257,23 +1190,13 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
1257
1190
  * ```
1258
1191
  */
1259
1192
  addInDatabase(id: string, data: any, options?: { force?: boolean }): boolean {
1260
- const database = this.database();
1261
-
1262
- // Check if ID already exists
1263
- if (database[id] !== undefined && !options?.force) {
1264
- // Ignore the addition if ID exists and force is not enabled
1265
- return false;
1266
- }
1267
-
1268
- // Add or overwrite the data
1269
- database[id] = data;
1270
- return true;
1193
+ return BaseRoom.prototype.addInDatabase.call(this, id, data, options);
1271
1194
  }
1272
1195
 
1273
1196
  /**
1274
1197
  * Remove data from the map's database
1275
1198
  *
1276
- * This method allows you to remove items or data from the map's database.
1199
+ * This method delegates to BaseRoom's implementation to avoid code duplication.
1277
1200
  *
1278
1201
  * @param id - Unique identifier of the data to remove
1279
1202
  * @returns true if data was removed, false if ID didn't exist
@@ -1291,16 +1214,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
1291
1214
  * ```
1292
1215
  */
1293
1216
  removeInDatabase(id: string): boolean {
1294
- const database = this.database();
1295
-
1296
- // Check if ID exists
1297
- if (database[id] === undefined) {
1298
- return false;
1299
- }
1300
-
1301
- // Remove the data
1302
- delete database[id];
1303
- return true;
1217
+ return BaseRoom.prototype.removeInDatabase.call(this, id);
1304
1218
  }
1305
1219
 
1306
1220
  /**
@@ -1408,7 +1322,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
1408
1322
 
1409
1323
  eventInstance.x.set(x);
1410
1324
  eventInstance.y.set(y);
1411
-
1325
+
1412
1326
  this.events()[id] = eventInstance;
1413
1327
 
1414
1328
  await eventInstance.execMethod('onInit')
@@ -1708,6 +1622,20 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
1708
1622
  }
1709
1623
  }
1710
1624
 
1625
+ /**
1626
+ * Apply sync to the client
1627
+ *
1628
+ * This method applies sync to the client by calling the `$applySync()` method.
1629
+ *
1630
+ * @example
1631
+ * ```ts
1632
+ * map.applySyncToClient();
1633
+ * ```
1634
+ */
1635
+ applySyncToClient() {
1636
+ this.$applySync();
1637
+ }
1638
+
1711
1639
  /**
1712
1640
  * Create a shape dynamically on the map
1713
1641
  *
@@ -1784,7 +1712,7 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
1784
1712
  properties?: Record<string, any>;
1785
1713
  }): RpgShape {
1786
1714
  const { x, y, width, height } = obj;
1787
-
1715
+
1788
1716
  // Validate required parameters
1789
1717
  if (typeof x !== 'number' || typeof y !== 'number') {
1790
1718
  throw new Error('Shape x and y must be numbers');
@@ -2110,8 +2038,4 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
2110
2038
  }
2111
2039
  }
2112
2040
 
2113
- export interface RpgMap {
2114
- $send: (conn: MockConnection, data: any) => void;
2115
- $broadcast: (data: any) => void;
2116
- $sessionTransfer: (userOrPublicId: any | string, targetRoomId: string) => void;
2117
- }
2041
+ export interface RpgMap extends RoomMethods { }