Skip to content

Commit f58d414

Browse files
authored
feat: loot highlight effect (#500)
apply this fix opentibiabr/canary@5bdb379
1 parent 7ea1e9b commit f58d414

File tree

10 files changed

+227
-47
lines changed

10 files changed

+227
-47
lines changed

src/creatures/creature.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,8 @@ bool Creature::dropCorpse(const std::shared_ptr<Creature> &lastHitCreature, cons
723723
},
724724
"Game::playerQuickLootCorpse");
725725
}
726+
727+
corpse->sendUpdateToClient(player);
726728
}
727729
}
728730

src/creatures/players/player.hpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,7 +1483,7 @@ class Player final : public Creature, public Cylinder, public Bankable {
14831483
void sendPlayerTyping(const std::shared_ptr<Creature> &creature, uint8_t typing) const;
14841484

14851485
void resetOldCharms();
1486-
bool isFirstOnStack() const;
1486+
[[nodiscard]] bool isFirstOnStack() const;
14871487

14881488
/*******************************************************************************
14891489
* Deflect Condition
@@ -1529,6 +1529,11 @@ class Player final : public Creature, public Cylinder, public Bankable {
15291529

15301530
std::unordered_map<uint16_t, uint8_t> spellActivedAimMap;
15311531

1532+
using ManagedContainerMap = std::map<ObjectCategory_t, std::pair<std::shared_ptr<Container>, std::shared_ptr<Container>>>;
1533+
[[nodiscard]] const ManagedContainerMap &getManagedContainers() const {
1534+
return m_managedContainers;
1535+
}
1536+
15321537
private:
15331538
friend class PlayerLock;
15341539
std::mutex mutex;
@@ -1614,7 +1619,7 @@ class Player final : public Creature, public Cylinder, public Bankable {
16141619

16151620
std::map<uint64_t, std::shared_ptr<Reward>> rewardMap;
16161621

1617-
std::map<ObjectCategory_t, std::pair<std::shared_ptr<Container>, std::shared_ptr<Container>>> m_managedContainers;
1622+
ManagedContainerMap m_managedContainers;
16181623
std::vector<ForgeHistory> forgeHistoryVector;
16191624

16201625
std::vector<uint16_t> quickLootListItemIds;

src/enums/container_type.hpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
////////////////////////////////////////////////////////////////////////
2+
// Crystal Server - an opensource roleplaying game
3+
////////////////////////////////////////////////////////////////////////
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
////////////////////////////////////////////////////////////////////////
17+
18+
#pragma once
19+
20+
#ifndef USE_PRECOMPILED_HEADERS
21+
#include <cstdint>
22+
#endif
23+
24+
enum class ContainerSpecial_t : uint8_t {
25+
None = 0,
26+
LootContainer = 1,
27+
ContentCounter = 2,
28+
LootHighlight = 4,
29+
Obtain = 8,
30+
Manager = 9,
31+
QuiverLoot = 11,
32+
};

src/game/game.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3067,6 +3067,26 @@ void Game::playerQuickLootCorpse(const std::shared_ptr<Player> &player, const st
30673067
}
30683068
}
30693069

3070+
bool hasLootavaible = false;
3071+
for (ContainerIterator it = corpse->iterator(); it.hasNext(); it.advance()) {
3072+
const auto &corpseItem = *it;
3073+
if (!corpseItem) {
3074+
continue;
3075+
}
3076+
3077+
const bool listed = player->isQuickLootListedItem(corpseItem);
3078+
if ((listed && ignoreListItems) || (!listed && !ignoreListItems)) {
3079+
continue;
3080+
}
3081+
3082+
hasLootavaible = true;
3083+
break;
3084+
}
3085+
3086+
if (!hasLootavaible) {
3087+
corpse->clearLootHighlight(player);
3088+
}
3089+
30703090
std::stringstream ss;
30713091
if (totalLootedGold != 0 || missedAnyGold || totalLootedItems != 0 || missedAnyItem) {
30723092
bool lootedAllGold = totalLootedGold != 0 && !missedAnyGold;
@@ -3141,6 +3161,8 @@ void Game::playerQuickLootCorpse(const std::shared_ptr<Player> &player, const st
31413161
player->sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str());
31423162
}
31433163

3164+
corpse->sendUpdateToClient(player);
3165+
31443166
player->lastQuickLootNotification = OTSYS_TIME();
31453167
}
31463168

@@ -5783,6 +5805,8 @@ void Game::handleCorpseLoot(const std::shared_ptr<Player> &player, const std::sh
57835805
} else {
57845806
playerLootAllCorpses(player, pos, lootAll);
57855807
}
5808+
5809+
corpse->sendUpdateToClient(player);
57865810
}
57875811

57885812
void Game::sendLootMessageWithCooldown(const std::shared_ptr<Player> &player, const std::string &message) {

src/items/containers/container.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,10 @@ void Container::removeThing(const std::shared_ptr<Thing> &thing, uint32_t count)
831831

832832
item->resetParent();
833833
itemlist.erase(itemlist.begin() + index);
834+
835+
if (isCorpse() && empty()) {
836+
clearLootHighlight();
837+
}
834838
}
835839
}
836840

@@ -970,8 +974,24 @@ void Container::removeItem(const std::shared_ptr<Thing> &thing, bool sendUpdateT
970974

971975
itemlist.erase(it);
972976
itemToRemove->resetParent();
977+
978+
if (isCorpse() && empty()) {
979+
clearLootHighlight();
980+
}
973981
}
974982
}
983+
void Container::clearLootHighlight(const std::shared_ptr<Player> &player) {
984+
if (!isCorpse()) {
985+
return;
986+
}
987+
988+
if (!m_lootHighlightActive) {
989+
return;
990+
}
991+
992+
m_lootHighlightActive = false;
993+
sendUpdateToClient(player);
994+
}
975995

976996
uint32_t Container::getOwnerId() const {
977997
uint32_t ownerId = Item::getOwnerId();
@@ -1095,3 +1115,62 @@ size_t ContainerIterator::getCurrentIndex() const {
10951115
const auto &top = states.back();
10961116
return top.index;
10971117
}
1118+
1119+
ContainerSpecial_t Container::getSpecialCategory(const std::shared_ptr<Player> &player) {
1120+
const auto &holdingPlayer = getHoldingPlayer();
1121+
using enum ContainerSpecial_t;
1122+
1123+
if (isCorpse() && hasLootHighlight() && !isRewardCorpse() && !empty() && (getCorpseOwner() == static_cast<uint32_t>(std::numeric_limits<int32_t>::max()) || (getCorpseOwner() == 0 || player->canOpenCorpse(getCorpseOwner())))) {
1124+
return LootHighlight;
1125+
}
1126+
1127+
if (holdingPlayer == player) {
1128+
if (isQuiver() && getSlotPosition() & SLOTP_RIGHT) {
1129+
return QuiverLoot;
1130+
}
1131+
1132+
auto [lootFlags, obtainFlags] = getObjectCategoryFlags(player);
1133+
if (lootFlags != 0 || obtainFlags != 0) {
1134+
return Manager;
1135+
}
1136+
}
1137+
1138+
return None;
1139+
}
1140+
1141+
std::pair<uint32_t, uint32_t> Container::getObjectCategoryFlags(const std::shared_ptr<Player> &player) const {
1142+
uint32_t lootFlags = 0;
1143+
uint32_t obtainFlags = 0;
1144+
// Cycle through all containers managed by the player
1145+
for (const auto &[category, containerPair] : player->getManagedContainers()) {
1146+
// Check if the category is valid before continuing
1147+
if (!isValidObjectCategory(category)) {
1148+
continue;
1149+
}
1150+
1151+
// containerPair.first refers to loot containers
1152+
if (containerPair.first == static_self_cast<Container>()) {
1153+
lootFlags |= 1 << category;
1154+
}
1155+
1156+
// containerPair.second refers to the obtain containers
1157+
if (containerPair.second == static_self_cast<Container>()) {
1158+
obtainFlags |= 1 << category;
1159+
}
1160+
}
1161+
1162+
return { lootFlags, obtainFlags };
1163+
}
1164+
1165+
uint32_t Container::getAmmoAmount(const std::shared_ptr<Player> &player) const {
1166+
uint32_t ammoTotal = 0;
1167+
if (isQuiver()) {
1168+
for (const auto &listItem : getItemList()) {
1169+
if (player->getLevel() >= Item::items[listItem->getID()].minReqLevel) {
1170+
ammoTotal += listItem->getItemAmount();
1171+
}
1172+
}
1173+
}
1174+
1175+
return ammoTotal;
1176+
}

src/items/containers/container.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include "items/item.hpp"
2222
#include "items/tile.hpp"
2323

24+
#include "enums/container_type.hpp"
25+
2426
class Container;
2527
class DepotChest;
2628
class DepotLocker;
@@ -293,6 +295,14 @@ class Container : public Item, public Cylinder {
293295
bool isBrowseFieldAndHoldsRewardChest();
294296
bool isInsideContainerWithId(uint16_t id);
295297

298+
ContainerSpecial_t getSpecialCategory(const std::shared_ptr<Player> &player);
299+
std::pair<uint32_t, uint32_t> getObjectCategoryFlags(const std::shared_ptr<Player> &player) const;
300+
uint32_t getAmmoAmount(const std::shared_ptr<Player> &player) const;
301+
void clearLootHighlight(const std::shared_ptr<Player> &player = nullptr);
302+
[[nodiscard]] bool hasLootHighlight() const {
303+
return m_lootHighlightActive;
304+
}
305+
296306
protected:
297307
uint32_t m_maxItems {};
298308
uint32_t maxSize {};
@@ -302,6 +312,7 @@ class Container : public Item, public Cylinder {
302312

303313
bool unlocked {};
304314
bool pagination {};
315+
bool m_lootHighlightActive { true };
305316

306317
friend class MapCache;
307318

src/items/item.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
#include "items/trashholder.hpp"
3636
#include "lua/creature/actions.hpp"
3737
#include "map/house/house.hpp"
38+
#include "map/spectators.hpp"
39+
#include "creatures/players/grouping/party.hpp"
3840

3941
#define ITEM_IMBUEMENT_SLOT 500
4042

@@ -3565,3 +3567,33 @@ std::string ItemProperties::getShader() const {
35653567
const CustomAttribute* shader = getCustomAttribute("shader");
35663568
return shader ? shader->getString() : "";
35673569
}
3570+
3571+
void Item::sendUpdateToClient(const std::shared_ptr<Player> &player /* = nullptr */) {
3572+
const auto &tile = getTile();
3573+
if (!tile) {
3574+
return;
3575+
}
3576+
3577+
auto selfItem = getItem();
3578+
3579+
auto sendUpdateTo = [&](const std::shared_ptr<Player> &target) {
3580+
if (target) {
3581+
target->sendUpdateTileItem(tile, getPosition(), selfItem);
3582+
}
3583+
};
3584+
3585+
if (player) {
3586+
if (auto party = player->getParty()) {
3587+
for (const auto &participant : party->getPlayers()) {
3588+
sendUpdateTo(participant);
3589+
}
3590+
} else {
3591+
sendUpdateTo(player);
3592+
}
3593+
return;
3594+
}
3595+
3596+
for (const auto &spectator : Spectators().find<Creature>(getPosition(), true)) {
3597+
sendUpdateTo(spectator->getPlayer());
3598+
}
3599+
}

