@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.
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Base class for rooms that need database functionality
3
+ *
4
+ * This class provides common database management functionality that is shared
5
+ * between RpgMap and LobbyRoom. It includes methods for adding and managing
6
+ * items, classes, and other game data in the room's database.
7
+ *
8
+ * ## Architecture
9
+ *
10
+ * Both RpgMap and LobbyRoom need to store game entities (items, classes, skills, etc.)
11
+ * in a database. This base class provides the common implementation to avoid code duplication.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * class MyCustomRoom extends BaseRoom {
16
+ * // Your custom room implementation
17
+ * }
18
+ * ```
19
+ */
20
+ export declare abstract class BaseRoom {
21
+ /**
22
+ * Signal containing the room's database of items, classes, and other game data
23
+ *
24
+ * This database can be dynamically populated using `addInDatabase()` and
25
+ * `removeInDatabase()` methods. It's used to store game entities like items,
26
+ * classes, skills, etc. that are available in this room.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * // Add data to database
31
+ * room.addInDatabase('Potion', PotionClass);
32
+ *
33
+ * // Access database
34
+ * const potion = room.database()['Potion'];
35
+ * ```
36
+ */
37
+ database: import('@signe/reactive').WritableObjectSignal<{}>;
38
+ /**
39
+ * Add data to the room's database
40
+ *
41
+ * Adds an item, class, or other game entity to the room's database.
42
+ * If the ID already exists and `force` is not enabled, the addition is ignored.
43
+ *
44
+ * ## Architecture
45
+ *
46
+ * This method is used by the item management system to store item definitions
47
+ * in the room's database. When a player adds an item, the system first checks
48
+ * if the item exists in the database, and if not, adds it using this method.
49
+ *
50
+ * @param id - Unique identifier for the data
51
+ * @param data - The data to add (can be a class, object, etc.)
52
+ * @param options - Optional configuration
53
+ * @param options.force - If true, overwrites existing data with the same ID
54
+ * @returns `true` if data was added, `false` if it was ignored (ID already exists)
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * // Add a class to the database
59
+ * room.addInDatabase('Potion', PotionClass);
60
+ *
61
+ * // Add an item object to the database
62
+ * room.addInDatabase('custom-item', {
63
+ * name: 'Custom Item',
64
+ * price: 100
65
+ * });
66
+ *
67
+ * // Force overwrite existing data
68
+ * room.addInDatabase('Potion', UpdatedPotionClass, { force: true });
69
+ * ```
70
+ */
71
+ addInDatabase(id: string, data: any, options?: {
72
+ force?: boolean;
73
+ }): boolean;
74
+ /**
75
+ * Remove data from the room's database
76
+ *
77
+ * This method allows you to remove items or data from the room's database.
78
+ *
79
+ * @param id - Unique identifier of the data to remove
80
+ * @returns `true` if data was removed, `false` if ID didn't exist
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * // Remove an item from the database
85
+ * room.removeInDatabase('Potion');
86
+ *
87
+ * // Check if removal was successful
88
+ * const removed = room.removeInDatabase('custom-item');
89
+ * if (removed) {
90
+ * console.log('Item removed successfully');
91
+ * }
92
+ * ```
93
+ */
94
+ removeInDatabase(id: string): boolean;
95
+ }
@@ -1,6 +1,9 @@
1
1
  import { MockConnection } from '@signe/room';
2
2
  import { RpgPlayer } from '../Player/Player';
