@rpgjs/server 5.0.0-alpha.4 → 5.0.0-alpha.41

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.
Files changed (101) hide show
  1. package/dist/Gui/DialogGui.d.ts +5 -0
  2. package/dist/Gui/GameoverGui.d.ts +23 -0
  3. package/dist/Gui/Gui.d.ts +6 -0
  4. package/dist/Gui/MenuGui.d.ts +22 -3
  5. package/dist/Gui/NotificationGui.d.ts +1 -2
  6. package/dist/Gui/SaveLoadGui.d.ts +13 -0
  7. package/dist/Gui/ShopGui.d.ts +28 -3
  8. package/dist/Gui/TitleGui.d.ts +23 -0
  9. package/dist/Gui/index.d.ts +10 -1
  10. package/dist/Player/BattleManager.d.ts +34 -12
  11. package/dist/Player/ClassManager.d.ts +46 -13
  12. package/dist/Player/ComponentManager.d.ts +123 -0
  13. package/dist/Player/Components.d.ts +345 -0
  14. package/dist/Player/EffectManager.d.ts +86 -0
  15. package/dist/Player/ElementManager.d.ts +104 -0
  16. package/dist/Player/GoldManager.d.ts +22 -0
  17. package/dist/Player/GuiManager.d.ts +259 -0
  18. package/dist/Player/ItemFixture.d.ts +6 -0
  19. package/dist/Player/ItemManager.d.ts +450 -9
  20. package/dist/Player/MoveManager.d.ts +324 -69
  21. package/dist/Player/ParameterManager.d.ts +344 -14
  22. package/dist/Player/Player.d.ts +460 -8
  23. package/dist/Player/SkillManager.d.ts +197 -15
  24. package/dist/Player/StateManager.d.ts +89 -25
  25. package/dist/Player/VariableManager.d.ts +74 -0
  26. package/dist/RpgServer.d.ts +502 -64
  27. package/dist/RpgServerEngine.d.ts +2 -1
  28. package/dist/decorators/event.d.ts +46 -0
  29. package/dist/decorators/map.d.ts +287 -0
  30. package/dist/index.d.ts +10 -0
  31. package/dist/index.js +21653 -20900
  32. package/dist/index.js.map +1 -1
  33. package/dist/logs/log.d.ts +2 -3
  34. package/dist/module.d.ts +43 -1
  35. package/dist/presets/index.d.ts +0 -9
  36. package/dist/rooms/BaseRoom.d.ts +132 -0
  37. package/dist/rooms/lobby.d.ts +10 -2
  38. package/dist/rooms/map.d.ts +1236 -17
  39. package/dist/services/save.d.ts +43 -0
  40. package/dist/storage/index.d.ts +1 -0
  41. package/dist/storage/localStorage.d.ts +23 -0
  42. package/package.json +14 -10
  43. package/src/Gui/DialogGui.ts +19 -4
  44. package/src/Gui/GameoverGui.ts +39 -0
  45. package/src/Gui/Gui.ts +23 -1
  46. package/src/Gui/MenuGui.ts +155 -6
  47. package/src/Gui/NotificationGui.ts +1 -2
  48. package/src/Gui/SaveLoadGui.ts +60 -0
  49. package/src/Gui/ShopGui.ts +146 -16
  50. package/src/Gui/TitleGui.ts +39 -0
  51. package/src/Gui/index.ts +15 -2
  52. package/src/Player/BattleManager.ts +91 -49
  53. package/src/Player/ClassManager.ts +118 -50
  54. package/src/Player/ComponentManager.ts +425 -19
  55. package/src/Player/Components.ts +380 -0
  56. package/src/Player/EffectManager.ts +81 -44
  57. package/src/Player/ElementManager.ts +109 -86
  58. package/src/Player/GoldManager.ts +32 -35
  59. package/src/Player/GuiManager.ts +308 -150
  60. package/src/Player/ItemFixture.ts +4 -5
  61. package/src/Player/ItemManager.ts +774 -355
  62. package/src/Player/MoveManager.ts +1544 -774
  63. package/src/Player/ParameterManager.ts +546 -104
  64. package/src/Player/Player.ts +1163 -88
  65. package/src/Player/SkillManager.ts +520 -195
  66. package/src/Player/StateManager.ts +170 -182
  67. package/src/Player/VariableManager.ts +101 -63
  68. package/src/RpgServer.ts +525 -63
  69. package/src/core/context.ts +1 -0
  70. package/src/decorators/event.ts +61 -0
  71. package/src/decorators/map.ts +327 -0
  72. package/src/index.ts +11 -1
  73. package/src/logs/log.ts +10 -3
  74. package/src/module.ts +126 -3
  75. package/src/presets/index.ts +1 -10
  76. package/src/rooms/BaseRoom.ts +232 -0
  77. package/src/rooms/lobby.ts +25 -7
  78. package/src/rooms/map.ts +2502 -194
  79. package/src/services/save.ts +147 -0
  80. package/src/storage/index.ts +1 -0
  81. package/src/storage/localStorage.ts +76 -0
  82. package/tests/battle.spec.ts +375 -0
  83. package/tests/change-map.spec.ts +72 -0
  84. package/tests/class.spec.ts +274 -0
  85. package/tests/effect.spec.ts +219 -0
  86. package/tests/element.spec.ts +221 -0
  87. package/tests/event.spec.ts +80 -0
  88. package/tests/gold.spec.ts +99 -0
  89. package/tests/item.spec.ts +609 -0
  90. package/tests/module.spec.ts +38 -0
  91. package/tests/move.spec.ts +601 -0
  92. package/tests/player-param.spec.ts +28 -0
  93. package/tests/prediction-reconciliation.spec.ts +182 -0
  94. package/tests/random-move.spec.ts +65 -0
  95. package/tests/skill.spec.ts +658 -0
  96. package/tests/state.spec.ts +467 -0
  97. package/tests/variable.spec.ts +185 -0
  98. package/tests/world-maps.spec.ts +896 -0
  99. package/vite.config.ts +16 -0
  100. package/dist/Player/Event.d.ts +0 -0
  101. package/src/Player/Event.ts +0 -0
@@ -1,8 +1,8 @@
1
- import { isInstanceOf, isString, Item, type Constructor } from "@rpgjs/common";
2
- import { RpgCommonPlayer, Matter } from "@rpgjs/common";
3
- import { ATK, PDEF, SDEF } from "../presets";
1
+ import { isInstanceOf, isString, Item, type PlayerCtor} from "@rpgjs/common";
2
+ import { ATK, PDEF, SDEF } from "@rpgjs/common";
4
3
  import { ItemLog } from "../logs";
