载入中...
搜索中...
未找到
ContainerAdapters.cpp
浏览该文件的文档.
2
4#include "inventory/Item.h"
5
6#include <algorithm>
7#include <exception>
8#include <limits>
9#include <unordered_set>
10#include <utility>
11
12namespace eve::inventory {
13namespace {
14
15[[nodiscard]]
16[[nodiscard]] bool hasId(const Bag& bag, const eve::container::MembershipId& id) {
17 return std::any_of(bag.slots().begin(), bag.slots().end(), [&](const ItemStack& stack) {
18 return !stack.empty() && eve::container::MembershipId("inventory:" + std::to_string(stack.instanceId)) == id;
19 });
20}
21
22void swapStacks(ItemStack& lhs, ItemStack& rhs) noexcept {
23 using std::swap;
24 swap(lhs.instanceId, rhs.instanceId);
25 lhs.itemId.swap(rhs.itemId);
26 swap(lhs.quantity, rhs.quantity);
27 swap(lhs.durability, rhs.durability);
28 lhs.props.swap(rhs.props);
29 lhs.tags.swap(rhs.tags);
30}
31
32[[nodiscard]] bool sameLayout(const eve::container::ContainerSnapshot& lhs,
34 if (lhs.id != rhs.id || lhs.revision != rhs.revision || lhs.entries.size() != rhs.entries.size()) return false;
35 for (std::size_t index = 0; index < lhs.entries.size(); ++index) {
36 const auto& left = lhs.entries[index];
37 const auto& right = rhs.entries[index];
38 if (left.membership.object != right.membership.object || left.membership.slot != right.membership.slot ||
39 left.membership.generation != right.membership.generation || left.object.id != right.object.id ||
40 left.object.type != right.object.type || left.object.quantity != right.object.quantity)
41 return false;
42 }
43 return true;
44}
45
46} // namespace
47
49public:
50 PreparedState(InventoryContainerAdapter& owner, std::vector<ItemStack> staged, eve::Revision revision)
51 : owner_(owner), staged_(std::move(staged)), revision_(revision) {}
52
53 void commit() noexcept override {
54 if (committed_) return;
55 if (owner_.kind_ == InventoryContainerKind::Bag) {
56 if (owner_.bag_ == nullptr || owner_.bag_->slots().size() != staged_.size()) std::terminate();
57 owner_.bag_->slots().swap(staged_);
58 } else {
59 if (owner_.equipment_ == nullptr) std::terminate();
60 for (std::size_t index = 0; index < staged_.size(); ++index) {
61 auto* live = owner_.stackAt(eve::container::SlotIndex(static_cast<std::int32_t>(index)));
62 if (live == nullptr) std::terminate();
63 swapStacks(*live, staged_[index]);
64 }
65 }
66 owner_.revision_ = revision_;
67 committed_ = true;
68 }
69
70 void rollback() noexcept override {
71 if (!committed_) staged_.clear();
72 }
73
74private:
76 std::vector<ItemStack> staged_;
77 eve::Revision revision_;
78 bool committed_ = false;
79};
80
83 : descriptor_{std::move(id),
84 bag != nullptr && capacity.isUnlimited()
85 ? eve::container::Capacity::fixed(static_cast<std::size_t>(bag->getSlotCount()))
86 : (bag != nullptr ? capacity : eve::container::Capacity::fixed(0)),
87 eve::container::Ordering::ExplicitSlots, eve::container::Filter{}},
89 bag_(bag) {}
90
92 : descriptor_{std::move(id),
93 eve::container::Capacity::fixed(equipment ? static_cast<std::size_t>(equipment->getSlotCount()) : 0u),
94 eve::container::Ordering::ExplicitSlots, eve::container::Filter{}},
96 equipment_(equipment) {}
97
98eve::container::MembershipId InventoryContainerAdapter::objectId(const ItemStack& stack) {
99 return eve::container::MembershipId("inventory:" + std::to_string(stack.instanceId));
100}
101
102eve::container::ContainerObject InventoryContainerAdapter::describe(const ItemStack& stack, std::string equipmentSlot) {
104 object.id = objectId(stack);
105 object.type = stack.itemId;
106 object.quantity = stack.quantity > 0 ? static_cast<std::uint32_t>(stack.quantity) : 0u;
107 object.tags = stack.tags;
108 if (const auto* definition = ItemRegistry::find(stack.itemId)) {
109 for (const auto& tag : definition->tags)
110 if (std::find(object.tags.begin(), object.tags.end(), tag) == object.tags.end()) object.tags.push_back(tag);
111 }
112 auto payload = std::make_shared<InventoryContainerObject>();
113 payload->stack = stack;
114 payload->equipmentSlot = std::move(equipmentSlot);
115 object.payload = std::move(payload);
116 return object;
117}
118
119const ItemStack* InventoryContainerAdapter::stackAt(eve::container::SlotIndex slot) const {
120 if (!slot.isValid()) return nullptr;
121 if (kind_ == InventoryContainerKind::Bag) {
122 if (bag_ == nullptr || slot.value() >= bag_->getSlotCount()) return nullptr;
123 return &bag_->slots()[static_cast<std::size_t>(slot.value())];
124 }
125 if (equipment_ == nullptr || slot.value() >= equipment_->getSlotCount()) return nullptr;
126 const std::string name = equipment_->getSlotName(slot.value());
127 return equipment_->stackAt(name);
128}
129
130ItemStack* InventoryContainerAdapter::stackAt(eve::container::SlotIndex slot) {
131 return const_cast<ItemStack*>(static_cast<const InventoryContainerAdapter*>(this)->stackAt(slot));
132}
133
134std::string InventoryContainerAdapter::slotName(eve::container::SlotIndex slot) const {
135 if (kind_ != InventoryContainerKind::Equipment || equipment_ == nullptr || !slot.isValid() ||
136 slot.value() >= equipment_->getSlotCount())
137 return {};
138 return equipment_->getSlotName(slot.value());
139}
140
142 if (!descriptor_.id.isValid() || (kind_ == InventoryContainerKind::Bag ? bag_ == nullptr : equipment_ == nullptr))
144 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "inventory container adapter is not bound"));
145 eve::container::ContainerSnapshot snapshot{descriptor_.id, revision_, {}};
146 std::unordered_set<eve::container::MembershipId> ids;
147 const int count = kind_ == InventoryContainerKind::Bag ? bag_->getSlotCount() : equipment_->getSlotCount();
148 for (int index = 0; index < count; ++index) {
149 const auto slot = eve::container::SlotIndex(index);
150 const auto* stack = stackAt(slot);
151 if (stack == nullptr || stack->empty()) continue;
152 if (!descriptor_.capacity.isUnlimited() && static_cast<std::size_t>(index) >= descriptor_.capacity.value())
154 eve::DiagnosticCode::InvariantViolation, "inventory membership exceeds configured capacity"));
155 if (stack->instanceId <= 0)
157 eve::DiagnosticCode::InvariantViolation, "inventory stack has no instance identity"));
158 const auto id = objectId(*stack);
159 if (!ids.insert(id).second)
161 eve::DiagnosticCode::Conflict, "inventory container contains duplicate instance identity"));
162 snapshot.entries.push_back({{id, slot, eve::Generation(1)}, describe(*stack, slotName(slot))});
163 }
164 if (!descriptor_.capacity.isUnlimited() && snapshot.entries.size() > descriptor_.capacity.value())
166 eve::DiagnosticCode::InvariantViolation, "inventory container exceeds configured capacity"));
168}
169
170eve::Result<void> InventoryContainerAdapter::validateObject(const InventoryContainerObject& object) const {
171 if (object.stack.empty() || object.stack.instanceId <= 0 || object.stack.itemId.empty())
172 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "inventory transfer stack is incomplete"));
174}
175
177 const eve::container::ContainerObject& object, std::optional<eve::container::SlotIndex> destination,
178 std::optional<eve::container::MembershipId> ignoredObject) const {
179 const auto* payload = dynamic_cast<const InventoryContainerObject*>(object.payload.get());
180 if (payload == nullptr) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "inventory payload is stale"));
181 auto validObject = validateObject(*payload);
182 if (!validObject) return validObject;
183 if (object.id != objectId(payload->stack))
184 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "inventory membership identity is stale"));
185 if (object.type != payload->stack.itemId || object.quantity != static_cast<std::uint32_t>(payload->stack.quantity))
187 const auto* definition = ItemRegistry::find(payload->stack.itemId);
188 if (definition == nullptr) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "inventory item definition was not found"));
189 if (definition->maxStack <= 0 || payload->stack.quantity > definition->maxStack)
190 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::PreconditionViolation, "inventory stack exceeds item definition maxStack"));
191
192 if (kind_ == InventoryContainerKind::Bag) {
193 if (bag_ == nullptr) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "bag adapter is unbound"));
194 const std::size_t slotCount = static_cast<std::size_t>(bag_->getSlotCount());
195 const std::size_t slotLimit =
196 descriptor_.capacity.isUnlimited() ? slotCount : std::min(slotCount, descriptor_.capacity.value());
197 if (hasId(*bag_, object.id) && (!ignoredObject || *ignoredObject != object.id))
198 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "bag already contains inventory object"));
199 if (destination) {
200 if (!destination->isValid() || static_cast<std::size_t>(destination->value()) >= slotLimit)
201 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "bag destination slot is out of range"));
202 if (!bag_->slots()[static_cast<std::size_t>(destination->value())].empty() &&
203 (!ignoredObject || *ignoredObject != object.id))
204 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "bag destination slot is occupied"));
205 } else {
206 const bool hasEmptySlot =
207 std::any_of(bag_->slots().begin(), bag_->slots().begin() + static_cast<std::ptrdiff_t>(slotLimit),
208 [](const ItemStack& stack) { return stack.empty(); });
209 if (!hasEmptySlot && (!ignoredObject || *ignoredObject != object.id))
210 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "bag has no empty membership slot"));
211 }
212 if (!ignoredObject || *ignoredObject != object.id) {
213 std::string reason;
214 if (!InventorySystem::canAdd(bag_, payload->stack.itemId, payload->stack.quantity, &reason))
215 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::PreconditionViolation, reason.empty() ? "bag rejected inventory object" : reason));
216 }
218 }
219
220 if (equipment_ == nullptr) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "equipment is unbound"));
221 if (!destination || !destination->isValid() ||
222 static_cast<std::size_t>(destination->value()) >= static_cast<std::size_t>(equipment_->getSlotCount()))
223 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::PreconditionViolation, "equipment transfer requires a valid destination slot"));
224 const std::string target = slotName(*destination);
225 if (target.empty()) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "equipment slot was not found"));
226 const auto* current = equipment_->stackAt(target);
227 if (current != nullptr && !current->empty() && (!ignoredObject || object.id != objectId(*current)))
228 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "equipment destination slot is occupied"));
229 if (!definition->equipSlot.empty() && definition->equipSlot != target)
230 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::PreconditionViolation, "inventory item does not fit equipment slot"));
231 const auto* allowed = equipment_->allowedTags(target);
232 if (allowed != nullptr && !allowed->empty()) {
233 const bool accepted = std::any_of(allowed->begin(), allowed->end(), [&](const std::string& tag) {
234 return definition->hasTag(tag) || payload->stack.hasTag(tag);
235 });
236 if (!accepted)
238 }
240}
241
244 const bool bound = kind_ == InventoryContainerKind::Bag ? bag_ != nullptr : equipment_ != nullptr;
245 if (!bound || !descriptor_.id.isValid())
247 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "inventory adapter is not bound"));
248 if (expected.id != descriptor_.id || candidate.id != descriptor_.id)
250 eve::DiagnosticCode::InvalidArgument, "inventory prepare targets another container"));
251 auto currentResult = snapshot();
252 if (!currentResult)
253 return eve::Result<std::unique_ptr<eve::container::IContainer::PreparedState>>::failure(currentResult.status());
254 if (!sameLayout(currentResult.value(), expected))
256 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "inventory prepare expected snapshot is stale"));
257 const auto nextRevision = expected.revision.incremented();
258 if (!nextRevision || candidate.revision != *nextRevision)
260 eve::DiagnosticCode::InvariantViolation, "inventory candidate revision is not the next revision"));
261 const int slotCount = kind_ == InventoryContainerKind::Bag ? bag_->getSlotCount() : equipment_->getSlotCount();
262 if (!descriptor_.capacity.isUnlimited() && candidate.entries.size() > descriptor_.capacity.value())
264 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "inventory candidate exceeds container capacity"));
265
266 std::vector<ItemStack> staged(static_cast<std::size_t>(slotCount));
267 std::unordered_set<eve::container::MembershipId> ids;
268 std::unordered_set<std::int32_t> slots;
269 for (const auto& entry : candidate.entries) {
270 if (!entry.membership.object.isValid() || !entry.membership.slot.isValid() ||
271 entry.membership.slot.value() >= slotCount ||
272 (!descriptor_.capacity.isUnlimited() &&
273 static_cast<std::size_t>(entry.membership.slot.value()) >= descriptor_.capacity.value()) ||
274 entry.membership.generation.isZero() || entry.object.id != entry.membership.object ||
275 !ids.insert(entry.membership.object).second || !slots.insert(entry.membership.slot.value()).second)
278 "inventory candidate contains invalid membership"));
279 const auto* payload = dynamic_cast<const InventoryContainerObject*>(entry.object.payload.get());
280 if (payload == nullptr)
282 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "inventory candidate payload is stale"));
283 auto validObject = validateObject(*payload);
284 if (!validObject)
286 validObject.status());
287 if (entry.object.type != payload->stack.itemId ||
288 entry.object.quantity != static_cast<std::uint32_t>(payload->stack.quantity) ||
289 objectId(payload->stack) != entry.membership.object ||
290 (kind_ == InventoryContainerKind::Equipment && slotName(entry.membership.slot).empty()))
292 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "inventory candidate object facts are stale"));
293 staged[static_cast<std::size_t>(entry.membership.slot.value())] = payload->stack;
294 }
295
296 std::unique_ptr<eve::container::IContainer::PreparedState> prepared =
297 std::make_unique<PreparedState>(*this, std::move(staged), *nextRevision);
299}
300
301} // namespace eve::inventory
LogicalId target
Value::Object payload
std::uint32_t capacity
glm::uvec4 ids
float u
Definition Grass.cpp:233
HexVec3 left
HexVec3 right
std::int32_t second
std::string name
const std::string * tag
std::string id
Definition PlayHost.cpp:108
float begin
double current
LocalPageCacheEntry slots[ShadowConfig::kLocalSlots]
std::uint32_t count
Json object
uint32_t index
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
Non-negative capacity measured in membership slots.
Definition Container.h:92
constexpr std::size_t value() const noexcept
Definition Container.h:99
constexpr bool isUnlimited() const noexcept
Definition Container.h:98
Strong identity of one runtime or persistent container.
Definition Container.h:44
bool isValid() const noexcept
Return whether this ID is usable by a transfer request.
Definition Container.h:58
Detached state prepared for an atomic container replacement.
Definition Container.h:296
Strong identity of an object which can have one container membership.
Definition Container.h:68
Explicit slot number; it never implicitly converts to an integer.
Definition Container.h:117
constexpr std::int32_t value() const noexcept
Value.
Definition Container.h:128
constexpr bool isValid() const noexcept
True when valid.
Definition Container.h:126
constexpr std::optional< StrongUint64 > incremented() const noexcept
Returns the next value, or empty instead of unsigned wraparound.
格子型物品容器(脚本可直接操作)。
Definition Bag.h:21
const std::vector< ItemStack > & slots() const
Slots.
Definition Bag.h:149
int getSlotCount() const
格数。
Definition Bag.h:45
EVENGINE_API_FOUNDATION public API.
Definition Equipment.h:19
const std::vector< std::string > * allowedTags(const std::string &slotName) const
Allowed tags.
Definition Equipment.cpp:78
std::string getSlotName(int index) const
Returns the slot name.
Definition Equipment.cpp:39
const ItemStack * stackAt(const std::string &slotName) const
Stack at.
Definition Equipment.cpp:68
int getSlotCount() const
Returns the slot count.
Definition Equipment.cpp:37
PreparedState(InventoryContainerAdapter &owner, std::vector< ItemStack > staged, eve::Revision revision)
void commit() noexcept override
Publish the staged state; the operation is non-throwing.
void rollback() noexcept override
Abandon an unpublished stage; the operation is non-throwing.
Adapts one Bag or EquipmentSet without changing their legacy APIs.
eve::Result< std::unique_ptr< eve::container::IContainer::PreparedState > > prepare(const eve::container::ContainerSnapshot &expected, const eve::container::ContainerSnapshot &candidate) override
Prepare an exact replacement from expected to candidate.
eve::Result< void > validateInsert(const eve::container::ContainerObject &object, std::optional< eve::container::SlotIndex > destination, std::optional< eve::container::MembershipId > ignoredObject=std::nullopt) const override
Validate insert.
InventoryContainerAdapter(eve::container::ContainerId id, Bag *bag, eve::container::Capacity capacity=eve::container::Capacity::unlimited())
Bind a borrowed bag.
eve::Revision revision() const noexcept
Return the adapter revision for optimistic transfer requests.
eve::Result< eve::container::ContainerSnapshot > snapshot() const override
Snapshot.
static bool canAdd(Bag *bag, const std::string &itemId, int quantity, std::string *reason=nullptr)
Can add.
static const ItemDefinition * find(const std::string &id)
Finds find.
Definition Item.cpp:53
Inventory adapters for generic Bag and EquipmentSet containers.
运行时容器:固定格数的背包 / 箱子 / 商店栏等。 行为由 InventorySystem 提供;本类暴露便于脚本绑定的薄封装方法。
InventoryContainerKind
InventoryContainerKind public API.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
detail::StrongUint64< detail::GenerationTag > Generation
Registry/object replacement generation used to reject stale handles.
Definition Generation.h:13
Generic object facts used by filters and transfer diagnostics.
Definition Container.h:168
Lossless snapshot of one container at one revision.
Definition Container.h:190
std::vector< MembershipEntry > entries
Definition Container.h:193
Owning transfer payload for one inventory stack.
容器中的一格堆叠(空槽:itemId 为空或 quantity <= 0)。
Definition ItemTypes.h:34