3
- export declare class LobbyRoom {
3
+ import { BaseRoom } from './BaseRoom';
4
+ export declare class LobbyRoom extends BaseRoom {
4
5
  players: import('@signe/reactive').WritableObjectSignal<{}>;
6
+ autoSync: boolean;
7
+ constructor(room: any);
5
8
  onJoin(player: RpgPlayer, conn: MockConnection): void;
6
9
  }
@@ -1,4 +1,4 @@
1
- import { MockConnection, RoomOnJoin } from '@signe/room';
1
+ import { MockConnection, RoomMethods, RoomOnJoin } from '@signe/room';
2
2
  import { Hooks, RpgCommonMap, RpgShape, WorldMapsManager, WorldMapConfig } from '../../../common/src';
3
3
  import { RpgPlayer, RpgEvent } from '../Player/Player';
4
4
  import { BehaviorSubject } from 'rxjs';
@@ -166,7 +166,8 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
166
166
  private _inputLoopSubscription?;
167
167
  /** Enable/disable automatic tick processing (useful for unit tests) */
168
168
  private _autoTickEnabled;
169
- constructor();
169
+ autoSync: boolean;
170
+ constructor(room: any);
170
171
  /**
171
172
  * Setup collision detection between players, events, and shapes
172
173
  *
@@ -300,72 +301,6 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
300
301
  * ```
301
302
  */
302
303
  get hooks(): Hooks;
303
- /**
304
- * Get the width of the map in pixels
305
- *
306
- * @returns The width of the map in pixels, or 0 if not loaded
307
- *
308
- * @example
309
- * ```ts
310
- * const width = map.widthPx;
311
- * console.log(`Map width: ${width}px`);
312
- * ```
313
- */
314
- get widthPx(): number;
315
- /**
316
- * Get the height of the map in pixels
317
- *
318
- * @returns The height of the map in pixels, or 0 if not loaded
319
- *
320
- * @example
321
- * ```ts
322
- * const height = map.heightPx;
323
- * console.log(`Map height: ${height}px`);
324
- * ```
325
- */
326
- get heightPx(): number;
327
- /**
328
- * Get the unique identifier of the map
329
- *
330
- * @returns The map ID, or empty string if not loaded
331
- *
332
- * @example
333
- * ```ts
334
- * const mapId = map.id;
335
- * console.log(`Current map: ${mapId}`);
336
- * ```
337
- */
338
- get id(): string;
339
- /**
340
- * Get the X position of this map in the world coordinate system
341
- *
342
- * This is used when maps are part of a larger world map. The world position
343
- * indicates where this map is located relative to other maps.
344
- *
345
- * @returns The X position in world coordinates, or 0 if not in a world
346
- *
347
- * @example
348
- * ```ts
349
- * const worldX = map.worldX;
350
- * console.log(`Map is at world position (${worldX}, ${map.worldY})`);
351
- * ```
352
- */
353
- get worldX(): number;
354
- /**
355
- * Get the Y position of this map in the world coordinate system
356
- *
357
- * This is used when maps are part of a larger world map. The world position
358
- * indicates where this map is located relative to other maps.
359
- *
360
- * @returns The Y position in world coordinates, or 0 if not in a world
361
- *
362
- * @example
363
- * ```ts
364
- * const worldY = map.worldY;
365
- * console.log(`Map is at world position (${map.worldX}, ${worldY})`);
366
- * ```
367
- */
368
- get worldY(): number;
369
304
  /**
370
305
  * Handle GUI interaction from a player
371
306
  *
@@ -670,8 +605,7 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
670
605
  /**
671
606
  * Add data to the map's database
672
607
  *
673
- * This method allows you to dynamically add items, classes, or any data to the map's database.
674
- * By default, if an ID already exists, the operation is ignored to prevent overwriting existing data.
608
+ * This method delegates to BaseRoom's implementation to avoid code duplication.
675
609
  *
676
610
  * @param id - Unique identifier for the data
677
611
  * @param data - The data to store (can be a class, object, or any value)
@@ -700,7 +634,7 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
700
634
  /**
701
635
  * Remove data from the map's database
702
636
  *
703
- * This method allows you to remove items or data from the map's database.
637
+ * This method delegates to BaseRoom's implementation to avoid code duplication.
704
638
  *
705
639
  * @param id - Unique identifier of the data to remove
706
640
  * @returns true if data was removed, false if ID didn't exist
@@ -1009,6 +943,17 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
1009
943
  * ```
1010
944
  */
1011
945
  setSync(schema: Record<string, any>): void;
946
+ /**
947
+ * Apply sync to the client
948
+ *
949
+ * This method applies sync to the client by calling the `$applySync()` method.
950
+ *
951
+ * @example
952
+ * ```ts
953
+ * map.applySyncToClient();
954
+ * ```
955
+ */
956
+ applySyncToClient(): void;
1012
957
  /**
1013
958
  * Create a shape dynamically on the map
1014
959
  *
@@ -1289,8 +1234,5 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
1289
1234
  */
1290
1235
  clear(): void;
1291
1236
  }