5
- import { ArmorInstance, ItemClass, ItemInstance, WeaponInstance } from "@rpgjs/database";
4
+ import type { ItemClass, ItemInstance } from "@rpgjs/database";
5
+ import { RpgPlayer } from "./Player";
6
6
 
7
7
  // Ajout des enums manquants
8
8
  enum Effect {
@@ -13,143 +13,429 @@ enum ClassHooks {
13
13
  canEquip = 'canEquip'
14
14
  }
15
15
 
16
+ type Inventory = { nb: number; item: ItemInstance };
17
+
16
18
  /**
17
- * Interface defining what MoveManager adds to a class
19
+ * Interface defining the hooks that can be implemented on item classes or objects
20
+ *
21
+ * These hooks are called at specific moments during the item lifecycle:
22
+ * - `onAdd`: When the item is added to the player's inventory
23
+ * - `onUse`: When the item is successfully used
24
+ * - `onUseFailed`: When the item usage fails (e.g., chance roll failed)
25
+ * - `onRemove`: When the item is removed from the inventory
26
+ * - `onEquip`: When the item is equipped or unequipped
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const itemHooks: ItemHooks = {
31
+ * onAdd(player) {
32
+ * console.log('Item added to inventory');
33
+ * },
34
+ * onUse(player) {
35
+ * player.hp += 100;
36
+ * }
37
+ * };
38
+ * ```
18
39
  */
19
- export interface IItemManager {
20
- databaseById(id: string): ItemClass;
40
+ export interface ItemHooks {
41
+ /**
42
+ * Called when the item is added to the player's inventory
43
+ *
44
+ * @param player - The player receiving the item
45
+ */
46
+ onAdd?: (player: RpgPlayer) => void | Promise<void>;
47
+
48
+ /**
49
+ * Called when the item is successfully used
50
+ *
51
+ * @param player - The player using the item
52
+ */
53
+ onUse?: (player: RpgPlayer) => void | Promise<void>;
54
+
55
+ /**
56
+ * Called when the item usage fails (e.g., chance roll failed)
57
+ *
58
+ * @param player - The player attempting to use the item
59
+ */
60
+ onUseFailed?: (player: RpgPlayer) => void | Promise<void>;
61
+
62
+ /**
63
+ * Called when the item is removed from the inventory
64
+ *
65
+ * @param player - The player losing the item
66
+ */
67
+ onRemove?: (player: RpgPlayer) => void | Promise<void>;
68
+
69
+ /**
70
+ * Called when the item is equipped or unequipped
71
+ *
72
+ * @param player - The player equipping/unequipping the item
73
+ * @param equip - true if equipping, false if unequipping
74
+ */
75
+ onEquip?: (player: RpgPlayer, equip: boolean) => void | Promise<void>;
21
76
  }
22
77
 
23
- type Inventory = { nb: number; item: ItemInstance };
78
+ /**
79
+ * Base properties that can be included in an item object
80
+ *
81
+ * This interface defines the common properties that items can have.
82
+ * Use this as a base and extend it with specific item types.
83
+ *
84
+ * @template T - Additional properties specific to the item type
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * interface PotionData extends ItemData {
89
+ * hpValue: number;
90
+ * mpValue: number;
91
+ * }
92
+ *
93
+ * const potion: ItemObject<PotionData> = {
94
+ * name: 'Health Potion',
95
+ * description: 'Restores 100 HP',
96
+ * price: 200,
97
+ * hpValue: 100,
98
+ * mpValue: 0,
99
+ * onUse(player) {
100
+ * player.hp += this.hpValue;
101
+ * }
102
+ * };
103
+ * ```
104
+ */
105
+ export interface ItemData {
106
+ /** Item name */
107
+ name?: string;
108
+ /** Item description */
109
+ description?: string;
110
+ /** Item price */
111
+ price?: number;
112
+ /** HP value restored when used */
113
+ hpValue?: number;
114
+ /** MP/SP value restored when used */
115
+ mpValue?: number;
116
+ /** Chance to successfully use the item (0-1) */
117
+ hitRate?: number;
118
+ /** Whether the item is consumable */
119
+ consumable?: boolean;
120
+ /** States to add when used */
121
+ addStates?: any[];
122
+ /** States to remove when used */
123
+ removeStates?: any[];
124
+ /** Elemental properties */
125
+ elements?: any[];
126
+ /** Parameter modifiers */
127
+ paramsModifier?: Record<string, any>;
128
+ /** Item type (for equipment validation) */
129
+ _type?: 'item' | 'weapon' | 'armor';
130
+ }
131
+
132
+ /**
133
+ * Item object type that combines data properties with hooks
134
+ *
135
+ * This type allows you to create item objects directly without needing a class.
136
+ * The object can contain both item data properties and lifecycle hooks.
137
+ *
138
+ * @template T - Additional properties specific to the item type (extends ItemData)
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * const potion: ItemObject = {
143
+ * name: 'Health Potion',
144
+ * description: 'Restores 100 HP',
145
+ * price: 200,
146
+ * hpValue: 100,
147
+ * consumable: true,
148
+ * onAdd(player) {
149
+ * console.log('Potion added!');
150
+ * },
151
+ * onUse(player) {
152
+ * player.hp += 100;
153
+ * }
154
+ * };
155
+ *
156
+ * player.addItem(potion);
157
+ * ```
158
+ */
159
+ export type ItemObject<T extends ItemData = ItemData> = T & ItemHooks & {
160
+ /** Item identifier (required if not using class or string) */
161
+ id?: string;
162
+ };
24
163
 
25
164
  /**
26
- * Move Manager mixin
165
+ * Item Manager Mixin
27
166
  *
28
- * Adds methods to manage player movement
167
+ * Provides comprehensive item management capabilities to any class. This mixin handles
168
+ * inventory management, item usage, equipment, buying/selling, and item effects.
169
+ * It manages the complete item system including restrictions, transactions, and equipment.
29
170
  *
30
- * @param Base - The base class to extend
31
- * @returns A new class with move management capabilities
171
+ * @param Base - The base class to extend with item management
172
+ * @returns Extended class with item management methods
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * class MyPlayer extends WithItemManager(BasePlayer) {
177
+ * constructor() {
178
+ * super();
179
+ * // Item system is automatically initialized
180
+ * }
181
+ * }
182
+ *
183
+ * const player = new MyPlayer();
184
+ * player.addItem('potion', 5);
185
+ * player.useItem('potion');
186
+ * ```
32
187
  */
33
- export function WithItemManager<TBase extends Constructor<RpgCommonPlayer>>(
34
- Base: TBase
35
- ): Constructor<IItemManager> & TBase {
36
- return class extends Base implements IItemManager {
188
+ export function WithItemManager<TBase extends PlayerCtor>(Base: TBase) {
189
+ return class extends Base {
37
190
 
38
- /**
39
- * Retrieves the information of an object: the number and the instance
40
- * @title Get Item
41
- * @method player.getItem(itemClass)
42
- * @param {ItemClass | string} itemClass Identifier of the object if the parameter is a string
43
- * @returns {{ nb: number, item: instance of ItemClass }}
44
- * @memberof ItemManager
45
- * @example
46
- *
47
- * ```ts
48
- * import Potion from 'your-database/potion'
49
- *
50
- * player.addItem(Potion, 5)
51
- * const inventory = player.getItem(Potion)
52
- * console.log(inventory) // { nb: 5, item: <instance of Potion> }
53
- * ```
54
- */
55
191
  getItem(itemClass: ItemClass | string): Item {
56
192
  const index: number = this._getItemIndex(itemClass);
57
- return this.items()[index];
193
+ return (this as any).items()[index];
58
194
  }
59
195
 
60
- /**
61
- * Check if the player has the item in his inventory.
62
- * @title Has Item
63
- * @method player.hasItem(itemClass)
64
- * @param {ItemClass | string} itemClass Identifier of the object if the parameter is a string
65
- * @returns {boolean}
66
- * @memberof ItemManager
67
- * @example
68
- *
69
- * ```ts
70
- * import Potion from 'your-database/potion'
71
- *
72
- * player.hasItem(Potion) // false
73
- * ```
74
- */
75
196
  hasItem(itemClass: ItemClass | string): boolean {
76
197
  return !!this.getItem(itemClass);
77
198
  }
78
199
 
79
200
  _getItemIndex(itemClass: ItemClass | string): number {
80
- return this.items().findIndex((it: Item): boolean => {
201
+ return (this as any).items().findIndex((it: Item): boolean => {
81
202
  if (isString(itemClass)) {
82
203
  return it.id() == itemClass;
83
204
  }
84
205
  return isInstanceOf(it, itemClass);
85
206
  });
86
207
  }
208
+
209
+ private _getItemMap(required: boolean = true) {
210
+ // Use this.map directly to support both RpgMap and LobbyRoom
211
+ const map = (this as any).getCurrentMap?.() || (this as any).map;
212
+ if (required && (!map || !map.database)) {
213
+ throw new Error('Player must be on a map to add items');
214
+ }
215
+ return map;
216
+ }
217
+
218
+ private _resolveItemInput(
219
+ item: ItemClass | ItemObject | string,
220
+ map: any,
221
+ databaseByIdOverride?: (id: string) => any
222
+ ) {
223
+ let itemId: string;
224
+ let data: any;
225
+ let itemInstance: any = null;
226
+
227
+ if (isString(item)) {
228
+ itemId = item as string;
229
+ data = databaseByIdOverride
230
+ ? databaseByIdOverride(itemId)
231
+ : (this as any).databaseById(itemId);
232
+ } else if (typeof item === 'function' || (item as any).prototype) {
233
+ itemId = (item as any).name;
234
+
235
+ const existingData = map.database()[itemId];
236
+ if (existingData) {
237
+ data = existingData;
238
+ } else {
239
+ map.addInDatabase(itemId, item as ItemClass);
240
+ data = item as ItemClass;
241
+ }
242
+
243
+ itemInstance = new (item as ItemClass)();
244
+ } else {
245
+ const itemObj = item as ItemObject;
246
+ itemId = itemObj.id || `item-${Date.now()}`;
247
+
248
+ const existingData = map.database()[itemId];
249
+ if (existingData) {
250
+ data = { ...existingData, ...itemObj };
251
+ map.addInDatabase(itemId, data, { force: true });
252
+ } else {
253
+ map.addInDatabase(itemId, itemObj);
254
+ data = itemObj;
255
+ }
256
+
257
+ itemInstance = itemObj;
258
+ }
259
+
260
+ return { itemId, data, itemInstance };
261
+ }
262
+
263
+ private _createItemInstance(
264
+ itemId: string,
265
+ data: any,
266
+ nb: number,
267
+ itemInstance: any
268
+ ): Item {
269
+ const instance = new Item(data);
270
+ instance.id.set(itemId);
271
+ instance.quantity.set(nb);
272
+
273
+ if (itemInstance) {
274
+ (instance as any)._itemInstance = itemInstance;
275
+ if (itemInstance.onAdd) {
276
+ instance.onAdd = itemInstance.onAdd.bind(itemInstance);
277
+ }
278
+ }
279
+
280
+ return instance;
281
+ }
282
+
283
+ /**
284
+ * Create an item instance without inventory changes or hook execution.
285
+ */
286
+ createItemInstance(item: ItemClass | ItemObject | string, nb: number = 1) {
287
+ const map = this._getItemMap();
288
+ const { itemId, data, itemInstance } = this._resolveItemInput(item, map);
289
+ const instance = this._createItemInstance(itemId, data, nb, itemInstance);
290
+ return { itemId, data, itemInstance, instance };
291
+ }
292
+
293
+ /**
294
+ * Resolve item snapshot entries into Item instances without side effects.
295
+ */
296
+ resolveItemsSnapshot(snapshot: { items?: any[] }, mapOverride?: any) {
297
+ if (!snapshot || !Array.isArray(snapshot.items)) {
298
+ return snapshot;
299
+ }
300
+
301
+ const map = mapOverride ?? this._getItemMap(false);
302
+ if (!map || !map.database) {
303
+ return snapshot;
304
+ }
305
+
306
+ const databaseByIdOverride = (id: string) => {
307
+ const data = map.database()[id];
308
+ if (!data) {
309
+ throw new Error(
310
+ `The ID=${id} data is not found in the database. Add the data in the property "database"`
311
+ );
312
+ }
313
+ return data;
314
+ };
315
+
316
+ const items = snapshot.items.map((entry: any) => {
317
+ const itemId = isString(entry) ? entry : entry?.id;
318
+ if (!itemId) {
319
+ return entry;
320
+ }
321
+
322
+ const nb =
323
+ !isString(entry) && typeof entry?.nb === 'number'
324
+ ? entry.nb
325
+ : !isString(entry) && typeof entry?.quantity === 'number'
326
+ ? entry.quantity
327
+ : 1;
328
+
329
+ const { data, itemInstance } = this._resolveItemInput(
330
+ itemId,
331
+ map,
332
+ databaseByIdOverride
333
+ );
334
+ return this._createItemInstance(itemId, data, nb, itemInstance);
335
+ });
336
+
337
+ return { ...snapshot, items };
338
+ }
339
+
87
340
  /**
88
- * Add an item in the player's inventory. You can give more than one by specifying `nb`
89
- *
90
- * `onAdd()` method is called on the ItemClass
91
- *
92
- * @title Add Item
93
- * @method player.addItem(item,nb=1)
94
- * @param {ItemClass} itemClass
95
- * @param {number} [nb] Default 1
96
- * @returns {{ nb: number, item: instance of ItemClass }}
97
- * @memberof ItemManager
98
- * @example
99
- *
100
- * ```ts
101
- * import Potion from 'your-database/potion'
102
- * player.addItem(Potion, 5)
103
- * ```
341
+ * Resolve equipment snapshot entries into Item instances without side effects.
104
342
  */
105
- addItem(itemId: string, nb: number = 1): Item {
106
- const data = this.databaseById(itemId);
107
- const item = this.items().find((it) => it.id() == itemId);
343
+ resolveEquipmentsSnapshot(snapshot: { equipments?: any[]; items?: any[] }, mapOverride?: any) {
344
+ if (!snapshot || !Array.isArray(snapshot.equipments)) {
345
+ return snapshot;
346
+ }
347
+
348
+ const map = mapOverride ?? this._getItemMap(false);
349
+ if (!map || !map.database) {
350
+ return snapshot;
351
+ }
352
+
353
+ const databaseByIdOverride = (id: string) => {
354
+ const data = map.database()[id];
355
+ if (!data) {
356
+ throw new Error(
357
+ `The ID=${id} data is not found in the database. Add the data in the property "database"`
358
+ );
359
+ }
360
+ return data;
361
+ };
362
+
363
+ const resolvedItems = Array.isArray(snapshot.items) ? snapshot.items : [];
364
+ const getItemId = (entry: any) => {
365
+ if (isString(entry)) return entry;
366
+ if (typeof entry?.id === 'function') return entry.id();
367
+ return entry?.id;
368
+ };
369
+
370
+ const equipments = snapshot.equipments.map((entry: any) => {
371
+ const itemId = getItemId(entry);
372
+ if (!itemId) {
373
+ return entry;
374
+ }
375
+
376
+ const existing = resolvedItems.find((item: any) => {
377
+ const existingId = getItemId(item);
378
+ return existingId === itemId;
379
+ });
380
+ if (existing) {
381
+ return existing;
382
+ }
383
+
384
+ const { data, itemInstance } = this._resolveItemInput(
385
+ itemId,
386
+ map,
387
+ databaseByIdOverride
388
+ );
389
+ return this._createItemInstance(itemId, data, 1, itemInstance);
390
+ });
391
+
392
+ return { ...snapshot, equipments };
393
+ }
394
+ addItem(item: ItemClass | ItemObject | string, nb: number = 1): Item {
395
+ const map = this._getItemMap();
396
+ const { itemId, data, itemInstance } = this._resolveItemInput(item, map);
397
+
398
+ // Find existing item in inventory
399
+ const existingItem = (this as any).items().find((it: Item) => it.id() == itemId);
108
400
  let instance: Item;
109
- if (item) {
110
- instance = item;
401
+
402
+ if (existingItem) {
403
+ // Item already exists, update quantity and merge properties
404
+ instance = existingItem;
111
405
  instance.quantity.update((it) => it + nb);
406
+
407
+ // Update item properties from merged data (e.g., name, description, price)
408
+ if (data.name !== undefined) {
409
+ instance.name.set(data.name);
410
+ }
411
+ if (data.description !== undefined) {
412
+ instance.description.set(data.description);
413
+ }
414
+ if (data.price !== undefined) {
415
+ instance.price.set(data.price);
416
+ }
417
+
418
+ // Update stored instance if it's an object with hooks
419
+ if (itemInstance && typeof itemInstance === 'object' && !(itemInstance instanceof Function)) {
420
+ (instance as any)._itemInstance = itemInstance;
421
+ // Update hooks if they exist
422
+ if (itemInstance.onAdd) {
423
+ instance.onAdd = itemInstance.onAdd.bind(itemInstance);
424
+ }
425
+ }
112
426
  } else {
113
- instance = new Item(data);
114
- instance.id.set(itemId);
115
- this.items().push(instance);
427
+ // Create new item instance
428
+ instance = this._createItemInstance(itemId, data, nb, itemInstance);
429
+ (this as any).items().push(instance);
116
430
  }
117
- this["execMethod"]("onAdd", [this], instance);
431
+
432
+ // Call onAdd hook - use stored instance if available
433
+ const hookTarget = (instance as any)._itemInstance || instance;
434
+ // Only call onAdd if it exists and is a function
435
+ (this as any)["execMethod"]("onAdd", [this], hookTarget);
118
436
  return instance;
119
437
  }
120
438
 
121
- /**
122
- * Deletes an item. Decreases the value `nb`. If the number falls to 0, then the item is removed from the inventory. The method then returns `undefined`
123
- *
124
- * `onRemove()` method is called on the ItemClass
125
- *
126
- * @title Remove Item
127
- * @method player.removeItem(item,nb=1)
128
- * @param {ItemClass | string} itemClass string is item id
129
- * @param {number} [nb] Default 1
130
- * @returns {{ nb: number, item: instance of ItemClass } | undefined}
131
- * @throws {ItemLog} notInInventory
132
- * If the object is not in the inventory, an exception is raised
133
- * ```
134
- * {
135
- * id: ITEM_NOT_INVENTORY,
136
- * msg: '...'
137
- * }
138
- * ```
139
- * @memberof ItemManager
140
- * @example
141
- *
142
- * ```ts
143
- * import Potion from 'your-database/potion'
144
- *
145
- * try {
146
- * player.removeItem(Potion, 5)
147
- * }
148
- * catch (err) {
149
- * console.log(err)
150
- * }
151
- * ```
152
- */
153
439
  removeItem(
154
440
  itemClass: ItemClass | string,
155
441
  nb: number = 1
@@ -165,52 +451,35 @@ export function WithItemManager<TBase extends Constructor<RpgCommonPlayer>>(
165
451
  } else {
166
452
  this.items()[itemIndex].quantity.update((it) => it - nb);
167
453
  }
168
- this["execMethod"]("onRemove", [this], item);
454
+ // Call onRemove hook - use stored instance if available
455
+ const hookTarget = (item as any)._itemInstance || item;
456
+ if (hookTarget && typeof hookTarget.onRemove === 'function') {
457
+ this["execMethod"]("onRemove", [this], hookTarget);
458
+ }
169
459
  return this.items()[itemIndex];
170
460
  }
171
461
 
172
- /**
173
- * Purchases an item and reduces the amount of gold
174
- *
175
- * `onAdd()` method is called on the ItemClass
176
- *
177
- * @title Buy Item
178
- * @method player.buyItem(item,nb=1)
179
- * @param {ItemClass | string} itemClass string is item id
180
- * @param {number} [nb] Default 1
181
- * @returns {{ nb: number, item: instance of ItemClass }}
182
- * @throws {ItemLog} haveNotPrice
183
- * If you have not set a price on the item
184
- * ```
185
- * {
186
- * id: NOT_PRICE,
187
- * msg: '...'
188
- * }
189
- * ```
190
- * @throws {ItemLog} notEnoughGold
191
- * If the player does not have enough money
192
- * ```
193
- * {
194
- * id: NOT_ENOUGH_GOLD,
195
- * msg: '...'
196
- * }
197
- * ```
198
- * @memberof ItemManager
199
- * @example
200
- *
201
- * ```ts
202
- * import Potion from 'your-database/potion'
203
- *
204
- * try {
205
- * player.buyItem(Potion)
206
- * }
207
- * catch (err) {
208
- * console.log(err)
209
- * }
210
- * ```
211
- */
212
- buyItem(itemId: string, nb = 1): Item {
213
- const data = this.databaseById(itemId);
462
+ buyItem(item: ItemClass | ItemObject | string, nb = 1): Item {
463
+ let itemId: string;
464
+ let data: any;
465
+
466
+ if (isString(item)) {
467
+ itemId = item as string;
468
+ data = (this as any).databaseById(itemId);
469
+ } else if (typeof item === 'function' || (item as any).prototype) {
470
+ itemId = (item as any).name;
471
+ data = (this as any).databaseById(itemId);
472
+ } else {
473
+ const itemObj = item as ItemObject;
474
+ itemId = itemObj.id || `item-${Date.now()}`;
475
+ try {
476
+ const dbData = (this as any).databaseById(itemId);
477
+ data = { ...dbData, ...itemObj };
478
+ } catch {
479
+ data = itemObj;
480
+ }
481
+ }
482
+
214
483
  if (!data.price) {
215
484
  throw ItemLog.haveNotPrice(itemId);
216
485
  }
@@ -219,61 +488,13 @@ export function WithItemManager<TBase extends Constructor<RpgCommonPlayer>>(
219
488
  throw ItemLog.notEnoughGold(itemId, nb);
220
489
  }
221
490
  this._gold.update((gold) => gold - totalPrice);
222
- return this.addItem(itemId, nb);
491
+ return this.addItem(item, nb);
223
492
  }
224
493
 
225
- /**
226
- * Sell an item and the player wins the amount of the item divided by 2
227
- *
228
- * `onRemove()` method is called on the ItemClass
229
- *
230
- * @title Sell Item
231
- * @method player.sellItem(item,nb=1)
232
- * @param {ItemClass | string} itemClass string is item id
233
- * @param {number} [nbToSell] Default 1
234
- * @returns {{ nb: number, item: instance of ItemClass }}
235
- * @throws {ItemLog} haveNotPrice
236
- * If you have not set a price on the item
237
- * ```
238
- * {
239
- * id: NOT_PRICE,
240
- * msg: '...'
241
- * }
242
- * ```
243
- * @throws {ItemLog} notInInventory
244
- * If the object is not in the inventory, an exception is raised
245
- * ```
246
- * {
247
- * id: ITEM_NOT_INVENTORY,
248
- * msg: '...'
249
- * }
250
- * ```
251
- * @throws {ItemLog} tooManyToSell
252
- * If the number of items for sale exceeds the number of actual items in the inventory
253
- * ```
254
- * {
255
- * id: TOO_MANY_ITEM_TO_SELL,
256
- * msg: '...'
257
- * }
258
- * ```
259
- * @memberof ItemManager
260
- * @example
261
- *
262
- * ```ts
263
- * import Potion from 'your-database/potion'
264
- *
265
- * try {
266
- * player.addItem(Potion)
267
- * player.sellItem(Potion)
268
- * }
269
- * catch (err) {
270
- * console.log(err)
271
- * }
272
- * ```
273
- */
274
- sellItem(itemId: string, nbToSell = 1): Item {
275
- const data = this.databaseById(itemId);
276
- const inventory = this.getItem(itemId);
494
+ sellItem(itemClass: ItemClass | string, nbToSell = 1): Item {
495
+ const itemId = isString(itemClass) ? itemClass : (itemClass as any).name;
496
+ const data = (this as any).databaseById(itemId);
497
+ const inventory = this.getItem(itemClass);
277
498
  if (!inventory) {
278
499
  throw ItemLog.notInInventory(itemId);
279
500
  }
@@ -285,14 +506,20 @@ export function WithItemManager<TBase extends Constructor<RpgCommonPlayer>>(
285
506
  throw ItemLog.haveNotPrice(itemId);
286
507
  }
287
508
  this._gold.update((gold) => gold + (data.price / 2) * nbToSell);
288
- this.removeItem(itemId, nbToSell);
509
+ this.removeItem(itemClass, nbToSell);
289
510
  return inventory;
290
511
  }
291
512
 
292
513
  getParamItem(name: string): number {
293
514
  let nb = 0;
294
515
  for (let item of this.equipments()) {
295
- nb += item[name] || 0;
516
+ // Retrieve item data from database to get properties like atk, pdef, sdef
517
+ try {
518
+ const itemData = (this as any).databaseById(item.id());
519
+ nb += itemData[name] || 0;
520
+ } catch {
521
+ // If item not in database, skip it
522
+ }
296
523
  }
297
524
  const modifier = (this as any).paramsModifier?.[name];
298
525
  if (modifier) {
@@ -302,183 +529,71 @@ export function WithItemManager<TBase extends Constructor<RpgCommonPlayer>>(
302
529
  return nb;
303
530
  }
304
531
 
305
- /**
306
- * recover the attack sum of items equipped on the player.
307
- *
308
- * @title Get the player's attack
309
- * @prop {number} player.atk
310
- * @memberof ItemManager
311
- */
312
532
  get atk(): number {
313
533
  return this.getParamItem(ATK);
314
534
  }
315
535
 
316
- /**
317
- * recover the physic defense sum of items equipped on the player.
318
- *
319
- * @title Get the player's pdef
320
- * @prop {number} player.pdef
321
- * @memberof ItemManager
322
- */
323
536
  get pdef(): number {
324
537
  return this.getParamItem(PDEF);
325
538
  }
326
539
 
327
- /**
328
- * recover the skill defense sum of items equipped on the player.
329
- *
330
- * @title Get the player's sdef
331
- * @prop {number} player.sdef
332
- * @memberof ItemManager
333
- */
334
540
  get sdef(): number {
335
541
  return this.getParamItem(SDEF);
336
542
  }
337
543
 
338
- /**
339
- * Use an object. Applies effects and states. Removes the object from the inventory then
340
- *
341
- * `onUse()` method is called on the ItemClass (If the use has worked)
342
- * `onRemove()` method is called on the ItemClass
343
- *
344
- * @title Use an Item
345
- * @method player.useItem(item,nb=1)
346
- * @param {ItemClass | string} itemClass string is item id
347
- * @returns {{ nb: number, item: instance of ItemClass }}
348
- * @throws {ItemLog} restriction
349
- * If the player has the `Effect.CAN_NOT_ITEM` effect
350
- * ```
351
- * {
352
- * id: RESTRICTION_ITEM,
353
- * msg: '...'
354
- * }
355
- * ```
356
- * @throws {ItemLog} notInInventory
357
- * If the object is not in the inventory, an exception is raised
358
- * ```
359
- * {
360
- * id: ITEM_NOT_INVENTORY,
361
- * msg: '...'
362
- * }
363
- * ```
364
- * @throws {ItemLog} notUseItem
365
- * If the `consumable` property is on false
366
- * ```
367
- * {
368
- * id: NOT_USE_ITEM,
369
- * msg: '...'
370
- * }
371
- * ```
372
- * @throws {ItemLog} chanceToUseFailed
373
- * Chance to use the item has failed. Chances of use is defined with `ItemClass.hitRate`
374
- * ```
375
- * {
376
- * id: USE_CHANCE_ITEM_FAILED,
377
- * msg: '...'
378
- * }
379
- * ```
380
- * > the item is still deleted from the inventory
381
- *
382
- * `onUseFailed()` method is called on the ItemClass
383
- *
384
- * @memberof ItemManager
385
- * @example
386
- *
387
- * ```ts
388
- * import Potion from 'your-database/potion'
389
- *
390
- * try {
391
- * player.addItem(Potion)
392
- * player.useItem(Potion)
393
- * }
394
- * catch (err) {
395
- * console.log(err)
396
- * }
397
- * ```
398
- */
399
- useItem(itemId: string): Item {
400
- const inventory = this.getItem(itemId);
544
+ useItem(itemClass: ItemClass | string): Item {
545
+ const itemId = isString(itemClass) ? itemClass : (itemClass as any).name;
546
+ const inventory = this.getItem(itemClass);
401
547
  if ((this as any).hasEffect?.(Effect.CAN_NOT_ITEM)) {
402
548
  throw ItemLog.restriction(itemId);
403
549
  }
404
550
  if (!inventory) {
405
551
  throw ItemLog.notInInventory(itemId);
406
552
  }
407
- const item = inventory;
408
- if ((item as any).consumable === false) {
553
+
554
+ // Retrieve item data from database to check consumable and hitRate
555
+ const itemData = (this as any).databaseById(itemId);
556
+ const consumable = itemData?.consumable;
557
+
558
+ // If consumable is explicitly false, throw error
559
+ if (consumable === false) {
409
560
  throw ItemLog.notUseItem(itemId);
410
561
  }
411
- const hitRate = (item as any).hitRate ?? 1;
562
+
563
+ // If consumable is undefined and item is not of type 'item', it's not consumable
564
+ if (consumable === undefined && itemData?._type && itemData._type !== 'item') {
565
+ throw ItemLog.notUseItem(itemId);
566
+ }
567
+
568
+ const hitRate = itemData?.hitRate ?? 1;
569
+ const hookTarget = (inventory as any)._itemInstance || inventory;
570
+
412
571
  if (Math.random() > hitRate) {
413
- this.removeItem(itemId);
414
- this["execMethod"]("onUseFailed", [this], item);
572
+ this.removeItem(itemClass);
573
+ this["execMethod"]("onUseFailed", [this], hookTarget);
415
574
  throw ItemLog.chanceToUseFailed(itemId);
416
575
  }
417
- (this as any).applyEffect?.(item);
418
- (this as any).applyStates?.(this, item);
419
- this["execMethod"]("onUse", [this], item);
420
- this.removeItem(itemId);
576
+ (this as any).applyEffect?.(itemData);
577
+ (this as any).applyStates?.(this, itemData);
578
+ this["execMethod"]("onUse", [this], hookTarget);
579
+ this.removeItem(itemClass);
421
580
  return inventory;
422
581
  }
423
582
 
424
- /**
425
- * Equips a weapon or armor on a player. Think first to add the item in the inventory with the `addItem()` method before equipping the item.
426
- *
427
- * `onEquip()` method is called on the ItemClass
428
- *
429
- * @title Equip Weapon or Armor
430
- * @method player.equip(itemClass,equip=true)
431
- * @param {ItemClass | string} itemClass string is item id
432
- * @param {number} [equip] Equip the object if true or un-equipped if false
433
- * @returns {void}
434
- * @throws {ItemLog} notInInventory
435
- * If the item is not in the inventory
436
- * ```
437
- {
438
- id: ITEM_NOT_INVENTORY,
439
- msg: '...'
440
- }
441
- ```
442
- * @throws {ItemLog} invalidToEquiped
443
- If the item is not by a weapon or armor
444
- ```
445
- {
446
- id: INVALID_ITEM_TO_EQUIP,
447
- msg: '...'
448
- }
449
- ```
450
- * @throws {ItemLog} isAlreadyEquiped
451
- If the item Is already equipped
452
- ```
453
- {
454
- id: ITEM_ALREADY_EQUIPED,
455
- msg: '...'
456
- }
457
- ```
458
- * @memberof ItemManager
459
- * @example
460
- *
461
- * ```ts
462
- * import Sword from 'your-database/sword'
463
- *
464
- * try {
465
- * player.addItem(Sword)
466
- * player.equip(Sword)
467
- * }
468
- * catch (err) {
469
- * console.log(err)
470
- * }
471
- * ```
472
- */
473
583
  equip(
474
584
  itemId: string,
475
- equip: boolean = true
585
+ equip: boolean | 'auto' = true
476
586
  ): void {
477
- const inventory: Item = this.getItem(itemId);
587
+ const autoAdd = equip === 'auto';
588
+ const equipState = equip === 'auto' ? true : equip;
589
+ const data = (this as any).databaseById(itemId);
590
+ let inventory: Item = this.getItem(itemId);
591
+ if (!inventory && autoAdd) {
592
+ inventory = this.addItem(itemId, 1);
593
+ }
478
594
  if (!inventory) {
479
595
  throw ItemLog.notInInventory(itemId);
480
596
  }
481
- const data = this.databaseById(itemId);
482
597
  if (data._type == "item") {
483
598
  throw ItemLog.invalidToEquiped(itemId);
484
599
  }
@@ -496,17 +611,321 @@ export function WithItemManager<TBase extends Constructor<RpgCommonPlayer>>(
496
611
 
497
612
  const item = inventory;
498
613
 
499
- if ((item as any).equipped && equip) {
614
+ if ((item as any).equipped && equipState) {
500
615
  throw ItemLog.isAlreadyEquiped(itemId);
501
616
  }
502
- (item as any).equipped = equip;
503
- if (!equip) {
617
+ (item as any).equipped = equipState;
618
+ if (!equipState) {
504
619
  const index = this.equipments().findIndex((it) => it.id() == item.id());
505
620
  this.equipments().splice(index, 1);
506
621
  } else {
507
622
  this.equipments().push(item);
508
623
  }
509
- this["execMethod"]("onEquip", [this, equip], item);
624
+ // Call onEquip hook - use stored instance if available
625
+ const hookTarget = (item as any)._itemInstance || item;
626
+ this["execMethod"]("onEquip", [this, equipState], hookTarget);
510
627
  }
511
- };
628
+ } as unknown as TBase;
629
+ }
630
+
631
+ /**
632
+ * Interface for Item Manager functionality
633
+ *
634
+ * Provides comprehensive item management capabilities including inventory management,
635
+ * item usage, equipment, buying/selling, and item effects. This interface defines
636
+ * the public API of the ItemManager mixin.
637
+ */
638
+ export interface IItemManager {
639
+ /**
640
+ * Retrieves the information of an object: the number and the instance
641
+ *
642
+ * The returned Item instance contains the quantity information accessible via `quantity()` method.
643
+ *
644
+ * @param itemClass - Item class or string identifier. If string, it's the item ID
645
+ * @returns Item instance containing quantity and item data
646
+ *
647
+ * @example
648
+ * ```ts
649
+ * import Potion from 'your-database/potion'
650
+ *
651
+ * player.addItem(Potion, 5)
652
+ * const inventory = player.getItem(Potion)
653
+ * console.log(inventory.quantity()) // 5
654
+ * console.log(inventory) // <instance of Item>
655
+ * ```
656
+ */
657
+ getItem(itemClass: ItemClass | string): Item;
658
+
659
+ /**
660
+ * Check if the player has the item in his inventory
661
+ *
662
+ * @param itemClass - Item class or string identifier. If string, it's the item ID
663
+ * @returns `true` if player has the item, `false` otherwise
664
+ *
665
+ * @example
666
+ * ```ts
667
+ * import Potion from 'your-database/potion'
668
+ *
669
+ * player.hasItem(Potion) // false
670
+ * player.addItem(Potion, 1)
671
+ * player.hasItem(Potion) // true
672
+ * ```
673
+ */
674
+ hasItem(itemClass: ItemClass | string): boolean;
675
+
676
+ /**
677
+ * Add an item in the player's inventory
678
+ *
679
+ * You can add items using:
680
+ * - Item class (automatically registered in database if needed)
681
+ * - Item object (automatically registered in database if needed)
682
+ * - String ID (must be pre-registered in database)
683
+ *
684
+ * The `onAdd()` method is called on the ItemClass or ItemObject when the item is added.
685
+ *
686
+ * @param item - Item class, object, or string identifier
687
+ * @param nb - Number of items to add (default: 1)
688
+ * @returns The item instance added to inventory
689
+ *
690
+ * @example
691
+ * ```ts
692
+ * import Potion from 'your-database/potion'
693
+ *
694
+ * // Add using class
695
+ * player.addItem(Potion, 5)
696
+ *
697
+ * // Add using string ID (must be registered in database)
698
+ * player.addItem('Potion', 3)
699
+ *
700
+ * // Add using object
701
+ * player.addItem({
702
+ * id: 'custom-potion',
703
+ * name: 'Custom Potion',
704
+ * price: 200,
705
+ * onAdd(player) {
706
+ * console.log('Custom potion added!')
707
+ * }
708
+ * }, 2)
709
+ * ```
710
+ */
711
+ addItem(item: ItemClass | ItemObject | string, nb?: number): Item;
712
+
713
+ /**
714
+ * Deletes an item from inventory
715
+ *
716
+ * Decreases the quantity by `nb`. If the quantity falls to 0 or below, the item is removed from the inventory.
717
+ * The method returns `undefined` if the item is completely removed.
718
+ *
719
+ * The `onRemove()` method is called on the ItemClass when the item is removed.
720
+ *
721
+ * @param itemClass - Item class or string identifier. If string, it's the item ID
722
+ * @param nb - Number of items to remove (default: 1)
723
+ * @returns Item instance or `undefined` if the item was completely removed
724
+ * @throws {Object} ItemLog.notInInventory - If the item is not in the inventory
725
+ * - `id`: `ITEM_NOT_INVENTORY`
726
+ * - `msg`: Error message
727
+ *
728
+ * @example
729
+ * ```ts
730
+ * import Potion from 'your-database/potion'
731
+ *
732
+ * try {
733
+ * player.removeItem(Potion, 5)
734
+ * } catch (err) {
735
+ * console.log(err) // { id: 'ITEM_NOT_INVENTORY', msg: '...' }
736
+ * }
737
+ * ```
738
+ */
739
+ removeItem(itemClass: ItemClass | string, nb?: number): Item | undefined;
740
+
741
+ /**
742
+ * Purchases an item and reduces the amount of gold
743
+ *
744
+ * The player's gold is reduced by `nb * item.price`. The item is then added to the inventory.
745
+ * The `onAdd()` method is called on the ItemClass when the item is added.
746
+ *
747
+ * @param item - Item class, object, or string identifier
748
+ * @param nb - Number of items to buy (default: 1)
749
+ * @returns Item instance added to inventory
750
+ * @throws {Object} ItemLog.haveNotPrice - If the item has no price set
751
+ * - `id`: `NOT_PRICE`
752
+ * - `msg`: Error message
753
+ * @throws {Object} ItemLog.notEnoughGold - If the player doesn't have enough gold
754
+ * - `id`: `NOT_ENOUGH_GOLD`
755
+ * - `msg`: Error message
756
+ *
757
+ * @example
758
+ * ```ts
759
+ * import Potion from 'your-database/potion'
760
+ *
761
+ * try {
762
+ * player.buyItem(Potion)
763
+ * } catch (err) {
764
+ * if (err.id === 'NOT_ENOUGH_GOLD') {
765
+ * console.log('Not enough gold!')
766
+ * } else if (err.id === 'NOT_PRICE') {
767
+ * console.log('Item has no price!')
768
+ * }
769
+ * }
770
+ * ```
771
+ */
772
+ buyItem(item: ItemClass | ItemObject | string, nb?: number): Item;
773
+
774
+ /**
775
+ * Sell an item and the player wins the amount of the item divided by 2
776
+ *
777
+ * The player receives `(item.price / 2) * nbToSell` gold. The item is removed from the inventory.
778
+ * The `onRemove()` method is called on the ItemClass when the item is removed.
779
+ *
780
+ * @param itemClass - Item class or string identifier. If string, it's the item ID
781
+ * @param nbToSell - Number of items to sell (default: 1)
782
+ * @returns Item instance that was sold
783
+ * @throws {Object} ItemLog.haveNotPrice - If the item has no price set
784
+ * - `id`: `NOT_PRICE`
785
+ * - `msg`: Error message
786
+ * @throws {Object} ItemLog.notInInventory - If the item is not in the inventory
787
+ * - `id`: `ITEM_NOT_INVENTORY`
788
+ * - `msg`: Error message
789
+ * @throws {Object} ItemLog.tooManyToSell - If trying to sell more items than available
790
+ * - `id`: `TOO_MANY_ITEM_TO_SELL`
791
+ * - `msg`: Error message
792
+ *
793
+ * @example
794
+ * ```ts
795
+ * import Potion from 'your-database/potion'
796
+ *
797
+ * try {
798
+ * player.addItem(Potion)
799
+ * player.sellItem(Potion)
800
+ * } catch (err) {
801
+ * console.log(err)
802
+ * }
803
+ * ```
804
+ */
805
+ sellItem(itemClass: ItemClass | string, nbToSell?: number): Item;
806
+
807
+ /**
808
+ * Use an object. Applies effects and states. Removes the object from the inventory
809
+ *
810
+ * When an item is used:
811
+ * - Effects are applied to the player (HP/MP restoration, etc.)
812
+ * - States are applied/removed as defined in the item
813
+ * - The item is removed from inventory (consumed)
814
+ *
815
+ * If the item has a `hitRate` property (0-1), there's a chance the usage might fail.
816
+ * If usage fails, the item is still removed and `onUseFailed()` is called instead of `onUse()`.
817
+ *
818
+ * The `onUse()` method is called on the ItemClass if the use was successful.
819
+ * The `onUseFailed()` method is called on the ItemClass if the chance roll failed.
820
+ * The `onRemove()` method is called on the ItemClass when the item is removed.
821
+ *
822
+ * @param itemClass - Item class or string identifier. If string, it's the item ID
823
+ * @returns Item instance that was used
824
+ * @throws {Object} ItemLog.restriction - If the player has the `Effect.CAN_NOT_ITEM` effect
825
+ * - `id`: `RESTRICTION_ITEM`
826
+ * - `msg`: Error message
827
+ * @throws {Object} ItemLog.notInInventory - If the item is not in the inventory
828
+ * - `id`: `ITEM_NOT_INVENTORY`
829
+ * - `msg`: Error message
830
+ * @throws {Object} ItemLog.notUseItem - If the item's `consumable` property is `false`
831
+ * - `id`: `NOT_USE_ITEM`
832
+ * - `msg`: Error message
833
+ * @throws {Object} ItemLog.chanceToUseFailed - If the chance to use the item failed (hitRate roll failed)
834
+ * - `id`: `USE_CHANCE_ITEM_FAILED`
835
+ * - `msg`: Error message
836
+ * - Note: The item is still deleted from the inventory even if usage failed
837
+ *
838
+ * @example
839
+ * ```ts
840
+ * import Potion from 'your-database/potion'
841
+ *
842
+ * try {
843
+ * player.addItem(Potion)
844
+ * player.useItem(Potion)
845
+ * } catch (err) {
846
+ * if (err.id === 'USE_CHANCE_ITEM_FAILED') {
847
+ * console.log('Item usage failed due to chance roll')
848
+ * } else {
849
+ * console.log(err)
850
+ * }
851
+ * }
852
+ * ```
853
+ */
854
+ useItem(itemClass: ItemClass | string): Item;
855
+
856
+ /**
857
+ * Equips a weapon or armor on a player
858
+ *
859
+ * Think first to add the item in the inventory with the `addItem()` method before equipping the item,
860
+ * or pass `"auto"` to add the item if it is missing and equip it.
861
+ *
862
+ * The `onEquip()` method is called on the ItemClass when the item is equipped or unequipped.
863
+ *
864
+ * @param itemId - Item identifier to resolve from the database
865
+ * @param equip - Equip the item if `true`, unequip if `false`, or `"auto"` to add then equip (default: `true`)
866
+ * @throws {Object} ItemLog.notInInventory - If the item is not in the inventory
867
+ * - `id`: `ITEM_NOT_INVENTORY`
868
+ * - `msg`: Error message
869
+ * @throws {Object} ItemLog.invalidToEquiped - If the item is not a weapon or armor (item._type is "item")
870
+ * - `id`: `INVALID_ITEM_TO_EQUIP`
871
+ * - `msg`: Error message
872
+ * @throws {Object} ItemLog.isAlreadyEquiped - If the item is already equipped
873
+ * - `id`: `ITEM_ALREADY_EQUIPED`
874
+ * - `msg`: Error message
875
+ *
876
+ * @example
877
+ * ```ts
878
+ * try {
879
+ * player.addItem('sword')
880
+ * player.equip('sword')
881
+ * // Later, unequip it
882
+ * player.equip('sword', false)
883
+ * } catch (err) {
884
+ * console.log(err)
885
+ * }
886
+ * ```
887
+ */
888
+ equip(itemId: string, equip?: boolean | 'auto'): void;
889
+
890
+ /**
891
+ * Get the player's attack (sum of items equipped)
892
+ *
893
+ * Returns the total attack value from all equipped items on the player.
894
+ *
895
+ * @returns Total attack value from equipped items
896
+ *
897
+ * @example
898
+ * ```ts
899
+ * console.log(player.atk) // 150 (sum of all equipped weapons/armors attack)
900
+ * ```
901
+ */
902
+ readonly atk: number;
903
+
904
+ /**
905
+ * Get the player's physical defense (sum of items equipped)
906
+ *
907
+ * Returns the total physical defense value from all equipped items on the player.
908
+ *
909
+ * @returns Total physical defense value from equipped items
910
+ *
911
+ * @example
912
+ * ```ts
913
+ * console.log(player.pdef) // 80 (sum of all equipped armors physical defense)
914
+ * ```
915
+ */
916
+ readonly pdef: number;
917
+
918
+ /**
919
+ * Get the player's skill defense (sum of items equipped)
920
+ *
921
+ * Returns the total skill defense value from all equipped items on the player.
922
+ *
923
+ * @returns Total skill defense value from equipped items
924
+ *
925
+ * @example
926
+ * ```ts
927
+ * console.log(player.sdef) // 60 (sum of all equipped armors skill defense)
928
+ * ```
929
+ */
930
+ readonly sdef: number;
512
931
  }