From c19ea1f7ad1a65b237772fb4c01657ecc8ef78df Mon Sep 17 00:00:00 2001 From: UltraNix <80540499+UltraNix@users.noreply.github.com> Date: Wed, 7 Apr 2021 13:30:34 +0200 Subject: [PATCH] fix(Core/Loot): Items below group threshold should not be blocked by master looter. (#5041) --- src/server/game/Entities/Player/Player.cpp | 4 +- src/server/game/Handlers/LootHandler.cpp | 3 +- src/server/game/Loot/LootMgr.cpp | 49 ++++++++++++++++++---- src/server/game/Loot/LootMgr.h | 2 +- 4 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 1d2efa21e..314f818df 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -25705,9 +25705,9 @@ void Player::StoreLootItem(uint8 lootSlot, Loot* loot) return; } - // Xinef: exploit protection, dont allow to loot normal items if player is not master loot + // Xinef: exploit protection, dont allow to loot normal items if player is not master loot and not below loot threshold // Xinef: only quest, ffa and conditioned items - if (!IS_ITEM_GUID(GetLootGUID()) && GetGroup() && GetGroup()->GetLootMethod() == MASTER_LOOT && GetGUID() != GetGroup()->GetMasterLooterGuid()) + if (!item->is_underthreshold && !IS_ITEM_GUID(GetLootGUID()) && GetGroup() && GetGroup()->GetLootMethod() == MASTER_LOOT && GetGUID() != GetGroup()->GetMasterLooterGuid()) if (qitem == nullptr && ffaitem == nullptr && conditem == nullptr) { SendLootRelease(GetLootGUID()); diff --git a/src/server/game/Handlers/LootHandler.cpp b/src/server/game/Handlers/LootHandler.cpp index 3cb3b27b6..72cfad8de 100644 --- a/src/server/game/Handlers/LootHandler.cpp +++ b/src/server/game/Handlers/LootHandler.cpp @@ -474,7 +474,7 @@ void WorldSession::HandleLootMasterGiveOpcode(WorldPacket& recvData) ItemPosCountVec dest; InventoryResult msg = target->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, item.itemid, item.count); - if (item.follow_loot_rules && !item.AllowedForPlayer(target)) + if (!item.AllowedForPlayer(target, true)) msg = EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM; if (msg != EQUIP_ERR_OK) { @@ -485,7 +485,6 @@ void WorldSession::HandleLootMasterGiveOpcode(WorldPacket& recvData) else _player->SendLootError(lootguid, LOOT_ERROR_MASTER_OTHER); - target->SendEquipError(msg, nullptr, nullptr, item.itemid); return; } diff --git a/src/server/game/Loot/LootMgr.cpp b/src/server/game/Loot/LootMgr.cpp index 7b9b5f026..662419ef0 100644 --- a/src/server/game/Loot/LootMgr.cpp +++ b/src/server/game/Loot/LootMgr.cpp @@ -387,19 +387,28 @@ LootItem::LootItem(LootStoreItem const& li) } // Basic checks for player/item compatibility - if false no chance to see the item in the loot -bool LootItem::AllowedForPlayer(Player const* player) const +bool LootItem::AllowedForPlayer(Player const* player, bool isGivenByMasterLooter /*= false*/, bool allowQuestLoot /*= true*/) const { - // DB conditions check - if (!sConditionMgr->IsObjectMeetToConditions(const_cast(player), conditions)) - return false; - ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(itemid); if (!pProto) return false; - // not show loot for players without profession or those who already know the recipe - if ((pProto->Flags & ITEM_FLAG_HIDE_UNUSABLE_RECIPE) && (!player->HasSkill(pProto->RequiredSkill) || player->HasSpell(pProto->Spells[1].SpellId))) + bool isMasterLooter = player->GetGroup() && player->GetGroup()->GetMasterLooterGuid() == player->GetGUID(); + + // DB conditions check + if (!sConditionMgr->IsObjectMeetToConditions(const_cast(player), conditions)) + { + // Master Looter can see conditioned recipes + if (!isGivenByMasterLooter && isMasterLooter) + { + if ((pProto->Flags & ITEM_FLAG_HIDE_UNUSABLE_RECIPE) || (pProto->Class == ITEM_CLASS_RECIPE && pProto->Bonding == BIND_WHEN_PICKED_UP && pProto->Spells[1].SpellId != 0)) + { + return true; + } + } + return false; + } // not show loot for not own team if ((pProto->Flags2 & ITEM_FLAGS_EXTRA_HORDE_ONLY) && player->GetTeamId(true) != TEAM_HORDE) @@ -408,6 +417,26 @@ bool LootItem::AllowedForPlayer(Player const* player) const if ((pProto->Flags2 & ITEM_FLAGS_EXTRA_ALLIANCE_ONLY) && player->GetTeamId(true) != TEAM_ALLIANCE) return false; + // Master looter can see certain items even if the character can't loot them + if (!isGivenByMasterLooter && isMasterLooter && allowQuestLoot) + { + // check quest requirements (exclude items not under threshold) + if (!(pProto->FlagsCu & ITEM_FLAGS_CU_IGNORE_QUEST_STATUS) && (needs_quest || pProto->StartQuest)) + { + return !is_underthreshold; + } + + return true; + } + + // Don't allow loot for players without profession or those who already know the recipe + if ((pProto->Flags & ITEM_FLAG_HIDE_UNUSABLE_RECIPE) && (!player->HasSkill(pProto->RequiredSkill) || player->HasSpell(pProto->Spells[1].SpellId))) + return false; + + // Don't allow to loot soulbound recipes that the player has already learned + if (pProto->Class == ITEM_CLASS_RECIPE && pProto->Bonding == BIND_WHEN_PICKED_UP && pProto->Spells[1].SpellId != 0 && player->HasSpell(pProto->Spells[1].SpellId)) + return false; + // check quest requirements if (!(pProto->FlagsCu & ITEM_FLAGS_CU_IGNORE_QUEST_STATUS) && ((needs_quest || (pProto->StartQuest && player->GetQuestStatus(pProto->StartQuest) != QUEST_STATUS_NONE)) && !player->HasQuestForItem(itemid))) return false; @@ -571,7 +600,7 @@ QuestItemList* Loot::FillQuestLoot(Player* player) { LootItem& item = quest_items[i]; - if (!item.is_looted && (item.AllowedForPlayer(player) || (item.follow_loot_rules && player->GetGroup() && ((player->GetGroup()->GetLootMethod() == MASTER_LOOT && player->GetGroup()->GetMasterLooterGuid() == player->GetGUID()) || player->GetGroup()->GetLootMethod() != MASTER_LOOT )))) + if (!item.is_looted && (item.AllowedForPlayer(player, false, false) || (item.follow_loot_rules && player->GetGroup() && ((player->GetGroup()->GetLootMethod() == MASTER_LOOT && player->GetGroup()->GetMasterLooterGuid() == player->GetGUID()) || player->GetGroup()->GetLootMethod() != MASTER_LOOT )))) { ql->push_back(QuestItem(i)); @@ -887,11 +916,13 @@ ByteBuffer& operator<<(ByteBuffer& b, LootView const& lv) case MASTER_PERMISSION: case RESTRICTED_PERMISSION: { + bool isMasterLooter = lv.viewer->GetGroup() && lv.viewer->GetGroup()->GetMasterLooterGuid() == lv.viewer->GetGUID(); + // if you are not the round-robin group looter, you can only see // blocked rolled items and quest items, and !ffa items for (uint8 i = 0; i < l.items.size(); ++i) { - if (!l.items[i].is_looted && !l.items[i].freeforall && l.items[i].conditions.empty() && l.items[i].AllowedForPlayer(lv.viewer)) + if (!l.items[i].is_looted && !l.items[i].freeforall && (l.items[i].conditions.empty() || isMasterLooter) && l.items[i].AllowedForPlayer(lv.viewer)) { uint8 slot_type = 0; diff --git a/src/server/game/Loot/LootMgr.h b/src/server/game/Loot/LootMgr.h index 93f02d041..878759a66 100644 --- a/src/server/game/Loot/LootMgr.h +++ b/src/server/game/Loot/LootMgr.h @@ -161,7 +161,7 @@ struct LootItem LootItem() = default; // Basic checks for player/item compatibility - if false no chance to see the item in the loot - bool AllowedForPlayer(Player const* player) const; + bool AllowedForPlayer(Player const* player, bool isGivenByMasterLooter = false, bool allowQuestLoot = true) const; void AddAllowedLooter(Player const* player); [[nodiscard]] const AllowedLooterSet& GetAllowedLooters() const { return allowedGUIDs; }