1292
- export interface RpgMap {
1293
- $send: (conn: MockConnection, data: any) => void;
1294
- $broadcast: (data: any) => void;
1295
- $sessionTransfer: (userOrPublicId: any | string, targetRoomId: string) => void;
1237
+ export interface RpgMap extends RoomMethods {
1296
1238
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpgjs/server",
3
- "version": "5.0.0-alpha.25",
3
+ "version": "5.0.0-alpha.26",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "publishConfig": {
@@ -11,14 +11,14 @@
11
11
  "license": "MIT",
12
12
  "description": "",
13
13
  "dependencies": {
14
- "@rpgjs/common": "5.0.0-alpha.25",
15
- "@rpgjs/physic": "5.0.0-alpha.25",
16
- "@rpgjs/testing": "5.0.0-alpha.25",
14
+ "@rpgjs/common": "5.0.0-alpha.26",
15
+ "@rpgjs/physic": "5.0.0-alpha.26",
16
+ "@rpgjs/testing": "5.0.0-alpha.26",
17
17
  "@rpgjs/database": "^4.3.0",
18
- "@signe/di": "^2.6.0",
19
- "@signe/reactive": "^2.6.0",
20
- "@signe/room": "^2.6.0",
21
- "@signe/sync": "^2.6.0",
18
+ "@signe/di": "^2.7.2",
19
+ "@signe/reactive": "^2.7.2",
20
+ "@signe/room": "^2.7.2",
21
+ "@signe/sync": "^2.7.2",
22
22
  "rxjs": "^7.8.2",
23
23
  "zod": "^4.1.13"
24
24
  },
@@ -206,8 +206,10 @@ export function WithItemManager<TBase extends PlayerCtor>(Base: TBase) {
206
206
  });
207
207
  }
208
208
  addItem(item: ItemClass | ItemObject | string, nb: number = 1): Item {
209
- const map = (this as any).getCurrentMap();
210
- if (!map) {
209
+ // Use this.map directly to support both RpgMap and LobbyRoom
210
+ // If no map, player is in Lobby
211
+ const map = (this as any).getCurrentMap() || (this as any).map;
212
+ if (!map || !map.database) {
211
213
  throw new Error('Player must be on a map to add items');
212
214
  }
213
215
 
@@ -219,11 +221,6 @@ export function WithItemManager<TBase extends PlayerCtor>(Base: TBase) {
219
221
  if (isString(item)) {
220
222
  itemId = item as string;
221
223
  data = (this as any).databaseById(itemId);
222
- if (!data) {
223
- throw new Error(
224
- `The ID=${itemId} data is not found in the database. Add the data in the property "database"`
225
- );
226
- }
227
224
  }
228
225
  // Handle class: create instance and add to database if needed
229
226
  else if (typeof item === 'function' || (item as any).prototype) {
@@ -271,9 +268,29 @@ export function WithItemManager<TBase extends PlayerCtor>(Base: TBase) {
271
268
  let instance: Item;
272
269
 
273
270
  if (existingItem) {
274
- // Item already exists, just update quantity
271
+ // Item already exists, update quantity and merge properties
275
272
  instance = existingItem;
276
273
  instance.quantity.update((it) => it + nb);
274
+
275
+ // Update item properties from merged data (e.g., name, description, price)
276
+ if (data.name !== undefined) {
277
+ instance.name.set(data.name);
278
+ }
279
+ if (data.description !== undefined) {
280
+ instance.description.set(data.description);
281
+ }
282
+ if (data.price !== undefined) {
283
+ instance.price.set(data.price);
284
+ }
285
+
286
+ // Update stored instance if it's an object with hooks
287
+ if (itemInstance && typeof itemInstance === 'object' && !(itemInstance instanceof Function)) {
288
+ (instance as any)._itemInstance = itemInstance;
289
+ // Update hooks if they exist
290
+ if (itemInstance.onAdd) {
291
+ instance.onAdd = itemInstance.onAdd.bind(itemInstance);
292
+ }
293
+ }
277
294
  } else {
278
295
  // Create new item instance
279
296
  instance = new Item(data);
@@ -377,7 +394,13 @@ export function WithItemManager<TBase extends PlayerCtor>(Base: TBase) {
377
394
  getParamItem(name: string): number {
378
395
  let nb = 0;
379
396
  for (let item of this.equipments()) {
380
- nb += item[name] || 0;
397
+ // Retrieve item data from database to get properties like atk, pdef, sdef
398
+ try {
399
+ const itemData = (this as any).databaseById(item.id());
400
+ nb += itemData[name] || 0;
401
+ } catch {
402
+ // If item not in database, skip it
403
+ }
381
404
  }
382
405
  const modifier = (this as any).paramsModifier?.[name];
383
406
  if (modifier) {
@@ -408,19 +431,31 @@ export function WithItemManager<TBase extends PlayerCtor>(Base: TBase) {
408
431
  if (!inventory) {
409
432
  throw ItemLog.notInInventory(itemId);
410
433
  }
411
- const item = inventory;
412
- if ((item as any).consumable === false) {
434
+
435
+ // Retrieve item data from database to check consumable and hitRate
436
+ const itemData = (this as any).databaseById(itemId);
437
+ const consumable = itemData?.consumable;
438
+
439
+ // If consumable is explicitly false, throw error
440
+ if (consumable === false) {
413
441
  throw ItemLog.notUseItem(itemId);
414
442
  }
415
- const hitRate = (item as any).hitRate ?? 1;
416
- const hookTarget = (item as any)._itemInstance || item;
443
+
444
+ // If consumable is undefined and item is not of type 'item', it's not consumable
445
+ if (consumable === undefined && itemData?._type && itemData._type !== 'item') {
446
+ throw ItemLog.notUseItem(itemId);
447
+ }
448
+
449
+ const hitRate = itemData?.hitRate ?? 1;
450
+ const hookTarget = (inventory as any)._itemInstance || inventory;
451
+
417
452
  if (Math.random() > hitRate) {
418
453
  this.removeItem(itemClass);
419
454
  this["execMethod"]("onUseFailed", [this], hookTarget);
420
455
  throw ItemLog.chanceToUseFailed(itemId);
421
456
  }
422
- (this as any).applyEffect?.(item);
423
- (this as any).applyStates?.(this, item);
457
+ (this as any).applyEffect?.(itemData);
458
+ (this as any).applyStates?.(this, itemData);
424
459
  this["execMethod"]("onUse", [this], hookTarget);
425
460
  this.removeItem(itemClass);
426
461
  return inventory;