osb/source/game/StarItemBag.cpp
2023-06-20 14:33:09 +10:00

373 lines
8.7 KiB
C++

#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<ItemPtr> ItemBag::resize(size_t size) {
List<ItemPtr> 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<ItemBag*>(this)->m_items) {
if (items && items->empty()) {
cleanupDone = true;
items = {};
}
}
return cleanupDone;
}
List<ItemPtr>& 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<ItemPtr> const& ItemBag::items() const {
return const_cast<ItemBag*>(this)->items();
}
ItemPtr const& ItemBag::at(size_t i) const {
return const_cast<ItemBag*>(this)->at(i);
}
ItemPtr& ItemBag::at(size_t i) {
auto& item = m_items.at(i);
if (item && item->empty())
item = {};
return item;
}
List<ItemPtr> ItemBag::takeAll() {
List<ItemPtr> taken;
for (size_t i = 0; i < size(); ++i) {
if (auto& item = at(i))
taken.append(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<std::pair<size_t, uint64_t>> 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<size_t> 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 = 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 = 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<ItemDescriptor>(), 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;
}
}