@nice2dev/game-engine 0.1.0 → 1.0.2
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/CHANGELOG.md +193 -1
- package/dist/cjs/audio/AudioBridge.js +454 -0
- package/dist/cjs/audio/AudioBridge.js.map +1 -0
- package/dist/cjs/devtools/GameplayAnalytics.js +651 -0
- package/dist/cjs/devtools/GameplayAnalytics.js.map +1 -0
- package/dist/cjs/dialogue/DialogueSystem.js +1023 -0
- package/dist/cjs/dialogue/DialogueSystem.js.map +1 -0
- package/dist/cjs/editor/NiceGameEditor.js +569 -71
- package/dist/cjs/editor/NiceGameEditor.js.map +1 -1
- package/dist/cjs/engine/SaveSystemV2.js +494 -0
- package/dist/cjs/engine/SaveSystemV2.js.map +1 -0
- package/dist/cjs/i18n/useTranslation.js +11 -11
- package/dist/cjs/index.js +90 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/input/GamepadNavigation.js +21 -21
- package/dist/cjs/input/useGamepads.js +6 -6
- package/dist/cjs/integration/IconSprite.js +281 -0
- package/dist/cjs/integration/IconSprite.js.map +1 -0
- package/dist/cjs/inventory/InventorySystem.js +930 -0
- package/dist/cjs/inventory/InventorySystem.js.map +1 -0
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/AbortController.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/AccessTokenHttpClient.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/DefaultHttpClient.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/DefaultReconnectPolicy.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/Errors.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/FetchHttpClient.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HandshakeProtocol.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HeaderNames.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HttpClient.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HttpConnection.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HubConnection.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HubConnectionBuilder.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/IHubProtocol.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/ILogger.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/ITransport.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/JsonHubProtocol.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/Loggers.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/LongPollingTransport.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/MessageBuffer.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/ServerSentEventsTransport.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/Subject.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/TextMessageFormat.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/Utils.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/WebSocketTransport.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/XhrHttpClient.js.map +1 -1
- package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/pkg-version.js.map +1 -1
- package/dist/cjs/quest/QuestSystem.js +924 -0
- package/dist/cjs/quest/QuestSystem.js.map +1 -0
- package/dist/cjs/rendering/WebGPURenderPipeline.js +658 -0
- package/dist/cjs/rendering/WebGPURenderPipeline.js.map +1 -0
- package/dist/cjs/xr/ARVR.js.map +1 -1
- package/dist/esm/audio/AudioBridge.js +446 -0
- package/dist/esm/audio/AudioBridge.js.map +1 -0
- package/dist/esm/devtools/GameplayAnalytics.js +639 -0
- package/dist/esm/devtools/GameplayAnalytics.js.map +1 -0
- package/dist/esm/dialogue/DialogueSystem.js +1008 -0
- package/dist/esm/dialogue/DialogueSystem.js.map +1 -0
- package/dist/esm/editor/NiceGameEditor.js +556 -58
- package/dist/esm/editor/NiceGameEditor.js.map +1 -1
- package/dist/esm/engine/SaveSystemV2.js +487 -0
- package/dist/esm/engine/SaveSystemV2.js.map +1 -0
- package/dist/esm/index.js +11 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/integration/IconSprite.js +266 -0
- package/dist/esm/integration/IconSprite.js.map +1 -0
- package/dist/esm/inventory/InventorySystem.js +924 -0
- package/dist/esm/inventory/InventorySystem.js.map +1 -0
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/AbortController.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/AccessTokenHttpClient.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/DefaultHttpClient.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/DefaultReconnectPolicy.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/Errors.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/FetchHttpClient.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HandshakeProtocol.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HeaderNames.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HttpClient.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HttpConnection.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HubConnection.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HubConnectionBuilder.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/IHubProtocol.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/ILogger.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/ITransport.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/JsonHubProtocol.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/Loggers.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/LongPollingTransport.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/MessageBuffer.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/ServerSentEventsTransport.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/Subject.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/TextMessageFormat.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/Utils.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/WebSocketTransport.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/XhrHttpClient.js.map +1 -1
- package/dist/esm/node_modules/@microsoft/signalr/dist/esm/pkg-version.js.map +1 -1
- package/dist/esm/quest/QuestSystem.js +916 -0
- package/dist/esm/quest/QuestSystem.js.map +1 -0
- package/dist/esm/rendering/WebGPURenderPipeline.js +642 -0
- package/dist/esm/rendering/WebGPURenderPipeline.js.map +1 -0
- package/dist/esm/xr/ARVR.js.map +1 -1
- package/dist/types/__tests__/setup.d.ts +1 -1
- package/dist/types/audio/AudioBridge.d.ts +199 -0
- package/dist/types/devtools/GameplayAnalytics.d.ts +279 -0
- package/dist/types/dialogue/DialogueSystem.d.ts +326 -0
- package/dist/types/dialogue/index.d.ts +2 -0
- package/dist/types/editor/NiceGameEditor.d.ts +12 -1
- package/dist/types/engine/SaveSystemV2.d.ts +155 -0
- package/dist/types/index.d.ts +19 -3
- package/dist/types/integration/IconSprite.d.ts +196 -0
- package/dist/types/inventory/InventorySystem.d.ts +336 -0
- package/dist/types/performance/WebGPUCompute.d.ts +0 -10
- package/dist/types/quest/QuestSystem.d.ts +287 -0
- package/dist/types/rendering/WebGPURenderPipeline.d.ts +255 -0
- package/package.json +7 -1
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
|
|
5
|
+
/* ────────────────────────────────────────────────────────────────
|
|
6
|
+
Inventory System
|
|
7
|
+
|
|
8
|
+
A comprehensive inventory management system with:
|
|
9
|
+
- Multiple container types (player, chest, shop, crafting)
|
|
10
|
+
- Equipment slots with stat modifiers
|
|
11
|
+
- Item stacking, splitting, and merging
|
|
12
|
+
- Weight/capacity limits
|
|
13
|
+
- Item categories and rarity
|
|
14
|
+
- Crafting recipes
|
|
15
|
+
- Trade/shop transactions
|
|
16
|
+
- Loot tables and drop rates
|
|
17
|
+
- Item durability and repair
|
|
18
|
+
- Consumables with effects
|
|
19
|
+
- State persistence
|
|
20
|
+
──────────────────────────────────────────────────────────────── */
|
|
21
|
+
const DEFAULT_CONFIG = {
|
|
22
|
+
defaultSlots: 40,
|
|
23
|
+
enableWeight: true,
|
|
24
|
+
enableDurability: true,
|
|
25
|
+
autoStack: true,
|
|
26
|
+
autoSort: false,
|
|
27
|
+
maxContainersPerEntity: 5,
|
|
28
|
+
};
|
|
29
|
+
class InventoryManager {
|
|
30
|
+
constructor(config = {}) {
|
|
31
|
+
this.eventBus = null;
|
|
32
|
+
// Registries
|
|
33
|
+
this.itemDefinitions = new Map();
|
|
34
|
+
this.containers = new Map();
|
|
35
|
+
this.equipment = new Map();
|
|
36
|
+
this.recipes = new Map();
|
|
37
|
+
this.lootTables = new Map();
|
|
38
|
+
this.shops = new Map();
|
|
39
|
+
this.learnedRecipes = new Map();
|
|
40
|
+
this.cooldowns = new Map(); // instanceId -> lastUsed
|
|
41
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
42
|
+
}
|
|
43
|
+
/* ── Initialization ─────────────────────────────────────────── */
|
|
44
|
+
setEventBus(bus) {
|
|
45
|
+
this.eventBus = bus;
|
|
46
|
+
}
|
|
47
|
+
emit(event) {
|
|
48
|
+
var _a;
|
|
49
|
+
(_a = this.eventBus) === null || _a === void 0 ? void 0 : _a.emit(`inventory:${event.type}`, event);
|
|
50
|
+
}
|
|
51
|
+
/* ── Item Definitions ───────────────────────────────────────── */
|
|
52
|
+
registerItem(definition) {
|
|
53
|
+
this.itemDefinitions.set(definition.id, definition);
|
|
54
|
+
}
|
|
55
|
+
registerItems(definitions) {
|
|
56
|
+
definitions.forEach(def => this.registerItem(def));
|
|
57
|
+
}
|
|
58
|
+
getItemDefinition(itemId) {
|
|
59
|
+
return this.itemDefinitions.get(itemId);
|
|
60
|
+
}
|
|
61
|
+
getAllItemDefinitions() {
|
|
62
|
+
return Array.from(this.itemDefinitions.values());
|
|
63
|
+
}
|
|
64
|
+
/* ── Container Management ───────────────────────────────────── */
|
|
65
|
+
createContainer(type, ownerId, options = {}) {
|
|
66
|
+
var _a, _b, _c, _d;
|
|
67
|
+
const id = (_a = options.id) !== null && _a !== void 0 ? _a : `container_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
68
|
+
const maxSlots = (_b = options.maxSlots) !== null && _b !== void 0 ? _b : this.config.defaultSlots;
|
|
69
|
+
const container = {
|
|
70
|
+
id,
|
|
71
|
+
type,
|
|
72
|
+
name: (_c = options.name) !== null && _c !== void 0 ? _c : `${type} container`,
|
|
73
|
+
ownerId,
|
|
74
|
+
maxSlots,
|
|
75
|
+
maxWeight: options.maxWeight,
|
|
76
|
+
slots: Array.from({ length: maxSlots }, (_, i) => ({ index: i, item: null })),
|
|
77
|
+
currency: (_d = options.currency) !== null && _d !== void 0 ? _d : new Map(),
|
|
78
|
+
allowedCategories: options.allowedCategories,
|
|
79
|
+
disallowedCategories: options.disallowedCategories,
|
|
80
|
+
};
|
|
81
|
+
this.containers.set(id, container);
|
|
82
|
+
this.emit({ type: 'container:created', containerId: id, ownerId, timestamp: Date.now() });
|
|
83
|
+
return container;
|
|
84
|
+
}
|
|
85
|
+
getContainer(containerId) {
|
|
86
|
+
return this.containers.get(containerId);
|
|
87
|
+
}
|
|
88
|
+
getContainersByOwner(ownerId) {
|
|
89
|
+
return Array.from(this.containers.values()).filter(c => c.ownerId === ownerId);
|
|
90
|
+
}
|
|
91
|
+
destroyContainer(containerId) {
|
|
92
|
+
const container = this.containers.get(containerId);
|
|
93
|
+
if (!container)
|
|
94
|
+
return false;
|
|
95
|
+
this.containers.delete(containerId);
|
|
96
|
+
this.emit({ type: 'container:destroyed', containerId, ownerId: container.ownerId, timestamp: Date.now() });
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
/* ── Item Instance Creation ─────────────────────────────────── */
|
|
100
|
+
createItemInstance(itemId, count = 1, options = {}) {
|
|
101
|
+
var _a;
|
|
102
|
+
const def = this.itemDefinitions.get(itemId);
|
|
103
|
+
if (!def)
|
|
104
|
+
return null;
|
|
105
|
+
// Extract count from options to prevent override
|
|
106
|
+
const { count: _ignoredCount, ...restOptions } = options;
|
|
107
|
+
const instance = {
|
|
108
|
+
instanceId: (_a = restOptions.instanceId) !== null && _a !== void 0 ? _a : `item_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
|
|
109
|
+
itemId,
|
|
110
|
+
count: Math.min(count, def.maxStack),
|
|
111
|
+
durability: def.maxDurability,
|
|
112
|
+
createdAt: Date.now(),
|
|
113
|
+
...restOptions,
|
|
114
|
+
};
|
|
115
|
+
return instance;
|
|
116
|
+
}
|
|
117
|
+
/* ── Basic Operations ───────────────────────────────────────── */
|
|
118
|
+
addItem(containerId, itemId, count = 1, options = {}) {
|
|
119
|
+
var _a, _b, _c;
|
|
120
|
+
const container = this.containers.get(containerId);
|
|
121
|
+
if (!container)
|
|
122
|
+
return null;
|
|
123
|
+
const def = this.itemDefinitions.get(itemId);
|
|
124
|
+
if (!def)
|
|
125
|
+
return null;
|
|
126
|
+
// Check category restrictions
|
|
127
|
+
if (container.allowedCategories && !container.allowedCategories.includes(def.category)) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
if ((_a = container.disallowedCategories) === null || _a === void 0 ? void 0 : _a.includes(def.category)) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
// Check weight
|
|
134
|
+
if (this.config.enableWeight && container.maxWeight !== undefined) {
|
|
135
|
+
const currentWeight = this.getContainerWeight(containerId);
|
|
136
|
+
if (currentWeight + (def.weight * count) > container.maxWeight) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Try to stack with existing items first
|
|
141
|
+
if (this.config.autoStack && def.maxStack > 1) {
|
|
142
|
+
for (const slot of container.slots) {
|
|
143
|
+
if (slot.item && slot.item.itemId === itemId && slot.item.count < def.maxStack) {
|
|
144
|
+
const space = def.maxStack - slot.item.count;
|
|
145
|
+
const toAdd = Math.min(count, space);
|
|
146
|
+
slot.item.count += toAdd;
|
|
147
|
+
count -= toAdd;
|
|
148
|
+
this.emit({
|
|
149
|
+
type: 'item:stacked',
|
|
150
|
+
containerId,
|
|
151
|
+
ownerId: container.ownerId,
|
|
152
|
+
item: slot.item,
|
|
153
|
+
slot: slot.index,
|
|
154
|
+
count: toAdd,
|
|
155
|
+
timestamp: Date.now(),
|
|
156
|
+
});
|
|
157
|
+
if (count <= 0)
|
|
158
|
+
return slot.item;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Find empty slot for remaining
|
|
163
|
+
while (count > 0) {
|
|
164
|
+
const emptySlot = container.slots.find(s => !s.item && !s.locked);
|
|
165
|
+
if (!emptySlot)
|
|
166
|
+
return null; // No space
|
|
167
|
+
const stackCount = Math.min(count, def.maxStack);
|
|
168
|
+
const instance = this.createItemInstance(itemId, stackCount, options);
|
|
169
|
+
if (!instance)
|
|
170
|
+
return null;
|
|
171
|
+
emptySlot.item = instance;
|
|
172
|
+
count -= stackCount;
|
|
173
|
+
this.emit({
|
|
174
|
+
type: 'item:added',
|
|
175
|
+
containerId,
|
|
176
|
+
ownerId: container.ownerId,
|
|
177
|
+
item: instance,
|
|
178
|
+
slot: emptySlot.index,
|
|
179
|
+
count: stackCount,
|
|
180
|
+
timestamp: Date.now(),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return (_c = (_b = container.slots.find(s => { var _a; return ((_a = s.item) === null || _a === void 0 ? void 0 : _a.itemId) === itemId; })) === null || _b === void 0 ? void 0 : _b.item) !== null && _c !== void 0 ? _c : null;
|
|
184
|
+
}
|
|
185
|
+
removeItem(containerId, slotIndex, count = 1) {
|
|
186
|
+
const container = this.containers.get(containerId);
|
|
187
|
+
if (!container)
|
|
188
|
+
return null;
|
|
189
|
+
const slot = container.slots[slotIndex];
|
|
190
|
+
if (!(slot === null || slot === void 0 ? void 0 : slot.item))
|
|
191
|
+
return null;
|
|
192
|
+
const removed = { ...slot.item };
|
|
193
|
+
if (slot.item.count <= count) {
|
|
194
|
+
slot.item = null;
|
|
195
|
+
// removed already has the correct count from the spread above
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
slot.item.count -= count;
|
|
199
|
+
removed.count = count;
|
|
200
|
+
}
|
|
201
|
+
this.emit({
|
|
202
|
+
type: 'item:removed',
|
|
203
|
+
containerId,
|
|
204
|
+
ownerId: container.ownerId,
|
|
205
|
+
item: removed,
|
|
206
|
+
slot: slotIndex,
|
|
207
|
+
count: removed.count,
|
|
208
|
+
timestamp: Date.now(),
|
|
209
|
+
});
|
|
210
|
+
return removed;
|
|
211
|
+
}
|
|
212
|
+
moveItem(fromContainerId, fromSlot, toContainerId, toSlot) {
|
|
213
|
+
const fromContainer = this.containers.get(fromContainerId);
|
|
214
|
+
const toContainer = this.containers.get(toContainerId);
|
|
215
|
+
if (!fromContainer || !toContainer)
|
|
216
|
+
return false;
|
|
217
|
+
const sourceSlot = fromContainer.slots[fromSlot];
|
|
218
|
+
const targetSlot = toContainer.slots[toSlot];
|
|
219
|
+
if (!(sourceSlot === null || sourceSlot === void 0 ? void 0 : sourceSlot.item) || !targetSlot)
|
|
220
|
+
return false;
|
|
221
|
+
const def = this.itemDefinitions.get(sourceSlot.item.itemId);
|
|
222
|
+
if (!def)
|
|
223
|
+
return false;
|
|
224
|
+
// Check target container restrictions
|
|
225
|
+
if (toContainer.allowedCategories && !toContainer.allowedCategories.includes(def.category)) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
// Swap or stack
|
|
229
|
+
if (targetSlot.item) {
|
|
230
|
+
if (targetSlot.item.itemId === sourceSlot.item.itemId &&
|
|
231
|
+
targetSlot.item.count < def.maxStack) {
|
|
232
|
+
// Stack
|
|
233
|
+
const space = def.maxStack - targetSlot.item.count;
|
|
234
|
+
const toMove = Math.min(sourceSlot.item.count, space);
|
|
235
|
+
targetSlot.item.count += toMove;
|
|
236
|
+
sourceSlot.item.count -= toMove;
|
|
237
|
+
if (sourceSlot.item.count <= 0)
|
|
238
|
+
sourceSlot.item = null;
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
// Swap
|
|
242
|
+
const temp = sourceSlot.item;
|
|
243
|
+
sourceSlot.item = targetSlot.item;
|
|
244
|
+
targetSlot.item = temp;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
// Move to empty slot
|
|
249
|
+
targetSlot.item = sourceSlot.item;
|
|
250
|
+
sourceSlot.item = null;
|
|
251
|
+
}
|
|
252
|
+
this.emit({
|
|
253
|
+
type: 'item:moved',
|
|
254
|
+
containerId: fromContainerId,
|
|
255
|
+
ownerId: fromContainer.ownerId,
|
|
256
|
+
fromSlot,
|
|
257
|
+
toSlot,
|
|
258
|
+
timestamp: Date.now(),
|
|
259
|
+
});
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
splitStack(containerId, slotIndex, splitCount) {
|
|
263
|
+
const container = this.containers.get(containerId);
|
|
264
|
+
if (!container)
|
|
265
|
+
return false;
|
|
266
|
+
const slot = container.slots[slotIndex];
|
|
267
|
+
if (!(slot === null || slot === void 0 ? void 0 : slot.item) || slot.item.count <= splitCount)
|
|
268
|
+
return false;
|
|
269
|
+
const emptySlot = container.slots.find(s => !s.item && !s.locked);
|
|
270
|
+
if (!emptySlot)
|
|
271
|
+
return false;
|
|
272
|
+
const newInstance = this.createItemInstance(slot.item.itemId, splitCount, { ...slot.item, instanceId: undefined });
|
|
273
|
+
if (!newInstance)
|
|
274
|
+
return false;
|
|
275
|
+
slot.item.count -= splitCount;
|
|
276
|
+
emptySlot.item = newInstance;
|
|
277
|
+
this.emit({
|
|
278
|
+
type: 'item:split',
|
|
279
|
+
containerId,
|
|
280
|
+
ownerId: container.ownerId,
|
|
281
|
+
item: newInstance,
|
|
282
|
+
fromSlot: slotIndex,
|
|
283
|
+
toSlot: emptySlot.index,
|
|
284
|
+
count: splitCount,
|
|
285
|
+
timestamp: Date.now(),
|
|
286
|
+
});
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
/* ── Weight & Capacity ──────────────────────────────────────── */
|
|
290
|
+
getContainerWeight(containerId) {
|
|
291
|
+
const container = this.containers.get(containerId);
|
|
292
|
+
if (!container)
|
|
293
|
+
return 0;
|
|
294
|
+
let weight = 0;
|
|
295
|
+
for (const slot of container.slots) {
|
|
296
|
+
if (slot.item) {
|
|
297
|
+
const def = this.itemDefinitions.get(slot.item.itemId);
|
|
298
|
+
if (def)
|
|
299
|
+
weight += def.weight * slot.item.count;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return weight;
|
|
303
|
+
}
|
|
304
|
+
getContainerFreeSlots(containerId) {
|
|
305
|
+
const container = this.containers.get(containerId);
|
|
306
|
+
if (!container)
|
|
307
|
+
return 0;
|
|
308
|
+
return container.slots.filter(s => !s.item && !s.locked).length;
|
|
309
|
+
}
|
|
310
|
+
/* ── Equipment System ───────────────────────────────────────── */
|
|
311
|
+
initializeEquipment(ownerId) {
|
|
312
|
+
const state = {
|
|
313
|
+
ownerId,
|
|
314
|
+
slots: new Map(),
|
|
315
|
+
totalStats: new Map(),
|
|
316
|
+
};
|
|
317
|
+
// Initialize all slots as empty
|
|
318
|
+
const allSlots = [
|
|
319
|
+
'head', 'chest', 'legs', 'feet', 'hands', 'shoulders', 'back', 'waist',
|
|
320
|
+
'mainHand', 'offHand', 'ring1', 'ring2', 'neck', 'trinket1', 'trinket2',
|
|
321
|
+
];
|
|
322
|
+
allSlots.forEach(slot => state.slots.set(slot, null));
|
|
323
|
+
this.equipment.set(ownerId, state);
|
|
324
|
+
return state;
|
|
325
|
+
}
|
|
326
|
+
getEquipment(ownerId) {
|
|
327
|
+
return this.equipment.get(ownerId);
|
|
328
|
+
}
|
|
329
|
+
equipItem(ownerId, containerId, slotIndex) {
|
|
330
|
+
let equipState = this.equipment.get(ownerId);
|
|
331
|
+
if (!equipState) {
|
|
332
|
+
equipState = this.initializeEquipment(ownerId);
|
|
333
|
+
}
|
|
334
|
+
const container = this.containers.get(containerId);
|
|
335
|
+
if (!container)
|
|
336
|
+
return false;
|
|
337
|
+
const invSlot = container.slots[slotIndex];
|
|
338
|
+
if (!(invSlot === null || invSlot === void 0 ? void 0 : invSlot.item))
|
|
339
|
+
return false;
|
|
340
|
+
const def = this.itemDefinitions.get(invSlot.item.itemId);
|
|
341
|
+
if (!(def === null || def === void 0 ? void 0 : def.equipSlot))
|
|
342
|
+
return false;
|
|
343
|
+
// Check requirements
|
|
344
|
+
// (In real implementation, would check level, class, stats)
|
|
345
|
+
// Handle two-hand weapons
|
|
346
|
+
let targetSlot = def.equipSlot;
|
|
347
|
+
if (targetSlot === 'twoHand') {
|
|
348
|
+
// Unequip both hands
|
|
349
|
+
const mainHand = equipState.slots.get('mainHand');
|
|
350
|
+
const offHand = equipState.slots.get('offHand');
|
|
351
|
+
if (mainHand)
|
|
352
|
+
this.unequipToContainer(ownerId, 'mainHand', containerId);
|
|
353
|
+
if (offHand)
|
|
354
|
+
this.unequipToContainer(ownerId, 'offHand', containerId);
|
|
355
|
+
targetSlot = 'mainHand';
|
|
356
|
+
}
|
|
357
|
+
// Clone the item to equip BEFORE modifying the slot
|
|
358
|
+
const itemToEquip = { ...invSlot.item };
|
|
359
|
+
// Swap with existing equipped item
|
|
360
|
+
const currentEquipped = equipState.slots.get(targetSlot);
|
|
361
|
+
if (currentEquipped) {
|
|
362
|
+
// Put currently equipped item back in inventory
|
|
363
|
+
invSlot.item = currentEquipped;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
invSlot.item = null;
|
|
367
|
+
}
|
|
368
|
+
equipState.slots.set(targetSlot, itemToEquip);
|
|
369
|
+
// Apply binding
|
|
370
|
+
if (def.binding === 'onEquip' && !itemToEquip.boundTo) {
|
|
371
|
+
itemToEquip.boundTo = String(ownerId);
|
|
372
|
+
}
|
|
373
|
+
// Recalculate stats
|
|
374
|
+
this.recalculateEquipmentStats(ownerId);
|
|
375
|
+
this.emit({
|
|
376
|
+
type: 'item:equipped',
|
|
377
|
+
ownerId,
|
|
378
|
+
item: itemToEquip,
|
|
379
|
+
slot: slotIndex,
|
|
380
|
+
timestamp: Date.now(),
|
|
381
|
+
});
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
unequipItem(ownerId, equipSlot, containerId) {
|
|
385
|
+
return this.unequipToContainer(ownerId, equipSlot, containerId);
|
|
386
|
+
}
|
|
387
|
+
unequipToContainer(ownerId, equipSlot, containerId) {
|
|
388
|
+
const equipState = this.equipment.get(ownerId);
|
|
389
|
+
if (!equipState)
|
|
390
|
+
return false;
|
|
391
|
+
const equippedItem = equipState.slots.get(equipSlot);
|
|
392
|
+
if (!equippedItem)
|
|
393
|
+
return false;
|
|
394
|
+
const container = this.containers.get(containerId);
|
|
395
|
+
if (!container)
|
|
396
|
+
return false;
|
|
397
|
+
// Find empty slot in container
|
|
398
|
+
const emptySlot = container.slots.find(s => !s.item && !s.locked);
|
|
399
|
+
if (!emptySlot)
|
|
400
|
+
return false; // Inventory full
|
|
401
|
+
emptySlot.item = equippedItem;
|
|
402
|
+
equipState.slots.set(equipSlot, null);
|
|
403
|
+
this.recalculateEquipmentStats(ownerId);
|
|
404
|
+
this.emit({
|
|
405
|
+
type: 'item:unequipped',
|
|
406
|
+
ownerId,
|
|
407
|
+
item: equippedItem,
|
|
408
|
+
slot: emptySlot.index,
|
|
409
|
+
timestamp: Date.now(),
|
|
410
|
+
});
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
recalculateEquipmentStats(ownerId) {
|
|
414
|
+
var _a, _b;
|
|
415
|
+
const equipState = this.equipment.get(ownerId);
|
|
416
|
+
if (!equipState)
|
|
417
|
+
return;
|
|
418
|
+
equipState.totalStats.clear();
|
|
419
|
+
for (const [, item] of equipState.slots) {
|
|
420
|
+
if (!item)
|
|
421
|
+
continue;
|
|
422
|
+
const def = this.itemDefinitions.get(item.itemId);
|
|
423
|
+
if (!(def === null || def === void 0 ? void 0 : def.stats))
|
|
424
|
+
continue;
|
|
425
|
+
for (const mod of def.stats) {
|
|
426
|
+
const current = (_a = equipState.totalStats.get(mod.stat)) !== null && _a !== void 0 ? _a : 0;
|
|
427
|
+
equipState.totalStats.set(mod.stat, current + ((_b = mod.flat) !== null && _b !== void 0 ? _b : 0));
|
|
428
|
+
// Percent modifiers would need separate handling
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/* ── Durability & Repair ────────────────────────────────────── */
|
|
433
|
+
damageDurability(instance, damage) {
|
|
434
|
+
if (!this.config.enableDurability)
|
|
435
|
+
return false;
|
|
436
|
+
if (instance.durability === undefined)
|
|
437
|
+
return false;
|
|
438
|
+
instance.durability = Math.max(0, instance.durability - damage);
|
|
439
|
+
if (instance.durability <= 0) {
|
|
440
|
+
this.emit({
|
|
441
|
+
type: 'item:destroyed',
|
|
442
|
+
item: instance,
|
|
443
|
+
timestamp: Date.now(),
|
|
444
|
+
});
|
|
445
|
+
return true; // Item broken
|
|
446
|
+
}
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
repairItem(instance, amount) {
|
|
450
|
+
const def = this.itemDefinitions.get(instance.itemId);
|
|
451
|
+
if (!(def === null || def === void 0 ? void 0 : def.maxDurability) || instance.durability === undefined)
|
|
452
|
+
return false;
|
|
453
|
+
instance.durability = Math.min(def.maxDurability, amount !== undefined ? instance.durability + amount : def.maxDurability);
|
|
454
|
+
this.emit({
|
|
455
|
+
type: 'item:repaired',
|
|
456
|
+
item: instance,
|
|
457
|
+
timestamp: Date.now(),
|
|
458
|
+
});
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
/* ── Consumables ────────────────────────────────────────────── */
|
|
462
|
+
useItem(containerId, slotIndex, targetEntityId) {
|
|
463
|
+
var _a;
|
|
464
|
+
const container = this.containers.get(containerId);
|
|
465
|
+
if (!container)
|
|
466
|
+
return false;
|
|
467
|
+
const slot = container.slots[slotIndex];
|
|
468
|
+
if (!(slot === null || slot === void 0 ? void 0 : slot.item))
|
|
469
|
+
return false;
|
|
470
|
+
const def = this.itemDefinitions.get(slot.item.itemId);
|
|
471
|
+
if (!def)
|
|
472
|
+
return false;
|
|
473
|
+
// Check cooldown
|
|
474
|
+
if (def.cooldown) {
|
|
475
|
+
const lastUsed = (_a = this.cooldowns.get(slot.item.instanceId)) !== null && _a !== void 0 ? _a : 0;
|
|
476
|
+
if (Date.now() - lastUsed < def.cooldown) {
|
|
477
|
+
return false; // On cooldown
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// Set cooldown
|
|
481
|
+
if (def.cooldown) {
|
|
482
|
+
this.cooldowns.set(slot.item.instanceId, Date.now());
|
|
483
|
+
}
|
|
484
|
+
this.emit({
|
|
485
|
+
type: 'item:used',
|
|
486
|
+
containerId,
|
|
487
|
+
ownerId: container.ownerId,
|
|
488
|
+
item: slot.item,
|
|
489
|
+
slot: slotIndex,
|
|
490
|
+
customData: { targetEntityId, effectId: def.effectId },
|
|
491
|
+
timestamp: Date.now(),
|
|
492
|
+
});
|
|
493
|
+
// Consume if applicable
|
|
494
|
+
if (def.consumeOnUse) {
|
|
495
|
+
slot.item.count--;
|
|
496
|
+
if (slot.item.count <= 0) {
|
|
497
|
+
slot.item = null;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
/* ── Crafting System ────────────────────────────────────────── */
|
|
503
|
+
registerRecipe(recipe) {
|
|
504
|
+
this.recipes.set(recipe.id, recipe);
|
|
505
|
+
}
|
|
506
|
+
learnRecipe(entityId, recipeId) {
|
|
507
|
+
const recipe = this.recipes.get(recipeId);
|
|
508
|
+
if (!recipe || !recipe.requiresLearning)
|
|
509
|
+
return false;
|
|
510
|
+
let learned = this.learnedRecipes.get(entityId);
|
|
511
|
+
if (!learned) {
|
|
512
|
+
learned = new Set();
|
|
513
|
+
this.learnedRecipes.set(entityId, learned);
|
|
514
|
+
}
|
|
515
|
+
learned.add(recipeId);
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
canCraft(entityId, recipeId, containerId) {
|
|
519
|
+
const recipe = this.recipes.get(recipeId);
|
|
520
|
+
if (!recipe)
|
|
521
|
+
return false;
|
|
522
|
+
// Check if recipe is learned
|
|
523
|
+
if (recipe.requiresLearning) {
|
|
524
|
+
const learned = this.learnedRecipes.get(entityId);
|
|
525
|
+
if (!(learned === null || learned === void 0 ? void 0 : learned.has(recipeId)))
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
// Check ingredients
|
|
529
|
+
const container = this.containers.get(containerId);
|
|
530
|
+
if (!container)
|
|
531
|
+
return false;
|
|
532
|
+
for (const ingredient of recipe.ingredients) {
|
|
533
|
+
const available = this.countItemsInContainer(containerId, ingredient.itemId);
|
|
534
|
+
if (available < ingredient.count)
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
craft(entityId, recipeId, containerId) {
|
|
540
|
+
if (!this.canCraft(entityId, recipeId, containerId))
|
|
541
|
+
return null;
|
|
542
|
+
const recipe = this.recipes.get(recipeId);
|
|
543
|
+
this.containers.get(containerId);
|
|
544
|
+
// Check success
|
|
545
|
+
if (Math.random() > recipe.successRate) {
|
|
546
|
+
// Failed - consume ingredients anyway if consumed flag set
|
|
547
|
+
for (const ingredient of recipe.ingredients) {
|
|
548
|
+
if (ingredient.consumed) {
|
|
549
|
+
this.removeItemsByIdFromContainer(containerId, ingredient.itemId, ingredient.count);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
this.emit({
|
|
553
|
+
type: 'craft:failed',
|
|
554
|
+
ownerId: entityId,
|
|
555
|
+
recipeId,
|
|
556
|
+
containerId,
|
|
557
|
+
timestamp: Date.now(),
|
|
558
|
+
});
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
// Consume ingredients
|
|
562
|
+
for (const ingredient of recipe.ingredients) {
|
|
563
|
+
if (ingredient.consumed) {
|
|
564
|
+
this.removeItemsByIdFromContainer(containerId, ingredient.itemId, ingredient.count);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
// Create output
|
|
568
|
+
const output = this.addItem(containerId, recipe.outputItemId, recipe.outputCount);
|
|
569
|
+
this.emit({
|
|
570
|
+
type: 'craft:completed',
|
|
571
|
+
ownerId: entityId,
|
|
572
|
+
recipeId,
|
|
573
|
+
containerId,
|
|
574
|
+
item: output !== null && output !== void 0 ? output : undefined,
|
|
575
|
+
timestamp: Date.now(),
|
|
576
|
+
});
|
|
577
|
+
return output;
|
|
578
|
+
}
|
|
579
|
+
/* ── Loot Tables ────────────────────────────────────────────── */
|
|
580
|
+
registerLootTable(table) {
|
|
581
|
+
this.lootTables.set(table.id, table);
|
|
582
|
+
}
|
|
583
|
+
generateLoot(tableId, modifiers = {}) {
|
|
584
|
+
var _a;
|
|
585
|
+
const table = this.lootTables.get(tableId);
|
|
586
|
+
if (!table)
|
|
587
|
+
return [];
|
|
588
|
+
const loot = [];
|
|
589
|
+
// Guaranteed drops
|
|
590
|
+
if (table.guaranteedDrops) {
|
|
591
|
+
for (const drop of table.guaranteedDrops) {
|
|
592
|
+
const instance = this.createItemInstance(drop.itemId, drop.count);
|
|
593
|
+
if (instance)
|
|
594
|
+
loot.push(instance);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
// Roll on table
|
|
598
|
+
const validEntries = table.entries.filter(e => {
|
|
599
|
+
var _a, _b;
|
|
600
|
+
if (((_a = e.conditions) === null || _a === void 0 ? void 0 : _a.minLevel) && modifiers.levelContext && modifiers.levelContext < e.conditions.minLevel)
|
|
601
|
+
return false;
|
|
602
|
+
if (((_b = e.conditions) === null || _b === void 0 ? void 0 : _b.maxLevel) && modifiers.levelContext && modifiers.levelContext > e.conditions.maxLevel)
|
|
603
|
+
return false;
|
|
604
|
+
return true;
|
|
605
|
+
});
|
|
606
|
+
const totalWeight = validEntries.reduce((sum, e) => sum + e.weight, 0);
|
|
607
|
+
for (let roll = 0; roll < table.rolls; roll++) {
|
|
608
|
+
let random = Math.random() * totalWeight;
|
|
609
|
+
for (const entry of validEntries) {
|
|
610
|
+
random -= entry.weight;
|
|
611
|
+
if (random <= 0) {
|
|
612
|
+
// Apply drop chance with luck modifier
|
|
613
|
+
const effectiveChance = Math.min(1, entry.dropChance + ((_a = modifiers.luckBonus) !== null && _a !== void 0 ? _a : 0));
|
|
614
|
+
if (Math.random() <= effectiveChance) {
|
|
615
|
+
const count = Math.floor(entry.minCount + Math.random() * (entry.maxCount - entry.minCount + 1));
|
|
616
|
+
const instance = this.createItemInstance(entry.itemId, count);
|
|
617
|
+
if (instance)
|
|
618
|
+
loot.push(instance);
|
|
619
|
+
}
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
this.emit({
|
|
625
|
+
type: 'loot:generated',
|
|
626
|
+
customData: { tableId, loot },
|
|
627
|
+
timestamp: Date.now(),
|
|
628
|
+
});
|
|
629
|
+
return loot;
|
|
630
|
+
}
|
|
631
|
+
/* ── Shop System ────────────────────────────────────────────── */
|
|
632
|
+
registerShop(shop) {
|
|
633
|
+
this.shops.set(shop.id, shop);
|
|
634
|
+
}
|
|
635
|
+
getShop(shopId) {
|
|
636
|
+
return this.shops.get(shopId);
|
|
637
|
+
}
|
|
638
|
+
buyFromShop(shopId, listingIndex, quantity, buyerContainerId) {
|
|
639
|
+
var _a, _b, _c;
|
|
640
|
+
const shop = this.shops.get(shopId);
|
|
641
|
+
const container = this.containers.get(buyerContainerId);
|
|
642
|
+
if (!shop || !container)
|
|
643
|
+
return false;
|
|
644
|
+
const listing = shop.listings[listingIndex];
|
|
645
|
+
if (!listing)
|
|
646
|
+
return false;
|
|
647
|
+
// Check stock
|
|
648
|
+
if (listing.stock !== -1 && listing.stock < quantity)
|
|
649
|
+
return false;
|
|
650
|
+
// Calculate price
|
|
651
|
+
const def = this.itemDefinitions.get(listing.itemId);
|
|
652
|
+
if (!def)
|
|
653
|
+
return false;
|
|
654
|
+
const unitPrice = (_a = listing.price) !== null && _a !== void 0 ? _a : Math.ceil(def.value * shop.sellRate);
|
|
655
|
+
const totalPrice = unitPrice * quantity;
|
|
656
|
+
// Check buyer currency
|
|
657
|
+
const buyerCurrency = (_c = (_b = container.currency) === null || _b === void 0 ? void 0 : _b.get(listing.currencyId)) !== null && _c !== void 0 ? _c : 0;
|
|
658
|
+
if (buyerCurrency < totalPrice)
|
|
659
|
+
return false;
|
|
660
|
+
// Execute transaction
|
|
661
|
+
container.currency.set(listing.currencyId, buyerCurrency - totalPrice);
|
|
662
|
+
const purchased = this.addItem(buyerContainerId, listing.itemId, quantity);
|
|
663
|
+
if (!purchased) {
|
|
664
|
+
// Refund if couldn't add item
|
|
665
|
+
container.currency.set(listing.currencyId, buyerCurrency);
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
// Update stock
|
|
669
|
+
if (listing.stock !== -1) {
|
|
670
|
+
listing.stock -= quantity;
|
|
671
|
+
}
|
|
672
|
+
this.emit({
|
|
673
|
+
type: 'shop:bought',
|
|
674
|
+
shopId,
|
|
675
|
+
containerId: buyerContainerId,
|
|
676
|
+
ownerId: container.ownerId,
|
|
677
|
+
itemId: listing.itemId,
|
|
678
|
+
count: quantity,
|
|
679
|
+
currencyId: listing.currencyId,
|
|
680
|
+
amount: totalPrice,
|
|
681
|
+
timestamp: Date.now(),
|
|
682
|
+
});
|
|
683
|
+
return true;
|
|
684
|
+
}
|
|
685
|
+
sellToShop(shopId, sellerContainerId, slotIndex, quantity) {
|
|
686
|
+
var _a, _b;
|
|
687
|
+
const shop = this.shops.get(shopId);
|
|
688
|
+
const container = this.containers.get(sellerContainerId);
|
|
689
|
+
if (!shop || !container)
|
|
690
|
+
return false;
|
|
691
|
+
const slot = container.slots[slotIndex];
|
|
692
|
+
if (!(slot === null || slot === void 0 ? void 0 : slot.item) || slot.item.count < quantity)
|
|
693
|
+
return false;
|
|
694
|
+
const def = this.itemDefinitions.get(slot.item.itemId);
|
|
695
|
+
if (!def || !def.sellable)
|
|
696
|
+
return false;
|
|
697
|
+
// Calculate sell price
|
|
698
|
+
const unitPrice = Math.floor(def.value * shop.buyRate);
|
|
699
|
+
const totalPrice = unitPrice * quantity;
|
|
700
|
+
// Use first accepted currency
|
|
701
|
+
const currencyId = shop.acceptedCurrencies[0];
|
|
702
|
+
if (!currencyId)
|
|
703
|
+
return false;
|
|
704
|
+
// Remove items
|
|
705
|
+
this.removeItem(sellerContainerId, slotIndex, quantity);
|
|
706
|
+
// Add currency
|
|
707
|
+
const currentCurrency = (_b = (_a = container.currency) === null || _a === void 0 ? void 0 : _a.get(currencyId)) !== null && _b !== void 0 ? _b : 0;
|
|
708
|
+
container.currency.set(currencyId, currentCurrency + totalPrice);
|
|
709
|
+
this.emit({
|
|
710
|
+
type: 'shop:sold',
|
|
711
|
+
shopId,
|
|
712
|
+
containerId: sellerContainerId,
|
|
713
|
+
ownerId: container.ownerId,
|
|
714
|
+
itemId: def.id,
|
|
715
|
+
count: quantity,
|
|
716
|
+
currencyId,
|
|
717
|
+
amount: totalPrice,
|
|
718
|
+
timestamp: Date.now(),
|
|
719
|
+
});
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
/* ── Currency ───────────────────────────────────────────────── */
|
|
723
|
+
addCurrency(containerId, currencyId, amount) {
|
|
724
|
+
var _a;
|
|
725
|
+
const container = this.containers.get(containerId);
|
|
726
|
+
if (!container)
|
|
727
|
+
return false;
|
|
728
|
+
if (!container.currency)
|
|
729
|
+
container.currency = new Map();
|
|
730
|
+
const current = (_a = container.currency.get(currencyId)) !== null && _a !== void 0 ? _a : 0;
|
|
731
|
+
container.currency.set(currencyId, current + amount);
|
|
732
|
+
this.emit({
|
|
733
|
+
type: 'currency:changed',
|
|
734
|
+
containerId,
|
|
735
|
+
ownerId: container.ownerId,
|
|
736
|
+
currencyId,
|
|
737
|
+
amount,
|
|
738
|
+
timestamp: Date.now(),
|
|
739
|
+
});
|
|
740
|
+
return true;
|
|
741
|
+
}
|
|
742
|
+
removeCurrency(containerId, currencyId, amount) {
|
|
743
|
+
var _a;
|
|
744
|
+
const container = this.containers.get(containerId);
|
|
745
|
+
if (!(container === null || container === void 0 ? void 0 : container.currency))
|
|
746
|
+
return false;
|
|
747
|
+
const current = (_a = container.currency.get(currencyId)) !== null && _a !== void 0 ? _a : 0;
|
|
748
|
+
if (current < amount)
|
|
749
|
+
return false;
|
|
750
|
+
container.currency.set(currencyId, current - amount);
|
|
751
|
+
this.emit({
|
|
752
|
+
type: 'currency:changed',
|
|
753
|
+
containerId,
|
|
754
|
+
ownerId: container.ownerId,
|
|
755
|
+
currencyId,
|
|
756
|
+
amount: -amount,
|
|
757
|
+
timestamp: Date.now(),
|
|
758
|
+
});
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
getCurrency(containerId, currencyId) {
|
|
762
|
+
var _a, _b, _c;
|
|
763
|
+
return (_c = (_b = (_a = this.containers.get(containerId)) === null || _a === void 0 ? void 0 : _a.currency) === null || _b === void 0 ? void 0 : _b.get(currencyId)) !== null && _c !== void 0 ? _c : 0;
|
|
764
|
+
}
|
|
765
|
+
/* ── Sorting ────────────────────────────────────────────────── */
|
|
766
|
+
sortContainer(containerId, criteria = 'category') {
|
|
767
|
+
const container = this.containers.get(containerId);
|
|
768
|
+
if (!container)
|
|
769
|
+
return;
|
|
770
|
+
const items = container.slots
|
|
771
|
+
.filter(s => s.item && !s.locked)
|
|
772
|
+
.map(s => s.item)
|
|
773
|
+
.sort((a, b) => {
|
|
774
|
+
const defA = this.itemDefinitions.get(a.itemId);
|
|
775
|
+
const defB = this.itemDefinitions.get(b.itemId);
|
|
776
|
+
if (!defA || !defB)
|
|
777
|
+
return 0;
|
|
778
|
+
switch (criteria) {
|
|
779
|
+
case 'category':
|
|
780
|
+
return defA.category.localeCompare(defB.category) || defA.name.localeCompare(defB.name);
|
|
781
|
+
case 'rarity': {
|
|
782
|
+
const rarityOrder = ['common', 'uncommon', 'rare', 'epic', 'legendary', 'mythic', 'unique'];
|
|
783
|
+
return rarityOrder.indexOf(defB.rarity) - rarityOrder.indexOf(defA.rarity);
|
|
784
|
+
}
|
|
785
|
+
case 'name':
|
|
786
|
+
return defA.name.localeCompare(defB.name);
|
|
787
|
+
case 'value':
|
|
788
|
+
return defB.value - defA.value;
|
|
789
|
+
default:
|
|
790
|
+
return 0;
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
// Clear unlocked slots
|
|
794
|
+
container.slots.forEach(s => {
|
|
795
|
+
if (!s.locked)
|
|
796
|
+
s.item = null;
|
|
797
|
+
});
|
|
798
|
+
// Fill with sorted items
|
|
799
|
+
let itemIndex = 0;
|
|
800
|
+
for (const slot of container.slots) {
|
|
801
|
+
if (!slot.locked && itemIndex < items.length) {
|
|
802
|
+
slot.item = items[itemIndex++];
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
container.sorted = true;
|
|
806
|
+
this.emit({
|
|
807
|
+
type: 'container:sorted',
|
|
808
|
+
containerId,
|
|
809
|
+
ownerId: container.ownerId,
|
|
810
|
+
timestamp: Date.now(),
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
/* ── Utility Methods ────────────────────────────────────────── */
|
|
814
|
+
countItemsInContainer(containerId, itemId) {
|
|
815
|
+
const container = this.containers.get(containerId);
|
|
816
|
+
if (!container)
|
|
817
|
+
return 0;
|
|
818
|
+
return container.slots.reduce((sum, slot) => {
|
|
819
|
+
var _a;
|
|
820
|
+
if (((_a = slot.item) === null || _a === void 0 ? void 0 : _a.itemId) === itemId)
|
|
821
|
+
return sum + slot.item.count;
|
|
822
|
+
return sum;
|
|
823
|
+
}, 0);
|
|
824
|
+
}
|
|
825
|
+
removeItemsByIdFromContainer(containerId, itemId, count) {
|
|
826
|
+
const container = this.containers.get(containerId);
|
|
827
|
+
if (!container)
|
|
828
|
+
return 0;
|
|
829
|
+
let removed = 0;
|
|
830
|
+
for (const slot of container.slots) {
|
|
831
|
+
if (!slot.item || slot.item.itemId !== itemId)
|
|
832
|
+
continue;
|
|
833
|
+
const toRemove = Math.min(count - removed, slot.item.count);
|
|
834
|
+
slot.item.count -= toRemove;
|
|
835
|
+
removed += toRemove;
|
|
836
|
+
if (slot.item.count <= 0)
|
|
837
|
+
slot.item = null;
|
|
838
|
+
if (removed >= count)
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
841
|
+
return removed;
|
|
842
|
+
}
|
|
843
|
+
findItem(containerId, itemId) {
|
|
844
|
+
var _a;
|
|
845
|
+
const container = this.containers.get(containerId);
|
|
846
|
+
if (!container)
|
|
847
|
+
return null;
|
|
848
|
+
for (const slot of container.slots) {
|
|
849
|
+
if (((_a = slot.item) === null || _a === void 0 ? void 0 : _a.itemId) === itemId) {
|
|
850
|
+
return { slot, instance: slot.item };
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
/* ── Serialization ──────────────────────────────────────────── */
|
|
856
|
+
serialize() {
|
|
857
|
+
return {
|
|
858
|
+
containers: Array.from(this.containers.entries()).map(([id, c]) => ({
|
|
859
|
+
...c,
|
|
860
|
+
currency: c.currency ? Array.from(c.currency.entries()) : [],
|
|
861
|
+
})),
|
|
862
|
+
equipment: Array.from(this.equipment.entries()).map(([id, e]) => ({
|
|
863
|
+
ownerId: id,
|
|
864
|
+
slots: Array.from(e.slots.entries()),
|
|
865
|
+
totalStats: Array.from(e.totalStats.entries()),
|
|
866
|
+
})),
|
|
867
|
+
learnedRecipes: Array.from(this.learnedRecipes.entries()).map(([id, recipes]) => ({
|
|
868
|
+
entityId: id,
|
|
869
|
+
recipes: Array.from(recipes),
|
|
870
|
+
})),
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
deserialize(snapshot) {
|
|
874
|
+
this.containers.clear();
|
|
875
|
+
this.equipment.clear();
|
|
876
|
+
this.learnedRecipes.clear();
|
|
877
|
+
for (const c of snapshot.containers) {
|
|
878
|
+
this.containers.set(c.id, {
|
|
879
|
+
...c,
|
|
880
|
+
currency: new Map(c.currency),
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
for (const e of snapshot.equipment) {
|
|
884
|
+
this.equipment.set(e.ownerId, {
|
|
885
|
+
ownerId: e.ownerId,
|
|
886
|
+
slots: new Map(e.slots),
|
|
887
|
+
totalStats: new Map(e.totalStats),
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
for (const r of snapshot.learnedRecipes) {
|
|
891
|
+
this.learnedRecipes.set(r.entityId, new Set(r.recipes));
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
896
|
+
REACT INTEGRATION
|
|
897
|
+
══════════════════════════════════════════════════════════════════ */
|
|
898
|
+
const InventoryContext = React.createContext(null);
|
|
899
|
+
const InventoryProvider = ({ manager, children }) => {
|
|
900
|
+
return React.createElement(InventoryContext.Provider, { value: manager }, children);
|
|
901
|
+
};
|
|
902
|
+
function useInventoryManager() {
|
|
903
|
+
const ctx = React.useContext(InventoryContext);
|
|
904
|
+
if (!ctx)
|
|
905
|
+
throw new Error('useInventoryManager must be used within InventoryProvider');
|
|
906
|
+
return ctx;
|
|
907
|
+
}
|
|
908
|
+
function useContainer(containerId) {
|
|
909
|
+
const manager = useInventoryManager();
|
|
910
|
+
const [container, setContainer] = React.useState(() => manager.getContainer(containerId));
|
|
911
|
+
React.useEffect(() => {
|
|
912
|
+
setContainer(manager.getContainer(containerId));
|
|
913
|
+
}, [manager, containerId]);
|
|
914
|
+
return container;
|
|
915
|
+
}
|
|
916
|
+
function useEquipment(ownerId) {
|
|
917
|
+
const manager = useInventoryManager();
|
|
918
|
+
const [equipment, setEquipment] = React.useState(() => manager.getEquipment(ownerId));
|
|
919
|
+
React.useEffect(() => {
|
|
920
|
+
setEquipment(manager.getEquipment(ownerId));
|
|
921
|
+
}, [manager, ownerId]);
|
|
922
|
+
return equipment;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
exports.InventoryManager = InventoryManager;
|
|
926
|
+
exports.InventoryProvider = InventoryProvider;
|
|
927
|
+
exports.useContainer = useContainer;
|
|
928
|
+
exports.useEquipment = useEquipment;
|
|
929
|
+
exports.useInventoryManager = useInventoryManager;
|
|
930
|
+
//# sourceMappingURL=InventorySystem.js.map
|