载入中...
搜索中...
未找到
InventorySystem.cpp
浏览该文件的文档.
2#include "inventory/Bag.h"
4#include "inventory/Item.h"
5
6#include <algorithm>
7#include <cmath>
8
9namespace eve::inventory {
10
11namespace {
12
13bool tagsIntersect(const std::vector<std::string> &a, const std::vector<std::string> &b) {
14 for (const auto &t : a) {
15 if (std::find(b.begin(), b.end(), t) != b.end()) return true;
16 }
17 return false;
18}
19
20bool propsEqual(const std::unordered_map<std::string, std::string> &a,
21 const std::unordered_map<std::string, std::string> &b) {
22 return a == b;
23}
24
25} // namespace
26
27std::unordered_map<std::string, InventorySystem::AcceptFn> &InventorySystem::acceptRules() {
28 static std::unordered_map<std::string, AcceptFn> t;
29 return t;
30}
31
32std::unordered_map<std::string, InventorySystem::CapacityFn> &InventorySystem::capacityPolicies() {
33 static std::unordered_map<std::string, CapacityFn> t;
34 return t;
35}
36
37std::unordered_map<std::string, InventorySystem::StackFn> &InventorySystem::stackRules() {
38 static std::unordered_map<std::string, StackFn> t;
39 return t;
40}
41
42std::unordered_map<std::string, InventorySystem::ChangeHook> &InventorySystem::changeHooks() {
43 static std::unordered_map<std::string, ChangeHook> t;
44 return t;
45}
46
47std::vector<InventoryChangeEvent> &InventorySystem::eventQueue() {
48 static std::vector<InventoryChangeEvent> q;
49 return q;
50}
51
52int &InventorySystem::instanceCounter() {
53 static int c = 1;
54 return c;
55}
56
57bool &InventorySystem::builtinsReady() {
58 static bool ready = false;
59 return ready;
60}
61
63 if (name.empty() || !fn) return;
64 acceptRules()[name] = std::move(fn);
65}
66
67void InventorySystem::unregisterAcceptRule(const std::string &name) { acceptRules().erase(name); }
68
69bool InventorySystem::hasAcceptRule(const std::string &name) {
71 return acceptRules().count(name) > 0;
72}
73
75 if (name.empty() || !fn) return;
76 capacityPolicies()[name] = std::move(fn);
77}
78
80 capacityPolicies().erase(name);
81}
82
83bool InventorySystem::hasCapacityPolicy(const std::string &name) {
85 return capacityPolicies().count(name) > 0;
86}
87
89 if (name.empty() || !fn) return;
90 stackRules()[name] = std::move(fn);
91}
92
93void InventorySystem::unregisterStackRule(const std::string &name) { stackRules().erase(name); }
94
95bool InventorySystem::hasStackRule(const std::string &name) {
97 return stackRules().count(name) > 0;
98}
99
101 if (name.empty() || !fn) return;
102 changeHooks()[name] = std::move(fn);
103}
104
105void InventorySystem::unregisterChangeHook(const std::string &name) { changeHooks().erase(name); }
106
107bool InventorySystem::hasChangeHook(const std::string &name) {
108 return changeHooks().count(name) > 0;
109}
110
112 if (builtinsReady()) return;
113 builtinsReady() = true;
114
115 registerAcceptRule("any", [](const Bag &, const ItemDefinition &, int, std::string *) {
116 return true;
117 });
118
119 registerAcceptRule("default", [](const Bag &bag, const ItemDefinition &def, int,
120 std::string *reason) {
121 if (!bag.rejectTags().empty() && tagsIntersect(def.tags, bag.rejectTags())) {
122 if (reason) *reason = "rejected_tag";
123 return false;
124 }
125 if (!bag.acceptTags().empty() && !tagsIntersect(def.tags, bag.acceptTags())) {
126 if (reason) *reason = "accept_tag_mismatch";
127 return false;
128 }
129 return true;
130 });
131
132 auto slotsOk = [](const Bag &bag, const ItemDefinition &def, int quantity, std::string *reason) {
133 int need = quantity;
134 for (int i = 0; i < bag.getSlotCount() && need > 0; ++i) {
135 int space = freeSpaceInSlot(bag, i, def);
136 if (space > 0) need -= space;
137 }
138 if (need > 0) {
139 if (reason) *reason = "no_slot";
140 return false;
141 }
142 return true;
143 };
144
145 auto weightOk = [](const Bag &bag, const ItemDefinition &def, int quantity, std::string *reason) {
146 if (bag.getMaxWeight() <= 0.f) return true;
147 float add = def.weight * float(quantity);
148 if (usedWeight(&bag) + add > bag.getMaxWeight() + 1e-6f) {
149 if (reason) *reason = "over_weight";
150 return false;
151 }
152 return true;
153 };
154
155 auto volumeOk = [](const Bag &bag, const ItemDefinition &def, int quantity, std::string *reason) {
156 if (bag.getMaxVolume() <= 0.f) return true;
157 float add = def.volume * float(quantity);
158 if (usedVolume(&bag) + add > bag.getMaxVolume() + 1e-6f) {
159 if (reason) *reason = "over_volume";
160 return false;
161 }
162 return true;
163 };
164
165 registerCapacityPolicy("unlimited",
166 [](const Bag &, const ItemDefinition &, int, std::string *) { return true; });
167 registerCapacityPolicy("slots", slotsOk);
168 registerCapacityPolicy("weight", weightOk);
169 registerCapacityPolicy("volume", volumeOk);
170 registerCapacityPolicy("slotsAndWeight", [slotsOk, weightOk](const Bag &bag,
171 const ItemDefinition &def,
172 int quantity, std::string *reason) {
173 return slotsOk(bag, def, quantity, reason) && weightOk(bag, def, quantity, reason);
174 });
175 registerCapacityPolicy("slotsWeightVolume",
176 [slotsOk, weightOk, volumeOk](const Bag &bag, const ItemDefinition &def,
177 int quantity, std::string *reason) {
178 return slotsOk(bag, def, quantity, reason) &&
179 weightOk(bag, def, quantity, reason) &&
180 volumeOk(bag, def, quantity, reason);
181 });
182
183 registerStackRule("sameItem",
184 [](const ItemStack &a, const ItemStack &b, const ItemDefinition &) {
185 return !a.empty() && !b.empty() && a.itemId == b.itemId;
186 });
187 registerStackRule("sameItemAndProps",
188 [](const ItemStack &a, const ItemStack &b, const ItemDefinition &) {
189 return !a.empty() && !b.empty() && a.itemId == b.itemId &&
190 propsEqual(a.props, b.props) &&
191 std::fabs(a.durability - b.durability) < 1e-6f;
192 });
193 registerStackRule("never", [](const ItemStack &, const ItemStack &, const ItemDefinition &) {
194 return false;
195 });
196}
197
198int InventorySystem::nextInstanceId() { return instanceCounter()++; }
199
200void InventorySystem::emit(InventoryChangeEvent ev) {
201 for (auto &kv : changeHooks()) {
202 if (kv.second) kv.second(ev);
203 }
204 eventQueue().push_back(std::move(ev));
205}
206
207void InventorySystem::pushEvent(InventoryChangeEvent ev) { emit(std::move(ev)); }
208
209void InventorySystem::pollEvents(std::vector<InventoryChangeEvent> &out) {
210 out = eventQueue();
211 eventQueue().clear();
212}
213
214void InventorySystem::clearEvents() { eventQueue().clear(); }
215
216const std::vector<InventoryChangeEvent> &InventorySystem::events() { return eventQueue(); }
217
218bool InventorySystem::canStackTogether(const Bag &bag, const ItemStack &a, const ItemStack &b,
219 const ItemDefinition &def) {
221 auto it = stackRules().find(bag.getStackRule());
222 if (it == stackRules().end() || !it->second) {
223 return !a.empty() && !b.empty() && a.itemId == b.itemId;
224 }
225 return it->second(a, b, def);
226}
227
228bool InventorySystem::checkAccept(const Bag &bag, const ItemDefinition &def, int quantity,
229 std::string *reason) {
231 auto it = acceptRules().find(bag.getAcceptRule());
232 if (it == acceptRules().end() || !it->second) {
233 if (reason) *reason = "unknown_accept_rule";
234 return false;
235 }
236 return it->second(bag, def, quantity, reason);
237}
238
239bool InventorySystem::checkCapacity(const Bag &bag, const ItemDefinition &def, int quantity,
240 std::string *reason) {
242 auto it = capacityPolicies().find(bag.getCapacityPolicy());
243 if (it == capacityPolicies().end() || !it->second) {
244 if (reason) *reason = "unknown_capacity_policy";
245 return false;
246 }
247 return it->second(bag, def, quantity, reason);
248}
249
250int InventorySystem::freeSpaceInSlot(const Bag &bag, int slot, const ItemDefinition &def) {
251 if (slot < 0 || slot >= bag.getSlotCount()) return 0;
252 const auto &s = bag.slots()[size_t(slot)];
253 int maxStack = def.maxStack > 0 ? def.maxStack : 1;
254 // If the bag's stack rule rejects merging two identical probes, treat every
255 // slot as capacity 1 (e.g. built-in "never").
256 {
257 ItemStack probeA;
258 probeA.itemId = def.id;
259 probeA.quantity = 1;
260 ItemStack probeB = probeA;
261 probeB.instanceId = 1;
262 if (!canStackTogether(bag, probeA, probeB, def)) maxStack = 1;
263 }
264 if (s.empty()) return maxStack;
265 ItemStack probe;
266 probe.itemId = def.id;
267 probe.quantity = 1;
268 if (!canStackTogether(bag, s, probe, def)) return 0;
269 return std::max(0, maxStack - s.quantity);
270}
271
272bool InventorySystem::canAdd(Bag *bag, const std::string &itemId, int quantity,
273 std::string *reason) {
274 if (!bag || quantity <= 0) {
275 if (reason) *reason = "invalid_args";
276 return false;
277 }
278 const ItemDefinition *def = ItemRegistry::find(itemId);
279 if (!def) {
280 if (reason) *reason = "unknown_item";
281 return false;
282 }
283 if (!checkAccept(*bag, *def, quantity, reason)) return false;
284 if (!checkCapacity(*bag, *def, quantity, reason)) return false;
285 return true;
286}
287
288int InventorySystem::addItem(Bag *bag, const std::string &itemId, int quantity) {
289 if (!bag || quantity <= 0) return 0;
290 const ItemDefinition *def = ItemRegistry::find(itemId);
291 if (!def) return 0;
292 // Accept is all-or-nothing for the requested item type (not quantity-scaled).
293 if (!checkAccept(*bag, *def, 1, nullptr)) return 0;
294
295 int remaining = quantity;
296 int added = 0;
297
298 auto maxFittable = [&](int space) -> int {
299 int lo = 0, hi = std::min(space, remaining);
300 while (lo < hi) {
301 int mid = lo + (hi - lo + 1) / 2;
302 if (checkCapacity(*bag, *def, mid, nullptr))
303 lo = mid;
304 else
305 hi = mid - 1;
306 }
307 return lo;
308 };
309
310 // Pass 1: fill existing stacks.
311 for (int i = 0; i < bag->getSlotCount() && remaining > 0; ++i) {
312 int space = freeSpaceInSlot(*bag, i, *def);
313 auto &s = bag->slots()[size_t(i)];
314 if (s.empty() || space <= 0) continue;
315 int put = maxFittable(space);
316 if (put <= 0) continue;
317 s.quantity += put;
318 remaining -= put;
319 added += put;
320 }
321
322 // Pass 2: empty slots.
323 for (int i = 0; i < bag->getSlotCount() && remaining > 0; ++i) {
324 auto &s = bag->slots()[size_t(i)];
325 if (!s.empty()) continue;
326 int space = freeSpaceInSlot(*bag, i, *def);
327 int put = maxFittable(space);
328 if (put <= 0) continue;
329 s.instanceId = nextInstanceId();
330 s.itemId = def->id;
331 s.quantity = put;
332 s.durability = -1.f;
333 s.props.clear();
334 s.tags.clear();
335 remaining -= put;
336 added += put;
337 }
338
339 if (added > 0) {
341 ev.action = "add";
342 ev.bagId = bag->getId();
343 ev.itemId = itemId;
344 ev.quantity = added;
345 emit(std::move(ev));
346 }
347 return added;
348}
349
350int InventorySystem::removeItem(Bag *bag, const std::string &itemId, int quantity) {
351 if (!bag || quantity <= 0 || itemId.empty()) return 0;
352 int remaining = quantity;
353 int removed = 0;
354 for (int i = 0; i < bag->getSlotCount() && remaining > 0; ++i) {
355 auto &s = bag->slots()[size_t(i)];
356 if (s.empty() || s.itemId != itemId) continue;
357 int take = std::min(s.quantity, remaining);
358 s.quantity -= take;
359 remaining -= take;
360 removed += take;
361 if (s.quantity <= 0) s.clear();
362 }
363 if (removed > 0) {
365 ev.action = "remove";
366 ev.bagId = bag->getId();
367 ev.itemId = itemId;
368 ev.quantity = removed;
369 emit(std::move(ev));
370 }
371 return removed;
372}
373
374int InventorySystem::removeAt(Bag *bag, int slot, int quantity) {
375 if (!bag || slot < 0 || slot >= bag->getSlotCount() || quantity <= 0) return 0;
376 auto &s = bag->slots()[size_t(slot)];
377 if (s.empty()) return 0;
378 int take = std::min(s.quantity, quantity);
379 std::string itemId = s.itemId;
380 s.quantity -= take;
381 if (s.quantity <= 0) s.clear();
383 ev.action = "remove";
384 ev.bagId = bag->getId();
385 ev.itemId = itemId;
386 ev.quantity = take;
387 ev.slot = slot;
388 emit(std::move(ev));
389 return take;
390}
391
392bool InventorySystem::swapSlots(Bag *bag, int slotA, int slotB) {
393 if (!bag || slotA < 0 || slotB < 0 || slotA >= bag->getSlotCount() ||
394 slotB >= bag->getSlotCount() || slotA == slotB)
395 return false;
396 std::swap(bag->slots()[size_t(slotA)], bag->slots()[size_t(slotB)]);
398 ev.action = "swap";
399 ev.bagId = bag->getId();
400 ev.slot = slotA;
401 ev.otherSlot = slotB;
402 emit(std::move(ev));
403 return true;
404}
405
406bool InventorySystem::moveSlot(Bag *bag, int fromSlot, int toSlot) {
407 if (!bag || fromSlot < 0 || toSlot < 0 || fromSlot >= bag->getSlotCount() ||
408 toSlot >= bag->getSlotCount() || fromSlot == toSlot)
409 return false;
410 auto &from = bag->slots()[size_t(fromSlot)];
411 auto &to = bag->slots()[size_t(toSlot)];
412 if (from.empty()) return false;
413
414 if (to.empty()) {
415 to = from;
416 from.clear();
418 ev.action = "move";
419 ev.bagId = bag->getId();
420 ev.itemId = to.itemId;
421 ev.quantity = to.quantity;
422 ev.slot = fromSlot;
423 ev.otherSlot = toSlot;
424 emit(std::move(ev));
425 return true;
426 }
427
428 const ItemDefinition *def = ItemRegistry::find(from.itemId);
429 if (!def) return false;
430 if (canStackTogether(*bag, from, to, *def)) {
431 int maxStack = def->maxStack > 0 ? def->maxStack : 1;
432 int space = maxStack - to.quantity;
433 if (space <= 0) return false;
434 int put = std::min(space, from.quantity);
435 to.quantity += put;
436 from.quantity -= put;
437 if (from.quantity <= 0) from.clear();
439 ev.action = "merge";
440 ev.bagId = bag->getId();
441 ev.itemId = to.itemId;
442 ev.quantity = put;
443 ev.slot = fromSlot;
444 ev.otherSlot = toSlot;
445 emit(std::move(ev));
446 return true;
447 }
448 return swapSlots(bag, fromSlot, toSlot);
449}
450
451bool InventorySystem::splitStack(Bag *bag, int slot, int quantity, int toSlot) {
452 if (!bag || slot < 0 || toSlot < 0 || slot >= bag->getSlotCount() ||
453 toSlot >= bag->getSlotCount() || slot == toSlot || quantity <= 0)
454 return false;
455 auto &from = bag->slots()[size_t(slot)];
456 auto &to = bag->slots()[size_t(toSlot)];
457 if (from.empty() || !to.empty()) return false;
458 if (quantity >= from.quantity) return false;
459
460 to = from;
461 to.instanceId = nextInstanceId();
462 to.quantity = quantity;
463 from.quantity -= quantity;
464
466 ev.action = "split";
467 ev.bagId = bag->getId();
468 ev.itemId = from.itemId;
469 ev.quantity = quantity;
470 ev.slot = slot;
471 ev.otherSlot = toSlot;
472 emit(std::move(ev));
473 return true;
474}
475
476int InventorySystem::transfer(Bag *from, Bag *to, const std::string &itemId, int quantity) {
477 if (!from || !to || quantity <= 0 || itemId.empty()) return 0;
478 int available = countItem(from, itemId);
479 int want = std::min(available, quantity);
480 if (want <= 0) return 0;
481
482 for (int n = want; n >= 1; --n) {
483 if (!canAdd(to, itemId, n, nullptr)) continue;
484
485 // Detach from source without emitting per-slot remove events.
486 int remaining = n;
487 for (int i = 0; i < from->getSlotCount() && remaining > 0; ++i) {
488 auto &s = from->slots()[size_t(i)];
489 if (s.empty() || s.itemId != itemId) continue;
490 int take = std::min(s.quantity, remaining);
491 s.quantity -= take;
492 remaining -= take;
493 if (s.quantity <= 0) s.clear();
494 }
495
496 int added = addItem(to, itemId, n);
497 if (added < n) {
498 // Roll back shortfall into source (best-effort).
499 int needRestore = n - added;
500 addItem(from, itemId, needRestore);
501 }
502 if (added > 0) {
504 ev.action = "transfer";
505 ev.bagId = from->getId();
506 ev.otherBagId = to->getId();
507 ev.itemId = itemId;
508 ev.quantity = added;
509 emit(std::move(ev));
510 }
511 return added;
512 }
513 return 0;
514}
515
516int InventorySystem::transferSlot(Bag *from, int fromSlot, Bag *to, int quantity) {
517 if (!from || !to || fromSlot < 0 || fromSlot >= from->getSlotCount() || quantity <= 0)
518 return 0;
519 auto &s = from->slots()[size_t(fromSlot)];
520 if (s.empty()) return 0;
521 int want = std::min(s.quantity, quantity);
522 std::string itemId = s.itemId;
523
524 for (int n = want; n >= 1; --n) {
525 if (!canAdd(to, itemId, n, nullptr)) continue;
526
527 // Prefer preserving instance/props when moving a whole stack into an empty target slot.
528 if (n == s.quantity) {
529 int empty = -1;
530 for (int i = 0; i < to->getSlotCount(); ++i) {
531 if (to->slots()[size_t(i)].empty()) {
532 empty = i;
533 break;
534 }
535 }
536 const ItemDefinition *def = ItemRegistry::find(itemId);
537 bool canPlaceWhole = empty >= 0 && def != nullptr;
538 if (canPlaceWhole) {
539 // Also allow merging into an existing compatible stack instead.
540 bool merged = false;
541 for (int i = 0; i < to->getSlotCount(); ++i) {
542 auto &ts = to->slots()[size_t(i)];
543 if (ts.empty()) continue;
544 if (!canStackTogether(*to, s, ts, *def)) continue;
545 int maxStack = def->maxStack > 0 ? def->maxStack : 1;
546 int space = maxStack - ts.quantity;
547 if (space < n) continue;
548 if (!checkCapacity(*to, *def, n, nullptr)) continue;
549 ts.quantity += n;
550 s.clear();
552 ev.action = "transfer";
553 ev.bagId = from->getId();
554 ev.otherBagId = to->getId();
555 ev.itemId = itemId;
556 ev.quantity = n;
557 ev.slot = fromSlot;
558 ev.otherSlot = i;
559 emit(std::move(ev));
560 merged = true;
561 break;
562 }
563 if (merged) return n;
564
565 if (checkCapacity(*to, *def, n, nullptr) && checkAccept(*to, *def, n, nullptr)) {
566 to->slots()[size_t(empty)] = s;
567 s.clear();
569 ev.action = "transfer";
570 ev.bagId = from->getId();
571 ev.otherBagId = to->getId();
572 ev.itemId = itemId;
573 ev.quantity = n;
574 ev.slot = fromSlot;
575 ev.otherSlot = empty;
576 emit(std::move(ev));
577 return n;
578 }
579 }
580 }
581
582 s.quantity -= n;
583 if (s.quantity <= 0) s.clear();
584 int added = addItem(to, itemId, n);
585 if (added < n) {
586 // Restore remainder into original slot if possible.
587 auto &fs = from->slots()[size_t(fromSlot)];
588 if (fs.empty()) {
589 fs.itemId = itemId;
590 fs.quantity = n - added;
591 fs.instanceId = nextInstanceId();
592 } else if (fs.itemId == itemId) {
593 fs.quantity += (n - added);
594 } else {
595 addItem(from, itemId, n - added);
596 }
597 }
598 if (added > 0) {
600 ev.action = "transfer";
601 ev.bagId = from->getId();
602 ev.otherBagId = to->getId();
603 ev.itemId = itemId;
604 ev.quantity = added;
605 ev.slot = fromSlot;
606 emit(std::move(ev));
607 }
608 return added;
609 }
610 return 0;
611}
612
613int InventorySystem::countItem(const Bag *bag, const std::string &itemId) {
614 if (!bag) return 0;
615 int n = 0;
616 for (const auto &s : bag->slots()) {
617 if (!s.empty() && s.itemId == itemId) n += s.quantity;
618 }
619 return n;
620}
621
622int InventorySystem::findItem(const Bag *bag, const std::string &itemId) {
623 if (!bag) return -1;
624 for (int i = 0; i < bag->getSlotCount(); ++i) {
625 const auto &s = bag->slots()[size_t(i)];
626 if (!s.empty() && s.itemId == itemId) return i;
627 }
628 return -1;
629}
630
631int InventorySystem::findItemByTag(const Bag *bag, const std::string &tag) {
632 if (!bag || tag.empty()) return -1;
633 for (int i = 0; i < bag->getSlotCount(); ++i) {
634 const auto &s = bag->slots()[size_t(i)];
635 if (s.empty()) continue;
636 if (s.hasTag(tag)) return i;
637 const ItemDefinition *def = ItemRegistry::find(s.itemId);
638 if (def && def->hasTag(tag)) return i;
639 }
640 return -1;
641}
642
644 if (!bag) return 0.f;
645 float w = 0.f;
646 for (const auto &s : bag->slots()) {
647 if (s.empty()) continue;
648 const ItemDefinition *def = ItemRegistry::find(s.itemId);
649 if (def) w += def->weight * float(s.quantity);
650 }
651 return w;
652}
653
655 if (!bag) return 0.f;
656 float v = 0.f;
657 for (const auto &s : bag->slots()) {
658 if (s.empty()) continue;
659 const ItemDefinition *def = ItemRegistry::find(s.itemId);
660 if (def) v += def->volume * float(s.quantity);
661 }
662 return v;
663}
664
666 if (!bag) return 0;
667 int n = 0;
668 for (const auto &s : bag->slots()) {
669 if (!s.empty()) ++n;
670 }
671 return n;
672}
673
675 if (!bag) return;
676 for (auto &s : bag->slots()) s.clear();
678 ev.action = "remove";
679 ev.bagId = bag->getId();
680 ev.quantity = 0;
681 emit(std::move(ev));
682}
683
684bool InventorySystem::equipFromBag(EquipmentSet *eq, const std::string &equipSlot, Bag *bag,
685 int bagSlot) {
686 if (!eq || !bag) return false;
687 std::string reason;
688 if (!eq->canEquipFromBag(bag, bagSlot, equipSlot, &reason)) return false;
689
690 auto &bs = bag->slots()[size_t(bagSlot)];
691 ItemStack moving = bs;
692 // Equipment slots hold one logical item (quantity preserved but typically 1).
693 bs.clear();
694
695 auto *dest = eq->stackAt(equipSlot);
696 if (!dest) {
697 // restore
698 bag->slots()[size_t(bagSlot)] = moving;
699 return false;
700 }
701
702 // If slot occupied, try swap into bagSlot (now empty).
703 if (!dest->empty()) {
704 bag->slots()[size_t(bagSlot)] = *dest;
705 }
706 *dest = moving;
707
709 ev.action = "equip";
710 ev.bagId = bag->getId();
711 ev.itemId = dest->itemId;
712 ev.quantity = dest->quantity;
713 ev.slot = bagSlot;
714 ev.equipSlot = equipSlot;
715 emit(std::move(ev));
716 return true;
717}
718
719bool InventorySystem::unequipToBag(EquipmentSet *eq, const std::string &equipSlot, Bag *bag) {
720 if (!eq || !bag) return false;
721 auto *src = eq->stackAt(equipSlot);
722 if (!src || src->empty()) return false;
723
724 // Find empty slot first to preserve instance/props.
725 int empty = -1;
726 for (int i = 0; i < bag->getSlotCount(); ++i) {
727 if (bag->slots()[size_t(i)].empty()) {
728 empty = i;
729 break;
730 }
731 }
732 if (empty < 0) {
733 // Fall back to addItem (may stack, losing instance identity).
734 if (!canAdd(bag, src->itemId, src->quantity, nullptr)) return false;
735 std::string itemId = src->itemId;
736 int qty = src->quantity;
737 src->clear();
738 int added = addItem(bag, itemId, qty);
739 if (added < qty) {
740 // restore
741 src->itemId = itemId;
742 src->quantity = qty - added;
743 src->instanceId = nextInstanceId();
744 }
746 ev.action = "unequip";
747 ev.bagId = bag->getId();
748 ev.itemId = itemId;
749 ev.quantity = added;
750 ev.equipSlot = equipSlot;
751 emit(std::move(ev));
752 return added > 0;
753 }
754
755 bag->slots()[size_t(empty)] = *src;
756 std::string itemId = src->itemId;
757 int qty = src->quantity;
758 src->clear();
759
761 ev.action = "unequip";
762 ev.bagId = bag->getId();
763 ev.itemId = itemId;
764 ev.quantity = qty;
765 ev.slot = empty;
766 ev.equipSlot = equipSlot;
767 emit(std::move(ev));
768 return true;
769}
770
771} // namespace eve::inventory
bool removed
glm::vec3 n
Definition Grass.cpp:64
int w
uint32_t a
uint32_t b
uint32_t c
const char * name
Definition RockMesh.cpp:21
SettlementPipeline::Stage fn
int v
uint32_t s
Definition Weather.cpp:28
格子型物品容器(脚本可直接操作)。
Definition Bag.h:17
std::string getId() const
容器 id(变更事件定位用)。
Definition Bag.h:30
float getMaxVolume() const
Definition Bag.h:45
const std::vector< ItemStack > & slots() const
Definition Bag.h:101
const std::vector< std::string > & rejectTags() const
Definition Bag.h:104
float getMaxWeight() const
重量 / 体积上限(容量策略用)。
Definition Bag.h:43
const std::vector< std::string > & acceptTags() const
Definition Bag.h:103
std::string getStackRule() const
Definition Bag.h:53
int getSlotCount() const
格数。
Definition Bag.h:38
const ItemStack * stackAt(const std::string &slotName) const
Definition Equipment.cpp:68
bool canEquipFromBag(Bag *bag, int bagSlot, const std::string &equipSlot, std::string *reason=nullptr) const
Definition Equipment.cpp:83
static void unregisterStackRule(const std::string &name)
static int addItem(Bag *bag, const std::string &itemId, int quantity)
返回实际放入数量(可能部分成功)。
static void registerStackRule(const std::string &name, StackFn fn)
static bool equipFromBag(EquipmentSet *eq, const std::string &equipSlot, Bag *bag, int bagSlot)
static void ensureBuiltins()
确保内置规则已注册(模块首次使用时自动调用)。
static const std::vector< InventoryChangeEvent > & events()
static bool unequipToBag(EquipmentSet *eq, const std::string &equipSlot, Bag *bag)
static bool canAdd(Bag *bag, const std::string &itemId, int quantity, std::string *reason=nullptr)
static bool swapSlots(Bag *bag, int slotA, int slotB)
static float usedWeight(const Bag *bag)
static bool hasChangeHook(const std::string &name)
std::function< void(const InventoryChangeEvent &ev)> ChangeHook
static void registerCapacityPolicy(const std::string &name, CapacityFn fn)
static float usedVolume(const Bag *bag)
std::function< bool(const Bag &bag, const ItemDefinition &def, int quantity, std::string *reason)> CapacityFn
static void unregisterCapacityPolicy(const std::string &name)
static void pushEvent(InventoryChangeEvent ev)
static bool splitStack(Bag *bag, int slot, int quantity, int toSlot)
std::function< bool(const Bag &bag, const ItemDefinition &def, int quantity, std::string *reason)> AcceptFn
static void registerAcceptRule(const std::string &name, AcceptFn fn)
static int countItem(const Bag *bag, const std::string &itemId)
static void registerChangeHook(const std::string &name, ChangeHook fn)
static int usedSlotCount(const Bag *bag)
static bool hasStackRule(const std::string &name)
static int transfer(Bag *from, Bag *to, const std::string &itemId, int quantity)
static bool hasAcceptRule(const std::string &name)
static void unregisterAcceptRule(const std::string &name)
std::function< bool(const ItemStack &a, const ItemStack &b, const ItemDefinition &def)> StackFn
static void unregisterChangeHook(const std::string &name)
static int removeAt(Bag *bag, int slot, int quantity)
static int removeItem(Bag *bag, const std::string &itemId, int quantity)
static bool moveSlot(Bag *bag, int fromSlot, int toSlot)
static bool hasCapacityPolicy(const std::string &name)
static int transferSlot(Bag *from, int fromSlot, Bag *to, int quantity)
static int findItemByTag(const Bag *bag, const std::string &tag)
static void pollEvents(std::vector< InventoryChangeEvent > &out)
static int findItem(const Bag *bag, const std::string &itemId)
static const ItemDefinition * find(const std::string &id)
Definition Item.cpp:53
运行时容器:固定格数的背包 / 箱子 / 商店栏等。 行为由 InventorySystem 提供;本类暴露便于脚本绑定的薄封装方法。
Definition Bag.cpp:6
一次成功库存变更的事件(供脚本 poll / C++ hook)。
Definition ItemTypes.h:46
std::string action
add/remove/move/swap/split/merge/transfer/equip/unequip
Definition ItemTypes.h:47
物品模板(进程级注册表中的定义,不含运行时数量)。
Definition ItemTypes.h:13
std::vector< std::string > tags
Definition ItemTypes.h:19
bool hasTag(const std::string &tag) const
Definition Item.cpp:11
容器中的一格堆叠(空槽:itemId 为空或 quantity <= 0)。
Definition ItemTypes.h:30