From c5b8a018e07dad883fcbab2fbe175727a4a7ef5d Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sun, 12 May 2024 02:00:35 -0300 Subject: [PATCH 1/5] feat: loot highlight effect --- src/creatures/creature.cpp | 2 + src/creatures/players/player.hpp | 7 ++- src/enums/container_type.hpp | 24 ++++++++ src/game/game.cpp | 4 ++ src/items/containers/container.cpp | 55 +++++++++++++++++ src/items/containers/container.hpp | 6 ++ src/items/item.cpp | 17 ++++++ src/items/item.hpp | 13 ++++ src/server/network/protocol/protocolgame.cpp | 62 ++++++-------------- 9 files changed, 145 insertions(+), 45 deletions(-) create mode 100644 src/enums/container_type.hpp diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index fd78e33ecf4..a3535839493 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -830,6 +830,8 @@ bool Creature::dropCorpse(std::shared_ptr lastHitCreature, std::shared }, "Game::playerQuickLootCorpse"); } + + corpse->sendUpdateToClient(player); } } diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index da4c0cec9e8..1d112bdef19 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -2636,6 +2636,11 @@ class Player final : public Creature, public Cylinder, public Bankable { uint16_t getPlayerVocationEnum() const; + using ManagedContainerMap = std::map, std::shared_ptr>>; + const ManagedContainerMap &getManagedContainers() const { + return m_managedContainers; + } + private: friend class PlayerLock; std::mutex mutex; @@ -2727,7 +2732,7 @@ class Player final : public Creature, public Cylinder, public Bankable { std::map> rewardMap; - std::map, std::shared_ptr>> m_managedContainers; + ManagedContainerMap m_managedContainers; std::vector forgeHistoryVector; std::vector quickLootListItemIds; diff --git a/src/enums/container_type.hpp b/src/enums/container_type.hpp new file mode 100644 index 00000000000..a33a75011f6 --- /dev/null +++ b/src/enums/container_type.hpp @@ -0,0 +1,24 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2024 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#pragma once + +#ifndef USE_PRECOMPILED_HEADERS + #include +#endif + +enum class ContainerSpecial_t : uint8_t { + None = 0, + LootContainer = 1, + ContentCounter = 2, + ManagerUnknown = 3, // Maybe was used on a few test servers and removed later? Original value was not being used + LootHighlight = 4, + Obtain = 8, + Manager = 9 +}; diff --git a/src/game/game.cpp b/src/game/game.cpp index d731bd766b6..8a141677d4b 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -2953,6 +2953,8 @@ void Game::playerQuickLootCorpse(std::shared_ptr player, std::shared_ptr player->sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); } + corpse->sendUpdateToClient(player); + player->lastQuickLootNotification = OTSYS_TIME(); } @@ -5454,6 +5456,8 @@ void Game::playerQuickLoot(uint32_t playerId, const Position &pos, uint16_t item } } } + + corpse->sendUpdateToClient(player); } void Game::playerLootAllCorpses(std::shared_ptr player, const Position &pos, bool lootAllCorpses) { diff --git a/src/items/containers/container.cpp b/src/items/containers/container.cpp index 5d2ffd98769..8c8b45c9949 100644 --- a/src/items/containers/container.cpp +++ b/src/items/containers/container.cpp @@ -986,3 +986,58 @@ uint32_t Container::getOwnerId() const { } return 0; } + +ContainerSpecial_t Container::getSpecialCategory(const std::shared_ptr &player) { + if (isCorpse() && !empty() && getHoldingPlayer() != player) { + return ContainerSpecial_t::LootHighlight; + } + + if (getHoldingPlayer() == player) { + if (isQuiver()) { + return ContainerSpecial_t::ContentCounter; + } + + auto [lootFlags, obtainFlags] = getObjectCategoryFlags(player); + if (lootFlags != 0 || obtainFlags != 0) { + return ContainerSpecial_t::Manager; + } + } + + return ContainerSpecial_t::None; +} + +std::pair Container::getObjectCategoryFlags(const std::shared_ptr &player) const { + uint32_t lootFlags = 0, obtainFlags = 0; + // Cycle through all containers managed by the player + for (auto [category, containerPair] : player->getManagedContainers()) { + // Check if the category is valid before continuing + if (!isValidObjectCategory(category)) { + continue; + } + + // containerPair.first refers to loot containers + if (containerPair.first == static_self_cast()) { + lootFlags |= 1 << category; + } + + // containerPair.second refers to the obtain containers + if (containerPair.second == static_self_cast()) { + obtainFlags |= 1 << category; + } + } + + return { lootFlags, obtainFlags }; +} + +uint16_t Container::getAmmoCount(const std::shared_ptr &player) const { + uint16_t ammoTotal = 0; + if (isQuiver()) { + for (std::shared_ptr listItem : getItemList()) { + if (player->getLevel() >= Item::items[listItem->getID()].minReqLevel) { + ammoTotal += listItem->getItemCount(); + } + } + } + + return ammoTotal; +} diff --git a/src/items/containers/container.hpp b/src/items/containers/container.hpp index 6bfb509b8d3..3c6c90a2991 100644 --- a/src/items/containers/container.hpp +++ b/src/items/containers/container.hpp @@ -13,6 +13,8 @@ #include "items/item.hpp" #include "items/tile.hpp" +#include "enums/container_type.hpp" + class Container; class DepotChest; class DepotLocker; @@ -178,6 +180,10 @@ class Container : public Item, public Cylinder { bool isBrowseFieldAndHoldsRewardChest(); bool isInsideContainerWithId(const uint16_t id); + ContainerSpecial_t getSpecialCategory(const std::shared_ptr &player); + std::pair getObjectCategoryFlags(const std::shared_ptr &player) const; + uint16_t getAmmoCount(const std::shared_ptr &player) const; + protected: std::ostringstream &getContentDescription(std::ostringstream &os, bool oldProtocol); diff --git a/src/items/item.cpp b/src/items/item.cpp index 81d24a790e7..c4e28e758ab 100644 --- a/src/items/item.cpp +++ b/src/items/item.cpp @@ -22,6 +22,7 @@ #include "creatures/players/imbuements/imbuements.hpp" #include "lua/creature/actions.hpp" #include "creatures/combat/spells.hpp" +#include "map/spectators.hpp" #define ITEM_IMBUEMENT_SLOT 500 @@ -3300,3 +3301,19 @@ void Item::updateTileFlags() { tile->updateTileFlags(static_self_cast()); } } + +void Item::sendUpdateToClient(const std::shared_ptr &player /* = nullptr*/) { + if (!player) { + auto spectators = Spectators().find(getPosition(), true); + for (const auto &spectator : spectators) { + spectator->getPlayer()->sendUpdateTileItem(getTile(), getPosition(), static_self_cast()); + } + + return; + } + + auto participants = player->getParty() ? player->getParty()->getPlayers() : std::vector> { player }; + for (const auto &participant : participants) { + participant->sendUpdateTileItem(getTile(), participant->getPosition(), static_self_cast()); + } +} diff --git a/src/items/item.hpp b/src/items/item.hpp index 9ff0d3d15aa..6c754c89ada 100644 --- a/src/items/item.hpp +++ b/src/items/item.hpp @@ -606,6 +606,19 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject { virtual void startDecaying(); virtual void stopDecaying(); + /** + * @brief Send "AddItem" update to the specified player or to all nearby players if none specified. + * + * This function sends updates about the item's state to a client. If a specific player is provided, + * the update is directed to that player and possibly their party members depending on the game logic. + * If no player is specified, the update is broadcast to all nearby players who are capable of viewing + * the item update, such as spectators around the item's location. + * + * @param player Optional shared pointer to a Player object. If provided, the update is directed to this player + * and their associated viewers or party members. If nullptr, the update goes to all nearby spectators. + */ + void sendUpdateToClient(const std::shared_ptr &player = nullptr); + std::shared_ptr transform(uint16_t itemId, uint16_t itemCount = -1); bool isLoadedFromMap() const { diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index d924e0bfb0e..11bea535b13 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -40,6 +40,7 @@ #include "enums/account_type.hpp" #include "enums/account_group_type.hpp" #include "enums/account_coins.hpp" +#include "enums/container_type.hpp" #include "creatures/players/highscore_category.hpp" @@ -340,48 +341,26 @@ void ProtocolGame::AddItem(NetworkMessage &msg, std::shared_ptr item) { return; } - if (it.isContainer()) { - uint8_t containerType = 0; - - std::shared_ptr container = item->getContainer(); - if (container && containerType == 0 && container->getHoldingPlayer() == player) { - uint32_t lootFlags = 0; - uint32_t obtainFlags = 0; - for (auto [category, containerMap] : player->m_managedContainers) { - if (!isValidObjectCategory(category)) { - continue; - } - if (containerMap.first == container) { - lootFlags |= 1 << category; - } - if (containerMap.second == container) { - obtainFlags |= 1 << category; - } - } - - if (lootFlags != 0 || obtainFlags != 0) { - containerType = 9; - msg.addByte(containerType); + const auto &container = item->getContainer(); + if (it.isContainer() && container) { + ContainerSpecial_t containerType = container->getSpecialCategory(player); + msg.addByte(enumToValue(containerType)); + switch (containerType) { + case ContainerSpecial_t::LootHighlight: + break; + case ContainerSpecial_t::Manager: { + auto [lootFlags, obtainFlags] = container->getObjectCategoryFlags(player); msg.add(lootFlags); msg.add(obtainFlags); + break; } - } - - // Quiver ammo count - if (container && containerType == 0 && item->isQuiver() && player->getThing(CONST_SLOT_RIGHT) == item) { - uint16_t ammoTotal = 0; - for (std::shared_ptr listItem : container->getItemList()) { - if (player->getLevel() >= Item::items[listItem->getID()].minReqLevel) { - ammoTotal += listItem->getItemCount(); - } + case ContainerSpecial_t::ContentCounter: { + uint16_t ammoTotal = container->getAmmoCount(player); + msg.add(ammoTotal); + break; } - containerType = 2; - msg.addByte(containerType); - msg.add(ammoTotal); - } - - if (containerType == 0) { - msg.addByte(0x00); + default: + break; } } @@ -4770,9 +4749,7 @@ void ProtocolGame::sendMarketEnter(uint32_t depotId) { // Only use here locker items, itemVector is for use of Game::createMarketOffer auto [itemVector, lockerItems] = player->requestLockerItems(depotLocker, true); auto totalItemsCountPosition = msg.getBufferPosition(); - msg.skipBytes(2); // Total items count - - uint16_t totalItemsCount = 0; + msg.add(lockerItems.size()); for (const auto &[itemId, tierAndCountMap] : lockerItems) { for (const auto &[tier, count] : tierAndCountMap) { msg.add(itemId); @@ -4780,12 +4757,9 @@ void ProtocolGame::sendMarketEnter(uint32_t depotId) { msg.addByte(tier); } msg.add(static_cast(count)); - totalItemsCount++; } } - msg.setBufferPosition(totalItemsCountPosition); - msg.add(totalItemsCount); writeToOutputBuffer(msg); updateCoinBalance(); From acc21fdb422671f7d0fddd7d89bfef0729252496 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Mon, 13 May 2024 11:49:02 -0300 Subject: [PATCH 2/5] fix: correct getPosition --- src/items/item.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/items/item.cpp b/src/items/item.cpp index c4e28e758ab..ff3a04129b8 100644 --- a/src/items/item.cpp +++ b/src/items/item.cpp @@ -3314,6 +3314,6 @@ void Item::sendUpdateToClient(const std::shared_ptr &player /* = nullptr auto participants = player->getParty() ? player->getParty()->getPlayers() : std::vector> { player }; for (const auto &participant : participants) { - participant->sendUpdateTileItem(getTile(), participant->getPosition(), static_self_cast()); + participant->sendUpdateTileItem(getTile(), getPosition(), static_self_cast()); } } From 8b224c419b36ca7a81010870a391614838c064b5 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Mon, 13 May 2024 18:56:08 -0300 Subject: [PATCH 3/5] feat: quiver loot category --- src/enums/container_type.hpp | 3 ++- src/items/containers/container.cpp | 4 ++-- src/server/network/protocol/protocolgame.cpp | 8 ++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/enums/container_type.hpp b/src/enums/container_type.hpp index a33a75011f6..e2bc7fe9d40 100644 --- a/src/enums/container_type.hpp +++ b/src/enums/container_type.hpp @@ -20,5 +20,6 @@ enum class ContainerSpecial_t : uint8_t { ManagerUnknown = 3, // Maybe was used on a few test servers and removed later? Original value was not being used LootHighlight = 4, Obtain = 8, - Manager = 9 + Manager = 9, + QuiverLoot = 11, }; diff --git a/src/items/containers/container.cpp b/src/items/containers/container.cpp index 8c8b45c9949..e356d321c4f 100644 --- a/src/items/containers/container.cpp +++ b/src/items/containers/container.cpp @@ -993,8 +993,8 @@ ContainerSpecial_t Container::getSpecialCategory(const std::shared_ptr & } if (getHoldingPlayer() == player) { - if (isQuiver()) { - return ContainerSpecial_t::ContentCounter; + if (isQuiver() && getSlotPosition() & SLOTP_RIGHT) { + return ContainerSpecial_t::QuiverLoot; } auto [lootFlags, obtainFlags] = getObjectCategoryFlags(player); diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 11bea535b13..e6b1152d131 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -359,6 +359,14 @@ void ProtocolGame::AddItem(NetworkMessage &msg, std::shared_ptr item) { msg.add(ammoTotal); break; } + case ContainerSpecial_t::QuiverLoot: { + uint16_t ammoTotal = container->getAmmoCount(player); + auto [lootFlags, obtainFlags] = container->getObjectCategoryFlags(player); + msg.add(lootFlags); + msg.add(ammoTotal); + msg.add(obtainFlags); + break; + } default: break; } From 897b3e091fb8669890ba338ab9dd8456378d6036 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 15 May 2024 15:01:09 -0300 Subject: [PATCH 4/5] fix: set to getAmmoAmount --- src/items/containers/container.cpp | 6 +++--- src/items/containers/container.hpp | 2 +- src/server/network/protocol/protocolgame.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/items/containers/container.cpp b/src/items/containers/container.cpp index e356d321c4f..23b9258c4f9 100644 --- a/src/items/containers/container.cpp +++ b/src/items/containers/container.cpp @@ -1029,12 +1029,12 @@ std::pair Container::getObjectCategoryFlags(const std::share return { lootFlags, obtainFlags }; } -uint16_t Container::getAmmoCount(const std::shared_ptr &player) const { - uint16_t ammoTotal = 0; +uint32_t Container::getAmmoAmount(const std::shared_ptr &player) const { + uint32_t ammoTotal = 0; if (isQuiver()) { for (std::shared_ptr listItem : getItemList()) { if (player->getLevel() >= Item::items[listItem->getID()].minReqLevel) { - ammoTotal += listItem->getItemCount(); + ammoTotal += listItem->getItemAmount(); } } } diff --git a/src/items/containers/container.hpp b/src/items/containers/container.hpp index 3c6c90a2991..18096d3f982 100644 --- a/src/items/containers/container.hpp +++ b/src/items/containers/container.hpp @@ -182,7 +182,7 @@ class Container : public Item, public Cylinder { ContainerSpecial_t getSpecialCategory(const std::shared_ptr &player); std::pair getObjectCategoryFlags(const std::shared_ptr &player) const; - uint16_t getAmmoCount(const std::shared_ptr &player) const; + uint32_t getAmmoAmount(const std::shared_ptr &player) const; protected: std::ostringstream &getContentDescription(std::ostringstream &os, bool oldProtocol); diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index e6b1152d131..217bb6f18db 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -355,12 +355,12 @@ void ProtocolGame::AddItem(NetworkMessage &msg, std::shared_ptr item) { break; } case ContainerSpecial_t::ContentCounter: { - uint16_t ammoTotal = container->getAmmoCount(player); + auto ammoTotal = container->getAmmoAmount(player); msg.add(ammoTotal); break; } case ContainerSpecial_t::QuiverLoot: { - uint16_t ammoTotal = container->getAmmoCount(player); + auto ammoTotal = container->getAmmoAmount(player); auto [lootFlags, obtainFlags] = container->getObjectCategoryFlags(player); msg.add(lootFlags); msg.add(ammoTotal); From b66d9672afb52dfe9e2e3d52d68b25cf88f712cf Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 15 May 2024 20:02:20 -0300 Subject: [PATCH 5/5] fix: remove ubuntu 20 from gha --- .github/workflows/build-ubuntu-dummy.yml | 4 +--- .github/workflows/build-ubuntu.yml | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-ubuntu-dummy.yml b/.github/workflows/build-ubuntu-dummy.yml index 00c4efdef87..f4ebb00ff27 100644 --- a/.github/workflows/build-ubuntu-dummy.yml +++ b/.github/workflows/build-ubuntu-dummy.yml @@ -17,11 +17,9 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-20.04, ubuntu-22.04] + os: [ubuntu-22.04] buildtype: [linux-release, linux-debug] include: - - os: ubuntu-20.04 - triplet: x64-linux - os: ubuntu-22.04 triplet: x64-linux diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index cdc54dc33f2..2b369e34875 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -36,11 +36,9 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-20.04, ubuntu-22.04] + os: [ubuntu-22.04] buildtype: [linux-release, linux-debug] include: - - os: ubuntu-20.04 - triplet: x64-linux - os: ubuntu-22.04 triplet: x64-linux