@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.
- package/dist/Gui/DialogGui.d.ts +5 -0
- package/dist/Gui/GameoverGui.d.ts +23 -0
- package/dist/Gui/Gui.d.ts +6 -0
- package/dist/Gui/MenuGui.d.ts +22 -3
- package/dist/Gui/NotificationGui.d.ts +1 -2
- package/dist/Gui/SaveLoadGui.d.ts +13 -0
- package/dist/Gui/ShopGui.d.ts +28 -3
- package/dist/Gui/TitleGui.d.ts +23 -0
- package/dist/Gui/index.d.ts +10 -1
- package/dist/Player/BattleManager.d.ts +34 -12
- package/dist/Player/ClassManager.d.ts +46 -13
- package/dist/Player/ComponentManager.d.ts +123 -0
- package/dist/Player/Components.d.ts +345 -0
- package/dist/Player/EffectManager.d.ts +86 -0
- package/dist/Player/ElementManager.d.ts +104 -0
- package/dist/Player/GoldManager.d.ts +22 -0
- package/dist/Player/GuiManager.d.ts +259 -0
- package/dist/Player/ItemFixture.d.ts +6 -0
- package/dist/Player/ItemManager.d.ts +450 -9
- package/dist/Player/MoveManager.d.ts +324 -69
- package/dist/Player/ParameterManager.d.ts +344 -14
- package/dist/Player/Player.d.ts +460 -8
- package/dist/Player/SkillManager.d.ts +197 -15
- package/dist/Player/StateManager.d.ts +89 -25
- package/dist/Player/VariableManager.d.ts +74 -0
- package/dist/RpgServer.d.ts +502 -64
- package/dist/RpgServerEngine.d.ts +2 -1
- package/dist/decorators/event.d.ts +46 -0
- package/dist/decorators/map.d.ts +287 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +21653 -20900
- package/dist/index.js.map +1 -1
- package/dist/logs/log.d.ts +2 -3
- package/dist/module.d.ts +43 -1
- package/dist/presets/index.d.ts +0 -9
- package/dist/rooms/BaseRoom.d.ts +132 -0
- package/dist/rooms/lobby.d.ts +10 -2
- package/dist/rooms/map.d.ts +1236 -17
- package/dist/services/save.d.ts +43 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/localStorage.d.ts +23 -0
- package/package.json +14 -10
- package/src/Gui/DialogGui.ts +19 -4
- package/src/Gui/GameoverGui.ts +39 -0
- package/src/Gui/Gui.ts +23 -1
- package/src/Gui/MenuGui.ts +155 -6
- package/src/Gui/NotificationGui.ts +1 -2
- package/src/Gui/SaveLoadGui.ts +60 -0
- package/src/Gui/ShopGui.ts +146 -16
- package/src/Gui/TitleGui.ts +39 -0
- package/src/Gui/index.ts +15 -2
- package/src/Player/BattleManager.ts +91 -49
- package/src/Player/ClassManager.ts +118 -50
- package/src/Player/ComponentManager.ts +425 -19
- package/src/Player/Components.ts +380 -0
- package/src/Player/EffectManager.ts +81 -44
- package/src/Player/ElementManager.ts +109 -86
- package/src/Player/GoldManager.ts +32 -35
- package/src/Player/GuiManager.ts +308 -150
- package/src/Player/ItemFixture.ts +4 -5
- package/src/Player/ItemManager.ts +774 -355
- package/src/Player/MoveManager.ts +1544 -774
- package/src/Player/ParameterManager.ts +546 -104
- package/src/Player/Player.ts +1163 -88
- package/src/Player/SkillManager.ts +520 -195
- package/src/Player/StateManager.ts +170 -182
- package/src/Player/VariableManager.ts +101 -63
- package/src/RpgServer.ts +525 -63
- package/src/core/context.ts +1 -0
- package/src/decorators/event.ts +61 -0
- package/src/decorators/map.ts +327 -0
- package/src/index.ts +11 -1
- package/src/logs/log.ts +10 -3
- package/src/module.ts +126 -3
- package/src/presets/index.ts +1 -10
- package/src/rooms/BaseRoom.ts +232 -0
- package/src/rooms/lobby.ts +25 -7
- package/src/rooms/map.ts +2502 -194
- package/src/services/save.ts +147 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/localStorage.ts +76 -0
- package/tests/battle.spec.ts +375 -0
- package/tests/change-map.spec.ts +72 -0
- package/tests/class.spec.ts +274 -0
- package/tests/effect.spec.ts +219 -0
- package/tests/element.spec.ts +221 -0
- package/tests/event.spec.ts +80 -0
- package/tests/gold.spec.ts +99 -0
- package/tests/item.spec.ts +609 -0
- package/tests/module.spec.ts +38 -0
- package/tests/move.spec.ts +601 -0
- package/tests/player-param.spec.ts +28 -0
- package/tests/prediction-reconciliation.spec.ts +182 -0
- package/tests/random-move.spec.ts +65 -0
- package/tests/skill.spec.ts +658 -0
- package/tests/state.spec.ts +467 -0
- package/tests/variable.spec.ts +185 -0
- package/tests/world-maps.spec.ts +896 -0
- package/vite.config.ts +16 -0
- package/dist/Player/Event.d.ts +0 -0
- package/src/Player/Event.ts +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { isInstanceOf, isString, Item, type
|
|
2
|
-
import {
|
|
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 {
|
|
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
|
|
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
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
165
|
+
* Item Manager Mixin
|
|
27
166
|
*
|
|
28
|
-
*
|
|
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
|
|
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
|
|
34
|
-
Base
|
|
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
|
-
*
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
114
|
-
instance.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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(
|
|
491
|
+
return this.addItem(item, nb);
|
|
223
492
|
}
|
|
224
493
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
408
|
-
|
|
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
|
-
|
|
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(
|
|
414
|
-
this["execMethod"]("onUseFailed", [this],
|
|
572
|
+
this.removeItem(itemClass);
|
|
573
|
+
this["execMethod"]("onUseFailed", [this], hookTarget);
|
|
415
574
|
throw ItemLog.chanceToUseFailed(itemId);
|
|
416
575
|
}
|
|
417
|
-
(this as any).applyEffect?.(
|
|
418
|
-
(this as any).applyStates?.(this,
|
|
419
|
-
this["execMethod"]("onUse", [this],
|
|
420
|
-
this.removeItem(
|
|
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
|
|
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 &&
|
|
614
|
+
if ((item as any).equipped && equipState) {
|
|
500
615
|
throw ItemLog.isAlreadyEquiped(itemId);
|
|
501
616
|
}
|
|
502
|
-
(item as any).equipped =
|
|
503
|
-
if (!
|
|
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
|
-
|
|
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
|
}
|