@rpgjs/server 5.0.0-alpha.24 → 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.
@@ -91,10 +91,69 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
91
91
  context?: Context;
92
92
  conn: MockConnection | null = null;
93
93
  touchSide: boolean = false; // Protection against map change loops
94
-
94
+
95
+ /**
96
+ * Computed signal for world X position
97
+ *
98
+ * Calculates the absolute world X position from the map's world position
99
+ * plus the player's local X position. Returns 0 if no map is assigned.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * const worldX = player.worldX();
104
+ * console.log(`Player is at world X: ${worldX}`);
105
+ * ```
106
+ */
107
+ get worldPositionX() {
108
+ return this._getComputedWorldPosition('x');
109
+ }
110
+
111
+ /**
112
+ * Computed signal for world Y position
113
+ *
114
+ * Calculates the absolute world Y position from the map's world position
115
+ * plus the player's local Y position. Returns 0 if no map is assigned.
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * const worldY = player.worldY();
120
+ * console.log(`Player is at world Y: ${worldY}`);
121
+ * ```
122
+ */
123
+ get worldPositionY() {
124
+ return this._getComputedWorldPosition('y');
125
+ }
126
+
127
+ private _worldPositionSignals = new WeakMap<any, any>();
128
+
129
+ private _getComputedWorldPosition(axis: 'x' | 'y') {
130
+ // We use a WeakMap to cache the computed signal per instance
131
+ // This ensures that if the player object is copied (e.g. in tests),
132
+ // the new instance gets its own signal bound to itself.
133
+ if (!this._worldPositionSignals) {
134
+ this._worldPositionSignals = new WeakMap();
135
+ }
136
+
137
+ const key = axis;
138
+ let signals = this._worldPositionSignals.get(this);
139
+ if (!signals) {
140
+ signals = {};
141
+ this._worldPositionSignals.set(this, signals);
142
+ }
143
+
144
+ if (!signals[key]) {
145
+ signals[key] = computed(() => {
146
+ const map = this.map as RpgMap | null;
147
+ const mapWorldPos = map ? (map[axis === 'x' ? 'worldX' : 'worldY'] ?? 0) : 0;
148
+ return mapWorldPos + (this[axis] as any)();
149
+ });
150
+ }
151
+ return signals[key];
152
+ }
153
+
95
154
  /** Internal: Shapes attached to this player */
96
155
  private _attachedShapes: Map<string, RpgShape> = new Map();
97
-
156
+
98
157
  /** Internal: Shapes where this player is currently located */
99
158
  private _inShapes: Set<RpgShape> = new Set();
100
159
  /** Last processed client input timestamp for reconciliation */
@@ -118,10 +177,10 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
118
177
  super();
119
178
  // Use type assertion to access mixin properties
120
179
  (this as any).expCurve = {
121
- basis: 30,
122
- extra: 20,
123
- accelerationA: 30,
124
- accelerationB: 30
180
+ basis: 30,
181
+ extra: 20,
182
+ accelerationA: 30,
183
+ accelerationB: 30
125
184
  };
126
185
 
127
186
  (this as any).addParameter(MAXHP, MAXHP_CURVE);
@@ -139,7 +198,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
139
198
  combineLatest([this.x.observable, this.y.observable])
140
199
  .subscribe(([x, y]) => {
141
200
  pendingUpdate = { x, y };
142
-
201
+
143
202
  // Schedule a synchronous update using queueMicrotask
144
203
  // This groups multiple rapid changes (x and y in the same tick) into a single frame
145
204
  if (!updateScheduled) {
@@ -163,7 +222,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
163
222
  }
164
223
  })
165
224
  }
166
-
225
+
167
226
  _onInit() {
168
227
  this.hooks.callHooks("server-playerProps-load", this).subscribe();
169
228
  }
@@ -177,6 +236,10 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
177
236
  return this.map
178
237
  }
179
238
 
