osb/source/game/StarPlayerInventory.cpp

1092 lines
35 KiB
C++

#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;
}
bool PlayerInventory::slotValid(InventorySlot const& slot) const {
if (auto bagSlot = slot.ptr<BagSlot>()) {
if (auto bag = bagContents(bagSlot->first)) {
if ((size_t)bagSlot->second >= bag->size())
return false;
}
else return false;
}
return true;
}
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 (unsigned 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 {
if (!m_bags.contains(type)) return nullptr;
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);
if (m_swapSlot && !m_swapSlot->empty())
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 (unsigned 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"));
//reuse ItemBags so the Inventory pane still works after load()'ing into the same PlayerInventory again (from swap)
auto itemBags = store.get("itemBags").toObject();
eraseWhere(m_bags, [&](auto const& p) { return !itemBags.contains(p.first); });
for (auto const& p : itemBags) {
auto& bagType = p.first;
auto newBag = ItemBag::loadStore(p.second);
auto& bagPtr = m_bags[bagType];
if (bagPtr)
*bagPtr = move(newBag);
else
bagPtr = make_shared<ItemBag>(move(newBag));
}
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.clear();
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);
}
}