From 4ad577d91422f29a81d3d9e721f893be93dbdccb Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sat, 21 Oct 2023 02:30:33 -0300 Subject: [PATCH] feat: add again remove reward containers if is empty (#1610) The feature was originally reverted due to concerns about security and stability, as detailed in pull request: https://github.com/opentibiabr/canary/pull/1364. Having since stabilized the underlying foundation by transitioning from raw pointers to automatic memory management, we are now able to re-implement this feature safely. --- src/creatures/players/player.cpp | 20 ++++++++++++++++++++ src/creatures/players/player.hpp | 3 +++ src/items/containers/container.cpp | 23 +++++++++++++++++++++++ src/items/containers/container.hpp | 5 +++++ src/lua/creature/actions.cpp | 4 ++++ 5 files changed, 55 insertions(+) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 7dd02371688..bd11cc3394e 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -790,11 +790,31 @@ void Player::closeContainer(uint8_t cid) { OpenContainer openContainer = it->second; std::shared_ptr container = openContainer.container; + if (container && container->isAnyKindOfRewardChest() && !hasOtherRewardContainerOpen(container)) { + removeEmptyRewards(); + } openContainers.erase(it); if (container && container->getID() == ITEM_BROWSEFIELD) { } } +void Player::removeEmptyRewards() { + std::erase_if(rewardMap, [this](const auto &rewardBag) { + auto [id, reward] = rewardBag; + if (reward->empty()) { + getRewardChest()->removeItem(reward); + return true; + } + return false; + }); +} + +bool Player::hasOtherRewardContainerOpen(const std::shared_ptr container) const { + return std::ranges::any_of(openContainers.begin(), openContainers.end(), [container](const auto &containerPair) { + return containerPair.second.container != container && containerPair.second.container->isAnyKindOfRewardContainer(); + }); +} + void Player::setContainerIndex(uint8_t cid, uint16_t index) { auto it = openContainers.find(cid); if (it == openContainers.end()) { diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 74398777104..6cefb184e9e 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -2915,4 +2915,7 @@ class Player final : public Creature, public Cylinder, public Bankable { void updateDamageReductionFromItemImbuement(std::array &combatReductionMap, std::shared_ptr item, uint16_t combatTypeIndex) const; void updateDamageReductionFromItemAbility(std::array &combatReductionMap, std::shared_ptr item, uint16_t combatTypeIndex) const; double_t calculateDamageReduction(double_t currentTotal, int16_t resistance) const; + + void removeEmptyRewards(); + bool hasOtherRewardContainerOpen(const std::shared_ptr container) const; }; diff --git a/src/items/containers/container.cpp b/src/items/containers/container.cpp index 0800f032adb..54ce00acec4 100644 --- a/src/items/containers/container.cpp +++ b/src/items/containers/container.cpp @@ -361,6 +361,29 @@ bool Container::isHoldingItemWithId(const uint16_t id) { return false; } +bool Container::isInsideContainerWithId(const uint16_t id) { + auto nextParent = getParent(); + while (nextParent != nullptr && nextParent->getContainer()) { + if (nextParent->getContainer()->getID() == id) { + return true; + } + nextParent = nextParent->getRealParent(); + } + return false; +} + +bool Container::isAnyKindOfRewardChest() { + return getID() == ITEM_REWARD_CHEST || getID() == ITEM_REWARD_CONTAINER && getParent() && getParent()->getContainer() && getParent()->getContainer()->getID() == ITEM_REWARD_CHEST || isBrowseFieldAndHoldsRewardChest(); +} + +bool Container::isAnyKindOfRewardContainer() { + return getID() == ITEM_REWARD_CHEST || getID() == ITEM_REWARD_CONTAINER || isHoldingItemWithId(ITEM_REWARD_CONTAINER) || isInsideContainerWithId(ITEM_REWARD_CONTAINER); +} + +bool Container::isBrowseFieldAndHoldsRewardChest() { + return getID() == ITEM_BROWSEFIELD && isHoldingItemWithId(ITEM_REWARD_CHEST); +} + void Container::onAddContainerItem(std::shared_ptr item) { auto spectators = Spectators().find(getPosition(), false, 2, 2, 2, 2); diff --git a/src/items/containers/container.hpp b/src/items/containers/container.hpp index 81561120042..0b014aee58f 100644 --- a/src/items/containers/container.hpp +++ b/src/items/containers/container.hpp @@ -161,6 +161,11 @@ class Container : public Item, public Cylinder { virtual void removeItem(std::shared_ptr thing, bool sendUpdateToClient = false); + bool isAnyKindOfRewardChest(); + bool isAnyKindOfRewardContainer(); + bool isBrowseFieldAndHoldsRewardChest(); + bool isInsideContainerWithId(const uint16_t id); + protected: std::ostringstream &getContentDescription(std::ostringstream &os, bool oldProtocol); diff --git a/src/lua/creature/actions.cpp b/src/lua/creature/actions.cpp index 6a79d697eb7..ba92369761d 100644 --- a/src/lua/creature/actions.cpp +++ b/src/lua/creature/actions.cpp @@ -310,6 +310,10 @@ ReturnValue Actions::internalUseItem(std::shared_ptr player, const Posit // reward chest if (container->getRewardChest() != nullptr && container->getParent()) { + if (!player->hasOtherRewardContainerOpen(container->getParent()->getContainer())) { + player->removeEmptyRewards(); + } + std::shared_ptr playerRewardChest = player->getRewardChest(); if (playerRewardChest->empty()) { return RETURNVALUE_REWARDCHESTISEMPTY;