239
+ setMap(map: RpgMap) {
240
+ this.map = map;
241
+ }
242
+
180
243
  applyFrames() {
181
244
  this._frames.set(this.frames)
182
245
  this.frames = []
@@ -185,7 +248,9 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
185
248
  async execMethod(method: string, methodData: any[] = [], target?: any) {
186
249
  let ret: any;
187
250
  if (target) {
188
- ret = await target[method](...methodData);
251
+ if (typeof target[method] === 'function') {
252
+ ret = await target[method](...methodData);
253
+ }
189
254
  }
190
255
  else {
191
256
  ret = await lastValueFrom(this.hooks
@@ -217,12 +282,12 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
217
282
  ): Promise<any | null | boolean> {
218
283
  const realMapId = 'map-' + mapId;
219
284
  const room = this.getCurrentMap();
220
-
285
+
221
286
  const canChange: boolean[] = await lastValueFrom(this.hooks.callHooks("server-player-canChangeMap", this, {
222
287
  id: mapId,
223
288
  }));
224
289
  if (canChange.some(v => v === false)) return false;
225
-
290
+
226
291
  if (positions && typeof positions === 'object') {
227
292
  this.teleport(positions)
228
293
  }
@@ -234,130 +299,81 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
234
299
  return true;
235
300
  }
236
301
 
237
- /**
238
- * Auto change map when player touches map borders
239
- *
240
- * This method checks if the player touches the current map borders
241
- * and automatically performs a change to the adjacent map if it exists.
242
- *
243
- * @param nextPosition - The next position of the player
244
- * @returns Promise<boolean> - true if a map change occurred
245
- *
246
- * @example
247
- * ```ts
248
- * // Called automatically by the movement system
249
- * const changed = await player.autoChangeMap({ x: newX, y: newY });
250
- * if (changed) {
251
- * console.log('Player changed map automatically');
252
- * }
253
- * ```
254
- */
255
- async autoChangeMap(nextPosition: { x: number; y: number }, forcedDirection?: any): Promise<boolean> {
256
- const map = this.getCurrentMap() as RpgMap; // Cast to access extended properties
257
- if (!map) return false;
258
-
259
- const worldMaps = map.getWorldMapsManager?.();
260
- let ret: boolean = false;
261
-
302
+ async autoChangeMap(nextPosition: Vector2): Promise<boolean> {
303
+ const map = this.getCurrentMap()
304
+ const worldMaps = map?.getInWorldMaps()
305
+ let ret: boolean = false
262
306
  if (worldMaps && map) {
263
- const direction = forcedDirection ?? this.getDirection();
264
- const marginLeftRight = (map.tileWidth ?? 32) / 2;
265
- const marginTopDown = (map.tileHeight ?? 32) / 2;
266
-
267
- // Current world position of the player
268
- const worldPositionX = (map.worldX ?? 0) + this.x();
269
- const worldPositionY = (map.worldY ?? 0) + this.y();
307
+ const direction = this.getDirection()
308
+ const marginLeftRight = map.tileWidth / 2
309
+ const marginTopDown = map.tileHeight / 2
270
310
 
271
- const changeMap = async (adjacentCoords: {x: number, y: number}, positionCalculator: (nextMapInfo: any) => {x: number, y: number}) => {
272
- if (this.touchSide) {
273
- return false;
311
+ const changeMap = async (adjacent, to) => {
312
+ if (this.touchSide) {
313
+ return false
314
+ }
315
+ this.touchSide = true
316
+ const [nextMap] = worldMaps.getAdjacentMaps(map, adjacent)
317
+ if (!nextMap) return false
318
+ const id = nextMap.id as string
319
+ const nextMapInfo = worldMaps.getMapInfo(id)
320
+ return !!(await this.changeMap(id, to(nextMapInfo)))
274
321
  }
275
- this.touchSide = true;
276
322
 
277
- const [nextMap] = worldMaps.getAdjacentMaps(map, adjacentCoords);
278
- if (!nextMap) {
279
- this.touchSide = false;
280
- return false;
323
+ if (nextPosition.x < marginLeftRight && direction == Direction.Left) {
324
+ ret = await changeMap({
325
+ x: map.worldX - 1,
326
+ y: this.worldPositionY() + 1
327
+ }, nextMapInfo => ({
328
+ x: (nextMapInfo.width) - this.hitbox().w - marginLeftRight,
329
+ y: map.worldY - nextMapInfo.y + nextPosition.y
330
+ }))
281
331
  }
282
-
283
- const id = nextMap.id as string;
284
- const nextMapInfo = worldMaps.getMapInfo(id);
285
- if (!nextMapInfo) {
286
- this.touchSide = false;
287
- return false;
332
+ else if (nextPosition.x > map.widthPx - this.hitbox().w - marginLeftRight && direction == Direction.Right) {
333
+ ret = await changeMap({
334
+ x: map.worldX + map.widthPx + 1,
335
+ y: this.worldPositionY() + 1
336
+ }, nextMapInfo => ({
337
+ x: marginLeftRight,
338
+ y: map.worldY - nextMapInfo.y + nextPosition.y
339
+ }))
340
+ }
341
+ else if (nextPosition.y < marginTopDown && direction == Direction.Up) {
342
+ ret = await changeMap({
343
+ x: this.worldPositionX() + 1,
344
+ y: map.worldY - 1
345
+ }, nextMapInfo => ({
346
+ x: map.worldX - nextMapInfo.x + nextPosition.x,
347
+ y: (nextMapInfo.height) - this.hitbox().h - marginTopDown,
348
+ }))
349
+ }
350
+ else if (nextPosition.y > map.heightPx - this.hitbox().h - marginTopDown && direction == Direction.Down) {
351
+ ret = await changeMap({
352
+ x: this.worldPositionX() + 1,
353
+ y: map.worldY + map.heightPx + 1
354
+ }, nextMapInfo => ({
355
+ x: map.worldX - nextMapInfo.x + nextPosition.x,
356
+ y: marginTopDown,
357
+ }))
358
+ }
359
+ else {
360
+ this.touchSide = false
288
361
  }
289
-
290
- const newPosition = positionCalculator(nextMapInfo);
291
- const success = await this.changeMap(id, newPosition);
292
-
293
- // Reset touchSide after a delay to allow the change
294
- setTimeout(() => {
295
- this.touchSide = false;
296
- }, 100);
297
-
298
- return !!success;
299
- };
300
- // Check left border
301
- if (nextPosition.x < marginLeftRight && direction === Direction.Left) {
302
- ret = await changeMap({
303
- x: (map.worldX ?? 0) - 1,
304
- y: worldPositionY
305
- }, nextMapInfo => ({
306
- x: nextMapInfo.width - (this.hitbox().w) - marginLeftRight,
307
- y: (map.worldY ?? 0) - (nextMapInfo.y ?? 0) + nextPosition.y
308
- }));
309
- }
310
- // Check right border
311
- else if (nextPosition.x > map.widthPx - this.hitbox().w - marginLeftRight && direction === Direction.Right) {
312
- ret = await changeMap({
313
- x: (map.worldX ?? 0) + map.widthPx + 1,
314
- y: worldPositionY
315
- }, nextMapInfo => ({
316
- x: marginLeftRight,
317
- y: (map.worldY ?? 0) - (nextMapInfo.y ?? 0) + nextPosition.y
318
- }));
319
- }
320
- // Check top border
321
- else if (nextPosition.y < marginTopDown && direction === Direction.Up) {
322
- ret = await changeMap({
323
- x: worldPositionX,
324
- y: (map.worldY ?? 0) - 1
325
- }, nextMapInfo => ({
326
- x: (map.worldX ?? 0) - (nextMapInfo.x ?? 0) + nextPosition.x,
327
- y: nextMapInfo.height - this.hitbox().h - marginTopDown
328
- }));
329
- }
330
- // Check bottom border
331
- else if (nextPosition.y > map.heightPx - this.hitbox().h - marginTopDown && direction === Direction.Down) {
332
- ret = await changeMap({
333
- x: worldPositionX,
334
- y: (map.worldY ?? 0) + map.heightPx + 1
335
- }, nextMapInfo => ({
336
- x: (map.worldX ?? 0) - (nextMapInfo.x ?? 0) + nextPosition.x,
337
- y: marginTopDown
338
- }));
339
- }
340
- else {
341
- this.touchSide = false;
342
- }
343
362
  }
344
-
345
- return ret;
346
- }
363
+ return ret
364
+ }
347
365
 
348
366
  async teleport(positions: { x: number; y: number }) {
349
367
  if (!this.map) return false;
350
- if (this.map.physic) {
368
+ if (this.map && this.map.physic) {
351
369
  // Skip collision check for teleportation (allow teleporting through walls)
352
370
  const entity = this.map.physic.getEntityByUUID(this.id);
353
371
  if (entity) {
354
372
  this.map.physic.teleport(entity, { x: positions.x, y: positions.y });
355
373
  }
356
374
  }
357
- else {
358
- this.x.set(positions.x)
359
- this.y.set(positions.y)
360
- }
375
+ this.x.set(positions.x)
376
+ this.y.set(positions.y)
361
377
  // Wait for the frame to be added before applying frames
362
378
  // This ensures the frame is added before applyFrames() is called
363
379
  queueMicrotask(() => {
@@ -450,8 +466,9 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
450
466
  }
451
467
 
452
468
  databaseById(id: string) {
453
- const map = this.getCurrentMap();
454
- if (!map) return;
469
+ // Use this.map directly to support both RpgMap and LobbyRoom
470
+ const map = this.map as any;
471
+ if (!map || !map.database) return;
455
472
  const data = map.database()[id];
456
473
  if (!data)
457
474
  throw new Error(
@@ -514,7 +531,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
514
531
  // Handle overloaded signature: attachShape(options) or attachShape(id, options)
515
532
  let zoneId: string;
516
533
  let shapeOptions: AttachShapeOptions;
517
-
534
+
518
535
  if (typeof idOrOptions === 'string') {
519
536
  zoneId = idOrOptions;
520
537
  if (!options) {
@@ -552,7 +569,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
552
569
  if (shapeOptions.positioning) {
553
570
  const playerWidth = playerEntity.width || playerEntity.radius * 2 || 32;
554
571
  const playerHeight = playerEntity.height || playerEntity.radius * 2 || 32;
555
-
572
+
556
573
  switch (shapeOptions.positioning) {
557
574
  case 'top':
558
575
  offset = new Vector2(0, -playerHeight / 2);
@@ -575,7 +592,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
575
592
 
576
593
  // Get zone manager and create attached zone
577
594
  const zoneManager = map.physic.getZoneManager();
578
-
595
+
579
596
  // Convert direction from Direction enum to string if needed
580
597
  // Direction enum values are already strings ("up", "down", "left", "right")
581
598
  let direction: 'up' | 'down' | 'left' | 'right' = 'down';
@@ -616,7 +633,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
616
633
  entities.forEach((entity) => {
617
634
  const event = map.getEvent<RpgEvent>(entity.uuid);
618
635
  const player = map.getPlayer(entity.uuid);
619
-
636
+
620
637
  if (event) {
621
638
  event.execMethod("onInShape", [shape, this]);
622
639
  // Track that this event is in the shape
@@ -637,7 +654,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
637
654
  entities.forEach((entity) => {
638
655
  const event = map.getEvent<RpgEvent>(entity.uuid);
639
656
  const player = map.getPlayer(entity.uuid);
640
-
657
+
641
658
  if (event) {
642
659
  event.execMethod("onOutShape", [shape, this]);
643
660
  // Remove from tracking
@@ -674,10 +691,10 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
674
691
  // Store mapping from zoneId to physicZoneId for future reference
675
692
  (this as any)._zoneIdMap = (this as any)._zoneIdMap || new Map();
676
693
  (this as any)._zoneIdMap.set(zoneId, physicZoneId);
677
-
694
+
678
695
  // Store the shape
679
696
  this._attachedShapes.set(zoneId, shape);
680
-
697
+
681
698
  // Update shape position when player moves
682
699
  const updateShapePosition = () => {
683
700
  const currentEntity = map.physic.getEntityByUUID(this.id);
@@ -688,7 +705,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
688
705
  }
689
706
  }
690
707
  };
691
-
708
+
692
709
  // Listen to position changes to update shape position
693
710
  playerEntity.onPositionChange(() => {
694
711
  updateShapePosition();
@@ -696,7 +713,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
696
713
 
697
714
  return shape;
698
715
  }
699
-
716
+
700
717
  /**
701
718
  * Get all shapes attached to this player
702
719
  *
@@ -716,7 +733,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
716
733
  getShapes(): RpgShape[] {
717
734
  return Array.from(this._attachedShapes.values());
718
735
  }
719
-
736
+
720
737
  /**
721
738
  * Get all shapes where this player is currently located
722
739
  *
@@ -1063,13 +1080,13 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
1063
1080
  if (typeof height !== 'number' || height <= 0) {
1064
1081
  throw new Error('setHitbox: height must be a positive number');
1065
1082
  }
1066
-
1083
+
1067
1084
  // Update hitbox signal
1068
1085
  this.hitbox.set({
1069
1086
  w: width,
1070
1087
  h: height,
1071
1088
  });
1072
-
1089
+
1073
1090
  // Update physics entity if map exists
1074
1091
  const map = this.getCurrentMap();
1075
1092
  if (map && map.physic) {
@@ -1118,18 +1135,17 @@ export class RpgEvent extends RpgPlayer {
1118
1135
  * Extends the RpgPlayer class with additional interfaces from mixins.
1119
1136
  * This provides proper TypeScript support for all mixin methods and properties.
1120
1137
  */
1121
- export interface RpgPlayer extends
1122
- IVariableManager,
1123
- IMoveManager,
1124
- IGoldManager,
1125
- IComponentManager,
1126
- IGuiManager,
1127
- IItemManager,
1128
- IEffectManager,
1129
- IParameterManager,
1130
- IElementManager,
1131
- ISkillManager,
1132
- IBattleManager,
1133
- IClassManager,
1134
- IStateManager
1135
- {}
1138
+ export interface RpgPlayer extends
1139
+ IVariableManager,
1140
+ IMoveManager,
1141
+ IGoldManager,
1142
+ IComponentManager,
1143
+ IGuiManager,
1144
+ IItemManager,
1145
+ IEffectManager,
1146
+ IParameterManager,
1147
+ IElementManager,
1148
+ ISkillManager,
1149
+ IBattleManager,
1150
+ IClassManager,
1151
+ IStateManager { }
package/src/module.ts CHANGED
@@ -127,6 +127,19 @@ export function provideServerModules(modules: RpgServerModule[]): FactoryProvide
127
127
  }
128
128
  };
129
129
  }
130
+ if (module.database && typeof module.database === 'object') {
131
+ const database = {...module.database};
132
+ module = {
133
+ ...module,
134
+ databaseHooks: {
135
+ load: (engine: RpgMap) => {
136
+ for (const key in database) {
137
+ engine.addInDatabase(key, database[key]);
138
+ }
139
+ },
140
+ }
141
+ };
142
+ }
130
143
  return module;
131
144
  })
132
145
  return modules
@@ -0,0 +1,120 @@
1
+ import { signal } from "@signe/reactive";
2
+
3
+ /**
4
+ * Base class for rooms that need database functionality
5
+ *
6
+ * This class provides common database management functionality that is shared
7
+ * between RpgMap and LobbyRoom. It includes methods for adding and managing
8
+ * items, classes, and other game data in the room's database.
9
+ *
10
+ * ## Architecture
11
+ *
12
+ * Both RpgMap and LobbyRoom need to store game entities (items, classes, skills, etc.)
13
+ * in a database. This base class provides the common implementation to avoid code duplication.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * class MyCustomRoom extends BaseRoom {
18
+ * // Your custom room implementation
19
+ * }
20
+ * ```
21
+ */
22
+ export abstract class BaseRoom {
23
+ /**
24
+ * Signal containing the room's database of items, classes, and other game data
25
+ *
26
+ * This database can be dynamically populated using `addInDatabase()` and
27
+ * `removeInDatabase()` methods. It's used to store game entities like items,
28
+ * classes, skills, etc. that are available in this room.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * // Add data to database
33
+ * room.addInDatabase('Potion', PotionClass);
34
+ *
35
+ * // Access database
36
+ * const potion = room.database()['Potion'];
37
+ * ```
38
+ */
39
+ database = signal({});
40
+
41
+ /**
42
+ * Add data to the room's database
43
+ *
44
+ * Adds an item, class, or other game entity to the room's database.
45
+ * If the ID already exists and `force` is not enabled, the addition is ignored.
46
+ *
47
+ * ## Architecture
48
+ *
49
+ * This method is used by the item management system to store item definitions
50
+ * in the room's database. When a player adds an item, the system first checks
51
+ * if the item exists in the database, and if not, adds it using this method.
52
+ *
53
+ * @param id - Unique identifier for the data
54
+ * @param data - The data to add (can be a class, object, etc.)
55
+ * @param options - Optional configuration
56
+ * @param options.force - If true, overwrites existing data with the same ID
57
+ * @returns `true` if data was added, `false` if it was ignored (ID already exists)
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * // Add a class to the database
62
+ * room.addInDatabase('Potion', PotionClass);
63
+ *
64
+ * // Add an item object to the database
65
+ * room.addInDatabase('custom-item', {
66
+ * name: 'Custom Item',
67
+ * price: 100
68
+ * });
69
+ *
70
+ * // Force overwrite existing data
71
+ * room.addInDatabase('Potion', UpdatedPotionClass, { force: true });
72
+ * ```
73
+ */
74
+ addInDatabase(id: string, data: any, options?: { force?: boolean }): boolean {
75
+ const database = this.database();
76
+
77
+ // Check if ID already exists
78
+ if (database[id] !== undefined && !options?.force) {
79
+ // Ignore the addition if ID exists and force is not enabled
80
+ return false;
81
+ }
82
+
83
+ // Add or overwrite the data
84
+ database[id] = data;
85
+ this.database.set(database);
86
+ return true;
87
+ }
88
+
89
+ /**
90
+ * Remove data from the room's database
91
+ *
92
+ * This method allows you to remove items or data from the room's database.
93
+ *
94
+ * @param id - Unique identifier of the data to remove
95
+ * @returns `true` if data was removed, `false` if ID didn't exist
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * // Remove an item from the database
100
+ * room.removeInDatabase('Potion');
101
+ *
102
+ * // Check if removal was successful
103
+ * const removed = room.removeInDatabase('custom-item');
104
+ * if (removed) {
105
+ * console.log('Item removed successfully');
106
+ * }
107
+ * ```
108
+ */
109
+ removeInDatabase(id: string): boolean {
110
+ const database = this.database();
111
+
112
+ if (database[id] === undefined) {
113
+ return false;
114
+ }
115
+
116
+ delete database[id];
117
+ this.database.set(database);
118
+ return true;
119
+ }
120
+ }
@@ -5,12 +5,22 @@ import { context } from "../core/context";
5
5
  import { users } from "@signe/sync";
6
6
  import { signal } from "@signe/reactive";
7
7
  import { RpgPlayer } from "../Player/Player";
8
+ import { BaseRoom } from "./BaseRoom";
8
9
 
9
10
  @Room({
10
11
  path: "lobby-{id}",
11
12
  })
12
- export class LobbyRoom {
13
+ export class LobbyRoom extends BaseRoom {
13
14
  @users(RpgPlayer) players = signal({});
15
+ autoSync: boolean = true;
16
+
17
+ constructor(room) {
18
+ super();
19
+ const isTest = room.env.TEST === 'true' ? true : false;
20
+ if (isTest) {
21
+ this.autoSync = false;
22
+ }
23
+ }
14
24
 
15
25
  onJoin(player: RpgPlayer, conn: MockConnection) {
16
26
  player.map = this;