From 010e6f7f49744b16e3ecececb7d9605f8b8db4d5 Mon Sep 17 00:00:00 2001 From: Shauren Date: Tue, 25 Oct 2022 00:30:52 +0200 Subject: Core/Loot: Implemented dungeon encounter personal loot Closes #20066 --- src/server/game/Entities/GameObject/GameObject.cpp | 29 ++- src/server/game/Entities/Item/ItemTemplate.h | 1 + src/server/game/Entities/Player/Player.cpp | 53 ++--- src/server/game/Entities/Player/Player.h | 2 +- src/server/game/Entities/Unit/Unit.cpp | 56 +++-- src/server/game/Groups/Group.cpp | 10 +- src/server/game/Handlers/GroupHandler.cpp | 7 +- src/server/game/Loot/Loot.cpp | 40 ++-- src/server/game/Loot/Loot.h | 9 +- src/server/game/Loot/LootItemStorage.cpp | 2 +- src/server/game/Loot/LootMgr.cpp | 247 +++++++++++++++++++-- src/server/game/Loot/LootMgr.h | 15 +- 12 files changed, 343 insertions(+), 128 deletions(-) (limited to 'src/server/game') diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index caa65afa59e..70e088a23ea 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -2268,15 +2268,32 @@ void GameObject::Use(Unit* user) { if (info->chest.chestPersonalLoot) { - Loot* loot = new Loot(GetMap(), GetGUID(), LOOT_CHEST, nullptr); - m_personalLoot[player->GetGUID()].reset(loot); + GameObjectTemplateAddon const* addon = GetTemplateAddon(); + if (info->chest.DungeonEncounter) + { + std::vector tappers; + for (ObjectGuid tapperGuid : GetTapList()) + if (Player* tapper = ObjectAccessor::GetPlayer(*this, tapperGuid)) + tappers.push_back(tapper); - loot->SetDungeonEncounterId(info->chest.DungeonEncounter); - loot->FillLoot(info->chest.chestPersonalLoot, LootTemplates_Gameobject, player, true, false, GetLootMode(), GetMap()->GetDifficultyLootItemContext()); + if (tappers.empty()) + tappers.push_back(player); - if (GetLootMode() > 0) - if (GameObjectTemplateAddon const* addon = GetTemplateAddon()) + m_personalLoot = GenerateDungeonEncounterPersonalLoot(info->chest.DungeonEncounter, info->chest.chestPersonalLoot, + LootTemplates_Gameobject, LOOT_CHEST, this, addon ? addon->Mingold : 0, addon ? addon->Maxgold : 0, + GetLootMode(), GetMap()->GetDifficultyLootItemContext(), tappers); + } + else + { + Loot* loot = new Loot(GetMap(), GetGUID(), LOOT_CHEST, nullptr); + m_personalLoot[player->GetGUID()].reset(loot); + + loot->SetDungeonEncounterId(info->chest.DungeonEncounter); + loot->FillLoot(info->chest.chestPersonalLoot, LootTemplates_Gameobject, player, true, false, GetLootMode(), GetMap()->GetDifficultyLootItemContext()); + + if (GetLootMode() > 0 && addon) loot->generateMoneyLoot(addon->Mingold, addon->Maxgold); + } } } diff --git a/src/server/game/Entities/Item/ItemTemplate.h b/src/server/game/Entities/Item/ItemTemplate.h index 992c736569f..d2fa2029fa8 100644 --- a/src/server/game/Entities/Item/ItemTemplate.h +++ b/src/server/game/Entities/Item/ItemTemplate.h @@ -821,6 +821,7 @@ struct TC_GAME_API ItemTemplate bool HasSignature() const; bool IsWeapon() const { return GetClass() == ITEM_CLASS_WEAPON; } + bool IsArmor() const { return GetClass() == ITEM_CLASS_ARMOR; } bool IsRangedWeapon() const { diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 7ece702802f..3b46774769e 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -11080,14 +11080,17 @@ InventoryResult Player::CanUseItem(ItemTemplate const* proto, bool skipRequiredL return EQUIP_ERR_OK; } -InventoryResult Player::CanRollForItemInLFG(ItemTemplate const* proto, Map const* map) const +InventoryResult Player::CanRollNeedForItem(ItemTemplate const* proto, Map const* map, bool restrictOnlyLfg) const { - if (!GetGroup() || !GetGroup()->isLFGGroup()) - return EQUIP_ERR_OK; // not in LFG group + if (restrictOnlyLfg) + { + if (!GetGroup() || !GetGroup()->isLFGGroup()) + return EQUIP_ERR_OK; // not in LFG group - // check if looted object is inside the lfg dungeon - if (!sLFGMgr->inLfgDungeonMap(GetGroup()->GetGUID(), map->GetId(), map->GetDifficultyID())) - return EQUIP_ERR_OK; + // check if looted object is inside the lfg dungeon + if (!sLFGMgr->inLfgDungeonMap(GetGroup()->GetGUID(), map->GetId(), map->GetDifficultyID())) + return EQUIP_ERR_OK; + } if (!proto) return EQUIP_ERR_ITEM_NOT_FOUND; @@ -11107,41 +11110,14 @@ InventoryResult Player::CanRollForItemInLFG(ItemTemplate const* proto, Map const return EQUIP_ERR_CANT_EQUIP_SKILL; } - uint8 _class = GetClass(); - if (proto->GetClass() == ITEM_CLASS_WEAPON && GetSkillValue(proto->GetSkill()) == 0) return EQUIP_ERR_PROFICIENCY_NEEDED; - if (proto->GetClass() == ITEM_CLASS_ARMOR && proto->GetSubClass() > ITEM_SUBCLASS_ARMOR_MISCELLANEOUS && proto->GetSubClass() < ITEM_SUBCLASS_ARMOR_COSMETIC && proto->GetInventoryType() != INVTYPE_CLOAK) + if (proto->GetClass() == ITEM_CLASS_ARMOR && proto->GetInventoryType() != INVTYPE_CLOAK) { - if (_class == CLASS_WARRIOR || _class == CLASS_PALADIN || _class == CLASS_DEATH_KNIGHT) - { - if (GetLevel() < 40) - { - if (proto->GetSubClass() != ITEM_SUBCLASS_ARMOR_MAIL) - return EQUIP_ERR_CLIENT_LOCKED_OUT; - } - else if (proto->GetSubClass() != ITEM_SUBCLASS_ARMOR_PLATE) - return EQUIP_ERR_CLIENT_LOCKED_OUT; - } - else if (_class == CLASS_HUNTER || _class == CLASS_SHAMAN) - { - if (GetLevel() < 40) - { - if (proto->GetSubClass() != ITEM_SUBCLASS_ARMOR_LEATHER) - return EQUIP_ERR_CLIENT_LOCKED_OUT; - } - else if (proto->GetSubClass() != ITEM_SUBCLASS_ARMOR_MAIL) - return EQUIP_ERR_CLIENT_LOCKED_OUT; - } - - if (_class == CLASS_ROGUE || _class == CLASS_DRUID) - if (proto->GetSubClass() != ITEM_SUBCLASS_ARMOR_LEATHER) - return EQUIP_ERR_CLIENT_LOCKED_OUT; - - if (_class == CLASS_MAGE || _class == CLASS_PRIEST || _class == CLASS_WARLOCK) - if (proto->GetSubClass() != ITEM_SUBCLASS_ARMOR_CLOTH) - return EQUIP_ERR_CLIENT_LOCKED_OUT; + ChrClassesEntry const* classesEntry = sChrClassesStore.AssertEntry(GetClass()); + if (!(classesEntry->ArmorTypeMask & 1 << proto->GetSubClass())) + return EQUIP_ERR_CLIENT_LOCKED_OUT; } return EQUIP_ERR_OK; @@ -17880,8 +17856,7 @@ bool Player::isAllowedToLoot(const Creature* creature) const switch (loot->GetLootMethod()) { - case PERSONAL_LOOT: /// @todo implement personal loot (http://wow.gamepedia.com/Loot#Personal_Loot) - return false; + case PERSONAL_LOOT: case FREE_FOR_ALL: return true; case ROUND_ROBIN: diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 7cf62d43c6e..c155ccd6d01 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1355,7 +1355,7 @@ class TC_GAME_API Player : public Unit, public GridObject InventoryResult CanUseItem(Item* pItem, bool not_loading = true) const; bool HasItemTotemCategory(uint32 TotemCategory) const; InventoryResult CanUseItem(ItemTemplate const* pItem, bool skipRequiredLevelCheck = false) const; - InventoryResult CanRollForItemInLFG(ItemTemplate const* item, Map const* map) const; + InventoryResult CanRollNeedForItem(ItemTemplate const* item, Map const* map, bool restrictOnlyLfg) const; Item* StoreNewItem(ItemPosCountVec const& pos, uint32 itemId, bool update, ItemRandomBonusListId randomBonusListId = 0, GuidSet const& allowedLooters = GuidSet(), ItemContext context = ItemContext::NONE, std::vector const& bonusListIDs = std::vector(), bool addToCollection = true); Item* StoreItem(ItemPosCountVec const& pos, Item* pItem, bool update); Item* EquipNewItem(uint16 pos, uint32 item, ItemContext context, bool update); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index db3caadc21c..a5a605fc19e 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -10583,9 +10583,9 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId) Creature* creature = victim->ToCreature(); - bool isRewardAllowed = true; + bool isRewardAllowed = attacker != victim; if (creature) - isRewardAllowed = !creature->GetTapList().empty(); + isRewardAllowed = isRewardAllowed && !creature->GetTapList().empty(); std::vector tappers; if (isRewardAllowed && creature) @@ -10601,11 +10601,6 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId) // call kill spell proc event (before real die and combat stop to triggering auras removed at death/combat stop) if (isRewardAllowed) { - WorldPackets::Party::PartyKillLog partyKillLog; - partyKillLog.Player = player->GetGUID(); - partyKillLog.Victim = victim->GetGUID(); - partyKillLog.Write(); - std::unordered_set groups; for (Player* tapper : tappers) { @@ -10613,6 +10608,11 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId) { if (groups.insert(tapperGroup).second) { + WorldPackets::Party::PartyKillLog partyKillLog; + partyKillLog.Player = player && tapperGroup->IsMember(player->GetGUID()) ? player->GetGUID() : tapper->GetGUID(); + partyKillLog.Victim = victim->GetGUID(); + partyKillLog.Write(); + tapperGroup->BroadcastPacket(partyKillLog.GetRawPacket(), tapperGroup->GetMemberGroup(tapper->GetGUID()) != 0); if (creature) @@ -10620,7 +10620,12 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId) } } else - tapper->SendDirectMessage(partyKillLog.GetRawPacket()); + { + WorldPackets::Party::PartyKillLog partyKillLog; + partyKillLog.Player = tapper->GetGUID(); + partyKillLog.Victim = victim->GetGUID(); + tapper->SendDirectMessage(partyKillLog.Write()); + } } // Generate loot before updating looter @@ -10635,29 +10640,32 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId) Group* group = !groups.empty() ? *groups.begin() : nullptr; Player* looter = group ? ASSERT_NOTNULL(ObjectAccessor::GetPlayer(*creature, group->GetLooterGuid())) : tappers[0]; - Loot* loot = new Loot(creature->GetMap(), creature->GetGUID(), LOOT_CORPSE, dungeonEncounter ? group : nullptr); - if (dungeonEncounter) - loot->SetDungeonEncounterId(dungeonEncounter->ID); + { + creature->m_personalLoot = GenerateDungeonEncounterPersonalLoot(dungeonEncounter->ID, creature->GetCreatureTemplate()->lootid, + LootTemplates_Creature, LOOT_CORPSE, creature, creature->GetCreatureTemplate()->mingold, creature->GetCreatureTemplate()->maxgold, + creature->GetLootMode(), creature->GetMap()->GetDifficultyLootItemContext(), tappers); + } + else + { + Loot* loot = new Loot(creature->GetMap(), creature->GetGUID(), LOOT_CORPSE, dungeonEncounter ? group : nullptr); - if (uint32 lootid = creature->GetCreatureTemplate()->lootid) - loot->FillLoot(lootid, LootTemplates_Creature, looter, dungeonEncounter != nullptr, false, creature->GetLootMode(), creature->GetMap()->GetDifficultyLootItemContext()); + if (uint32 lootid = creature->GetCreatureTemplate()->lootid) + loot->FillLoot(lootid, LootTemplates_Creature, looter, dungeonEncounter != nullptr, false, creature->GetLootMode(), creature->GetMap()->GetDifficultyLootItemContext()); - if (creature->GetLootMode() > 0) - loot->generateMoneyLoot(creature->GetCreatureTemplate()->mingold, creature->GetCreatureTemplate()->maxgold); + if (creature->GetLootMode() > 0) + loot->generateMoneyLoot(creature->GetCreatureTemplate()->mingold, creature->GetCreatureTemplate()->maxgold); - if (group) - loot->NotifyLootList(creature->GetMap()); + if (group) + loot->NotifyLootList(creature->GetMap()); - if (dungeonEncounter || groups.empty()) - creature->m_loot.reset(loot); // TODO: personal boss loot - else creature->m_personalLoot[looter->GetGUID()].reset(loot); // trash mob loot is personal, generated with round robin rules - // Update round robin looter only if the creature had loot - if (!loot->isLooted()) - for (Group* tapperGroup : groups) - tapperGroup->UpdateLooterGuid(creature); + // Update round robin looter only if the creature had loot + if (!loot->isLooted()) + for (Group* tapperGroup : groups) + tapperGroup->UpdateLooterGuid(creature); + } } else { diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index 11f07340955..07ebc653091 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -33,16 +33,13 @@ #include "ObjectMgr.h" #include "PartyPackets.h" #include "Pet.h" -#include "PhasingHandler.h" #include "Player.h" -#include "TerrainMgr.h" #include "UpdateData.h" -#include "World.h" #include "WorldSession.h" Group::Group() : m_leaderGuid(), m_leaderFactionGroup(0), m_leaderName(""), m_groupFlags(GROUP_FLAG_NONE), m_groupCategory(GROUP_CATEGORY_HOME), m_dungeonDifficulty(DIFFICULTY_NORMAL), m_raidDifficulty(DIFFICULTY_NORMAL_RAID), m_legacyRaidDifficulty(DIFFICULTY_10_N), -m_bgGroup(nullptr), m_bfGroup(nullptr), m_lootMethod(FREE_FOR_ALL), m_lootThreshold(ITEM_QUALITY_UNCOMMON), m_looterGuid(), +m_bgGroup(nullptr), m_bfGroup(nullptr), m_lootMethod(PERSONAL_LOOT), m_lootThreshold(ITEM_QUALITY_UNCOMMON), m_looterGuid(), m_masterLooterGuid(), m_subGroupsCounts(nullptr), m_guid(), m_dbStoreId(0), m_isLeaderOffline(false), m_readyCheckStarted(false), m_readyCheckTimer(Milliseconds::zero()), m_activeMarkers(0) { @@ -140,9 +137,6 @@ bool Group::Create(Player* leader) if (m_groupFlags & GROUP_FLAG_RAID) _initRaidSubGroupsCounter(); - if (!isLFGGroup()) - m_lootMethod = GROUP_LOOT; - m_lootThreshold = ITEM_QUALITY_UNCOMMON; m_looterGuid = leaderGuid; m_masterLooterGuid.Clear(); @@ -267,7 +261,7 @@ void Group::ConvertToLFG() { m_groupFlags = GroupFlags(m_groupFlags | GROUP_FLAG_LFG | GROUP_FLAG_LFG_RESTRICTED); m_groupCategory = GROUP_CATEGORY_INSTANCE; - m_lootMethod = GROUP_LOOT; + m_lootMethod = PERSONAL_LOOT; if (!isBGGroup() && !isBFGroup()) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_TYPE); diff --git a/src/server/game/Handlers/GroupHandler.cpp b/src/server/game/Handlers/GroupHandler.cpp index a309519028c..0f28952c5dd 100644 --- a/src/server/game/Handlers/GroupHandler.cpp +++ b/src/server/game/Handlers/GroupHandler.cpp @@ -360,13 +360,14 @@ void WorldSession::HandleLeaveGroupOpcode(WorldPackets::Party::LeaveGroup& /*pac } } -void WorldSession::HandleSetLootMethodOpcode(WorldPackets::Party::SetLootMethod& packet) +void WorldSession::HandleSetLootMethodOpcode(WorldPackets::Party::SetLootMethod& /*packet*/) { + // not allowed to change + /* Group* group = GetPlayer()->GetGroup(); if (!group) return; - /** error handling **/ if (!group->IsLeader(GetPlayer()->GetGUID())) return; @@ -389,13 +390,13 @@ void WorldSession::HandleSetLootMethodOpcode(WorldPackets::Party::SetLootMethod& if (packet.LootMethod == MASTER_LOOT && !group->IsMember(packet.LootMasterGUID)) return; - /********************/ // everything's fine, do it group->SetLootMethod(static_cast(packet.LootMethod)); group->SetMasterLooterGuid(packet.LootMasterGUID); group->SetLootThreshold(static_cast(packet.LootThreshold)); group->SendUpdate(); + */ } void WorldSession::HandleMinimapPingOpcode(WorldPackets::Party::MinimapPingClient& packet) diff --git a/src/server/game/Loot/Loot.cpp b/src/server/game/Loot/Loot.cpp index 5b12fcc0a4e..54863bf7395 100644 --- a/src/server/game/Loot/Loot.cpp +++ b/src/server/game/Loot/Loot.cpp @@ -68,7 +68,13 @@ LootItem& LootItem::operator=(LootItem&&) noexcept = default; LootItem::~LootItem() = default; // Basic checks for player/item compatibility - if false no chance to see the item in the loot -bool LootItem::AllowedForPlayer(Player const* player, Loot const& loot) const +bool LootItem::AllowedForPlayer(Player const* player, Loot const* loot) const +{ + return AllowedForPlayer(player, loot, itemid, needs_quest, follow_loot_rules, false, conditions); +} + +bool LootItem::AllowedForPlayer(Player const* player, Loot const* loot, uint32 itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck, + ConditionContainer const& conditions) { // DB conditions check if (!sConditionMgr->IsObjectMeetToConditions(player, conditions)) @@ -86,7 +92,7 @@ bool LootItem::AllowedForPlayer(Player const* player, Loot const& loot) const return false; // Master looter can see all items even if the character can't loot them - if (loot.GetLootMethod() == MASTER_LOOT && follow_loot_rules && player->GetGroup() && player->GetGroup()->GetMasterLooterGuid() == player->GetGUID()) + if (loot && loot->GetLootMethod() == MASTER_LOOT && follow_loot_rules && loot->GetLootMasterGUID() == player->GetGUID()) return true; // Don't allow loot for players without profession or those who already know the recipe @@ -105,23 +111,19 @@ bool LootItem::AllowedForPlayer(Player const* player, Loot const& loot) const } } - // Don't allow to loot soulbound recipes that the player has already learned - if (pProto->GetClass() == ITEM_CLASS_RECIPE && pProto->GetBonding() == BIND_ON_ACQUIRE) - { - for (ItemEffectEntry const* itemEffect : pProto->Effects) - { - if (itemEffect->TriggerType != ITEM_SPELLTRIGGER_ON_LEARN) - continue; - - if (player->HasSpell(itemEffect->SpellID)) - return false; - } - } - // check quest requirements if (!pProto->HasFlag(ITEM_FLAGS_CU_IGNORE_QUEST_STATUS) && ((needs_quest || (pProto->GetStartQuest() && player->GetQuestStatus(pProto->GetStartQuest()) != QUEST_STATUS_NONE)) && !player->HasQuestForItem(itemid))) return false; + if (strictUsabilityCheck) + { + if ((pProto->IsWeapon() || pProto->IsArmor()) && !pProto->IsUsableByLootSpecialization(player, true)) + return false; + + if (player->CanRollNeedForItem(pProto, nullptr, false) != EQUIP_ERR_OK) + return false; + } + return true; } @@ -228,7 +230,7 @@ void LootRoll::SendStartRoll() startLootRoll.Method = m_loot->GetLootMethod(); startLootRoll.ValidRolls = m_voteMask; // In NEED_BEFORE_GREED need disabled for non-usable item for player - if (m_loot->GetLootMethod() == NEED_BEFORE_GREED && player->CanRollForItemInLFG(itemTemplate, m_map) != EQUIP_ERR_OK) + if (m_loot->GetLootMethod() == NEED_BEFORE_GREED && player->CanRollNeedForItem(itemTemplate, m_map, true) != EQUIP_ERR_OK) startLootRoll.ValidRolls &= ~ROLL_FLAG_TYPE_NEED; FillPacket(startLootRoll.Item); @@ -1005,10 +1007,6 @@ void Loot::Update() void Loot::FillNotNormalLootFor(Player const* player) { - if (_dungeonEncounterId) - if (player->IsLockedToDungeonEncounter(_dungeonEncounterId)) - return; - ObjectGuid plguid = player->GetGUID(); _allowedLooters.insert(plguid); @@ -1016,7 +1014,7 @@ void Loot::FillNotNormalLootFor(Player const* player) for (LootItem& item : items) { - if (!item.AllowedForPlayer(player, *this)) + if (!item.AllowedForPlayer(player, this)) continue; item.AddAllowedLooter(player); diff --git a/src/server/game/Loot/Loot.h b/src/server/game/Loot/Loot.h index 8f1e717d8bd..29eabb373d9 100644 --- a/src/server/game/Loot/Loot.h +++ b/src/server/game/Loot/Loot.h @@ -193,7 +193,9 @@ struct TC_GAME_API LootItem ~LootItem(); // Basic checks for player/item compatibility - if false no chance to see the item in the loot - used only for loot generation - bool AllowedForPlayer(Player const* player, Loot const& loot) const; + bool AllowedForPlayer(Player const* player, Loot const* loot) const; + static bool AllowedForPlayer(Player const* player, Loot const* loot, uint32 itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck, + ConditionContainer const& conditions); void AddAllowedLooter(Player const* player); GuidSet const& GetAllowedLooters() const { return allowedGUIDs; } bool HasAllowedLooter(ObjectGuid const& looter) const; @@ -282,6 +284,8 @@ struct TC_GAME_API Loot ObjectGuid const& GetGUID() const { return _guid; } ObjectGuid const& GetOwnerGUID() const { return _owner; } + ItemContext GetItemContext() const { return _itemContext; } + void SetItemContext(ItemContext context) { _itemContext = context; } LootMethod GetLootMethod() const { return _lootMethod; } ObjectGuid const& GetLootMasterGUID() const { return _lootMaster; } uint32 GetDungeonEncounterId() const { return _dungeonEncounterId; } @@ -300,6 +304,7 @@ struct TC_GAME_API Loot void generateMoneyLoot(uint32 minAmount, uint32 maxAmount); bool FillLoot(uint32 lootId, LootStore const& store, Player* lootOwner, bool personal, bool noEmptyError = false, uint16 lootMode = LOOT_MODE_DEFAULT, ItemContext context = ItemContext::NONE); + void FillNotNormalLootFor(Player const* player); // count unlooted items // Inserts the item into the loot (called by LootTemplate processors) void AddItem(LootStoreItem const& item); @@ -318,8 +323,6 @@ struct TC_GAME_API Loot void Update(); private: - void FillNotNormalLootFor(Player const* player); - GuidSet PlayersLooting; NotNormalLootItemMap PlayerFFAItems; diff --git a/src/server/game/Loot/LootItemStorage.cpp b/src/server/game/Loot/LootItemStorage.cpp index c464b6ee914..d5a3ada1f9c 100644 --- a/src/server/game/Loot/LootItemStorage.cpp +++ b/src/server/game/Loot/LootItemStorage.cpp @@ -272,7 +272,7 @@ void LootItemStorage::AddNewStoredLoot(uint64 containerId, Loot* loot, Player* p // saved to the DB that the player never should have gotten. This check prevents that, so that only // items that the player should get in loot are in the DB. // IE: Horde items are not saved to the DB for Ally players. - if (!li.AllowedForPlayer(player, *loot)) + if (!li.AllowedForPlayer(player, loot)) continue; // Don't save currency tokens diff --git a/src/server/game/Loot/LootMgr.cpp b/src/server/game/Loot/LootMgr.cpp index 7ea8bd2b1d0..2a2362bdc76 100644 --- a/src/server/game/Loot/LootMgr.cpp +++ b/src/server/game/Loot/LootMgr.cpp @@ -56,32 +56,43 @@ LootStore LootTemplates_Spell("spell_loot_template", "spell id ( // Selects invalid loot items to be removed from group possible entries (before rolling) struct LootGroupInvalidSelector { - explicit LootGroupInvalidSelector(Loot const& /*loot*/, uint16 lootMode) : /*_loot(loot),*/ _lootMode(lootMode) { } + explicit LootGroupInvalidSelector(uint16 lootMode, Player const* personalLooter) : _lootMode(lootMode), _personalLooter(personalLooter) { } - bool operator()(LootStoreItem* item) const + bool operator()(LootStoreItem const* item) const { if (!(item->lootmode & _lootMode)) return true; + if (_personalLooter && !LootItem::AllowedForPlayer(_personalLooter, nullptr, item->itemid, item->needs_quest, + !item->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(item->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES), + true, item->conditions)) + return true; + return false; } private: - //Loot const& _loot; uint16 _lootMode; + Player const* _personalLooter; }; class LootTemplate::LootGroup // A set of loot definitions for items (refs are not allowed) { public: - LootGroup() { } + LootGroup() = default; + LootGroup(LootGroup const&) = delete; + LootGroup(LootGroup&&) = delete; + LootGroup& operator=(LootGroup const&) = delete; + LootGroup& operator=(LootGroup&&) = delete; ~LootGroup(); void AddEntry(LootStoreItem* item); // Adds an entry to the group (at loading stage) + bool HasDropForPlayer(Player const* player, bool strictUsabilityCheck) const; bool HasQuestDrop() const; // True if group includes at least 1 quest drop entry bool HasQuestDropForPlayer(Player const* player) const; // The same for active quests of the player - void Process(Loot& loot, uint16 lootMode) const; // Rolls an item from the group (if any) and adds the item to the loot + void Process(Loot& loot, uint16 lootMode, + Player const* personalLooter = nullptr) const; // Rolls an item from the group (if any) and adds the item to the loot float RawTotalChance() const; // Overall chance for the group (without equal chanced items) float TotalChance() const; // Overall chance for the group @@ -94,11 +105,8 @@ class LootTemplate::LootGroup // A set of loot def LootStoreItemList ExplicitlyChanced; // Entries with chances defined in DB LootStoreItemList EqualChanced; // Zero chances - every entry takes the same chance - LootStoreItem const* Roll(Loot& loot, uint16 lootMode) const; // Rolls an item from the group, returns NULL if all miss their chances - - // This class must never be copied - storing pointers - LootGroup(LootGroup const&) = delete; - LootGroup& operator=(LootGroup const&) = delete; + // Rolls an item from the group, returns NULL if all miss their chances + LootStoreItem const* Roll(uint16 lootMode, Player const* personalLooter = nullptr) const; }; //Remove all data and free all memory @@ -370,10 +378,10 @@ void LootTemplate::LootGroup::AddEntry(LootStoreItem* item) } // Rolls an item from the group, returns NULL if all miss their chances -LootStoreItem const* LootTemplate::LootGroup::Roll(Loot& loot, uint16 lootMode) const +LootStoreItem const* LootTemplate::LootGroup::Roll(uint16 lootMode, Player const* personalLooter /*= nullptr*/) const { LootStoreItemList possibleLoot = ExplicitlyChanced; - possibleLoot.remove_if(LootGroupInvalidSelector(loot, lootMode)); + possibleLoot.remove_if(LootGroupInvalidSelector(lootMode, personalLooter)); if (!possibleLoot.empty()) // First explicitly chanced entries are checked { @@ -392,13 +400,30 @@ LootStoreItem const* LootTemplate::LootGroup::Roll(Loot& loot, uint16 lootMode) } possibleLoot = EqualChanced; - possibleLoot.remove_if(LootGroupInvalidSelector(loot, lootMode)); + possibleLoot.remove_if(LootGroupInvalidSelector(lootMode, personalLooter)); if (!possibleLoot.empty()) // If nothing selected yet - an item is taken from equal-chanced part return Trinity::Containers::SelectRandomContainerElement(possibleLoot); return nullptr; // Empty drop from the group } +bool LootTemplate::LootGroup::HasDropForPlayer(Player const* player, bool strictUsabilityCheck) const +{ + for (LootStoreItem const* lootStoreItem : ExplicitlyChanced) + if (LootItem::AllowedForPlayer(player, nullptr, lootStoreItem->itemid, lootStoreItem->needs_quest, + !lootStoreItem->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(lootStoreItem->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES), + strictUsabilityCheck, lootStoreItem->conditions)) + return true; + + for (LootStoreItem const* lootStoreItem : EqualChanced) + if (LootItem::AllowedForPlayer(player, nullptr, lootStoreItem->itemid, lootStoreItem->needs_quest, + !lootStoreItem->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(lootStoreItem->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES), + strictUsabilityCheck, lootStoreItem->conditions)) + return true; + + return false; +} + // True if group includes at least 1 quest drop entry bool LootTemplate::LootGroup::HasQuestDrop() const { @@ -437,9 +462,9 @@ void LootTemplate::LootGroup::CopyConditions(ConditionContainer /*conditions*/) } // Rolls an item from the group (if any takes its chance) and adds the item to the loot -void LootTemplate::LootGroup::Process(Loot& loot, uint16 lootMode) const +void LootTemplate::LootGroup::Process(Loot& loot, uint16 lootMode, Player const* personalLooter /*= nullptr*/) const { - if (LootStoreItem const* item = Roll(loot, lootMode)) + if (LootStoreItem const* item = Roll(lootMode, personalLooter)) loot.AddItem(*item); } @@ -557,7 +582,7 @@ void LootTemplate::CopyConditions(LootItem* li) const } // Rolls for every item in the template and adds the rolled items the the loot -void LootTemplate::Process(Loot& loot, bool rate, uint16 lootMode, uint8 groupId) const +void LootTemplate::Process(Loot& loot, bool rate, uint16 lootMode, uint8 groupId, Player const* personalLooter /*= nullptr*/) const { if (groupId) // Group reference uses own processing of the group { @@ -567,7 +592,7 @@ void LootTemplate::Process(Loot& loot, bool rate, uint16 lootMode, uint8 groupId if (!Groups[groupId - 1]) return; - Groups[groupId - 1]->Process(loot, lootMode); + Groups[groupId - 1]->Process(loot, lootMode, personalLooter); return; } @@ -589,16 +614,162 @@ void LootTemplate::Process(Loot& loot, bool rate, uint16 lootMode, uint8 groupId uint32 maxcount = uint32(float(item->maxcount) * sWorld->getRate(RATE_DROP_ITEM_REFERENCED_AMOUNT)); for (uint32 loop = 0; loop < maxcount; ++loop) // Ref multiplicator - Referenced->Process(loot, rate, lootMode, item->groupid); + Referenced->Process(loot, rate, lootMode, item->groupid, personalLooter); + } + else + { + // Plain entries (not a reference, not grouped) + // Chance is already checked, just add + if (!personalLooter + || LootItem::AllowedForPlayer(personalLooter, nullptr, item->itemid, item->needs_quest, + !item->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(item->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES), + true, item->conditions)) + loot.AddItem(*item); } - else // Plain entries (not a reference, not grouped) - loot.AddItem(*item); // Chance is already checked, just add } // Now processing groups for (LootGroups::const_iterator i = Groups.begin(); i != Groups.end(); ++i) if (LootGroup* group = *i) - group->Process(loot, lootMode); + group->Process(loot, lootMode, personalLooter); +} + +void LootTemplate::ProcessPersonalLoot(std::unordered_map>& personalLoot, bool rate, uint16 lootMode) const +{ + auto getLootersForItem = [&personalLoot](auto&& predicate) + { + std::vector lootersForItem; + for (auto&& [looter, loot] : personalLoot) + { + if (predicate(looter)) + lootersForItem.push_back(looter); + } + return lootersForItem; + }; + + // Rolling non-grouped items + for (LootStoreItem const* item : Entries) + { + if (!(item->lootmode & lootMode)) // Do not add if mode mismatch + continue; + + if (!item->Roll(rate)) + continue; // Bad luck for the entry + + if (item->reference > 0) // References processing + { + LootTemplate const* referenced = LootTemplates_Reference.GetLootFor(item->reference); + if (!referenced) + continue; // Error message already printed at loading stage + + uint32 maxcount = uint32(float(item->maxcount) * sWorld->getRate(RATE_DROP_ITEM_REFERENCED_AMOUNT)); + std::vector gotLoot; + for (uint32 loop = 0; loop < maxcount; ++loop) // Ref multiplicator + { + std::vector lootersForItem = getLootersForItem([&](Player const* looter) + { + return referenced->HasDropForPlayer(looter, item->groupid, true); + }); + + // nobody can loot this, skip it + if (lootersForItem.empty()) + break; + + auto newEnd = std::remove_if(lootersForItem.begin(), lootersForItem.end(), [&](Player const* looter) + { + return std::find(gotLoot.begin(), gotLoot.end(), looter) != gotLoot.end(); + }); + + if (lootersForItem.begin() == newEnd) + { + // if we run out of looters this means that there are more items dropped than players + // start a new cycle adding one item to everyone + gotLoot.clear(); + } + else + lootersForItem.erase(newEnd, lootersForItem.end()); + + Player* chosenLooter = Trinity::Containers::SelectRandomContainerElement(lootersForItem); + referenced->Process(*personalLoot[chosenLooter], rate, lootMode, item->groupid, chosenLooter); + gotLoot.push_back(chosenLooter); + } + } + else + { + // Plain entries (not a reference, not grouped) + // Chance is already checked, just add + std::vector lootersForItem = getLootersForItem([&](Player const* looter) + { + return LootItem::AllowedForPlayer(looter, nullptr, item->itemid, item->needs_quest, + !item->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(item->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES), + true, item->conditions); + }); + + if (!lootersForItem.empty()) + { + Player* chosenLooter = Trinity::Containers::SelectRandomContainerElement(lootersForItem); + personalLoot[chosenLooter]->AddItem(*item); + } + } + } + + // Now processing groups + for (LootGroup const* group : Groups) + { + if (group) + { + std::vector lootersForGroup = getLootersForItem([&](Player const* looter) + { + return group->HasDropForPlayer(looter, true); + }); + + if (!lootersForGroup.empty()) + { + Player* chosenLooter = Trinity::Containers::SelectRandomContainerElement(lootersForGroup); + group->Process(*personalLoot[chosenLooter], lootMode); + } + } + } +} + +// True if template includes at least 1 drop for the player +bool LootTemplate::HasDropForPlayer(Player const* player, uint8 groupId, bool strictUsabilityCheck) const +{ + if (groupId) // Group reference + { + if (groupId > Groups.size()) + return false; // Error message already printed at loading stage + + if (!Groups[groupId - 1]) + return false; + + return Groups[groupId - 1]->HasDropForPlayer(player, strictUsabilityCheck); + } + + // Checking non-grouped entries + for (LootStoreItem* lootStoreItem : Entries) + { + if (lootStoreItem->reference > 0) // References processing + { + LootTemplate const* referenced = LootTemplates_Reference.GetLootFor(lootStoreItem->reference); + if (!referenced) + continue; // Error message already printed at loading stage + if (referenced->HasDropForPlayer(player, lootStoreItem->groupid, strictUsabilityCheck)) + return true; + } + else if (LootItem::AllowedForPlayer(player, nullptr, lootStoreItem->itemid, lootStoreItem->needs_quest, + !lootStoreItem->needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(lootStoreItem->itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES), + strictUsabilityCheck, lootStoreItem->conditions)) + return true; // active quest drop found + } + + // Now checking groups + for (LootGroup* group : Groups) + if (group) + if (group->HasDropForPlayer(player, strictUsabilityCheck)) + return true; + + return false; } // True if template includes at least 1 quest drop entry @@ -775,6 +946,40 @@ bool LootTemplate::isReference(uint32 id) return false;//not found or not reference } +std::unordered_map> GenerateDungeonEncounterPersonalLoot(uint32 dungeonEncounterId, uint32 lootId, LootStore const& store, + LootType type, WorldObject const* lootOwner, uint32 minMoney, uint32 maxMoney, uint16 lootMode, ItemContext context, std::vector const& tappers) +{ + std::unordered_map> tempLoot; + + for (Player* tapper : tappers) + { + if (tapper->IsLockedToDungeonEncounter(dungeonEncounterId)) + continue; + + std::unique_ptr& loot = tempLoot[tapper]; + loot.reset(new Loot(lootOwner->GetMap(), lootOwner->GetGUID(), type, nullptr)); + loot->SetItemContext(context); + loot->SetDungeonEncounterId(dungeonEncounterId); + loot->generateMoneyLoot(minMoney, maxMoney); + } + + if (LootTemplate const* tab = store.GetLootFor(lootId)) + tab->ProcessPersonalLoot(tempLoot, store.IsRatesAllowed(), lootMode); + + std::unordered_map> personalLoot; + for (auto&& [looter, loot] : tempLoot) + { + loot->FillNotNormalLootFor(looter); + + if (loot->isLooted()) + continue; + + personalLoot[looter->GetGUID()] = std::move(loot); + } + + return personalLoot; +} + void LoadLootTemplates_Creature() { TC_LOG_INFO("server.loading", "Loading creature loot templates..."); diff --git a/src/server/game/Loot/LootMgr.h b/src/server/game/Loot/LootMgr.h index 5fcd3f28ed6..5d7b0cb031d 100644 --- a/src/server/game/Loot/LootMgr.h +++ b/src/server/game/Loot/LootMgr.h @@ -20,7 +20,9 @@ #include "Define.h" #include "ConditionMgr.h" +#include "ObjectGuid.h" #include +#include #include #include #include @@ -30,6 +32,8 @@ class LootTemplate; class Player; struct Loot; struct LootItem; +enum LootType : uint8; +enum class ItemContext : uint8; struct TC_GAME_API LootStoreItem { @@ -108,10 +112,13 @@ class TC_GAME_API LootTemplate // Adds an entry to the group (at loading stage) void AddEntry(LootStoreItem* item); // Rolls for every item in the template and adds the rolled items the the loot - void Process(Loot& loot, bool rate, uint16 lootMode, uint8 groupId) const; + void Process(Loot& loot, bool rate, uint16 lootMode, uint8 groupId, Player const* personalLooter = nullptr) const; + void ProcessPersonalLoot(std::unordered_map>& personalLoot, bool rate, uint16 lootMode) const; void CopyConditions(ConditionContainer const& conditions); void CopyConditions(LootItem* li) const; + // True if template includes at least 1 drop for the player + bool HasDropForPlayer(Player const* player, uint8 groupId = 0, bool strictUsabilityCheck = false) const; // True if template includes at least 1 quest drop entry bool HasQuestDrop(LootTemplateMap const& store, uint8 groupId = 0) const; // True if template includes at least 1 quest drop for an active quest of the player @@ -132,6 +139,12 @@ class TC_GAME_API LootTemplate LootTemplate& operator=(LootTemplate const&) = delete; }; +std::unordered_map> GenerateDungeonEncounterPersonalLoot(uint32 dungeonEncounterId, + uint32 lootId, LootStore const& store, LootType type, WorldObject const* lootOwner, + uint32 minMoney, uint32 maxMoney, + uint16 lootMode, ItemContext context, + std::vector const& tappers); + //===================================================== TC_GAME_API extern LootStore LootTemplates_Creature; -- cgit v1.2.3