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;
20bool propsEqual(
const std::unordered_map<std::string, std::string> &
a,
21 const std::unordered_map<std::string, std::string> &
b) {
27std::unordered_map<std::string, InventorySystem::AcceptFn> &InventorySystem::acceptRules() {
28 static std::unordered_map<std::string, AcceptFn> t;
32std::unordered_map<std::string, InventorySystem::CapacityFn> &InventorySystem::capacityPolicies() {
33 static std::unordered_map<std::string, CapacityFn> t;
37std::unordered_map<std::string, InventorySystem::StackFn> &InventorySystem::stackRules() {
38 static std::unordered_map<std::string, StackFn> t;
42std::unordered_map<std::string, InventorySystem::ChangeHook> &InventorySystem::changeHooks() {
43 static std::unordered_map<std::string, ChangeHook> t;
47std::vector<InventoryChangeEvent> &InventorySystem::eventQueue() {
48 static std::vector<InventoryChangeEvent> q;
52int &InventorySystem::instanceCounter() {
57bool &InventorySystem::builtinsReady() {
58 static bool ready =
false;
63 if (
name.empty() || !
fn)
return;
64 acceptRules()[
name] = std::move(
fn);
71 return acceptRules().count(
name) > 0;
75 if (
name.empty() || !
fn)
return;
76 capacityPolicies()[
name] = std::move(
fn);
80 capacityPolicies().erase(
name);
85 return capacityPolicies().count(
name) > 0;
89 if (
name.empty() || !
fn)
return;
90 stackRules()[
name] = std::move(
fn);
97 return stackRules().count(
name) > 0;
101 if (
name.empty() || !
fn)
return;
102 changeHooks()[
name] = std::move(
fn);
108 return changeHooks().count(
name) > 0;
112 if (builtinsReady())
return;
113 builtinsReady() =
true;
120 std::string *reason) {
122 if (reason) *reason =
"rejected_tag";
126 if (reason) *reason =
"accept_tag_mismatch";
132 auto slotsOk = [](
const Bag &bag,
const ItemDefinition &def,
int quantity, std::string *reason) {
134 for (
int i = 0; i < bag.
getSlotCount() && need > 0; ++i) {
135 int space = freeSpaceInSlot(bag, i, def);
136 if (space > 0) need -= space;
139 if (reason) *reason =
"no_slot";
145 auto weightOk = [](
const Bag &bag,
const ItemDefinition &def,
int quantity, std::string *reason) {
147 float add = def.
weight * float(quantity);
149 if (reason) *reason =
"over_weight";
155 auto volumeOk = [](
const Bag &bag,
const ItemDefinition &def,
int quantity, std::string *reason) {
157 float add = def.
volume * float(quantity);
159 if (reason) *reason =
"over_volume";
172 int quantity, std::string *reason) {
173 return slotsOk(bag, def, quantity, reason) && weightOk(bag, def, quantity, reason);
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);
185 return !
a.empty() && !
b.empty() &&
a.itemId ==
b.itemId;
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;
201 for (
auto &kv : changeHooks()) {
202 if (kv.second) kv.second(ev);
204 eventQueue().push_back(std::move(ev));
211 eventQueue().clear();
222 if (it == stackRules().end() || !it->second) {
223 return !
a.empty() && !
b.empty() &&
a.itemId ==
b.itemId;
225 return it->second(
a,
b, def);
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";
236 return it->second(bag, def, quantity, reason);
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";
247 return it->second(bag, def, quantity, reason);
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;
258 probeA.itemId = def.id;
260 ItemStack probeB = probeA;
261 probeB.instanceId = 1;
262 if (!canStackTogether(bag, probeA, probeB, def)) maxStack = 1;
264 if (
s.empty())
return maxStack;
266 probe.itemId = def.id;
268 if (!canStackTogether(bag,
s, probe, def))
return 0;
269 return std::max(0, maxStack -
s.quantity);
273 std::string *reason) {
274 if (!bag || quantity <= 0) {
275 if (reason) *reason =
"invalid_args";
280 if (reason) *reason =
"unknown_item";
283 if (!checkAccept(*bag, *def, quantity, reason))
return false;
284 if (!checkCapacity(*bag, *def, quantity, reason))
return false;
289 if (!bag || quantity <= 0)
return 0;
293 if (!checkAccept(*bag, *def, 1,
nullptr))
return 0;
295 int remaining = quantity;
298 auto maxFittable = [&](
int space) ->
int {
299 int lo = 0, hi = std::min(space, remaining);
301 int mid = lo + (hi - lo + 1) / 2;
302 if (checkCapacity(*bag, *def, mid,
nullptr))
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;
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;
351 if (!bag || quantity <= 0 || itemId.empty())
return 0;
352 int remaining = quantity;
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);
361 if (
s.quantity <= 0)
s.clear();
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;
381 if (
s.quantity <= 0)
s.clear();
393 if (!bag || slotA < 0 || slotB < 0 || slotA >= bag->
getSlotCount() ||
396 std::swap(bag->
slots()[
size_t(slotA)], bag->
slots()[
size_t(slotB)]);
407 if (!bag || fromSlot < 0 || toSlot < 0 || fromSlot >= bag->
getSlotCount() ||
410 auto &from = bag->
slots()[size_t(fromSlot)];
411 auto &to = bag->
slots()[size_t(toSlot)];
412 if (from.empty())
return false;
429 if (!def)
return false;
430 if (canStackTogether(*bag, from, to, *def)) {
432 int space = maxStack - to.quantity;
433 if (space <= 0)
return false;
434 int put = std::min(space, from.quantity);
436 from.quantity -= put;
437 if (from.quantity <= 0) from.clear();
452 if (!bag || slot < 0 || toSlot < 0 || slot >= bag->
getSlotCount() ||
453 toSlot >= bag->
getSlotCount() || slot == toSlot || quantity <= 0)
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;
462 to.quantity = quantity;
463 from.quantity -= quantity;
477 if (!from || !to || quantity <= 0 || itemId.empty())
return 0;
479 int want = std::min(available, quantity);
480 if (want <= 0)
return 0;
482 for (
int n = want;
n >= 1; --
n) {
483 if (!
canAdd(to, itemId,
n,
nullptr))
continue;
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);
493 if (
s.quantity <= 0)
s.clear();
499 int needRestore =
n - added;
500 addItem(from, itemId, needRestore);
517 if (!from || !to || fromSlot < 0 || fromSlot >= from->
getSlotCount() || quantity <= 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;
524 for (
int n = want;
n >= 1; --
n) {
525 if (!
canAdd(to, itemId,
n,
nullptr))
continue;
528 if (
n ==
s.quantity) {
531 if (to->
slots()[
size_t(i)].empty()) {
537 bool canPlaceWhole = empty >= 0 && def !=
nullptr;
542 auto &ts = to->
slots()[size_t(i)];
543 if (ts.empty())
continue;
544 if (!canStackTogether(*to,
s, ts, *def))
continue;
546 int space = maxStack - ts.quantity;
547 if (space <
n)
continue;
548 if (!checkCapacity(*to, *def,
n,
nullptr))
continue;
563 if (merged)
return n;
565 if (checkCapacity(*to, *def,
n,
nullptr) && checkAccept(*to, *def,
n,
nullptr)) {
566 to->
slots()[size_t(empty)] =
s;
583 if (
s.quantity <= 0)
s.clear();
587 auto &fs = from->
slots()[size_t(fromSlot)];
590 fs.quantity =
n - added;
592 }
else if (fs.itemId == itemId) {
593 fs.quantity += (
n - added);
616 for (
const auto &
s : bag->
slots()) {
617 if (!
s.empty() &&
s.itemId == itemId)
n +=
s.quantity;
625 const auto &
s = bag->
slots()[size_t(i)];
626 if (!
s.empty() &&
s.itemId == itemId)
return i;
632 if (!bag || tag.empty())
return -1;
634 const auto &
s = bag->
slots()[size_t(i)];
635 if (
s.empty())
continue;
636 if (
s.hasTag(tag))
return i;
638 if (def && def->
hasTag(tag))
return i;
644 if (!bag)
return 0.f;
646 for (
const auto &
s : bag->
slots()) {
647 if (
s.empty())
continue;
649 if (def)
w += def->
weight * float(
s.quantity);
655 if (!bag)
return 0.f;
657 for (
const auto &
s : bag->
slots()) {
658 if (
s.empty())
continue;
660 if (def)
v += def->
volume * float(
s.quantity);
668 for (
const auto &
s : bag->
slots()) {
676 for (
auto &
s : bag->
slots())
s.clear();
686 if (!eq || !bag)
return false;
688 if (!eq->
canEquipFromBag(bag, bagSlot, equipSlot, &reason))
return false;
690 auto &bs = bag->
slots()[size_t(bagSlot)];
695 auto *dest = eq->
stackAt(equipSlot);
698 bag->
slots()[size_t(bagSlot)] = moving;
703 if (!dest->empty()) {
704 bag->
slots()[size_t(bagSlot)] = *dest;
720 if (!eq || !bag)
return false;
721 auto *src = eq->
stackAt(equipSlot);
722 if (!src || src->empty())
return false;
727 if (bag->
slots()[
size_t(i)].empty()) {
734 if (!
canAdd(bag, src->itemId, src->quantity,
nullptr))
return false;
735 std::string itemId = src->
itemId;
736 int qty = src->quantity;
738 int added =
addItem(bag, itemId, qty);
741 src->itemId = itemId;
742 src->quantity = qty - added;
755 bag->
slots()[size_t(empty)] = *src;
756 std::string itemId = src->itemId;
757 int qty = src->quantity;
SettlementPipeline::Stage fn
std::string getId() const
容器 id(变更事件定位用)。
float getMaxVolume() const
const std::vector< ItemStack > & slots() const
const std::vector< std::string > & rejectTags() const
float getMaxWeight() const
重量 / 体积上限(容量策略用)。
const std::vector< std::string > & acceptTags() const
std::string getStackRule() const
int getSlotCount() const
格数。
const ItemStack * stackAt(const std::string &slotName) const
bool canEquipFromBag(Bag *bag, int bagSlot, const std::string &equipSlot, std::string *reason=nullptr) const
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 int nextInstanceId()
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 void clearBag(Bag *bag)
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 clearEvents()
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)
运行时容器:固定格数的背包 / 箱子 / 商店栏等。 行为由 InventorySystem 提供;本类暴露便于脚本绑定的薄封装方法。
一次成功库存变更的事件(供脚本 poll / C++ hook)。
std::string action
add/remove/move/swap/split/merge/transfer/equip/unequip
物品模板(进程级注册表中的定义,不含运行时数量)。
std::vector< std::string > tags
bool hasTag(const std::string &tag) const
容器中的一格堆叠(空槽:itemId 为空或 quantity <= 0)。