载入中...
搜索中...
未找到
InventorySystem.cpp
浏览该文件的文档.
2#include "inventory/Bag.h"
4#include "inventory/Item.h"
5
6#include <algorithm>
7#include <cmath>
8#include <limits>
9
10namespace eve::inventory {
11
13 Bag *target = nullptr;
14 std::unique_ptr<Bag> baseline;
15 std::unique_ptr<Bag> candidate;
16 std::vector<InventoryChangeEvent> events;
17 int totalAdded = 0;
18};
19
21 Bag *target = nullptr;
22 std::unique_ptr<Bag> baseline;
23 std::unique_ptr<Bag> candidate;
25 int totalRemoved = 0;
26};
27
31PreparedInventoryAdd &PreparedInventoryAdd::operator=(PreparedInventoryAdd &&) noexcept = default;
36
37namespace {
38
39bool tagsIntersect(const std::vector<std::string> &a, const std::vector<std::string> &b) {
40 for (const auto &t : a) {
41 if (std::find(b.begin(), b.end(), t) != b.end()) return true;
42 }
43 return false;
44}
45
46bool propsEqual(const std::unordered_map<std::string, std::string> &a,
47 const std::unordered_map<std::string, std::string> &b) {
48 return a == b;
49}
50
51} // namespace
52
53std::unordered_map<std::string, InventorySystem::AcceptFn> &InventorySystem::acceptRules() {
54 static std::unordered_map<std::string, AcceptFn> t;
55 return t;
56}
57
58std::unordered_map<std::string, InventorySystem::CapacityFn> &InventorySystem::capacityPolicies() {
59 static std::unordered_map<std::string, CapacityFn> t;
60 return t;
61}
62
63std::unordered_map<std::string, InventorySystem::StackFn> &InventorySystem::stackRules() {
64 static std::unordered_map<std::string, StackFn> t;
65 return t;
66}
67
68std::unordered_map<std::string, InventorySystem::ChangeHook> &InventorySystem::changeHooks() {
69 static std::unordered_map<std::string, ChangeHook> t;
70 return t;
71}
72
73std::vector<InventoryChangeEvent> &InventorySystem::eventQueue() {
74 static std::vector<InventoryChangeEvent> q;
75 return q;
76}
77
78int &InventorySystem::instanceCounter() {
79 static int c = 1;
80 return c;
81}
82
83bool &InventorySystem::builtinsReady() {
84 static bool ready = false;
85 return ready;
86}
87
88bool &InventorySystem::changeHooksSuppressed() {
89 static bool suppressed = false;
90 return suppressed;
91}
92
94 if (name.empty() || !fn) return;
95 acceptRules()[name] = std::move(fn);
96}
97
98void InventorySystem::unregisterAcceptRule(const std::string &name) { acceptRules().erase(name); }
99
100bool InventorySystem::hasAcceptRule(const std::string &name) {
102 return acceptRules().count(name) > 0;
103}
104
106 if (name.empty() || !fn) return;
107 capacityPolicies()[name] = std::move(fn);
108}
109
111 capacityPolicies().erase(name);
112}
113
114bool InventorySystem::hasCapacityPolicy(const std::string &name) {
116 return capacityPolicies().count(name) > 0;
117}
118
120 if (name.empty() || !fn) return;
121 stackRules()[name] = std::move(fn);
122}
123
124void InventorySystem::unregisterStackRule(const std::string &name) { stackRules().erase(name); }
125
126bool InventorySystem::hasStackRule(const std::string &name) {
128 return stackRules().count(name) > 0;
129}
130
132 if (name.empty() || !fn) return;
133 changeHooks()[name] = std::move(fn);
134}
135
136void InventorySystem::unregisterChangeHook(const std::string &name) { changeHooks().erase(name); }
137
138bool InventorySystem::hasChangeHook(const std::string &name) {
139 return changeHooks().count(name) > 0;
140}
141
143 if (builtinsReady()) return;
144 builtinsReady() = true;
145
146 registerAcceptRule("any", [](const Bag &, const ItemDefinition &, int, std::string *) {
147 return true;
148 });
149
150 registerAcceptRule("default", [](const Bag &bag, const ItemDefinition &def, int,
151 std::string *reason) {
152 if (!bag.rejectTags().empty() && tagsIntersect(def.tags, bag.rejectTags())) {
153 if (reason) *reason = "rejected_tag";
154 return false;
155 }
156 if (!bag.acceptTags().empty() && !tagsIntersect(def.tags, bag.acceptTags())) {
157 if (reason) *reason = "accept_tag_mismatch";
158 return false;
159 }
160 return true;
161 });
162
163 auto slotsOk = [](const Bag &bag, const ItemDefinition &def, int quantity, std::string *reason) {
164 int need = quantity;
165 for (int i = 0; i < bag.getSlotCount() && need > 0; ++i) {
166 int space = freeSpaceInSlot(bag, i, def);
167 if (space > 0) need -= space;
168 }
169 if (need > 0) {
170 if (reason) *reason = "no_slot";
171 return false;
172 }
173 return true;
174 };
175
176 auto weightOk = [](const Bag &bag, const ItemDefinition &def, int quantity, std::string *reason) {
177 if (bag.getMaxWeight() <= 0.f) return true;
178 float add = def.weight * float(quantity);
179 if (usedWeight(&bag) + add > bag.getMaxWeight() + 1e-6f) {
180 if (reason) *reason = "over_weight";
181 return false;
182 }
183 return true;
184 };
185
186 auto volumeOk = [](const Bag &bag, const ItemDefinition &def, int quantity, std::string *reason) {
187 if (bag.getMaxVolume() <= 0.f) return true;
188 float add = def.volume * float(quantity);
189 if (usedVolume(&bag) + add > bag.getMaxVolume() + 1e-6f) {
190 if (reason) *reason = "over_volume";
191 return false;
192 }
193 return true;
194 };
195
196 registerCapacityPolicy("unlimited",
197 [](const Bag &, const ItemDefinition &, int, std::string *) { return true; });
198 registerCapacityPolicy("slots", slotsOk);
199 registerCapacityPolicy("weight", weightOk);
200 registerCapacityPolicy("volume", volumeOk);
201 registerCapacityPolicy("slotsAndWeight", [slotsOk, weightOk](const Bag &bag,
202 const ItemDefinition &def,
203 int quantity, std::string *reason) {
204 return slotsOk(bag, def, quantity, reason) && weightOk(bag, def, quantity, reason);
205 });
206 registerCapacityPolicy("slotsWeightVolume",
207 [slotsOk, weightOk, volumeOk](const Bag &bag, const ItemDefinition &def,
208 int quantity, std::string *reason) {
209 return slotsOk(bag, def, quantity, reason) &&
210 weightOk(bag, def, quantity, reason) &&
211 volumeOk(bag, def, quantity, reason);
212 });
213
214 registerStackRule("sameItem",
215 [](const ItemStack &a, const ItemStack &b, const ItemDefinition &) {
216 return !a.empty() && !b.empty() && a.itemId == b.itemId;
217 });
218 registerStackRule("sameItemAndProps",
219 [](const ItemStack &a, const ItemStack &b, const ItemDefinition &) {
220 return !a.empty() && !b.empty() && a.itemId == b.itemId &&
221 propsEqual(a.props, b.props) &&
222 std::fabs(a.durability - b.durability) < 1e-6f;
223 });
224 registerStackRule("never", [](const ItemStack &, const ItemStack &, const ItemDefinition &) {
225 return false;
226 });
227}
228
229int InventorySystem::nextInstanceId() { return instanceCounter()++; }
230
231void InventorySystem::ensureNextInstanceIdAbove(int usedInstanceId) noexcept {
232 if (instanceCounter() <= usedInstanceId) instanceCounter() = usedInstanceId + 1;
233}
234
235std::unique_ptr<Bag> InventorySystem::cloneBag(const Bag &source) {
236 auto clone = std::make_unique<Bag>(source.getSlotCount());
237 clone->id_ = source.id_;
238 clone->kind_ = source.kind_;
239 clone->maxWeight_ = source.maxWeight_;
240 clone->maxVolume_ = source.maxVolume_;
241 clone->acceptRule_ = source.acceptRule_;
242 clone->capacityPolicy_ = source.capacityPolicy_;
243 clone->stackRule_ = source.stackRule_;
244 clone->acceptTags_ = source.acceptTags_;
245 clone->rejectTags_ = source.rejectTags_;
246 clone->slots_ = source.slots_;
247 clone->extra_ = source.extra_;
248 return clone;
249}
250
251bool InventorySystem::bagsEqual(const Bag &target, const Bag &baseline) {
252 if (target.id_ != baseline.id_ || target.kind_ != baseline.kind_ ||
253 target.maxWeight_ != baseline.maxWeight_ || target.maxVolume_ != baseline.maxVolume_ ||
254 target.acceptRule_ != baseline.acceptRule_ ||
255 target.capacityPolicy_ != baseline.capacityPolicy_ || target.stackRule_ != baseline.stackRule_ ||
256 target.acceptTags_ != baseline.acceptTags_ || target.rejectTags_ != baseline.rejectTags_ ||
257 target.extra_ != baseline.extra_ || target.slots_.size() != baseline.slots_.size())
258 return false;
259 for (std::size_t i = 0; i < target.slots_.size(); ++i) {
260 const auto &a = target.slots_[i];
261 const auto &b = baseline.slots_[i];
262 if (a.instanceId != b.instanceId || a.itemId != b.itemId || a.quantity != b.quantity ||
263 a.durability != b.durability || a.props != b.props || a.tags != b.tags)
264 return false;
265 }
266 return true;
267}
268
269void InventorySystem::emit(InventoryChangeEvent ev) {
270 if (!changeHooksSuppressed()) {
271 for (auto &kv : changeHooks()) {
272 if (!kv.second) continue;
273 try {
274 kv.second(ev);
275 } catch (...) {
276 // Hooks are post-commit observers. One faulty observer must not
277 // unwind an already committed authoritative inventory change or
278 // prevent the remaining observers and poll queue from seeing it.
279 }
280 }
281 }
282 eventQueue().push_back(std::move(ev));
283}
284
285void InventorySystem::pushEvent(InventoryChangeEvent ev) { emit(std::move(ev)); }
286
287void InventorySystem::pollEvents(std::vector<InventoryChangeEvent> &out) {
288 out = eventQueue();
289 eventQueue().clear();
290}
291
292void InventorySystem::clearEvents() { eventQueue().clear(); }
293
294const std::vector<InventoryChangeEvent> &InventorySystem::events() { return eventQueue(); }
295
296bool InventorySystem::canStackTogether(const Bag &bag, const ItemStack &a, const ItemStack &b,
297 const ItemDefinition &def) {
299 auto it = stackRules().find(bag.getStackRule());
300 if (it == stackRules().end() || !it->second) {
301 return !a.empty() && !b.empty() && a.itemId == b.itemId;
302 }
303 return it->second(a, b, def);
304}
305
306bool InventorySystem::checkAccept(const Bag &bag, const ItemDefinition &def, int quantity,
307 std::string *reason) {
309 auto it = acceptRules().find(bag.getAcceptRule());
310 if (it == acceptRules().end() || !it->second) {
311 if (reason) *reason = "unknown_accept_rule";
312 return false;
313 }
314 return it->second(bag, def, quantity, reason);
315}
316
317bool InventorySystem::checkCapacity(const Bag &bag, const ItemDefinition &def, int quantity,
318 std::string *reason) {
320 auto it = capacityPolicies().find(bag.getCapacityPolicy());
321 if (it == capacityPolicies().end() || !it->second) {
322 if (reason) *reason = "unknown_capacity_policy";
323 return false;
324 }
325 return it->second(bag, def, quantity, reason);
326}
327
328int InventorySystem::freeSpaceInSlot(const Bag &bag, int slot, const ItemDefinition &def) {
329 if (slot < 0 || slot >= bag.getSlotCount()) return 0;
330 const auto &s = bag.slots()[size_t(slot)];
331 int maxStack = def.maxStack > 0 ? def.maxStack : 1;
332 // If the bag's stack rule rejects merging two identical probes, treat every
333 // slot as capacity 1 (e.g. built-in "never").
334 {
335 ItemStack probeA;
336 probeA.itemId = def.id;
337 probeA.quantity = 1;
338 ItemStack probeB = probeA;
339 probeB.instanceId = 1;
340 if (!canStackTogether(bag, probeA, probeB, def)) maxStack = 1;
341 }
342 if (s.empty()) return maxStack;
343 ItemStack probe;
344 probe.itemId = def.id;
345 probe.quantity = 1;
346 if (!canStackTogether(bag, s, probe, def)) return 0;
347 return std::max(0, maxStack - s.quantity);
348}
349
350bool InventorySystem::canAdd(Bag *bag, const std::string &itemId, int quantity,
351 std::string *reason) {
352 if (!bag || quantity <= 0) {
353 if (reason) *reason = "invalid_args";
354 return false;
355 }
357 if (!def) {
358 if (reason) *reason = "unknown_item";
359 return false;
360 }
361 if (!checkAccept(*bag, *def, quantity, reason)) return false;
362 if (!checkCapacity(*bag, *def, quantity, reason)) return false;
363 return true;
364}
365
366int InventorySystem::addItem(Bag *bag, const std::string &itemId, int quantity) {
367 return addItemImpl(bag, itemId, quantity, true);
368}
369
370int InventorySystem::addItemImpl(Bag *bag, const std::string &itemId, int quantity,
371 bool publish) {
372 if (!bag || quantity <= 0) return 0;
374 if (!def) return 0;
375 // Accept is all-or-nothing for the requested item type (not quantity-scaled).
376 if (!checkAccept(*bag, *def, 1, nullptr)) return 0;
377
378 int remaining = quantity;
379 int added = 0;
380
381 auto maxFittable = [&](int space) -> int {
382 int lo = 0, hi = std::min(space, remaining);
383 while (lo < hi) {
384 int mid = lo + (hi - lo + 1) / 2;
385 if (checkCapacity(*bag, *def, mid, nullptr))
386 lo = mid;
387 else
388 hi = mid - 1;
389 }
390 return lo;
391 };
392
393 // Pass 1: fill existing stacks.
394 for (int i = 0; i < bag->getSlotCount() && remaining > 0; ++i) {
395 int space = freeSpaceInSlot(*bag, i, *def);
396 auto &s = bag->slots()[size_t(i)];
397 if (s.empty() || space <= 0) continue;
398 int put = maxFittable(space);
399 if (put <= 0) continue;
400 s.quantity += put;
401 remaining -= put;
402 added += put;
403 }
404
405 // Pass 2: empty slots.
406 for (int i = 0; i < bag->getSlotCount() && remaining > 0; ++i) {
407 auto &s = bag->slots()[size_t(i)];
408 if (!s.empty()) continue;
409 int space = freeSpaceInSlot(*bag, i, *def);
410 int put = maxFittable(space);
411 if (put <= 0) continue;
412 s.instanceId = nextInstanceId();
413 s.itemId = def->id;
414 s.quantity = put;
415 s.durability = -1.f;
416 s.props.clear();
417 s.tags.clear();
418 remaining -= put;
419 added += put;
420 }
421
422 if (publish && added > 0) {
423 InventoryChangeEvent ev;
424 ev.action = "add";
425 ev.bagId = bag->getId();
426 ev.itemId = itemId;
427 ev.quantity = added;
428 emit(std::move(ev));
429 }
430 return added;
431}
432
434InventorySystem::prepareAddBatch(Bag *bag, const std::vector<InventoryItemGrant> &grants) {
435 if (!bag)
437 eve::DiagnosticCode::InvalidArgument, "inventory batch requires a Bag", "bag"));
438 if (grants.empty())
440 eve::DiagnosticCode::InvalidArgument, "inventory batch requires at least one grant", "grants"));
441
442 PreparedInventoryAdd prepared;
443 prepared.state_ = std::make_unique<PreparedInventoryAdd::State>();
444 prepared.state_->target = bag;
445 prepared.state_->baseline = cloneBag(*bag);
446 prepared.state_->candidate = cloneBag(*bag);
447 prepared.state_->events.reserve(grants.size());
448 for (std::size_t index = 0; index < grants.size(); ++index) {
449 const auto &grant = grants[index];
450 if (grant.itemId.empty() || grant.quantity <= 0)
452 eve::DiagnosticCode::InvalidArgument, "inventory grant requires an item id and positive quantity",
453 "grants[" + std::to_string(index) + "]"));
454 const int added = addItemImpl(prepared.state_->candidate.get(), grant.itemId, grant.quantity, false);
455 if (added != grant.quantity)
459 ItemRegistry::find(grant.itemId) ? "inventory cannot fit the complete reward batch"
460 : "inventory reward references an unknown item",
461 "grants[" + std::to_string(index) + "].itemId", {{"itemId", grant.itemId}},
462 "inventory.prepare-add-batch"));
463 if (prepared.state_->totalAdded > std::numeric_limits<int>::max() - added)
465 eve::DiagnosticCode::InvalidArgument, "inventory batch total quantity overflows", "grants"));
466 prepared.state_->totalAdded += added;
468 event.action = "add";
469 event.bagId = bag->getId();
470 event.itemId = grant.itemId;
471 event.quantity = added;
472 prepared.state_->events.push_back(std::move(event));
473 }
474 return eve::Result<PreparedInventoryAdd>::success(std::move(prepared),
476}
477
479 if (!prepared.state_ || !prepared.state_->target || !prepared.state_->baseline ||
480 !prepared.state_->candidate)
482 eve::DiagnosticCode::InvalidArgument, "inventory batch is empty or already consumed", "prepared"));
483 Bag &target = *prepared.state_->target;
484 const Bag &baseline = *prepared.state_->baseline;
485 if (!bagsEqual(target, baseline))
487 eve::DiagnosticCode::Conflict, "Bag changed after inventory batch preparation", "bag"));
488
489 target.slots_.swap(prepared.state_->candidate->slots_);
490 const int totalAdded = prepared.state_->totalAdded;
491 for (auto &event : prepared.state_->events) emit(std::move(event));
492 prepared.state_.reset();
494}
495
497InventorySystem::prepareRemove(Bag *bag, const std::string &itemId, int quantity) {
498 if (!bag || itemId.empty() || quantity <= 0)
501 "inventory removal requires a Bag, item id, and positive quantity", "removal"));
504 eve::DiagnosticCode::NotFound, "inventory removal references an unknown item", "itemId",
505 {{"itemId", itemId}}, "inventory.prepare-remove"));
506 if (countItem(bag, itemId) < quantity)
509 "inventory does not contain the complete removal quantity", "quantity",
510 {{"itemId", itemId}}, "inventory.prepare-remove"));
511
513 prepared.state_ = std::make_unique<PreparedInventoryRemove::State>();
514 prepared.state_->target = bag;
515 prepared.state_->baseline = cloneBag(*bag);
516 prepared.state_->candidate = cloneBag(*bag);
517 int remaining = quantity;
518 for (auto &stack : prepared.state_->candidate->slots_) {
519 if (remaining <= 0) break;
520 if (stack.empty() || stack.itemId != itemId) continue;
521 const int take = std::min(stack.quantity, remaining);
522 stack.quantity -= take;
523 remaining -= take;
524 if (stack.quantity <= 0) stack.clear();
525 }
526 prepared.state_->totalRemoved = quantity;
527 prepared.state_->event.action = "remove";
528 prepared.state_->event.bagId = bag->getId();
529 prepared.state_->event.itemId = itemId;
530 prepared.state_->event.quantity = quantity;
532 std::move(prepared), eve::Status::success(eve::StatusCode::Pending));
533}
534
536 if (!prepared.state_ || !prepared.state_->target || !prepared.state_->baseline ||
537 !prepared.state_->candidate)
539 eve::DiagnosticCode::InvalidArgument, "inventory removal is empty or already consumed", "prepared"));
540 Bag &target = *prepared.state_->target;
541 if (!bagsEqual(target, *prepared.state_->baseline))
543 eve::DiagnosticCode::Conflict, "Bag changed after inventory removal preparation", "bag"));
544 target.slots_.swap(prepared.state_->candidate->slots_);
545 const int removed = prepared.state_->totalRemoved;
546 emit(std::move(prepared.state_->event));
547 prepared.state_.reset();
549}
550
551int InventorySystem::removeItem(Bag *bag, const std::string &itemId, int quantity) {
552 if (!bag || quantity <= 0 || itemId.empty()) return 0;
553 int remaining = quantity;
554 int removed = 0;
555 for (int i = 0; i < bag->getSlotCount() && remaining > 0; ++i) {
556 auto &s = bag->slots()[size_t(i)];
557 if (s.empty() || s.itemId != itemId) continue;
558 int take = std::min(s.quantity, remaining);
559 s.quantity -= take;
560 remaining -= take;
561 removed += take;
562 if (s.quantity <= 0) s.clear();
563 }
564 if (removed > 0) {
566 ev.action = "remove";
567 ev.bagId = bag->getId();
568 ev.itemId = itemId;
569 ev.quantity = removed;
570 emit(std::move(ev));
571 }
572 return removed;
573}
574
575int InventorySystem::removeAt(Bag *bag, int slot, int quantity) {
576 if (!bag || slot < 0 || slot >= bag->getSlotCount() || quantity <= 0) return 0;
577 auto &s = bag->slots()[size_t(slot)];
578 if (s.empty()) return 0;
579 int take = std::min(s.quantity, quantity);
580 std::string itemId = s.itemId;
581 s.quantity -= take;
582 if (s.quantity <= 0) s.clear();
584 ev.action = "remove";
585 ev.bagId = bag->getId();
586 ev.itemId = itemId;
587 ev.quantity = take;
588 ev.slot = slot;
589 emit(std::move(ev));
590 return take;
591}
592
593bool InventorySystem::swapSlots(Bag *bag, int slotA, int slotB) {
594 if (!bag || slotA < 0 || slotB < 0 || slotA >= bag->getSlotCount() ||
595 slotB >= bag->getSlotCount() || slotA == slotB)
596 return false;
597 std::swap(bag->slots()[size_t(slotA)], bag->slots()[size_t(slotB)]);
599 ev.action = "swap";
600 ev.bagId = bag->getId();
601 ev.slot = slotA;
602 ev.otherSlot = slotB;
603 emit(std::move(ev));
604 return true;
605}
606
607bool InventorySystem::moveSlot(Bag *bag, int fromSlot, int toSlot) {
608 if (!bag || fromSlot < 0 || toSlot < 0 || fromSlot >= bag->getSlotCount() ||
609 toSlot >= bag->getSlotCount() || fromSlot == toSlot)
610 return false;
611 auto &from = bag->slots()[size_t(fromSlot)];
612 auto &to = bag->slots()[size_t(toSlot)];
613 if (from.empty()) return false;
614
615 if (to.empty()) {
616 to = from;
617 from.clear();
619 ev.action = "move";
620 ev.bagId = bag->getId();
621 ev.itemId = to.itemId;
622 ev.quantity = to.quantity;
623 ev.slot = fromSlot;
624 ev.otherSlot = toSlot;
625 emit(std::move(ev));
626 return true;
627 }
628
629 const ItemDefinition *def = ItemRegistry::find(from.itemId);
630 if (!def) return false;
631 if (canStackTogether(*bag, from, to, *def)) {
632 int maxStack = def->maxStack > 0 ? def->maxStack : 1;
633 int space = maxStack - to.quantity;
634 if (space <= 0) return false;
635 int put = std::min(space, from.quantity);
636 to.quantity += put;
637 from.quantity -= put;
638 if (from.quantity <= 0) from.clear();
640 ev.action = "merge";
641 ev.bagId = bag->getId();
642 ev.itemId = to.itemId;
643 ev.quantity = put;
644 ev.slot = fromSlot;
645 ev.otherSlot = toSlot;
646 emit(std::move(ev));
647 return true;
648 }
649 return swapSlots(bag, fromSlot, toSlot);
650}
651
652bool InventorySystem::splitStack(Bag *bag, int slot, int quantity, int toSlot) {
653 if (!bag || slot < 0 || toSlot < 0 || slot >= bag->getSlotCount() ||
654 toSlot >= bag->getSlotCount() || slot == toSlot || quantity <= 0)
655 return false;
656 auto &from = bag->slots()[size_t(slot)];
657 auto &to = bag->slots()[size_t(toSlot)];
658 if (from.empty() || !to.empty()) return false;
659 if (quantity >= from.quantity) return false;
660
661 to = from;
662 to.instanceId = nextInstanceId();
663 to.quantity = quantity;
664 from.quantity -= quantity;
665
667 ev.action = "split";
668 ev.bagId = bag->getId();
669 ev.itemId = from.itemId;
670 ev.quantity = quantity;
671 ev.slot = slot;
672 ev.otherSlot = toSlot;
673 emit(std::move(ev));
674 return true;
675}
676
677int InventorySystem::transfer(Bag *from, Bag *to, const std::string &itemId, int quantity) {
678 if (!from || !to || quantity <= 0 || itemId.empty()) return 0;
679 int available = countItem(from, itemId);
680 int want = std::min(available, quantity);
681 if (want <= 0) return 0;
682
683 for (int n = want; n >= 1; --n) {
684 if (!canAdd(to, itemId, n, nullptr)) continue;
685
686 // Detach from source without emitting per-slot remove events.
687 int remaining = n;
688 for (int i = 0; i < from->getSlotCount() && remaining > 0; ++i) {
689 auto &s = from->slots()[size_t(i)];
690 if (s.empty() || s.itemId != itemId) continue;
691 int take = std::min(s.quantity, remaining);
692 s.quantity -= take;
693 remaining -= take;
694 if (s.quantity <= 0) s.clear();
695 }
696
697 int added = addItem(to, itemId, n);
698 if (added < n) {
699 // Roll back shortfall into source (best-effort).
700 int needRestore = n - added;
701 addItem(from, itemId, needRestore);
702 }
703 if (added > 0) {
705 ev.action = "transfer";
706 ev.bagId = from->getId();
707 ev.otherBagId = to->getId();
708 ev.itemId = itemId;
709 ev.quantity = added;
710 emit(std::move(ev));
711 }
712 return added;
713 }
714 return 0;
715}
716
717int InventorySystem::transferSlot(Bag *from, int fromSlot, Bag *to, int quantity) {
718 if (!from || !to || fromSlot < 0 || fromSlot >= from->getSlotCount() || quantity <= 0)
719 return 0;
720 auto &s = from->slots()[size_t(fromSlot)];
721 if (s.empty()) return 0;
722 int want = std::min(s.quantity, quantity);
723 std::string itemId = s.itemId;
724
725 for (int n = want; n >= 1; --n) {
726 if (!canAdd(to, itemId, n, nullptr)) continue;
727
728 // Prefer preserving instance/props when moving a whole stack into an empty target slot.
729 if (n == s.quantity) {
730 int empty = -1;
731 for (int i = 0; i < to->getSlotCount(); ++i) {
732 if (to->slots()[size_t(i)].empty()) {
733 empty = i;
734 break;
735 }
736 }
738 bool canPlaceWhole = empty >= 0 && def != nullptr;
739 if (canPlaceWhole) {
740 // Also allow merging into an existing compatible stack instead.
741 bool merged = false;
742 for (int i = 0; i < to->getSlotCount(); ++i) {
743 auto &ts = to->slots()[size_t(i)];
744 if (ts.empty()) continue;
745 if (!canStackTogether(*to, s, ts, *def)) continue;
746 int maxStack = def->maxStack > 0 ? def->maxStack : 1;
747 int space = maxStack - ts.quantity;
748 if (space < n) continue;
749 if (!checkCapacity(*to, *def, n, nullptr)) continue;
750 ts.quantity += n;
751 s.clear();
753 ev.action = "transfer";
754 ev.bagId = from->getId();
755 ev.otherBagId = to->getId();
756 ev.itemId = itemId;
757 ev.quantity = n;
758 ev.slot = fromSlot;
759 ev.otherSlot = i;
760 emit(std::move(ev));
761 merged = true;
762 break;
763 }
764 if (merged) return n;
765
766 if (checkCapacity(*to, *def, n, nullptr) && checkAccept(*to, *def, n, nullptr)) {
767 to->slots()[size_t(empty)] = s;
768 s.clear();
770 ev.action = "transfer";
771 ev.bagId = from->getId();
772 ev.otherBagId = to->getId();
773 ev.itemId = itemId;
774 ev.quantity = n;
775 ev.slot = fromSlot;
776 ev.otherSlot = empty;
777 emit(std::move(ev));
778 return n;
779 }
780 }
781 }
782
783 s.quantity -= n;
784 if (s.quantity <= 0) s.clear();
785 int added = addItem(to, itemId, n);
786 if (added < n) {
787 // Restore remainder into original slot if possible.
788 auto &fs = from->slots()[size_t(fromSlot)];
789 if (fs.empty()) {
790 fs.itemId = itemId;
791 fs.quantity = n - added;
792 fs.instanceId = nextInstanceId();
793 } else if (fs.itemId == itemId) {
794 fs.quantity += (n - added);
795 } else {
796 addItem(from, itemId, n - added);
797 }
798 }
799 if (added > 0) {
801 ev.action = "transfer";
802 ev.bagId = from->getId();
803 ev.otherBagId = to->getId();
804 ev.itemId = itemId;
805 ev.quantity = added;
806 ev.slot = fromSlot;
807 emit(std::move(ev));
808 }
809 return added;
810 }
811 return 0;
812}
813
814int InventorySystem::countItem(const Bag *bag, const std::string &itemId) {
815 if (!bag) return 0;
816 int n = 0;
817 for (const auto &s : bag->slots()) {
818 if (!s.empty() && s.itemId == itemId) n += s.quantity;
819 }
820 return n;
821}
822
823int InventorySystem::findItem(const Bag *bag, const std::string &itemId) {
824 if (!bag) return -1;
825 for (int i = 0; i < bag->getSlotCount(); ++i) {
826 const auto &s = bag->slots()[size_t(i)];
827 if (!s.empty() && s.itemId == itemId) return i;
828 }
829 return -1;
830}
831
832int InventorySystem::findItemByTag(const Bag *bag, const std::string &tag) {
833 if (!bag || tag.empty()) return -1;
834 for (int i = 0; i < bag->getSlotCount(); ++i) {
835 const auto &s = bag->slots()[size_t(i)];
836 if (s.empty()) continue;
837 if (s.hasTag(tag)) return i;
838 const ItemDefinition *def = ItemRegistry::find(s.itemId);
839 if (def && def->hasTag(tag)) return i;
840 }
841 return -1;
842}
843
845 if (!bag) return 0.f;
846 float w = 0.f;
847 for (const auto &s : bag->slots()) {
848 if (s.empty()) continue;
849 const ItemDefinition *def = ItemRegistry::find(s.itemId);
850 if (def) w += def->weight * float(s.quantity);
851 }
852 return w;
853}
854
856 if (!bag) return 0.f;
857 float v = 0.f;
858 for (const auto &s : bag->slots()) {
859 if (s.empty()) continue;
860 const ItemDefinition *def = ItemRegistry::find(s.itemId);
861 if (def) v += def->volume * float(s.quantity);
862 }
863 return v;
864}
865
867 if (!bag) return 0;
868 int n = 0;
869 for (const auto &s : bag->slots()) {
870 if (!s.empty()) ++n;
871 }
872 return n;
873}
874
876 if (!bag) return;
877 for (auto &s : bag->slots()) s.clear();
879 ev.action = "remove";
880 ev.bagId = bag->getId();
881 ev.quantity = 0;
882 emit(std::move(ev));
883}
884
885bool InventorySystem::equipFromBag(EquipmentSet *eq, const std::string &equipSlot, Bag *bag,
886 int bagSlot) {
887 if (!eq || !bag) return false;
888 std::string reason;
889 if (!eq->canEquipFromBag(bag, bagSlot, equipSlot, &reason)) return false;
890
891 auto &bs = bag->slots()[size_t(bagSlot)];
892 ItemStack moving = bs;
893 // Equipment slots hold one logical item (quantity preserved but typically 1).
894 bs.clear();
895
896 auto *dest = eq->stackAt(equipSlot);
897 if (!dest) {
898 // restore
899 bag->slots()[size_t(bagSlot)] = moving;
900 return false;
901 }
902
903 // If slot occupied, try swap into bagSlot (now empty).
904 if (!dest->empty()) {
905 bag->slots()[size_t(bagSlot)] = *dest;
906 }
907 *dest = moving;
908
910 ev.action = "equip";
911 ev.bagId = bag->getId();
912 ev.itemId = dest->itemId;
913 ev.quantity = dest->quantity;
914 ev.slot = bagSlot;
915 ev.equipSlot = equipSlot;
916 emit(std::move(ev));
917 return true;
918}
919
920bool InventorySystem::unequipToBag(EquipmentSet *eq, const std::string &equipSlot, Bag *bag) {
921 if (!eq || !bag) return false;
922 auto *src = eq->stackAt(equipSlot);
923 if (!src || src->empty()) return false;
924
925 // Find empty slot first to preserve instance/props.
926 int empty = -1;
927 for (int i = 0; i < bag->getSlotCount(); ++i) {
928 if (bag->slots()[size_t(i)].empty()) {
929 empty = i;
930 break;
931 }
932 }
933 if (empty < 0) {
934 // Fall back to addItem (may stack, losing instance identity).
935 if (!canAdd(bag, src->itemId, src->quantity, nullptr)) return false;
936 std::string itemId = src->itemId;
937 int qty = src->quantity;
938 src->clear();
939 int added = addItem(bag, itemId, qty);
940 if (added < qty) {
941 // restore
942 src->itemId = itemId;
943 src->quantity = qty - added;
944 src->instanceId = nextInstanceId();
945 }
947 ev.action = "unequip";
948 ev.bagId = bag->getId();
949 ev.itemId = itemId;
950 ev.quantity = added;
951 ev.equipSlot = equipSlot;
952 emit(std::move(ev));
953 return added > 0;
954 }
955
956 bag->slots()[size_t(empty)] = *src;
957 std::string itemId = src->itemId;
958 int qty = src->quantity;
959 src->clear();
960
962 ev.action = "unequip";
963 ev.bagId = bag->getId();
964 ev.itemId = itemId;
965 ev.quantity = qty;
966 ev.slot = empty;
967 ev.equipSlot = equipSlot;
968 emit(std::move(ev));
969 return true;
970}
971
972} // namespace eve::inventory
LogicalId target
LogicalId itemId
float w
Definition AnimClip.cpp:738
int mid
Definition AnimSmr.cpp:120
std::string from
const std::string & s
glm::vec3 n
Definition Grass.cpp:63
std::array< double, 10 > q
float v
std::int32_t c
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
const std::string * tag
float t
int removed
uint32_t index
const UnitySourceAsset & source
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
格子型物品容器(脚本可直接操作)。
Definition Bag.h:21
std::string getId() const
容器 id(变更事件定位用)。
Definition Bag.h:35
float getMaxVolume() const
Returns the max volume.
Definition Bag.h:54
const std::vector< ItemStack > & slots() const
Slots.
Definition Bag.h:149
const std::vector< std::string > & rejectTags() const
Reject tags.
Definition Bag.h:155
float getMaxWeight() const
重量 / 体积上限(容量策略用)。
Definition Bag.h:50
const std::vector< std::string > & acceptTags() const
Accept tags.
Definition Bag.h:153
std::string getStackRule() const
Returns the stack rule.
Definition Bag.h:67
int getSlotCount() const
格数。
Definition Bag.h:45
EVENGINE_API_FOUNDATION public API.
Definition Equipment.h:19
const ItemStack * stackAt(const std::string &slotName) const
Stack at.
Definition Equipment.cpp:68
bool canEquipFromBag(Bag *bag, int bagSlot, const std::string &equipSlot, std::string *reason=nullptr) const
Can equip from bag.
Definition Equipment.cpp:83
static void unregisterStackRule(const std::string &name)
Unregisters stack rule.
static int addItem(Bag *bag, const std::string &itemId, int quantity)
返回实际放入数量(可能部分成功)。
static void registerStackRule(const std::string &name, StackFn fn)
Registers stack rule.
static bool equipFromBag(EquipmentSet *eq, const std::string &equipSlot, Bag *bag, int bagSlot)
Equip from bag.
static void ensureBuiltins()
确保内置规则已注册(模块首次使用时自动调用)。
static const std::vector< InventoryChangeEvent > & events()
Events.
static bool unequipToBag(EquipmentSet *eq, const std::string &equipSlot, Bag *bag)
Unequip to bag.
static bool canAdd(Bag *bag, const std::string &itemId, int quantity, std::string *reason=nullptr)
Can add.
static bool swapSlots(Bag *bag, int slotA, int slotB)
Swap slots.
static float usedWeight(const Bag *bag)
Used weight.
static bool hasChangeHook(const std::string &name)
True when change hook.
static eve::Result< int > commitRemove(PreparedInventoryRemove prepared)
Commit a prepared removal and publish its event after the no-fail slot swap.
std::function< void(const InventoryChangeEvent &ev)> ChangeHook
static void registerCapacityPolicy(const std::string &name, CapacityFn fn)
Registers capacity policy.
static float usedVolume(const Bag *bag)
Used volume.
std::function< bool(const Bag &bag, const ItemDefinition &def, int quantity, std::string *reason)> CapacityFn
static eve::Result< int > commitAddBatch(PreparedInventoryAdd prepared)
Commit one prepared addition and then publish its change events.
static void unregisterCapacityPolicy(const std::string &name)
Unregisters capacity policy.
static int nextInstanceId()
Next instance id.
static void pushEvent(InventoryChangeEvent ev)
Pushes event.
static eve::Result< PreparedInventoryAdd > prepareAddBatch(Bag *bag, const std::vector< InventoryItemGrant > &grants)
Validate a multi-item addition against a private candidate Bag.
static bool splitStack(Bag *bag, int slot, int quantity, int toSlot)
Split stack.
std::function< bool(const Bag &bag, const ItemDefinition &def, int quantity, std::string *reason)> AcceptFn
static void registerAcceptRule(const std::string &name, AcceptFn fn)
Registers accept rule.
static int countItem(const Bag *bag, const std::string &itemId)
Returns the number of item.
static void registerChangeHook(const std::string &name, ChangeHook fn)
Registers change hook.
static int usedSlotCount(const Bag *bag)
Used slot count.
static bool hasStackRule(const std::string &name)
True when stack rule.
static void clearBag(Bag *bag)
Clears bag.
static int transfer(Bag *from, Bag *to, const std::string &itemId, int quantity)
Transfer.
static bool hasAcceptRule(const std::string &name)
True when accept rule.
static void unregisterAcceptRule(const std::string &name)
Unregisters accept rule.
std::function< bool(const ItemStack &a, const ItemStack &b, const ItemDefinition &def)> StackFn
static void unregisterChangeHook(const std::string &name)
Unregisters change hook.
static int removeAt(Bag *bag, int slot, int quantity)
Removes at.
static int removeItem(Bag *bag, const std::string &itemId, int quantity)
static bool moveSlot(Bag *bag, int fromSlot, int toSlot)
Moves slot.
static bool hasCapacityPolicy(const std::string &name)
True when capacity policy.
static int transferSlot(Bag *from, int fromSlot, Bag *to, int quantity)
Transfer slot.
static int findItemByTag(const Bag *bag, const std::string &tag)
Finds item by tag.
static void clearEvents()
Clears events.
static void pollEvents(std::vector< InventoryChangeEvent > &out)
Polls events.
static eve::Result< PreparedInventoryRemove > prepareRemove(Bag *bag, const std::string &itemId, int quantity)
Prepare removal of one exact item quantity without mutation or events.
static int findItem(const Bag *bag, const std::string &itemId)
Finds item.
static const ItemDefinition * find(const std::string &id)
Finds find.
Definition Item.cpp:53
Move-only, validated inventory state prepared for one Bag.
PreparedInventoryAdd()
Prepared inventory add.
~PreparedInventoryAdd()
Prepared inventory add.
Move-only, validated inventory removal prepared for one Bag.
运行时容器:固定格数的背包 / 箱子 / 商店栏等。 行为由 InventorySystem 提供;本类暴露便于脚本绑定的薄封装方法。
SettlementPipeline::Stage fn
一次成功库存变更的事件(供脚本 poll / C++ hook)。
Definition ItemTypes.h:55
std::string action
add/remove/move/swap/split/merge/transfer/equip/unequip
Definition ItemTypes.h:56
物品模板(进程级注册表中的定义,不含运行时数量)。
Definition ItemTypes.h:15
std::vector< std::string > tags
Definition ItemTypes.h:21
bool hasTag(const std::string &tag) const
True when tag.
Definition Item.cpp:11
容器中的一格堆叠(空槽:itemId 为空或 quantity <= 0)。
Definition ItemTypes.h:34
void clear()
Clears clear.
Definition Item.cpp:20
std::vector< InventoryChangeEvent > events