diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/game/StarPlayerInventory.cpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/game/StarPlayerInventory.cpp')
-rw-r--r-- | source/game/StarPlayerInventory.cpp | 1068 |
1 files changed, 1068 insertions, 0 deletions
diff --git a/source/game/StarPlayerInventory.cpp b/source/game/StarPlayerInventory.cpp new file mode 100644 index 0000000..0ebb18c --- /dev/null +++ b/source/game/StarPlayerInventory.cpp @@ -0,0 +1,1068 @@ +#include "StarPlayerInventory.hpp" +#include "StarRoot.hpp" +#include "StarCurrency.hpp" +#include "StarArmors.hpp" +#include "StarLiquidItem.hpp" +#include "StarMaterialItem.hpp" +#include "StarObjectItem.hpp" +#include "StarItemDatabase.hpp" +#include "StarPointableItem.hpp" +#include "StarItemBag.hpp" +#include "StarAssets.hpp" +#include "StarJsonExtra.hpp" + +namespace Star { + +bool PlayerInventory::itemAllowedInBag(ItemPtr const& items, String const& bagType) { + // any inventory type can have empty slots + if (!items) + return true; + + return checkInventoryFilter(items, bagType); +} + +bool PlayerInventory::itemAllowedAsEquipment(ItemPtr const& item, EquipmentSlot equipmentSlot) { + // any equipment slot can be empty + if (!item) + return true; + + if (equipmentSlot == EquipmentSlot::Head || equipmentSlot == EquipmentSlot::HeadCosmetic) + return is<HeadArmor>(item); + else if (equipmentSlot == EquipmentSlot::Chest || equipmentSlot == EquipmentSlot::ChestCosmetic) + return is<ChestArmor>(item); + else if (equipmentSlot == EquipmentSlot::Legs || equipmentSlot == EquipmentSlot::LegsCosmetic) + return is<LegsArmor>(item); + else + return is<BackArmor>(item); +} + +PlayerInventory::PlayerInventory() { + auto config = Root::singleton().assets()->json("/player.config:inventory"); + + auto bags = config.get("itemBags"); + auto bagOrder = bags.toObject().keys().sorted([&bags](String const& a, String const& b) { + return bags.get(a).getInt("priority", 0) < bags.get(b).getInt("priority", 0); + }); + for (auto name : bagOrder) { + size_t size = bags.get(name).getUInt("size"); + m_bags[name] = make_shared<ItemBag>(size); + m_bagsNetState[name].resize(size); + } + + auto currenciesConfig = Root::singleton().assets()->json("/currencies.config"); + for (auto p : currenciesConfig.iterateObject()) + m_currencies[p.first] = 0; + + size_t customBarGroups = config.getUInt("customBarGroups"); + size_t customBarIndexes = config.getUInt("customBarIndexes"); + m_customBarGroup = 0; + m_customBar.resize(customBarGroups, customBarIndexes); + + addNetElement(&m_equipmentNetState[EquipmentSlot::Head]); + addNetElement(&m_equipmentNetState[EquipmentSlot::Chest]); + addNetElement(&m_equipmentNetState[EquipmentSlot::Legs]); + addNetElement(&m_equipmentNetState[EquipmentSlot::Back]); + addNetElement(&m_equipmentNetState[EquipmentSlot::HeadCosmetic]); + addNetElement(&m_equipmentNetState[EquipmentSlot::ChestCosmetic]); + addNetElement(&m_equipmentNetState[EquipmentSlot::LegsCosmetic]); + addNetElement(&m_equipmentNetState[EquipmentSlot::BackCosmetic]); + + for (auto& p : m_bagsNetState) { + for (auto& e : p.second) + addNetElement(&e); + } + + addNetElement(&m_swapSlotNetState); + addNetElement(&m_trashSlotNetState); + + addNetElement(&m_currenciesNetState); + + addNetElement(&m_customBarGroupNetState); + m_customBarNetState.resize(customBarGroups, customBarIndexes); + m_customBarNetState.forEach([this](Array2S const&, NetElementData<CustomBarLink>& e) { + addNetElement(&e); + }); + + addNetElement(&m_selectedActionBarNetState); + + addNetElement(&m_essentialNetState[EssentialItem::BeamAxe]); + addNetElement(&m_essentialNetState[EssentialItem::WireTool]); + addNetElement(&m_essentialNetState[EssentialItem::PaintTool]); + addNetElement(&m_essentialNetState[EssentialItem::InspectionTool]); +} + +ItemPtr PlayerInventory::itemsAt(InventorySlot const& slot) const { + return retrieve(slot); +} + +ItemPtr PlayerInventory::stackWith(InventorySlot const& slot, ItemPtr const& items) { + if (!items || items->empty()) + return {}; + + if (auto es = slot.ptr<EquipmentSlot>()) { + auto& itemSlot = retrieve(slot); + if (!itemSlot && itemAllowedAsEquipment(items, *es)) + m_equipment[*es] = items->take(1); + } else { + auto& dest = retrieve(slot); + if (dest && dest->stackableWith(items)) + dest->stackWith(items); + if (!dest) + dest = items->take(items->count()); + } + + if (items->empty()) + return {}; + + return items; +} + +ItemPtr PlayerInventory::takeSlot(InventorySlot const& slot) { + if (slot.is<SwapSlot>()) + m_swapReturnSlot = {}; + return take(retrieve(slot)); +} + +bool PlayerInventory::exchangeItems(InventorySlot const& first, InventorySlot const& second) { + auto& firstItems = retrieve(first); + auto& secondItems = retrieve(second); + + if (first.is<BagSlot>() && !itemAllowedInBag(secondItems, first.get<BagSlot>().first)) + return false; + if (second.is<BagSlot>() && !itemAllowedInBag(firstItems, second.get<BagSlot>().first)) + return false; + if (first.is<EquipmentSlot>() && (secondItems->count() > 1 || !itemAllowedAsEquipment(secondItems, first.get<EquipmentSlot>()))) + return false; + if (second.is<EquipmentSlot>() && (firstItems->count() > 1 || !itemAllowedAsEquipment(firstItems, second.get<EquipmentSlot>()))) + return false; + + swap(firstItems, secondItems); + swapCustomBarLinks(first, second); + + return true; +} + +bool PlayerInventory::setItem(InventorySlot const& slot, ItemPtr const& item) { + if (auto currencyItem = as<CurrencyItem>(item)) { + m_currencies[currencyItem->currencyType()] += currencyItem->totalValue(); + return true; + } else if (auto es = slot.ptr<EquipmentSlot>()) { + if (itemAllowedAsEquipment(item, *es)) { + m_equipment[*es] = item; + return true; + } + } else if (slot.is<SwapSlot>()) { + m_swapSlot = item; + return true; + } else if (slot.is<TrashSlot>()) { + m_trashSlot = item; + return true; + } else { + auto bs = slot.get<BagSlot>(); + if (itemAllowedInBag(item, bs.first)) { + m_bags[bs.first]->setItem(bs.second, item); + return true; + } + } + + return false; +} + +bool PlayerInventory::consumeSlot(InventorySlot const& slot, uint64_t count) { + if (count == 0) + return true; + + auto& item = retrieve(slot); + if (!item) + return false; + + bool consumed = item->consume(count); + if (item->empty()) + item = {}; + + return consumed; +} + +ItemPtr PlayerInventory::addItems(ItemPtr items) { + if (!items || items->empty()) + return {}; + + // First, add coins as monetary value. + if (auto currencyItem = as<CurrencyItem>(items)) { + addCurrency(currencyItem->currencyType(), currencyItem->totalValue()); + return {}; + } + + // Then, try adding equipment to the equipment slots. + if (is<HeadArmor>(items) && !headArmor()) + m_equipment[EquipmentSlot::Head] = items->take(1); + if (is<ChestArmor>(items) && !chestArmor()) + m_equipment[EquipmentSlot::Chest] = items->take(1); + if (is<LegsArmor>(items) && !legsArmor()) + m_equipment[EquipmentSlot::Legs] = items->take(1); + if (is<BackArmor>(items) && !backArmor()) + m_equipment[EquipmentSlot::Back] = items->take(1); + + // Then, finally the bags + return addToBags(move(items)); +} + +ItemPtr PlayerInventory::addToBags(ItemPtr items) { + if (!items || items->empty()) + return {}; + + for (auto pair : m_bags) { + if (!itemAllowedInBag(items, pair.first)) + continue; + + items = pair.second->stackItems(items); + if (!items) + break; + + for (uint8_t i = 0; i < pair.second->size(); ++i) { + if (!pair.second->at(i)) { + pair.second->setItem(i, take(items)); + autoAddToCustomBar(BagSlot(pair.first, i)); + break; + } + } + } + + return items; +} + +uint64_t PlayerInventory::itemsCanFit(ItemPtr const& items) const { + if (!items || items->empty()) + return 0; + + if (is<CurrencyItem>(items)) + return items->count(); + + uint64_t canFit = 0; + + // First, check the equipment slots + if (is<HeadArmor>(items) && !headArmor()) + ++canFit; + if (is<ChestArmor>(items) && !chestArmor()) + ++canFit; + if (is<LegsArmor>(items) && !legsArmor()) + ++canFit; + if (is<BackArmor>(items) && !backArmor()) + ++canFit; + + // Then add into bags + for (auto const& pair : m_bags) { + if (itemAllowedInBag(items, pair.first)) + canFit += pair.second->itemsCanFit(items); + } + + return min(canFit, items->count()); +} + +bool PlayerInventory::hasItem(ItemDescriptor const& descriptor, bool exactMatch) const { + return hasCountOfItem(descriptor, exactMatch) >= descriptor.count(); +} + +uint64_t PlayerInventory::hasCountOfItem(ItemDescriptor const& descriptor, bool exactMatch) const { + auto one = descriptor.singular(); + + uint64_t count = 0; + auto countItem = [&](ItemPtr const& ptr) { + if (ptr) + count += ptr->matches(one, exactMatch) ? ptr->count() : 0; + }; + + countItem(m_swapSlot); + countItem(m_trashSlot); + for (auto const& p : m_equipment) + countItem(p.second); + + for (auto const& pair : m_bags) + count += pair.second->available(one, exactMatch); + + return count; +} + +bool PlayerInventory::consumeItems(ItemDescriptor const& descriptor, bool exactMatch) { + if (descriptor.count() == 0) + return true; + + auto one = descriptor.singular(); + + Map<String, uint64_t> consumeFromItemBags; + for (auto pair : m_bags) + consumeFromItemBags[pair.first] = pair.second->available(one); + + uint64_t consumeFromEquipment = 0; + for (auto const& p : m_equipment) { + if (p.second) + consumeFromEquipment += p.second->matches(one, exactMatch) ? p.second->count() : 0; + } + + uint64_t consumeFromSwap = 0; + if (m_swapSlot) + consumeFromSwap += m_swapSlot->matches(one, exactMatch) ? m_swapSlot->count() : 0; + + uint64_t consumeFromTrash = 0; + if (m_trashSlot) + consumeFromTrash += m_trashSlot->matches(one, exactMatch) ? m_trashSlot->count() : 0; + + auto totalAvailable = consumeFromEquipment + consumeFromSwap + consumeFromTrash; + for (auto pair : consumeFromItemBags) + totalAvailable += pair.second; + + if (totalAvailable < descriptor.count()) + return false; + + uint64_t leftoverCount = descriptor.count(); + uint64_t quantity; + for (auto pair : m_bags) { + quantity = min(leftoverCount, consumeFromItemBags[pair.first]); + if (quantity > 0) { + auto res = pair.second->consumeItems(one.multiply(quantity), exactMatch); + _unused(res); + starAssert(res); + leftoverCount -= quantity; + } + } + + quantity = min(leftoverCount, consumeFromEquipment); + if (quantity > 0) { + auto leftoverQuantity = quantity; + for (auto const& p : m_equipment) { + if (p.second && p.second->matches(one, exactMatch)) { + auto toConsume = min(p.second->count(), quantity); + auto res = p.second->consume(toConsume); + _unused(res); + starAssert(res); + + leftoverQuantity -= toConsume; + } + } + starAssert(leftoverQuantity == 0); + leftoverCount -= quantity; + } + + quantity = std::min(leftoverCount, consumeFromSwap); + if (quantity > 0) { + if (m_swapSlot && m_swapSlot->matches(one, exactMatch)) { + auto toConsume = std::min(m_swapSlot->count(), quantity); + auto res = m_swapSlot->consume(toConsume); + _unused(res); + starAssert(res); + + quantity -= toConsume; + starAssert(quantity == 0); + } + leftoverCount -= std::min(leftoverCount, consumeFromSwap); + } + + quantity = std::min(leftoverCount, consumeFromTrash); + if (quantity > 0) { + if (m_trashSlot && m_trashSlot->matches(one, exactMatch)) { + auto toConsume = std::min(m_trashSlot->count(), quantity); + auto res = m_trashSlot->consume(toConsume); + _unused(res); + starAssert(res); + + quantity -= toConsume; + starAssert(quantity == 0); + } + leftoverCount -= std::min(leftoverCount, consumeFromTrash); + } + + starAssert(leftoverCount == 0); + return true; +} + +ItemDescriptor PlayerInventory::takeItems(ItemDescriptor const& descriptor, bool takePartial, bool exactMatch) { + uint64_t hasCount = hasCountOfItem(descriptor, exactMatch); + + if (hasCount >= descriptor.count() || (takePartial && hasCount > 0)) { + ItemDescriptor consumeDescriptor = descriptor.withCount(min(descriptor.count(), hasCount)); + consumeItems(consumeDescriptor, exactMatch); + return consumeDescriptor; + } + + return {}; +} + +HashMap<ItemDescriptor, uint64_t> PlayerInventory::availableItems() const { + return ItemDatabase::normalizeBag(allItems()); +} + +HeadArmorPtr PlayerInventory::headArmor() const { + return as<HeadArmor>(m_equipment.value(EquipmentSlot::Head)); +} + +ChestArmorPtr PlayerInventory::chestArmor() const { + return as<ChestArmor>(m_equipment.value(EquipmentSlot::Chest)); +} + +LegsArmorPtr PlayerInventory::legsArmor() const { + return as<LegsArmor>(m_equipment.value(EquipmentSlot::Legs)); +} + +BackArmorPtr PlayerInventory::backArmor() const { + return as<BackArmor>(m_equipment.value(EquipmentSlot::Back)); +} + +HeadArmorPtr PlayerInventory::headCosmetic() const { + return as<HeadArmor>(m_equipment.value(EquipmentSlot::HeadCosmetic)); +} + +ChestArmorPtr PlayerInventory::chestCosmetic() const { + return as<ChestArmor>(m_equipment.value(EquipmentSlot::ChestCosmetic)); +} + +LegsArmorPtr PlayerInventory::legsCosmetic() const { + return as<LegsArmor>(m_equipment.value(EquipmentSlot::LegsCosmetic)); +} + +BackArmorPtr PlayerInventory::backCosmetic() const { + return as<BackArmor>(m_equipment.value(EquipmentSlot::BackCosmetic)); +} + +ItemBagConstPtr PlayerInventory::bagContents(String const& type) const { + return m_bags.get(type); +} + +void PlayerInventory::condenseBagStacks(String const& bagType) {\ + auto bag = m_bags[bagType]; + + bag->condenseStacks(); + + m_customBar.forEach([&](auto const& index, CustomBarLink& link) { + if (link.first) { + if (auto bs = link.first->ptr<BagSlot>()) { + if (bs->first == bagType && !bag->at(bs->second)) + link.first = {}; + } + } + if (link.second) { + if (auto bs = link.second->ptr<BagSlot>()) { + if (bs->first == bagType && !bag->at(bs->second)) + link.second = {}; + } + } + }); +} + +void PlayerInventory::sortBag(String const& bagType) { + auto bag = m_bags[bagType]; + + // When sorting bags, we need to record where all the action bar links were + // pointing if any of them were pointing to the bag we are about to sort. + MultiArray<pair<ItemPtr, ItemPtr>, 2> savedCustomBar(m_customBar.size()); + m_customBar.forEach([&](auto const& index, CustomBarLink const& link) { + if (link.first) { + if (auto bs = link.first->ptr<BagSlot>()) { + if (bs->first == bagType) + savedCustomBar(index).first = bag->at(bs->second); + } + } + if (link.second) { + if (auto bs = link.second->ptr<BagSlot>()) { + if (bs->first == bagType) + savedCustomBar(index).second = bag->at(bs->second); + } + } + }); + + auto itemDatabase = Root::singletonPtr()->itemDatabase(); + bag->items().sort([itemDatabase](ItemPtr const& a, ItemPtr const& b) { + if (a && !b) + return true; + if (!a) + return false; + + auto aType = itemDatabase->itemType(a->name()); + auto bType = itemDatabase->itemType(b->name()); + if (aType != bType) + return aType < bType; + + if (a->rarity() != b->rarity()) + return a->rarity() > b->rarity(); + + if (a->name().compare(b->name()) != 0) + return a->name().compare(b->name()) < 0; + + if (a->count() != b->count()) + return a->count() > b->count(); + + return false; + }); + + // Once we are done sorting, we need to restore the potentially action bar + // links to point to where the item with the same identity is now residing. + + Map<ItemPtr, size_t> itemIndexes; + for (size_t i = 0; i < bag->size(); ++i) { + if (auto item = bag->at(i)) + itemIndexes[item] = i; + } + + savedCustomBar.forEach([&](auto const& index, auto const& savedItems) { + if (savedItems.first) + m_customBar.at(index).first.set(BagSlot(bagType, itemIndexes.get(savedItems.first))); + if (savedItems.second) + m_customBar.at(index).second.set(BagSlot(bagType, itemIndexes.get(savedItems.second))); + }); +} + +void PlayerInventory::shiftSwap(InventorySlot const& slot) { + if (auto es = slot.ptr<EquipmentSlot>()) { + if (itemAllowedAsEquipment(m_swapSlot, *es)) { + auto& equipSlot = m_equipment[*es]; + if (itemSafeCount(m_swapSlot) <= 1) { + swap(m_swapSlot, equipSlot); + swapCustomBarLinks(SwapSlot(), slot); + } else if (itemSafeCount(equipSlot) == 0) { + equipSlot = m_swapSlot->take(1); + } + } + } else if (slot.is<TrashSlot>()) { + swap(m_swapSlot, m_trashSlot); + swapCustomBarLinks(SwapSlot(), slot); + } else if (auto bs = slot.ptr<BagSlot>()) { + if (itemAllowedInBag(m_swapSlot, bs->first)) { + m_swapSlot = m_bags[bs->first]->swapItems(bs->second, m_swapSlot); + swapCustomBarLinks(SwapSlot(), slot); + } + } + + if (!m_swapSlot) + m_swapReturnSlot = {}; + else + m_swapReturnSlot = slot; +} + +bool PlayerInventory::clearSwap() { + auto trySlot = [&](InventorySlot slot) { + if (!m_swapSlot) + return; + + m_swapSlot = stackWith(slot, m_swapSlot); + if (!m_swapSlot) + swapCustomBarLinks(SwapSlot(), slot); + }; + + auto tryBag = [&](String const& bagType) { + for (uint8_t i = 0; i < m_bags[bagType]->size(); ++i) { + if (!m_swapSlot || !itemAllowedInBag(m_swapSlot, bagType)) + break; + trySlot(BagSlot(bagType, i)); + } + }; + + if (m_swapReturnSlot) + trySlot(m_swapReturnSlot.take()); + + trySlot(EquipmentSlot::Head); + trySlot(EquipmentSlot::Chest); + trySlot(EquipmentSlot::Legs); + trySlot(EquipmentSlot::Back); + + for (auto bagType : m_bags.keys()) + tryBag(bagType); + + return !m_swapSlot; +} + +ItemPtr PlayerInventory::swapSlotItem() const { + return m_swapSlot; +} + +void PlayerInventory::setSwapSlotItem(ItemPtr const& items) { + if (auto currencyItem = as<CurrencyItem>(items)) { + addCurrency(currencyItem->currencyType(), currencyItem->totalValue()); + m_swapSlot = {}; + } else { + m_swapSlot = items; + autoAddToCustomBar(SwapSlot()); + } +} + +ItemPtr PlayerInventory::essentialItem(EssentialItem essentialItem) const { + return m_essential.value(essentialItem); +} + +void PlayerInventory::setEssentialItem(EssentialItem essentialItem, ItemPtr item) { + m_essential[essentialItem] = item; +} + +StringMap<uint64_t> PlayerInventory::availableCurrencies() const { + return m_currencies; +} + +uint64_t PlayerInventory::currency(String const& currencyType) const { + return m_currencies.value(currencyType, 0); +} + +void PlayerInventory::addCurrency(String const& currencyType, uint64_t amount) { + uint64_t previousTotal = m_currencies[currencyType]; + uint64_t newTotal = previousTotal + amount; + if (newTotal < previousTotal) + newTotal = highest<uint64_t>(); + m_currencies[currencyType] = min(Root::singleton().assets()->json("/currencies.config").get(currencyType).getUInt("playerMax", highest<uint64_t>()), newTotal); +} + +bool PlayerInventory::consumeCurrency(String const& currencyType, uint64_t amount) { + if (m_currencies[currencyType] >= amount) { + m_currencies[currencyType] -= amount; + return true; + } else { + return false; + } +} + +Maybe<InventorySlot> PlayerInventory::customBarPrimarySlot(CustomBarIndex customBarIndex) const { + return m_customBar.at(m_customBarGroup, customBarIndex).first; +} + +Maybe<InventorySlot> PlayerInventory::customBarSecondarySlot(CustomBarIndex customBarIndex) const { + return m_customBar.at(m_customBarGroup, customBarIndex).second; +} + +void PlayerInventory::setCustomBarPrimarySlot(CustomBarIndex customBarIndex, Maybe<InventorySlot> slot) { + // The primary slot is not allowed to point to an empty item. + if (slot) { + if (!itemsAt(*slot)) + slot = {}; + } + + auto& cbl = m_customBar.at(m_customBarGroup, customBarIndex); + if (slot && cbl.second == slot) { + // If we match the secondary slot, just swap the slots for primary and + // secondary + swap(cbl.first, cbl.second); + } else { + cbl.first = slot; + } +} + +void PlayerInventory::setCustomBarSecondarySlot(CustomBarIndex customBarIndex, Maybe<InventorySlot> slot) { + auto& cbl = m_customBar.at(m_customBarGroup, customBarIndex); + // The secondary slot is not allowed to point to an empty item or a two + // handed item. + if (slot) { + if (!itemsAt(*slot) || itemSafeTwoHanded(itemsAt(*slot))) + slot = {}; + } + + if (cbl.first && cbl.first == slot && !itemSafeTwoHanded(itemsAt(*cbl.first))) { + // If we match the primary slot and the primary slot is not a two handed + // item, then just swap the two slots. + swap(cbl.first, cbl.second); + } else { + cbl.second = slot; + // If the primary slot was two handed, it is no longer valid so clear it. + if (cbl.first && itemSafeTwoHanded(itemsAt(*cbl.first))) + cbl.first = {}; + } +} + +void PlayerInventory::addToCustomBar(InventorySlot slot) { + for (size_t j = 0; j < m_customBar.size(1); ++j) { + auto& cbl = m_customBar.at(m_customBarGroup, j); + if (!cbl.first && !cbl.second) { + cbl.first.set(slot); + break; + } + } +} + +uint8_t PlayerInventory::customBarGroup() const { + return m_customBarGroup; +} + +void PlayerInventory::setCustomBarGroup(uint8_t group) { + m_customBarGroup = group; +} + +uint8_t PlayerInventory::customBarGroups() const { + return m_customBar.size(0); +} + +uint8_t PlayerInventory::customBarIndexes() const { + return m_customBar.size(1); +} + +SelectedActionBarLocation PlayerInventory::selectedActionBarLocation() const { + return m_selectedActionBar; +} + +void PlayerInventory::selectActionBarLocation(SelectedActionBarLocation location) { + m_selectedActionBar = location; +} + +ItemPtr PlayerInventory::primaryHeldItem() const { + if (m_swapSlot) + return m_swapSlot; + + if (m_selectedActionBar.is<EssentialItem>()) + return m_essential.value(m_selectedActionBar.get<EssentialItem>()); + + if (m_selectedActionBar.is<CustomBarIndex>()) { + if (auto slot = m_customBar.at(m_customBarGroup, m_selectedActionBar.get<CustomBarIndex>()).first) + return itemsAt(*slot); + } + + return {}; +} + +ItemPtr PlayerInventory::secondaryHeldItem() const { + auto pri = primaryHeldItem(); + if (itemSafeTwoHanded(pri) || m_swapSlot || !m_selectedActionBar || m_selectedActionBar.is<EssentialItem>()) + return {}; + + auto const& cbl = m_customBar.at(m_customBarGroup, m_selectedActionBar.get<CustomBarIndex>()); + + if (cbl.first && itemSafeTwoHanded(itemsAt(*cbl.first))) + return {}; + + if (cbl.second) + return itemsAt(*cbl.second); + + return {}; +} + +Maybe<InventorySlot> PlayerInventory::primaryHeldSlot() const { + if (m_swapSlot) + return InventorySlot(SwapSlot()); + if (m_selectedActionBar.is<CustomBarIndex>()) + return customBarPrimarySlot(m_selectedActionBar.get<CustomBarIndex>()); + return {}; +} + +Maybe<InventorySlot> PlayerInventory::secondaryHeldSlot() const { + if (m_swapSlot || itemSafeTwoHanded(primaryHeldItem())) + return {}; + if (m_selectedActionBar.is<CustomBarIndex>()) + return customBarSecondarySlot(m_selectedActionBar.get<CustomBarIndex>()); + return {}; +} + +void PlayerInventory::load(Json const& store) { + auto itemDatabase = Root::singleton().itemDatabase(); + + m_equipment[EquipmentSlot::Head] = itemDatabase->diskLoad(store.get("headSlot")); + m_equipment[EquipmentSlot::Chest] = itemDatabase->diskLoad(store.get("chestSlot")); + m_equipment[EquipmentSlot::Legs] = itemDatabase->diskLoad(store.get("legsSlot")); + m_equipment[EquipmentSlot::Back] = itemDatabase->diskLoad(store.get("backSlot")); + m_equipment[EquipmentSlot::HeadCosmetic] = itemDatabase->diskLoad(store.get("headCosmeticSlot")); + m_equipment[EquipmentSlot::ChestCosmetic] = itemDatabase->diskLoad(store.get("chestCosmeticSlot")); + m_equipment[EquipmentSlot::LegsCosmetic] = itemDatabase->diskLoad(store.get("legsCosmeticSlot")); + m_equipment[EquipmentSlot::BackCosmetic] = itemDatabase->diskLoad(store.get("backCosmeticSlot")); + + auto itemBags = store.get("itemBags"); + for (String const& bagType : itemBags.toObject().keys()) + m_bags[bagType] = make_shared<ItemBag>(ItemBag::loadStore(itemBags.get(bagType))); + + m_swapSlot = itemDatabase->diskLoad(store.get("swapSlot")); + m_trashSlot = itemDatabase->diskLoad(store.get("trashSlot")); + + m_currencies = jsonToMapV<StringMap<uint64_t>>(store.get("currencies"), mem_fn(&Json::toUInt)); + + m_customBarGroup = store.getUInt("customBarGroup"); + + for (size_t i = 0; i < m_customBar.size(0); ++i) { + for (size_t j = 0; j < m_customBar.size(1); ++j) { + Json cbl = store.get("customBar").get(i).get(j); + m_customBar.at(i, j) = CustomBarLink{ + jsonToMaybe<InventorySlot>(cbl.get(0, {}), jsonToInventorySlot), + jsonToMaybe<InventorySlot>(cbl.get(1, {}), jsonToInventorySlot) + }; + } + } + + m_selectedActionBar = jsonToSelectedActionBarLocation(store.get("selectedActionBar")); + + m_essential[EssentialItem::BeamAxe] = itemDatabase->diskLoad(store.get("beamAxe")); + m_essential[EssentialItem::WireTool] = itemDatabase->diskLoad(store.get("wireTool")); + m_essential[EssentialItem::PaintTool] = itemDatabase->diskLoad(store.get("paintTool")); + m_essential[EssentialItem::InspectionTool] = itemDatabase->diskLoad(store.get("inspectionTool")); +} + +Json PlayerInventory::store() const { + auto itemDatabase = Root::singleton().itemDatabase(); + + JsonArray customBar; + for (size_t i = 0; i < m_customBar.size(0); ++i) { + JsonArray customBarGroup; + for (size_t j = 0; j < m_customBar.size(1); ++j) { + auto const& cbl = m_customBar.at(i, j); + customBarGroup.append(JsonArray{jsonFromMaybe(cbl.first, jsonFromInventorySlot), jsonFromMaybe(cbl.second, jsonFromInventorySlot)}); + } + customBar.append(take(customBarGroup)); + } + + JsonObject itemBags; + for (auto bag : m_bags) + itemBags.add(bag.first, bag.second->diskStore()); + + return JsonObject{ + {"headSlot", itemDatabase->diskStore(m_equipment.value(EquipmentSlot::Head))}, + {"chestSlot", itemDatabase->diskStore(m_equipment.value(EquipmentSlot::Chest))}, + {"legsSlot", itemDatabase->diskStore(m_equipment.value(EquipmentSlot::Legs))}, + {"backSlot", itemDatabase->diskStore(m_equipment.value(EquipmentSlot::Back))}, + {"headCosmeticSlot", itemDatabase->diskStore(m_equipment.value(EquipmentSlot::HeadCosmetic))}, + {"chestCosmeticSlot", itemDatabase->diskStore(m_equipment.value(EquipmentSlot::ChestCosmetic))}, + {"legsCosmeticSlot", itemDatabase->diskStore(m_equipment.value(EquipmentSlot::LegsCosmetic))}, + {"backCosmeticSlot", itemDatabase->diskStore(m_equipment.value(EquipmentSlot::BackCosmetic))}, + {"itemBags", itemBags}, + {"swapSlot", itemDatabase->diskStore(m_swapSlot)}, + {"trashSlot", itemDatabase->diskStore(m_trashSlot)}, + {"currencies", jsonFromMap(m_currencies)}, + {"customBarGroup", m_customBarGroup}, + {"customBar", move(customBar)}, + {"selectedActionBar", jsonFromSelectedActionBarLocation(m_selectedActionBar)}, + {"beamAxe", itemDatabase->diskStore(m_essential.value(EssentialItem::BeamAxe))}, + {"wireTool", itemDatabase->diskStore(m_essential.value(EssentialItem::WireTool))}, + {"paintTool", itemDatabase->diskStore(m_essential.value(EssentialItem::PaintTool))}, + {"inspectionTool", itemDatabase->diskStore(m_essential.value(EssentialItem::InspectionTool))} + }; +} + +void PlayerInventory::forEveryItem(function<void(InventorySlot const&, ItemPtr&)> function) { + auto checkedFunction = [function = move(function)](InventorySlot const& slot, ItemPtr& item) { + if (item) + function(slot, item); + }; + + for (auto& p : m_equipment) + checkedFunction(p.first, p.second); + for (auto const& p : m_bags) { + for (size_t i = 0; i < p.second->size(); ++i) + checkedFunction(BagSlot(p.first, i), p.second->at(i)); + } + checkedFunction(SwapSlot(), m_swapSlot); + checkedFunction(TrashSlot(), m_trashSlot); +} + +void PlayerInventory::forEveryItem(function<void(InventorySlot const&, ItemPtr const&)> function) const { + return const_cast<PlayerInventory*>(this)->forEveryItem([function = move(function)](InventorySlot const& slot, ItemPtr& item) { + function(slot, item); + }); +} + +List<ItemPtr> PlayerInventory::allItems() const { + List<ItemPtr> items; + forEveryItem([&items](InventorySlot const&, ItemPtr const& item) { + items.append(item); + }); + return items; +} + +Map<String, uint64_t> PlayerInventory::itemSummary() const { + Map<String, uint64_t> result; + forEveryItem([&result](auto const&, auto const& item) { + result[item->name()] += item->count(); + }); + return result; +} + +void PlayerInventory::cleanup() { + for (auto pair : m_bags) + pair.second->cleanup(); + + for (auto& p : m_equipment) + if (p.second && p.second->empty()) + p.second = ItemPtr(); + + if (m_swapSlot && m_swapSlot->empty()) + m_swapSlot = ItemPtr(); + + if (m_trashSlot && m_trashSlot->empty()) + m_trashSlot = ItemPtr(); + + m_customBar.forEach([this](Array2S const&, CustomBarLink& p) { + ItemPtr primary = p.first ? retrieve(*p.first) : ItemPtr(); + ItemPtr secondary = p.second ? retrieve(*p.second) : ItemPtr(); + + // Reset the primary and secondary action bar link if the item is gone + if (!primary) + p.first.reset(); + if (!secondary) + p.second.reset(); + + // If the primary hand item is two handed, the secondary hand should not be + // set + if (itemSafeTwoHanded(primary)) + p.second.reset(); + // Two handed items are not allowed in the secondary slot + if (itemSafeTwoHanded(secondary)) + p.second.reset(); + }); +} + +bool PlayerInventory::checkInventoryFilter(ItemPtr const& items, String const& filterName) { + auto config = Root::singleton().assets()->json("/player.config:inventoryFilters"); + + // filter by item type if an itemTypes filter is set + auto itemDatabase = Root::singleton().itemDatabase(); + auto filterConfig = config.get(filterName); + auto itemTypeName = ItemTypeNames.getRight(itemDatabase->itemType(items->name())); + if (filterConfig.contains("typeWhitelist") && !filterConfig.getArray("typeWhitelist").contains(itemTypeName)) + return false; + + if (filterConfig.contains("typeBlacklist") && filterConfig.getArray("typeBlacklist").contains(itemTypeName)) + return false; + + // filter by item tags if an itemTags filter is set + // this is an inclusive filter + auto itemTags = itemDatabase->itemTags(items->name()); + if (filterConfig.contains("tagWhitelist")) { + auto whitelistedTags = filterConfig.getArray("tagWhitelist").filtered([itemTags](Json const& tag) { + return itemTags.contains(tag.toString()); + }); + if (whitelistedTags.size() == 0) + return false; + } + + if (filterConfig.contains("tagBlacklist")) { + auto blacklistedTags = filterConfig.getArray("tagBlacklist").filtered([itemTags](Json const& tag) { + return itemTags.contains(tag.toString()); + }); + if (blacklistedTags.size() > 0) + return false; + } + + auto itemCategory = items->category(); + if (auto categoryWhitelist = filterConfig.optArray("categoryWhitelist")) { + auto categoryWhiteset = jsonToStringSet(*categoryWhitelist); + if (!categoryWhiteset.contains(itemCategory)) + return false; + } + + if (auto categoryBlacklist = filterConfig.optArray("categoryBlacklist")) { + auto categoryBlackset = jsonToStringSet(*categoryBlacklist); + if (categoryBlackset.contains(itemCategory)) + return false; + } + + return true; +} + +ItemPtr const& PlayerInventory::retrieve(InventorySlot const& slot) const { + return const_cast<PlayerInventory*>(this)->retrieve(slot); +} + +ItemPtr& PlayerInventory::retrieve(InventorySlot const& slot) { + auto guardEmpty = [](ItemPtr& item) -> ItemPtr& { + if (item && item->empty()) + item = {}; + return item; + }; + + if (auto es = slot.ptr<EquipmentSlot>()) + return guardEmpty(m_equipment[*es]); + else if (auto bs = slot.ptr<BagSlot>()) + return guardEmpty(m_bags[bs->first]->at(bs->second)); + else if (slot.is<SwapSlot>()) + return guardEmpty(m_swapSlot); + else + return guardEmpty(m_trashSlot); +} + +void PlayerInventory::swapCustomBarLinks(InventorySlot a, InventorySlot b) { + m_customBar.forEach([&](Array2S const&, CustomBarLink& p) { + if (p.first == a) + p.first = b; + else if (p.first == b) + p.first = a; + + if (p.second == a) + p.second = b; + else if (p.second == b) + p.second = a; + }); +} + +void PlayerInventory::autoAddToCustomBar(InventorySlot slot) { + if (!Root::singleton().configuration()->getPath("inventory.pickupToActionBar").toBool()) + return; + + auto items = itemsAt(slot); + if (items && !items->empty() && checkInventoryFilter(items, "autoAddToCustomBar")) + addToCustomBar(slot); +} + +void PlayerInventory::netElementsNeedLoad(bool) { + auto itemDatabase = Root::singleton().itemDatabase(); + + auto deserializeItem = [&itemDatabase](NetElementData<ItemDescriptor>& netState, ItemPtr& item) { + if (netState.pullUpdated()) + itemDatabase->loadItem(netState.get(), item); + }; + + auto deserializeItemList = [&](List<NetElementData<ItemDescriptor>>& netStatesList, List<ItemPtr>& itemList) { + for (size_t i = 0; i < netStatesList.size(); ++i) + deserializeItem(netStatesList[i], itemList[i]); + }; + + auto deserializeItemMap = [&](auto& netStatesMap, auto& itemMap) { + for (auto k : netStatesMap.keys()) + deserializeItem(netStatesMap[k], itemMap[k]); + }; + + deserializeItemMap(m_equipmentNetState, m_equipment); + + for (auto bagType : m_bagsNetState.keys()) + deserializeItemList(m_bagsNetState[bagType], m_bags[bagType]->items()); + + deserializeItem(m_swapSlotNetState, m_swapSlot); + deserializeItem(m_trashSlotNetState, m_trashSlot); + + m_currencies = m_currenciesNetState.get(); + + m_customBarGroup = m_customBarGroupNetState.get(); + m_customBarNetState.forEach([&](auto const& index, auto& ns) { + m_customBar.at(index) = ns.get(); + }); + + m_selectedActionBar = m_selectedActionBarNetState.get(); + + deserializeItemMap(m_essentialNetState, m_essential); + + cleanup(); +} + +void PlayerInventory::netElementsNeedStore() { + cleanup(); + + auto serializeItem = [](NetElementData<ItemDescriptor>& netState, ItemPtr& item) { + netState.set(itemSafeDescriptor(item)); + }; + + auto serializeItemList = [&](List<NetElementData<ItemDescriptor>>& netStatesList, List<ItemPtr>& itemList) { + for (size_t i = 0; i < netStatesList.size(); ++i) + serializeItem(netStatesList[i], itemList[i]); + }; + + auto serializeItemMap = [&](auto& netStatesMap, auto& itemMap) { + for (auto k : netStatesMap.keys()) + serializeItem(netStatesMap[k], itemMap[k]); + }; + + serializeItemMap(m_equipmentNetState, m_equipment); + + for (auto bagType : m_bagsNetState.keys()) + serializeItemList(m_bagsNetState[bagType], m_bags[bagType]->items()); + + serializeItem(m_swapSlotNetState, m_swapSlot); + serializeItem(m_trashSlotNetState, m_trashSlot); + + m_currenciesNetState.set(m_currencies); + + m_customBarGroupNetState.set(m_customBarGroup); + m_customBar.forEach([&](auto const& index, auto& cbl) { + m_customBarNetState.at(index).set(cbl); + }); + + m_selectedActionBarNetState.set(m_selectedActionBar); + + serializeItemMap(m_essentialNetState, m_essential); +} + +} |