src/items/item.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,19 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject {
637637
virtual void startDecaying();
638638
virtual void stopDecaying();
639639

640+
/**
641+
* @brief Send "AddItem" update to the specified player or to all nearby players if none specified.
642+
*
643+
* This function sends updates about the item's state to a client. If a specific player is provided,
644+
* the update is directed to that player and possibly their party members depending on the game logic.
645+
* If no player is specified, the update is broadcast to all nearby players who are capable of viewing
646+
* the item update, such as spectators around the item's location.
647+
*
648+
* @param player Optional shared pointer to a Player object. If provided, the update is directed to this player
649+
* and their associated viewers or party members. If nullptr, the update goes to all nearby spectators.
650+
*/
651+
void sendUpdateToClient(const std::shared_ptr<Player> &player = nullptr);
652+
640653
std::shared_ptr<Item> transform(uint16_t itemId, uint16_t itemCount = -1);
641654

642655
bool isLoadedFromMap() const {

src/lua/creature/actions.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ ReturnValue Actions::internalUseItem(const std::shared_ptr<Player> &player, cons
381381
player->onCloseContainer(openContainer);
382382
player->closeContainer(oldContainerId);
383383
} else {
384+
container->clearLootHighlight();
384385
player->addContainer(index, openContainer);
385386
player->onSendContainer(openContainer);
386387
}

0 commit comments

Comments
 (0)