@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/dist/Player/Player.d.ts +31 -22
- package/dist/index.js +412 -196
- package/dist/index.js.map +1 -1
- package/dist/rooms/BaseRoom.d.ts +95 -0
- package/dist/rooms/lobby.d.ts +4 -1
- package/dist/rooms/map.d.ts +17 -75
- package/package.json +8 -8
- package/src/Player/ItemManager.ts +50 -15
- package/src/Player/Player.ts +161 -135
- package/src/module.ts +13 -0
- package/src/rooms/BaseRoom.ts +120 -0
- package/src/rooms/lobby.ts +11 -1
- package/src/rooms/map.ts +68 -144
- package/tests/item.spec.ts +455 -441
- package/tests/world-maps.spec.ts +43 -81
|
@@ -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
|
+
}
|
package/dist/rooms/lobby.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { MockConnection } from '@signe/room';
|
|
2
2
|
import { RpgPlayer } from '../Player/Player';
|
|
3
|
-
|
|
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
|
}
|
package/dist/rooms/map.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
15
|
-
"@rpgjs/physic": "5.0.0-alpha.
|
|
16
|
-
"@rpgjs/testing": "5.0.0-alpha.
|
|
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.
|
|
19
|
-
"@signe/reactive": "^2.
|
|
20
|
-
"@signe/room": "^2.
|
|
21
|
-
"@signe/sync": "^2.
|
|
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
|
-
|
|
210
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
412
|
-
|
|
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
|
-
|
|
416
|
-
|
|
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?.(
|
|
423
|
-
(this as any).applyStates?.(this,
|
|
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;
|