#include "StarItemBag.hpp" #include "StarRoot.hpp" #include "StarItemDatabase.hpp" #include "StarJsonExtra.hpp" namespace Star { ItemBag::ItemBag() {} ItemBag::ItemBag(size_t size) { m_items.resize(size); } ItemBag ItemBag::fromJson(Json const& store) { auto itemDatabase = Root::singleton().itemDatabase(); ItemBag res; res.m_items = store.toArray().transformed([itemDatabase](Json const& v) { return itemDatabase->fromJson(v); }); return res; } ItemBag ItemBag::loadStore(Json const& store) { auto itemDatabase = Root::singleton().itemDatabase(); ItemBag res; res.m_items = store.toArray().transformed([itemDatabase](Json const& v) { return itemDatabase->diskLoad(v); }); return res; } Json ItemBag::toJson() const { auto itemDatabase = Root::singleton().itemDatabase(); return m_items.transformed([itemDatabase](ItemConstPtr const& item) { return itemDatabase->toJson(item); }); } Json ItemBag::diskStore() const { auto itemDatabase = Root::singleton().itemDatabase(); return m_items.transformed([itemDatabase](ItemConstPtr const& item) { return itemDatabase->diskStore(item); }); } size_t ItemBag::size() const { return m_items.size(); } List ItemBag::resize(size_t size) { List lost; while (m_items.size() > size) { auto lastItem = m_items.takeLast(); lastItem = addItems(lastItem); if (lastItem && !lastItem->empty()) lost.append(lastItem); } m_items.resize(size); return lost; } void ItemBag::clearItems() { size_t oldSize = m_items.size(); m_items.clear(); m_items.resize(oldSize); } bool ItemBag::cleanup() const { bool cleanupDone = false; for (auto& items : const_cast(this)->m_items) { if (items && items->empty()) { cleanupDone = true; items = {}; } } return cleanupDone; } List& ItemBag::items() { // When returning the entire item collection, need to make sure that there // are no empty items before returning. cleanup(); return m_items; } List const& ItemBag::items() const { return const_cast(this)->items(); } ItemPtr const& ItemBag::at(size_t i) const { return const_cast(this)->at(i); } ItemPtr& ItemBag::at(size_t i) { auto& item = m_items.at(i); if (item && item->empty()) item = {}; return item; } List ItemBag::takeAll() { List taken; for (size_t i = 0; i < size(); ++i) { if (auto& item = at(i)) taken.append(std::move(item)); } return taken; } void ItemBag::setItem(size_t pos, ItemPtr item) { auto& storedItem = at(pos); storedItem = item; } ItemPtr ItemBag::putItems(size_t pos, ItemPtr items) { if (!items || items->empty()) return {}; auto& storedItem = at(pos); if (storedItem) { // Try to stack with an item that is already there storedItem->stackWith(items); if (!items->empty()) return items; else return {}; } else { // Otherwise just put the items there and return nothing. storedItem = items; return {}; } } ItemPtr ItemBag::takeItems(size_t pos, uint64_t count) { if (auto& storedItem = at(pos)) { auto taken = storedItem->take(count); if (storedItem->empty()) storedItem = {}; return taken; } else { return {}; } } ItemPtr ItemBag::swapItems(size_t pos, ItemPtr items, bool tryCombine) { auto& storedItem = at(pos); auto swapItems = items; if (!swapItems || swapItems->empty()) { // If we are passed in nothing, simply return what's there, if anything. swapItems = storedItem; storedItem = {}; } else if (storedItem) { // If something is there, try to stack with it first. If we can't stack, // then swap. if (!tryCombine || !storedItem->stackWith(swapItems)) std::swap(storedItem, swapItems); } else { // Otherwise just place the given items in the slot. storedItem = swapItems; swapItems = {}; } return swapItems; } bool ItemBag::consumeItems(size_t pos, uint64_t count) { bool consumed = false; if (auto& storedItem = at(pos)) { consumed = storedItem->consume(count); if (storedItem->empty()) storedItem = {}; } return consumed; } bool ItemBag::consumeItems(ItemDescriptor const& descriptor, bool exactMatch) { uint64_t countLeft = descriptor.count(); List> consumeLocations; for (size_t i = 0; i < m_items.size(); ++i) { auto& storedItem = at(i); if (storedItem && storedItem->matches(descriptor, exactMatch)) { uint64_t count = storedItem->count(); uint64_t take = std::min(count, countLeft); consumeLocations.append({i, take}); countLeft -= take; if (countLeft == 0) break; } } // Only consume any if we can consume them all if (countLeft > 0) return false; for (auto loc : consumeLocations) { bool res = consumeItems(loc.first, loc.second); _unused(res); starAssert(res); } return true; } uint64_t ItemBag::available(ItemDescriptor const& descriptor, bool exactMatch) const { uint64_t count = 0; for (auto const& items : m_items) { if (items && items->matches(descriptor, exactMatch)) count += items->count(); } return count / descriptor.count(); } uint64_t ItemBag::itemsCanFit(ItemConstPtr const& items) const { auto itemsFit = itemsFitWhere(items); return items->count() - itemsFit.leftover; } uint64_t ItemBag::itemsCanStack(ItemConstPtr const& items) const { auto itemsFit = itemsFitWhere(items); uint64_t stackable = 0; for (auto slot : itemsFit.slots) if (m_items[slot]) stackable += stackTransfer(at(slot), items); return stackable; } auto ItemBag::itemsFitWhere(ItemConstPtr const& items, uint64_t max) const -> ItemsFitWhereResult { if (!items || items->empty()) return ItemsFitWhereResult(); List slots; uint64_t count = std::min(items->count(), max); while (true) { if (count == 0) break; size_t slot = bestSlotAvailable(items, false); if (slot == NPos) break; else slots.append(slot); uint64_t available = stackTransfer(at(slot), items); if (available != 0) count -= std::min(available, count); else break; } return ItemsFitWhereResult{count, slots}; } ItemPtr ItemBag::addItems(ItemPtr items) { if (!items || items->empty()) return {}; while (true) { size_t slot = bestSlotAvailable(items, false); if (slot == NPos) return items; auto& storedItem = at(slot); if (storedItem) { storedItem->stackWith(items); if (items->empty()) return {}; } else { storedItem = std::move(items); return {}; } } } ItemPtr ItemBag::stackItems(ItemPtr items) { if (!items || items->empty()) return {}; while (true) { size_t slot = bestSlotAvailable(items, true); if (slot == NPos) return items; auto& storedItem = at(slot); if (storedItem) { storedItem->stackWith(items); if (items->empty()) return {}; } else { storedItem = std::move(items); return {}; } } } void ItemBag::condenseStacks() { for (size_t i = size() - 1; i > 0; --i) { if (auto& item = at(i)) { for (size_t j = 0; j < i; j++) { if (auto& stackWithItem = at(j)) item->stackWith(stackWithItem); if (item->empty()) break; } } } } void ItemBag::read(DataStream& ds) { auto itemDatabase = Root::singleton().itemDatabase(); m_items.clear(); m_items.resize(ds.readVlqU()); size_t setItemsSize = ds.readVlqU(); for (size_t i = 0; i < setItemsSize; ++i) itemDatabase->loadItem(ds.read(), at(i)); } void ItemBag::write(DataStream& ds) const { // Try not to write the whole bag if a large part of the end of the bag is // empty. ds.writeVlqU(m_items.size()); size_t setItemsSize = 0; for (size_t i = 0; i < m_items.size(); ++i) { if (at(i)) setItemsSize = i + 1; } ds.writeVlqU(setItemsSize); for (size_t i = 0; i < setItemsSize; ++i) ds.write(itemSafeDescriptor(at(i))); } uint64_t ItemBag::stackTransfer(ItemConstPtr const& to, ItemConstPtr const& from) { if (!from) return 0; else if (!to) return from->count(); else if (!to->stackableWith(from)) return 0; else return std::min(to->maxStack() - to->count(), from->count()); } size_t ItemBag::bestSlotAvailable(ItemConstPtr const& item, bool stacksOnly) const { // First look for any slots that can stack, before empty slots. for (size_t i = 0; i < m_items.size(); ++i) { auto const& storedItem = at(i); if (storedItem && stackTransfer(storedItem, item) != 0) return i; } if (!stacksOnly) { // Then, look for any empty slots. for (size_t i = 0; i < m_items.size(); ++i) { if (!at(i)) return i; } } return NPos; } }