diff options
author | Shauren <shauren.trinity@gmail.com> | 2022-09-16 16:58:03 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2022-09-16 16:58:03 +0200 |
commit | 3ef5079feeedfdafc9d3c1d9f865e96dbc77ecc8 (patch) | |
tree | c88a3e2c1a8ae8459eb43fa63c66081c37393170 /src | |
parent | 9700b2a78680452d80025121a031da340af51348 (diff) |
Core/Loot: Move loot rolls from Group to Loot
* Partial port of cmangos/mangos-wotlk@ffdf9a05d67a04c3c0304e9b021807fa5b867583
Diffstat (limited to 'src')
27 files changed, 818 insertions, 976 deletions
diff --git a/src/server/game/AI/CreatureAISelector.h b/src/server/game/AI/CreatureAISelector.h index 7e2c1acd683..0cb6c9632b3 100644 --- a/src/server/game/AI/CreatureAISelector.h +++ b/src/server/game/AI/CreatureAISelector.h @@ -18,6 +18,8 @@ #ifndef TRINITY_CREATUREAISELECTOR_H #define TRINITY_CREATUREAISELECTOR_H +#include "Define.h" + class CreatureAI; class Creature; class MovementGenerator; diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index c0bb138a643..d6f3ffe9509 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -17,6 +17,7 @@ #include "SmartAI.h" #include "AreaTrigger.h" +#include "ConditionMgr.h" #include "Creature.h" #include "CreatureGroups.h" #include "DB2Structure.h" diff --git a/src/server/game/Entities/Corpse/Corpse.cpp b/src/server/game/Entities/Corpse/Corpse.cpp index e5085e73849..37d36295a54 100644 --- a/src/server/game/Entities/Corpse/Corpse.cpp +++ b/src/server/game/Entities/Corpse/Corpse.cpp @@ -93,6 +93,14 @@ bool Corpse::Create(ObjectGuid::LowType guidlow, Player* owner) return true; } +void Corpse::Update(uint32 diff) +{ + WorldObject::Update(diff); + + if (m_loot) + m_loot->Update(); +} + void Corpse::SaveToDB() { // prevent DB data inconsistence problems and duplicates diff --git a/src/server/game/Entities/Corpse/Corpse.h b/src/server/game/Entities/Corpse/Corpse.h index b54742c3e9c..e34b8d73a50 100644 --- a/src/server/game/Entities/Corpse/Corpse.h +++ b/src/server/game/Entities/Corpse/Corpse.h @@ -81,6 +81,8 @@ class TC_GAME_API Corpse : public WorldObject, public GridObject<Corpse> bool Create(ObjectGuid::LowType guidlow, Map* map); bool Create(ObjectGuid::LowType guidlow, Player* owner); + void Update(uint32 diff) override; + void SaveToDB(); bool LoadCorpseFromDB(ObjectGuid::LowType guid, Field* fields); diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 940e20e95ae..34302dcbce9 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -19,7 +19,7 @@ #include "BattlegroundMgr.h" #include "CellImpl.h" #include "CombatPackets.h" -#include "Common.h" +#include "Containers.h" #include "CreatureAI.h" #include "CreatureAISelector.h" #include "CreatureGroups.h" @@ -31,8 +31,9 @@ #include "GridNotifiersImpl.h" #include "Group.h" #include "GroupMgr.h" -#include "InstanceScript.h" +#include "ItemTemplate.h" #include "Log.h" +#include "Loot.h" #include "LootMgr.h" #include "MapManager.h" #include "MiscPackets.h" @@ -44,14 +45,13 @@ #include "PoolMgr.h" #include "QueryPackets.h" #include "ScriptedGossip.h" +#include "Spell.h" #include "SpellAuraEffects.h" #include "SpellMgr.h" #include "TemporarySummon.h" -#include "Transport.h" -#include "Util.h" #include "Vehicle.h" #include "World.h" -#include "WorldPacket.h" +#include "ZoneScript.h" #include <G3D/g3dmath.h> #include <sstream> @@ -775,20 +775,10 @@ void Creature::Update(uint32 diff) if (IsEngaged()) Unit::AIUpdateTick(diff); - if (m_loot && m_groupLootTimer && !lootingGroupLowGUID.IsEmpty()) - { - if (m_groupLootTimer <= diff) - { - if (Group* group = sGroupMgr->GetGroupByGUID(lootingGroupLowGUID)) - group->EndRoll(m_loot.get(), GetMap()); + if (m_loot) + m_loot->Update(); - m_groupLootTimer = 0; - lootingGroupLowGUID.Clear(); - } - else - m_groupLootTimer -= diff; - } - else if (m_corpseRemoveTime <= GameTime::GetGameTime()) + if (m_corpseRemoveTime <= GameTime::GetGameTime()) { RemoveCorpse(false); TC_LOG_DEBUG("entities.unit", "Removing corpse... %u ", GetEntry()); diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index aadd60fde52..4b14899c0f7 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -292,9 +292,6 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma m_combatPulseTime = delay; } - uint32 m_groupLootTimer; // (msecs)timer used for group loot - ObjectGuid lootingGroupLowGUID; // used to find group which is looting corpse - void SendZoneUnderAttackMessage(Player* attacker); bool hasQuest(uint32 quest_id) const override; diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index c789fc6f64b..5e506dd8be0 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -37,6 +37,7 @@ #include "GroupMgr.h" #include "Item.h" #include "Log.h" +#include "Loot.h" #include "LootMgr.h" #include "Map.h" #include "MapManager.h" @@ -520,7 +521,6 @@ GameObject::GameObject() : WorldObject(false), MapObject(), m_spawnId = UI64LIT(0); - m_groupLootTimer = 0; m_lootGenerationTime = 0; ResetLootMode(); // restore default loot mode @@ -1173,19 +1173,8 @@ void GameObject::Update(uint32 diff) } break; case GAMEOBJECT_TYPE_CHEST: - if (m_loot && m_groupLootTimer) - { - if (m_groupLootTimer <= diff) - { - if (Group* group = sGroupMgr->GetGroupByGUID(lootingGroupLowGUID)) - group->EndRoll(m_loot.get(), GetMap()); - - m_groupLootTimer = 0; - lootingGroupLowGUID.Clear(); - } - else - m_groupLootTimer -= diff; - } + if (m_loot) + m_loot->Update(); // Non-consumable chest was partially looted and restock time passed, restock all loot now if (GetGOInfo()->chest.consumable == 0 && GameTime::GetGameTime() >= m_restockTime) diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index 133c1c4e460..fba6febb163 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -287,8 +287,6 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> bool IsLootAllowedFor(Player const* player) const; bool HasLootRecipient() const { return !m_lootRecipient.IsEmpty() || !m_lootRecipientGroup.IsEmpty(); } Loot* GetLootForPlayer(Player const* /*player*/) const override { return m_loot.get(); } - uint32 m_groupLootTimer; // (msecs)timer used for group loot - ObjectGuid lootingGroupLowGUID; // used to find group which is looting GameObject* GetLinkedTrap(); void SetLinkedTrap(GameObject* linkedTrap) { m_linkedTrap = linkedTrap->GetGUID(); } diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 143219d35c6..58bbc90a40c 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -75,6 +75,7 @@ #include "LanguageMgr.h" #include "LFGMgr.h" #include "Log.h" +#include "Loot.h" #include "LootItemStorage.h" #include "LootMgr.h" #include "LootPackets.h" @@ -1634,6 +1635,7 @@ void Player::RemoveFromWorld() UnsummonPetTemporaryIfAny(); ClearComboPoints(); m_session->DoLootReleaseAll(); + m_lootRolls.clear(); sOutdoorPvPMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId); sBattlefieldMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId); } @@ -8713,6 +8715,20 @@ Loot* Player::GetLootByWorldObjectGUID(ObjectGuid const& lootWorldObjectGuid) co return itr != m_AELootView.end() ? itr->second : nullptr; } +LootRoll* Player::GetLootRoll(ObjectGuid const& lootObjectGuid, uint8 lootListId) +{ + auto itr = std::find_if(m_lootRolls.begin(), m_lootRolls.end(), [&](LootRoll const* roll) + { + return roll->IsLootItem(lootObjectGuid, lootListId); + }); + return itr != m_lootRolls.end() ? *itr : nullptr; +} + +void Player::RemoveLootRoll(LootRoll* roll) +{ + m_lootRolls.erase(std::remove(m_lootRolls.begin(), m_lootRolls.end(), roll), m_lootRolls.end()); +} + /* If in a battleground a player dies, and an enemy removes the insignia, the player's bones is lootable Called by remove insignia spell effect */ void Player::RemovedInsignia(Player* looterPlr) @@ -8740,7 +8756,7 @@ void Player::RemovedInsignia(Player* looterPlr) // Now we must make bones lootable, and send player loot bones->SetCorpseDynamicFlag(CORPSE_DYNFLAG_LOOTABLE); - bones->m_loot.reset(new Loot(GetMap(), bones->GetGUID(), LOOT_INSIGNIA, looterPlr->GetGroup() ? looterPlr->GetGroup()->GetLootMethod() : FREE_FOR_ALL)); + bones->m_loot.reset(new Loot(GetMap(), bones->GetGUID(), LOOT_INSIGNIA, looterPlr->GetGroup())); // For AV Achievement if (Battleground* bg = GetBattleground()) @@ -8837,7 +8853,7 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type, bool aeLooting/* = fa Group* group = GetGroup(); bool groupRules = (group && go->GetGOInfo()->type == GAMEOBJECT_TYPE_CHEST && go->GetGOInfo()->chest.usegrouplootrules); - loot = new Loot(GetMap(), guid, loot_type, groupRules ? group->GetLootMethod() : FREE_FOR_ALL); + loot = new Loot(GetMap(), guid, loot_type, groupRules ? group : nullptr); if (go->GetMap()->Is25ManRaid()) loot->maxDuplicates = 3; @@ -8866,22 +8882,6 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type, bool aeLooting/* = fa else if (loot_type == LOOT_FISHING_JUNK) go->getFishLootJunk(loot, this); - if (go->GetGOInfo()->type == GAMEOBJECT_TYPE_CHEST && go->GetGOInfo()->chest.usegrouplootrules) - { - switch (loot->GetLootMethod()) - { - case GROUP_LOOT: - // GroupLoot: rolls items over threshold. Items with quality < threshold, round robin - group->GroupLoot(loot, go); - break; - case MASTER_LOOT: - group->MasterLoot(loot, go); - break; - default: - break; - } - } - go->SetLootState(GO_ACTIVATED, this); } @@ -8897,6 +8897,9 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type, bool aeLooting/* = fa case FREE_FOR_ALL: permission = ALL_PERMISSION; break; + case ROUND_ROBIN: + permission = ROUND_ROBIN_PERMISSION; + break; default: permission = GROUP_PERMISSION; break; @@ -8925,7 +8928,7 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type, bool aeLooting/* = fa if (!item->m_lootGenerated && !sLootItemStorage->LoadStoredLoot(item, this)) { item->m_lootGenerated = true; - loot = new Loot(GetMap(), guid, loot_type, FREE_FOR_ALL); + loot = new Loot(GetMap(), guid, loot_type, nullptr); item->m_loot.reset(loot); switch (loot_type) @@ -8996,7 +8999,7 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type, bool aeLooting/* = fa { creature->StartPickPocketRefillTimer(); - loot = new Loot(GetMap(), creature->GetGUID(), LOOT_PICKPOCKETING, FREE_FOR_ALL); + loot = new Loot(GetMap(), creature->GetGUID(), LOOT_PICKPOCKETING, nullptr); creature->m_loot.reset(loot); if (uint32 lootid = creature->GetCreatureTemplate()->pickpocketLootId) loot->FillLoot(lootid, LootTemplates_Pickpocketing, this, true); @@ -9032,26 +9035,6 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type, bool aeLooting/* = fa return; } - if (loot->loot_type == LOOT_NONE) - { - // for creature, loot is filled when creature is killed. - if (Group* group = creature->GetLootRecipientGroup()) - { - switch (loot->GetLootMethod()) - { - case GROUP_LOOT: - // GroupLoot: rolls items over threshold. Items with quality < threshold, round robin - group->GroupLoot(loot, creature); - break; - case MASTER_LOOT: - group->MasterLoot(loot, creature); - break; - default: - break; - } - } - } - // if loot is already skinning loot then don't do anything else if (loot->loot_type == LOOT_SKINNING) { @@ -9083,6 +9066,9 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type, bool aeLooting/* = fa case FREE_FOR_ALL: permission = ALL_PERMISSION; break; + case ROUND_ROBIN: + permission = ROUND_ROBIN_PERMISSION; + break; default: permission = GROUP_PERMISSION; break; @@ -9115,7 +9101,7 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type, bool aeLooting/* = fa SendDirectMessage(packet.Write()); // add 'this' player as one of the players that are looting 'loot' - loot->AddLooter(GetGUID()); + loot->OnLootOpened(GetMap(), GetGUID()); m_AELootView[loot->GetGUID()] = loot; if (loot_type == LOOT_CORPSE && !guid.IsItem()) @@ -11367,13 +11353,12 @@ InventoryResult Player::CanUseItem(ItemTemplate const* proto, bool skipRequiredL return EQUIP_ERR_OK; } -InventoryResult Player::CanRollForItemInLFG(ItemTemplate const* proto, WorldObject const* lootedObject) const +InventoryResult Player::CanRollForItemInLFG(ItemTemplate const* proto, Map const* map) const { if (!GetGroup() || !GetGroup()->isLFGGroup()) return EQUIP_ERR_OK; // not in LFG group // check if looted object is inside the lfg dungeon - Map const* map = lootedObject->GetMap(); if (!sLFGMgr->inLfgDungeonMap(GetGroup()->GetGUID(), map->GetId(), map->GetDifficultyID())) return EQUIP_ERR_OK; @@ -18197,7 +18182,15 @@ bool Player::isAllowedToLoot(const Creature* creature) const case MASTER_LOOT: case FREE_FOR_ALL: return true; + case ROUND_ROBIN: + // may only loot if the player is the loot roundrobin player + // or if there are free/quest/conditional item for the player + if (loot->roundRobinPlayer.IsEmpty() || loot->roundRobinPlayer == GetGUID()) + return true; + + return loot->hasItemFor(this); case GROUP_LOOT: + case NEED_BEFORE_GREED: // may only loot if the player is the loot roundrobin player // or item over threshold (so roll(s) can be launched) // or if there are free/quest/conditional item for the player @@ -25574,7 +25567,8 @@ PartyResult Player::CanUninviteFromGroup(ObjectGuid guidMember) const if (state == lfg::LFG_STATE_FINISHED_DUNGEON) return ERR_PARTY_LFG_BOOT_DUNGEON_COMPLETE; - if (grp->isRollLootActive()) + Player* player = ObjectAccessor::FindConnectedPlayer(guidMember); + if (!player->m_lootRolls.empty()) return ERR_PARTY_LFG_BOOT_LOOT_ROLLS; /// @todo Should also be sent when anyone has recently left combat, with an aprox ~5 seconds timer. @@ -26023,31 +26017,9 @@ void Player::InitRunes() void Player::AutoStoreLoot(uint8 bag, uint8 slot, uint32 loot_id, LootStore const& store, ItemContext context, bool broadcast, bool createdByPlayer) { - Loot loot(nullptr, ObjectGuid::Empty, LOOT_NONE, FREE_FOR_ALL); + Loot loot(nullptr, ObjectGuid::Empty, LOOT_NONE, nullptr); loot.FillLoot(loot_id, store, this, true, false, LOOT_MODE_DEFAULT, context); - - uint32 max_slot = loot.GetMaxSlotInLootFor(this); - for (uint32 i = 0; i < max_slot; ++i) - { - LootItem* lootItem = loot.LootItemInSlot(i, this); - - ItemPosCountVec dest; - InventoryResult msg = CanStoreNewItem(bag, slot, dest, lootItem->itemid, lootItem->count); - if (msg != EQUIP_ERR_OK && slot != NULL_SLOT) - msg = CanStoreNewItem(bag, NULL_SLOT, dest, lootItem->itemid, lootItem->count); - if (msg != EQUIP_ERR_OK && bag != NULL_BAG) - msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, lootItem->itemid, lootItem->count); - if (msg != EQUIP_ERR_OK) - { - SendEquipError(msg, nullptr, nullptr, lootItem->itemid); - continue; - } - - Item* pItem = StoreNewItem(dest, lootItem->itemid, true, lootItem->randomBonusListId, GuidSet(), lootItem->context, lootItem->BonusListIDs); - SendNewItem(pItem, lootItem->count, false, createdByPlayer, broadcast); - ApplyItemLootedSpell(pItem, true); - } - + loot.AutoStore(this, bag, slot, broadcast, createdByPlayer); Unit::ProcSkillsAndAuras(this, nullptr, PROC_FLAG_LOOTED, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); } @@ -26141,7 +26113,7 @@ void Player::StoreLootItem(ObjectGuid lootWorldObjectGuid, uint8 lootSlot, Loot* // LootItem is being removed (looted) from the container, delete it from the DB. if (loot->loot_type == LOOT_ITEM) - sLootItemStorage->RemoveStoredLootItemForContainer(lootWorldObjectGuid.GetCounter(), item->itemid, item->count, item->itemIndex); + sLootItemStorage->RemoveStoredLootItemForContainer(lootWorldObjectGuid.GetCounter(), item->itemid, item->count, item->LootListId); ApplyItemLootedSpell(newitem, true); } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 9b52a94a6d0..ce1d2c586f5 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -76,6 +76,7 @@ class Garrison; class Group; class Guild; class Item; +class LootRoll; class LootStore; class OutdoorPvP; class Pet; @@ -1401,7 +1402,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> 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, WorldObject const* lootedObject) const; + InventoryResult CanRollForItemInLFG(ItemTemplate const* item, Map const* map) const; Item* StoreNewItem(ItemPosCountVec const& pos, uint32 itemId, bool update, ItemRandomBonusListId randomBonusListId = 0, GuidSet const& allowedLooters = GuidSet(), ItemContext context = ItemContext::NONE, std::vector<int32> const& bonusListIDs = std::vector<int32>(), bool addToCollection = true); Item* StoreItem(ItemPosCountVec const& pos, Item* pItem, bool update); Item* EquipNewItem(uint16 pos, uint32 item, ItemContext context, bool update); @@ -2064,6 +2065,9 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void SetLootGUID(ObjectGuid const& guid) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::LootTargetGUID), guid); } Loot* GetLootByWorldObjectGUID(ObjectGuid const& lootWorldObjectGuid) const; std::unordered_map<ObjectGuid, Loot*> const& GetAELootView() const { return m_AELootView; } + LootRoll* GetLootRoll(ObjectGuid const& lootObjectGuid, uint8 lootListId); + void AddLootRoll(LootRoll* roll) { m_lootRolls.push_back(roll); } + void RemoveLootRoll(LootRoll* roll); void RemovedInsignia(Player* looterPlr); @@ -3177,6 +3181,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> SceneMgr m_sceneMgr; std::unordered_map<ObjectGuid /*LootObject*/, Loot*> m_AELootView; + std::vector<LootRoll*> m_lootRolls; // loot rolls waiting for answer void _InitHonorLevelOnLoadFromDB(uint32 honor, uint32 honorLevel); std::unique_ptr<RestMgr> _restMgr; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 0faf4a9f7f0..a0d14ec192e 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -46,6 +46,7 @@ #include "InstanceScript.h" #include "Item.h" #include "Log.h" +#include "Loot.h" #include "LootMgr.h" #include "LootPackets.h" #include "MiscPackets.h" @@ -10660,7 +10661,6 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId) Player* looter = player; Group* group = player->GetGroup(); - bool hasLooterGuid = false; if (group) { @@ -10673,10 +10673,7 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId) { looter = ObjectAccessor::FindPlayer(group->GetLooterGuid()); if (looter) - { - hasLooterGuid = true; creature->SetLootRecipient(looter); // update creature loot recipient to the allowed looter. - } } } } @@ -10686,7 +10683,7 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId) // Generate loot before updating looter if (creature) { - creature->m_loot.reset(new Loot(creature->GetMap(), creature->GetGUID(), LOOT_CORPSE, group ? group->GetLootMethod() : FREE_FOR_ALL)); + creature->m_loot.reset(new Loot(creature->GetMap(), creature->GetGUID(), LOOT_CORPSE, group)); Loot* loot = creature->m_loot.get(); if (creature->GetMap()->Is25ManRaid()) loot->maxDuplicates = 3; @@ -10697,24 +10694,11 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId) if (creature->GetLootMode() > 0) loot->generateMoneyLoot(creature->GetCreatureTemplate()->mingold, creature->GetCreatureTemplate()->maxgold); - if (group) - { - if (hasLooterGuid) - group->SendLooter(creature, looter); - else - group->SendLooter(creature, nullptr); + loot->NotifyLootList(creature->GetMap()); - // Update round robin looter only if the creature had loot - if (!loot->empty()) - group->UpdateLooterGuid(creature); - } - else - { - WorldPackets::Loot::LootList lootList; - lootList.Owner = creature->GetGUID(); - lootList.LootObj = creature->m_loot->GetGUID(); - player->SendMessageToSet(lootList.Write(), true); - } + // Update round robin looter only if the creature had loot + if (group && !loot->empty()) + group->UpdateLooterGuid(creature); } player->RewardPlayerAndGroupAtKill(victim, false); diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index 69c62de6370..127b0dab275 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -28,8 +28,7 @@ #include "Item.h" #include "LFGMgr.h" #include "Log.h" -#include "LootMgr.h" -#include "LootPackets.h" +#include "Loot.h" #include "MapManager.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" @@ -37,34 +36,15 @@ #include "Pet.h" #include "PhasingHandler.h" #include "Player.h" -#include "Random.h" #include "TerrainMgr.h" #include "UpdateData.h" -#include "Util.h" #include "World.h" #include "WorldSession.h" -Roll::Roll(LootItem const& li) : itemid(li.itemid), -itemRandomBonusListId(li.randomBonusListId), itemCount(li.count), -totalPlayersRolling(0), totalNeed(0), totalGreed(0), totalPass(0), itemSlot(0), -rollVoteMask(ROLL_ALL_TYPE_NO_DISENCHANT) { } - -Roll::~Roll() { } - -void Roll::setLoot(Loot* pLoot) -{ - link(pLoot, this); -} - -Loot* Roll::getLoot() -{ - return getTarget(); -} - 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_masterLooterGuid(), m_subGroupsCounts(nullptr), m_guid(), m_maxEnchantingLevel(0), m_dbStoreId(0), m_isLeaderOffline(false), +m_masterLooterGuid(), m_subGroupsCounts(nullptr), m_guid(), m_dbStoreId(0), m_isLeaderOffline(false), m_readyCheckStarted(false), m_readyCheckTimer(Milliseconds::zero()), m_activeMarkers(0) { for (uint8 i = 0; i < TARGET_ICONS_COUNT; ++i) @@ -86,14 +66,6 @@ Group::~Group() else TC_LOG_ERROR("misc", "Group::~Group: battleground group is not linked to the correct battleground."); } - Rolls::iterator itr; - while (!RollId.empty()) - { - itr = RollId.begin(); - Roll *r = *itr; - RollId.erase(itr); - delete(r); - } // this may unload some instance saves for (auto difficultyItr = m_boundInstances.begin(); difficultyItr != m_boundInstances.end(); ++difficultyItr) @@ -584,9 +556,6 @@ bool Group::AddMember(Player* player) } } - if (m_maxEnchantingLevel < player->GetSkillValue(SKILL_ENCHANTING)) - m_maxEnchantingLevel = player->GetSkillValue(SKILL_ENCHANTING); - return true; } @@ -653,37 +622,6 @@ bool Group::RemoveMember(ObjectGuid guid, RemoveMethod method /*= GROUP_REMOVEME DelinkMember(guid); } - // Reevaluate group enchanter if the leaving player had enchanting skill or the player is offline - if (!player || player->GetSkillValue(SKILL_ENCHANTING)) - ResetMaxEnchantingLevel(); - - // Remove player from loot rolls - for (Rolls::iterator it = RollId.begin(); it != RollId.end(); ++it) - { - Roll* roll = *it; - Roll::PlayerVote::iterator itr2 = roll->playerVote.find(guid); - if (itr2 == roll->playerVote.end()) - continue; - - if (itr2->second == GREED || itr2->second == DISENCHANT) - --roll->totalGreed; - else if (itr2->second == NEED) - --roll->totalNeed; - else if (itr2->second == PASS) - --roll->totalPass; - - if (itr2->second != NOT_VALID) - --roll->totalPlayersRolling; - - roll->playerVote.erase(itr2); - - if (roll->totalPass + roll->totalNeed + roll->totalGreed >= roll->totalPlayersRolling) - { - CountTheRoll(it, nullptr); - it = RollId.begin(); - } - } - // Update subgroups member_witerator slot = _getMemberWSlot(guid); if (slot != m_memberSlots.end()) @@ -914,571 +852,6 @@ void Group::Disband(bool hideDestroy /* = false */) delete this; } -/*********************************************************/ -/*** LOOT SYSTEM ***/ -/*********************************************************/ -void Group::SendLootStartRollToPlayer(uint32 countDown, uint32 mapId, Player* p, bool canNeed, Roll const& r) const -{ - WorldPackets::Loot::StartLootRoll startLootRoll; - startLootRoll.LootObj = r->GetGUID(); - startLootRoll.MapID = mapId; - startLootRoll.RollTime = countDown; - startLootRoll.ValidRolls = r.rollVoteMask; - if (!canNeed) - startLootRoll.ValidRolls &= ~ROLL_FLAG_TYPE_NEED; - startLootRoll.Method = GetLootMethod(); - r.FillPacket(startLootRoll.Item); - - if (ItemDisenchantLootEntry const* disenchant = r.GetItemDisenchantLoot(p)) - if (m_maxEnchantingLevel >= disenchant->SkillRequired) - startLootRoll.ValidRolls |= ROLL_FLAG_TYPE_DISENCHANT; - - p->SendDirectMessage(startLootRoll.Write()); -} - -void Group::SendLootRoll(ObjectGuid playerGuid, int32 rollNumber, uint8 rollType, Roll const& roll, bool autoPass) const -{ - WorldPackets::Loot::LootRollBroadcast lootRoll; - lootRoll.LootObj = roll->GetGUID(); - lootRoll.Player = playerGuid; - lootRoll.Roll = rollNumber; - lootRoll.RollType = rollType; - lootRoll.Autopassed = autoPass; - roll.FillPacket(lootRoll.Item); - lootRoll.Write(); - - for (Roll::PlayerVote::const_iterator itr = roll.playerVote.begin(); itr != roll.playerVote.end(); ++itr) - { - Player* p = ObjectAccessor::FindConnectedPlayer(itr->first); - if (!p || !p->GetSession()) - continue; - - if (itr->second != NOT_VALID) - p->SendDirectMessage(lootRoll.GetRawPacket()); - } -} - -void Group::SendLootRollWon(ObjectGuid winnerGuid, int32 rollNumber, uint8 rollType, Roll const& roll) const -{ - WorldPackets::Loot::LootRollWon lootRollWon; - lootRollWon.LootObj = roll->GetGUID(); - lootRollWon.Winner = winnerGuid; - lootRollWon.Roll = rollNumber; - lootRollWon.RollType = rollType; - roll.FillPacket(lootRollWon.Item); - lootRollWon.MainSpec = true; // offspec rolls not implemented - lootRollWon.Write(); - - for (Roll::PlayerVote::const_iterator itr = roll.playerVote.begin(); itr != roll.playerVote.end(); ++itr) - { - Player* p = ObjectAccessor::FindConnectedPlayer(itr->first); - if (!p || !p->GetSession()) - continue; - - if (itr->second != NOT_VALID) - p->SendDirectMessage(lootRollWon.GetRawPacket()); - } -} - -void Group::SendLootAllPassed(Roll const& roll) const -{ - WorldPackets::Loot::LootAllPassed lootAllPassed; - lootAllPassed.LootObj = roll->GetGUID(); - roll.FillPacket(lootAllPassed.Item); - lootAllPassed.Write(); - - for (Roll::PlayerVote::const_iterator itr = roll.playerVote.begin(); itr != roll.playerVote.end(); ++itr) - { - Player* player = ObjectAccessor::FindConnectedPlayer(itr->first); - if (!player || !player->GetSession()) - continue; - - if (itr->second != NOT_VALID) - player->SendDirectMessage(lootAllPassed.GetRawPacket()); - } -} - -void Group::SendLootRollsComplete(Roll const& roll) const -{ - WorldPackets::Loot::LootRollsComplete lootRollsComplete; - lootRollsComplete.LootObj = roll->GetGUID(); - lootRollsComplete.LootListID = roll.itemSlot + 1; - lootRollsComplete.Write(); - - for (Roll::PlayerVote::const_iterator itr = roll.playerVote.begin(); itr != roll.playerVote.end(); ++itr) - { - Player* player = ObjectAccessor::FindConnectedPlayer(itr->first); - if (!player || !player->GetSession()) - continue; - - if (itr->second != NOT_VALID) - player->SendDirectMessage(lootRollsComplete.GetRawPacket()); - } -} - -// notify group members which player is the allowed looter for the given creature -void Group::SendLooter(Creature* creature, Player* groupLooter) -{ - ASSERT(creature); - - WorldPackets::Loot::LootList lootList; - - lootList.Owner = creature->GetGUID(); - lootList.LootObj = creature->m_loot->GetGUID(); - - if (creature->m_loot->GetLootMethod() == MASTER_LOOT && creature->m_loot->hasOverThresholdItem()) - lootList.Master = GetMasterLooterGuid(); - - if (groupLooter) - lootList.RoundRobinWinner = groupLooter->GetGUID(); - - BroadcastPacket(lootList.Write(), false); -} - -bool CanRollOnItem(const LootItem& item, Player const* player) -{ - // Players can't roll on unique items if they already reached the maximum quantity of that item - ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item.itemid); - if (!proto) - return false; - - uint32 itemCount = player->GetItemCount(item.itemid); - if (proto->GetMaxCount() > 0 && itemCount >= proto->GetMaxCount()) - return false; - - if (!item.AllowedForPlayer(player)) - return false; - - return true; -} - -void Group::GroupLoot(Loot* loot, WorldObject* lootedObject) -{ - ItemTemplate const* item; - uint8 itemSlot = 0; - for (std::vector<LootItem>::iterator i = loot->items.begin(); i != loot->items.end(); ++i, ++itemSlot) - { - if (i->freeforall) - continue; - - item = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(i->itemid)); - ASSERT(item); - - //roll for over-threshold item if it's one-player loot - if (item->GetQuality() >= uint32(m_lootThreshold)) - { - Roll* r = new Roll(*i); - - for (GroupReference* itr = GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* playerToRoll = itr->GetSource(); - if (!playerToRoll || !playerToRoll->GetSession()) - continue; - - if (playerToRoll->IsAtGroupRewardDistance(lootedObject)) - { - r->totalPlayersRolling++; - RollVote vote = playerToRoll->GetPassOnGroupLoot() ? PASS : NOT_EMITED_YET; - if (!CanRollOnItem(*i, playerToRoll)) - { - vote = PASS; - r->totalPass++; // Can't broadcast the pass now. need to wait until all rolling players are known - } - r->playerVote[playerToRoll->GetGUID()] = vote; - } - } - - if (r->totalPlayersRolling > 0) - { - r->setLoot(loot); - r->itemSlot = itemSlot; - if (item->HasFlag(ITEM_FLAG2_CAN_ONLY_ROLL_GREED)) - r->rollVoteMask &= ~ROLL_FLAG_TYPE_NEED; - - loot->items[itemSlot].is_blocked = true; - - //Broadcast Pass and Send Rollstart - for (Roll::PlayerVote::const_iterator itr = r->playerVote.begin(); itr != r->playerVote.end(); ++itr) - { - Player* p = ObjectAccessor::FindConnectedPlayer(itr->first); - if (!p || !p->GetSession()) - continue; - - if (itr->second == PASS) - SendLootRoll(p->GetGUID(), -1, ROLL_PASS, *r, true); - else - SendLootStartRollToPlayer(60000, lootedObject->GetMapId(), p, p->CanRollForItemInLFG(item, lootedObject) == EQUIP_ERR_OK, *r); - } - - RollId.push_back(r); - - if (Creature* creature = lootedObject->ToCreature()) - { - creature->m_groupLootTimer = 60000; - creature->lootingGroupLowGUID = GetGUID(); - } - else if (GameObject* go = lootedObject->ToGameObject()) - { - go->m_groupLootTimer = 60000; - go->lootingGroupLowGUID = GetGUID(); - } - } - else - delete r; - } - else - i->is_underthreshold = true; - } - - for (std::vector<LootItem>::iterator i = loot->quest_items.begin(); i != loot->quest_items.end(); ++i, ++itemSlot) - { - if (!i->follow_loot_rules) - continue; - - item = sObjectMgr->GetItemTemplate(i->itemid); - Roll* r = new Roll(*i); - - for (GroupReference* itr = GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* playerToRoll = itr->GetSource(); - if (!playerToRoll || !playerToRoll->GetSession()) - continue; - - if (playerToRoll->IsAtGroupRewardDistance(lootedObject)) - { - r->totalPlayersRolling++; - RollVote vote = NOT_EMITED_YET; - if (!CanRollOnItem(*i, playerToRoll)) - { - vote = PASS; - ++r->totalPass; - } - r->playerVote[playerToRoll->GetGUID()] = vote; - } - } - - if (r->totalPlayersRolling > 0) - { - r->setLoot(loot); - r->itemSlot = itemSlot; - - loot->quest_items[itemSlot - loot->items.size()].is_blocked = true; - - //Broadcast Pass and Send Rollstart - for (Roll::PlayerVote::const_iterator itr = r->playerVote.begin(); itr != r->playerVote.end(); ++itr) - { - Player* p = ObjectAccessor::FindConnectedPlayer(itr->first); - if (!p || !p->GetSession()) - continue; - - if (itr->second == PASS) - SendLootRoll(p->GetGUID(), -1, ROLL_PASS, *r, true); - else - SendLootStartRollToPlayer(60000, lootedObject->GetMapId(), p, p->CanRollForItemInLFG(item, lootedObject) == EQUIP_ERR_OK, *r); - } - - RollId.push_back(r); - - if (Creature* creature = lootedObject->ToCreature()) - { - creature->m_groupLootTimer = 60000; - creature->lootingGroupLowGUID = GetGUID(); - } - else if (GameObject* go = lootedObject->ToGameObject()) - { - go->m_groupLootTimer = 60000; - go->lootingGroupLowGUID = GetGUID(); - } - } - else - delete r; - } -} - -void Group::MasterLoot(Loot* loot, WorldObject* pLootedObject) -{ - TC_LOG_DEBUG("network", "Group::MasterLoot (SMSG_MASTER_LOOT_CANDIDATE_LIST)"); - - for (std::vector<LootItem>::iterator i = loot->items.begin(); i != loot->items.end(); ++i) - { - if (i->freeforall) - continue; - - i->is_blocked = !i->is_underthreshold; - } - - for (std::vector<LootItem>::iterator i = loot->quest_items.begin(); i != loot->quest_items.end(); ++i) - { - if (!i->follow_loot_rules) - continue; - - i->is_blocked = !i->is_underthreshold; - } - - WorldPackets::Loot::MasterLootCandidateList masterLootCandidateList; - masterLootCandidateList.LootObj = loot->GetGUID(); - - for (GroupReference* itr = GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* looter = itr->GetSource(); - if (!looter->IsInWorld()) - continue; - - if (looter->IsAtGroupRewardDistance(pLootedObject)) - masterLootCandidateList.Players.push_back(looter->GetGUID()); - } - - masterLootCandidateList.Write(); - - for (GroupReference* itr = GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* looter = itr->GetSource(); - if (looter->IsAtGroupRewardDistance(pLootedObject)) - looter->SendDirectMessage(masterLootCandidateList.GetRawPacket()); - } -} - -void Group::CountRollVote(ObjectGuid playerGuid, ObjectGuid lootObjectGuid, uint8 lootListId, uint8 choice) -{ - Rolls::iterator rollI = GetRoll(lootObjectGuid, lootListId); - if (rollI == RollId.end()) - return; - Roll* roll = *rollI; - - Roll::PlayerVote::iterator itr = roll->playerVote.find(playerGuid); - // this condition means that player joins to the party after roll begins - if (itr == roll->playerVote.end()) - return; - - if (roll->getLoot()) - if (roll->getLoot()->items.empty()) - return; - - switch (choice) - { - case ROLL_PASS: // Player choose pass - SendLootRoll(playerGuid, -1, ROLL_PASS, *roll); - ++roll->totalPass; - itr->second = PASS; - break; - case ROLL_NEED: // player choose Need - SendLootRoll(playerGuid, 0, ROLL_NEED, *roll); - ++roll->totalNeed; - itr->second = NEED; - break; - case ROLL_GREED: // player choose Greed - SendLootRoll(playerGuid, -7, ROLL_GREED, *roll); - ++roll->totalGreed; - itr->second = GREED; - break; - case ROLL_DISENCHANT: // player choose Disenchant - SendLootRoll(playerGuid, -8, ROLL_DISENCHANT, *roll); - ++roll->totalGreed; - itr->second = DISENCHANT; - break; - } - - if (roll->totalPass + roll->totalNeed + roll->totalGreed >= roll->totalPlayersRolling) - CountTheRoll(rollI, nullptr); -} - -//called when roll timer expires -void Group::EndRoll(Loot* pLoot, Map* allowedMap) -{ - for (Rolls::iterator itr = RollId.begin(); itr != RollId.end();) - { - if ((*itr)->getLoot() == pLoot) - { - CountTheRoll(itr, allowedMap); //i don't have to edit player votes, who didn't vote ... he will pass - itr = RollId.begin(); - } - else - ++itr; - } -} - -void Group::CountTheRoll(Rolls::iterator rollI, Map* allowedMap) -{ - Roll* roll = *rollI; - if (!roll->isValid()) // is loot already deleted ? - { - RollId.erase(rollI); - delete roll; - return; - } - - //end of the roll - if (roll->totalNeed > 0) - { - if (!roll->playerVote.empty()) - { - uint8 maxresul = 0; - ObjectGuid maxguid = ObjectGuid::Empty; - Player* player = nullptr; - - for (Roll::PlayerVote::const_iterator itr = roll->playerVote.begin(); itr != roll->playerVote.end(); ++itr) - { - if (itr->second != NEED) - continue; - - player = ObjectAccessor::FindPlayer(itr->first); - if (!player || (allowedMap != nullptr && player->FindMap() != allowedMap)) - { - --roll->totalNeed; - continue; - } - - uint8 randomN = urand(1, 100); - SendLootRoll(itr->first, randomN, ROLL_NEED, *roll); - if (maxresul < randomN) - { - maxguid = itr->first; - maxresul = randomN; - } - } - - if (!maxguid.IsEmpty()) - { - SendLootRollWon(maxguid, maxresul, ROLL_NEED, *roll); - player = ObjectAccessor::FindConnectedPlayer(maxguid); - - if (player && player->GetSession()) - { - player->UpdateCriteria(CriteriaType::RollNeed, roll->itemid, maxresul); - - ItemPosCountVec dest; - LootItem* item = &(roll->itemSlot >= roll->getLoot()->items.size() ? roll->getLoot()->quest_items[roll->itemSlot - roll->getLoot()->items.size()] : roll->getLoot()->items[roll->itemSlot]); - InventoryResult msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, roll->itemid, item->count); - if (msg == EQUIP_ERR_OK) - { - item->is_looted = true; - roll->getLoot()->NotifyItemRemoved(roll->itemSlot, allowedMap); - roll->getLoot()->unlootedCount--; - player->StoreNewItem(dest, roll->itemid, true, item->randomBonusListId, item->GetAllowedLooters(), item->context, item->BonusListIDs); - } - else - { - item->is_blocked = false; - item->rollWinnerGUID = player->GetGUID(); - player->SendEquipError(msg, nullptr, nullptr, roll->itemid); - } - } - } - else - roll->totalNeed = 0; - } - } - - if (roll->totalNeed == 0 && roll->totalGreed > 0) // if (roll->totalNeed == 0 && ...), not else if, because numbers can be modified above if player is on a different map - { - if (!roll->playerVote.empty()) - { - uint8 maxresul = 0; - ObjectGuid maxguid = ObjectGuid::Empty; - Player* player = nullptr; - RollVote rollvote = NOT_VALID; - - Roll::PlayerVote::iterator itr; - for (itr = roll->playerVote.begin(); itr != roll->playerVote.end(); ++itr) - { - if (itr->second != GREED && itr->second != DISENCHANT) - continue; - - player = ObjectAccessor::FindPlayer(itr->first); - if (!player || (allowedMap != nullptr && player->FindMap() != allowedMap)) - { - --roll->totalGreed; - continue; - } - - uint8 randomN = urand(1, 100); - SendLootRoll(itr->first, randomN, itr->second, *roll); - if (maxresul < randomN) - { - maxguid = itr->first; - maxresul = randomN; - rollvote = itr->second; - } - } - - if (!maxguid.IsEmpty()) - { - SendLootRollWon(maxguid, maxresul, rollvote, *roll); - player = ObjectAccessor::FindConnectedPlayer(maxguid); - - if (player && player->GetSession()) - { - player->UpdateCriteria(CriteriaType::RollGreed, roll->itemid, maxresul); - - LootItem* item = &(roll->itemSlot >= roll->getLoot()->items.size() ? roll->getLoot()->quest_items[roll->itemSlot - roll->getLoot()->items.size()] : roll->getLoot()->items[roll->itemSlot]); - - if (rollvote == GREED) - { - ItemPosCountVec dest; - InventoryResult msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, roll->itemid, item->count); - if (msg == EQUIP_ERR_OK) - { - item->is_looted = true; - roll->getLoot()->NotifyItemRemoved(roll->itemSlot, allowedMap); - roll->getLoot()->unlootedCount--; - player->StoreNewItem(dest, roll->itemid, true, item->randomBonusListId, item->GetAllowedLooters(), item->context, item->BonusListIDs); - } - else - { - item->is_blocked = false; - item->rollWinnerGUID = player->GetGUID(); - player->SendEquipError(msg, nullptr, nullptr, roll->itemid); - } - } - else if (rollvote == DISENCHANT) - { - item->is_looted = true; - roll->getLoot()->NotifyItemRemoved(roll->itemSlot, allowedMap); - roll->getLoot()->unlootedCount--; - player->UpdateCriteria(CriteriaType::CastSpell, 13262); // Disenchant - - ItemDisenchantLootEntry const* disenchant = ASSERT_NOTNULL(roll->GetItemDisenchantLoot(player)); - - ItemPosCountVec dest; - InventoryResult msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, roll->itemid, item->count); - if (msg == EQUIP_ERR_OK) - player->AutoStoreLoot(disenchant->ID, LootTemplates_Disenchant, ItemContext::NONE, true); - else // If the player's inventory is full, send the disenchant result in a mail. - { - Loot loot(allowedMap, roll->getLoot()->GetOwnerGUID(), LOOT_DISENCHANTING, roll->getLoot()->GetLootMethod()); - loot.FillLoot(disenchant->ID, LootTemplates_Disenchant, player, true); - - uint32 max_slot = loot.GetMaxSlotInLootFor(player); - for (uint32 i = 0; i < max_slot; ++i) - { - LootItem* lootItem = loot.LootItemInSlot(i, player); - player->SendEquipError(msg, nullptr, nullptr, lootItem->itemid); - player->SendItemRetrievalMail(lootItem->itemid, lootItem->count, lootItem->context); - } - } - } - } - } - else - roll->totalGreed = 0; - } - } - - if (roll->totalNeed == 0 && roll->totalGreed == 0) // if, not else, because numbers can be modified above if player is on a different map - { - SendLootAllPassed(*roll); - - // remove is_blocked so that the item is lootable by all players - LootItem* item = &(roll->itemSlot >= roll->getLoot()->items.size() ? roll->getLoot()->quest_items[roll->itemSlot - roll->getLoot()->items.size()] : roll->getLoot()->items[roll->itemSlot]); - item->is_blocked = false; - } - - SendLootRollsComplete(*roll); - - RollId.erase(rollI); - delete roll; -} - void Group::SetTargetIcon(uint8 symbol, ObjectGuid target, ObjectGuid changedBy, uint8 partyIndex) { if (symbol >= TARGET_ICONS_COUNT) @@ -1965,48 +1338,6 @@ GroupJoinBattlegroundResult Group::CanJoinBattlegroundQueue(Battleground const* return ERR_BATTLEGROUND_NONE; } -//=================================================== -//============== Roll =============================== -//=================================================== - -void Roll::targetObjectBuildLink() -{ - // called from link() - getTarget()->addLootValidatorRef(this); -} - -void Roll::FillPacket(WorldPackets::Loot::LootItemData& lootItem) const -{ - lootItem.UIType = (totalPlayersRolling > totalNeed + totalGreed + totalPass) ? LOOT_SLOT_TYPE_ROLL_ONGOING : LOOT_SLOT_TYPE_ALLOW_LOOT; - lootItem.Quantity = itemCount; - lootItem.LootListID = itemSlot + 1; - if (LootItem const* lootItemInSlot = (*this)->GetItemInSlot(itemSlot)) - { - lootItem.CanTradeToTapList = lootItemInSlot->allowedGUIDs.size() > 1; - lootItem.Loot.Initialize(*lootItemInSlot); - } -} - -ItemDisenchantLootEntry const* Roll::GetItemDisenchantLoot(Player const* player) const -{ - if (LootItem const* lootItemInSlot = (*this)->GetItemInSlot(itemSlot)) - { - WorldPackets::Item::ItemInstance itemInstance; - itemInstance.Initialize(*lootItemInSlot); - - BonusData bonusData; - bonusData.Initialize(itemInstance); - if (!bonusData.CanDisenchant) - return nullptr; - - ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(itemid); - uint32 itemLevel = Item::GetItemLevel(itemTemplate, bonusData, player->GetLevel(), 0, 0, 0, 0, false, 0); - return Item::GetDisenchantLoot(itemTemplate, bonusData.Quality, itemLevel); - } - - return nullptr; -} - void Group::SetDungeonDifficultyID(Difficulty difficulty) { m_dungeonDifficulty = difficulty; @@ -2343,18 +1674,6 @@ void Group::BroadcastGroupUpdate(void) } } -void Group::ResetMaxEnchantingLevel() -{ - m_maxEnchantingLevel = 0; - Player* member = nullptr; - for (member_citerator citr = m_memberSlots.begin(); citr != m_memberSlots.end(); ++citr) - { - member = ObjectAccessor::FindPlayer(citr->guid); - if (member && m_maxEnchantingLevel < member->GetSkillValue(SKILL_ENCHANTING)) - m_maxEnchantingLevel = member->GetSkillValue(SKILL_ENCHANTING); - } -} - void Group::SetLootMethod(LootMethod method) { m_lootMethod = method; @@ -2711,15 +2030,6 @@ void Group::SetGroupMemberFlag(ObjectGuid guid, bool apply, GroupMemberFlags fla SendUpdate(); } -Group::Rolls::iterator Group::GetRoll(ObjectGuid lootObjectGuid, uint8 lootListId) -{ - for (Rolls::iterator iter = RollId.begin(); iter != RollId.end(); ++iter) - if ((*iter)->isValid() && (**iter)->GetGUID() == lootObjectGuid && (*iter)->itemSlot == lootListId) - return iter; - - return RollId.end(); -} - void Group::LinkMember(GroupReference* pRef) { m_memberMgr.insertFirst(pRef); diff --git a/src/server/game/Groups/Group.h b/src/server/game/Groups/Group.h index fd671978055..84d36794519 100644 --- a/src/server/game/Groups/Group.h +++ b/src/server/game/Groups/Group.h @@ -21,7 +21,6 @@ #include "DBCEnums.h" #include "DatabaseEnvFwd.h" #include "GroupRefManager.h" -#include "Loot.h" #include "Object.h" #include "RaceMask.h" #include "SharedDefines.h" @@ -42,13 +41,7 @@ class WorldSession; struct ItemDisenchantLootEntry; struct MapEntry; -namespace WorldPackets -{ - namespace Loot - { - struct LootItemData; - } -} +enum LootMethod : uint8; #define MAX_GROUP_SIZE 5 #define MAX_RAID_SIZE 40 @@ -59,16 +52,6 @@ namespace WorldPackets #define READYCHECK_DURATION 35000 -enum RollVote -{ - PASS = 0, - NEED = 1, - GREED = 2, - DISENCHANT = 3, - NOT_EMITED_YET = 4, - NOT_VALID = 5 -}; - enum GroupMemberOnlineStatus { MEMBER_STATUS_OFFLINE = 0x0000, @@ -171,30 +154,6 @@ enum GroupUpdatePetFlags GROUP_UPDATE_FLAG_PET_CUR_HP | GROUP_UPDATE_FLAG_PET_MAX_HP | GROUP_UPDATE_FLAG_PET_AURAS // all pet flags }; -class Roll : public LootValidatorRef -{ - public: - explicit Roll(LootItem const& li); - ~Roll(); - void setLoot(Loot* pLoot); - Loot* getLoot(); - void targetObjectBuildLink() override; - void FillPacket(WorldPackets::Loot::LootItemData& lootItem) const; - ItemDisenchantLootEntry const* GetItemDisenchantLoot(Player const* player) const; - - uint32 itemid; - ItemRandomBonusListId itemRandomBonusListId; - uint8 itemCount; - typedef std::map<ObjectGuid, RollVote> PlayerVote; - PlayerVote playerVote; //vote position correspond with player position (in group) - uint8 totalPlayersRolling; - uint8 totalNeed; - uint8 totalGreed; - uint8 totalPass; - uint8 itemSlot; - uint8 rollVoteMask; -}; - struct InstanceGroupBind { InstanceSave* save; @@ -240,8 +199,6 @@ class TC_GAME_API Group typedef MemberSlotList::iterator member_witerator; typedef std::set<Player*> InvitesList; - typedef std::vector<Roll*> Rolls; - public: Group(); ~Group(); @@ -385,27 +342,6 @@ class TC_GAME_API Group void BroadcastPacket(WorldPacket const* packet, bool ignorePlayersInBGRaid, int group = -1, ObjectGuid ignoredPlayer = ObjectGuid::Empty) const; void BroadcastAddonMessagePacket(WorldPacket const* packet, const std::string& prefix, bool ignorePlayersInBGRaid, int group = -1, ObjectGuid ignore = ObjectGuid::Empty) const; - /*********************************************************/ - /*** LOOT SYSTEM ***/ - /*********************************************************/ - - bool isRollLootActive() const { return !RollId.empty(); } - void SendLootStartRollToPlayer(uint32 countDown, uint32 mapId, Player* p, bool canNeed, Roll const& r) const; - void SendLootRoll(ObjectGuid playerGuid, int32 rollNumber, uint8 rollType, Roll const& roll, bool autoPass = false) const; - void SendLootRollWon(ObjectGuid winnerGuid, int32 rollNumber, uint8 rollType, Roll const& roll) const; - void SendLootAllPassed(Roll const& roll) const; - void SendLootRollsComplete(Roll const& roll) const; - void SendLooter(Creature* creature, Player* pLooter); - void GroupLoot(Loot* loot, WorldObject* pLootedObject); - void MasterLoot(Loot* loot, WorldObject* pLootedObject); - Rolls::iterator GetRoll(ObjectGuid lootObjectGuid, uint8 lootListId); - void CountTheRoll(Rolls::iterator roll, Map* allowedMap); - void CountRollVote(ObjectGuid playerGuid, ObjectGuid lootObjectGuid, uint8 lootListId, uint8 choice); - void EndRoll(Loot* loot, Map* allowedMap); - - // related to disenchant rolls - void ResetMaxEnchantingLevel(); - void LinkMember(GroupReference* pRef); void DelinkMember(ObjectGuid guid); @@ -454,11 +390,9 @@ class TC_GAME_API Group ItemQualities m_lootThreshold; ObjectGuid m_looterGuid; ObjectGuid m_masterLooterGuid; - Rolls RollId; BoundInstancesMap m_boundInstances; uint8* m_subGroupsCounts; ObjectGuid m_guid; - uint32 m_maxEnchantingLevel; uint32 m_dbStoreId; // Represents the ID used in database (Can be reused by other groups if group was disbanded) bool m_isLeaderOffline; TimeTracker m_leaderOfflineTimer; diff --git a/src/server/game/Groups/GroupMgr.cpp b/src/server/game/Groups/GroupMgr.cpp index d1cdc0e75c3..c8ed49365ad 100644 --- a/src/server/game/Groups/GroupMgr.cpp +++ b/src/server/game/Groups/GroupMgr.cpp @@ -19,6 +19,7 @@ #include "Common.h" #include "DatabaseEnv.h" #include "DB2Stores.h" +#include "Group.h" #include "InstanceSaveMgr.h" #include "Log.h" #include "World.h" diff --git a/src/server/game/Groups/GroupMgr.h b/src/server/game/Groups/GroupMgr.h index 781264b78f7..6e86142d62b 100644 --- a/src/server/game/Groups/GroupMgr.h +++ b/src/server/game/Groups/GroupMgr.h @@ -18,7 +18,10 @@ #ifndef _GROUPMGR_H #define _GROUPMGR_H -#include "Group.h" +#include "ObjectGuid.h" +#include <map> + +class Group; class TC_GAME_API GroupMgr { diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 57ccbefaf6a..56a958d37f8 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -1224,7 +1224,6 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder const& holder) { //pCurrChar->groupInfo.group->SendInit(this); // useless group->SendUpdate(); - group->ResetMaxEnchantingLevel(); if (group->GetLeaderGUID() == pCurrChar->GetGUID()) group->StopLeaderOfflineTimer(); } diff --git a/src/server/game/Handlers/GroupHandler.cpp b/src/server/game/Handlers/GroupHandler.cpp index 988249b9bb2..a309519028c 100644 --- a/src/server/game/Handlers/GroupHandler.cpp +++ b/src/server/game/Handlers/GroupHandler.cpp @@ -21,7 +21,7 @@ #include "Group.h" #include "GroupMgr.h" #include "Log.h" -#include "LootPackets.h" +#include "Loot.h" #include "MiscPackets.h" #include "ObjectAccessor.h" #include "PartyPackets.h" @@ -398,25 +398,6 @@ void WorldSession::HandleSetLootMethodOpcode(WorldPackets::Party::SetLootMethod& group->SendUpdate(); } -void WorldSession::HandleLootRoll(WorldPackets::Loot::LootRoll& packet) -{ - Group* group = GetPlayer()->GetGroup(); - if (!group) - return; - - group->CountRollVote(GetPlayer()->GetGUID(), packet.LootObj, packet.LootListID - 1, packet.RollType); - - switch (packet.RollType) - { - case ROLL_NEED: - GetPlayer()->UpdateCriteria(CriteriaType::RollAnyNeed, 1); - break; - case ROLL_GREED: - GetPlayer()->UpdateCriteria(CriteriaType::RollAnyGreed, 1); - break; - } -} - void WorldSession::HandleMinimapPingOpcode(WorldPackets::Party::MinimapPingClient& packet) { if (!GetPlayer()->GetGroup()) diff --git a/src/server/game/Handlers/LootHandler.cpp b/src/server/game/Handlers/LootHandler.cpp index 86422dc8926..d14810c2b32 100644 --- a/src/server/game/Handlers/LootHandler.cpp +++ b/src/server/game/Handlers/LootHandler.cpp @@ -28,6 +28,7 @@ #include "GuildMgr.h" #include "Item.h" #include "Log.h" +#include "Loot.h" #include "LootItemStorage.h" #include "LootMgr.h" #include "LootPackets.h" @@ -360,9 +361,7 @@ void WorldSession::DoLootRelease(Loot* loot) if (player->GetGUID() == loot->roundRobinPlayer) { loot->roundRobinPlayer.Clear(); - - if (Group* group = player->GetGroup()) - group->SendLooter(creature, nullptr); + loot->NotifyLootList(creature->GetMap()); } // force dynflag update to update looter and lootable info creature->ForceUpdateFieldChange(creature->m_values.ModifyValue(&Object::m_objectData).ModifyValue(&UF::ObjectData::DynamicFlags)); @@ -460,6 +459,15 @@ void WorldSession::HandleLootMasterGiveOpcode(WorldPackets::Loot::MasterLootItem } } +void WorldSession::HandleLootRoll(WorldPackets::Loot::LootRoll& packet) +{ + LootRoll* lootRoll = GetPlayer()->GetLootRoll(packet.LootObj, packet.LootListID - 1); + if (!lootRoll) + return; + + lootRoll->PlayerVote(GetPlayer(), RollVote(packet.RollType)); +} + void WorldSession::HandleSetLootSpecialization(WorldPackets::Loot::SetLootSpecialization& packet) { if (packet.SpecID) diff --git a/src/server/game/Loot/Loot.cpp b/src/server/game/Loot/Loot.cpp index a03c4a5e860..8f086facb80 100644 --- a/src/server/game/Loot/Loot.cpp +++ b/src/server/game/Loot/Loot.cpp @@ -16,9 +16,12 @@ */ #include "Loot.h" +#include "Containers.h" #include "DatabaseEnv.h" #include "DB2Stores.h" +#include "GameTime.h" #include "Group.h" +#include "Item.h" #include "ItemTemplate.h" #include "Log.h" #include "LootMgr.h" @@ -39,22 +42,22 @@ LootItem::LootItem(LootStoreItem const& li) { itemid = li.itemid; - itemIndex = 0; + LootListId = 0; conditions = li.conditions; ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemid); freeforall = proto && proto->HasFlag(ITEM_FLAG_MULTI_DROP); - follow_loot_rules = proto && (proto->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES)); + follow_loot_rules = !li.needs_quest || (proto && proto->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES)); needs_quest = li.needs_quest; randomBonusListId = GenerateItemRandomBonusListId(itemid); context = ItemContext::NONE; count = 0; - is_looted = 0; - is_blocked = 0; - is_underthreshold = 0; - is_counted = 0; + is_looted = false; + is_blocked = false; + is_underthreshold = false; + is_counted = false; rollWinnerGUID = ObjectGuid::Empty; } @@ -124,12 +127,420 @@ void LootItem::AddAllowedLooter(const Player* player) } // +// ------- Loot Roll ------- +// + +// Send the roll for the whole group +void LootRoll::SendStartRoll() +{ + ItemTemplate const* itemTemplate = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(m_lootItem->itemid)); + for (auto const& [playerGuid, roll] : m_rollVoteMap) + { + if (roll.Vote != RollVote::NotEmitedYet) + continue; + + Player* player = ObjectAccessor::GetPlayer(m_map, playerGuid); + if (!player) + continue; + + WorldPackets::Loot::StartLootRoll startLootRoll; + startLootRoll.LootObj = m_loot->GetGUID(); + startLootRoll.MapID = m_map->GetId(); + startLootRoll.RollTime = LOOT_ROLL_TIMEOUT; + 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) + startLootRoll.ValidRolls &= ~ROLL_FLAG_TYPE_NEED; + + FillPacket(startLootRoll.Item); + startLootRoll.Item.UIType = LOOT_SLOT_TYPE_ROLL_ONGOING; + + player->SendDirectMessage(startLootRoll.Write()); + } + + // Handle auto pass option + for (auto const& [playerGuid, roll] : m_rollVoteMap) + { + if (roll.Vote != RollVote::Pass) + continue; + + SendRoll(playerGuid, -1, RollVote::Pass, {}); + } +} + +// Send all passed message +void LootRoll::SendAllPassed() +{ + WorldPackets::Loot::LootAllPassed lootAllPassed; + lootAllPassed.LootObj = m_loot->GetGUID(); + FillPacket(lootAllPassed.Item); + lootAllPassed.Item.UIType = LOOT_SLOT_TYPE_ALLOW_LOOT; + lootAllPassed.Write(); + + for (auto const& [playerGuid, roll] : m_rollVoteMap) + { + if (roll.Vote != RollVote::NotValid) + continue; + + Player* player = ObjectAccessor::GetPlayer(m_map, playerGuid); + if (!player) + continue; + + player->SendDirectMessage(lootAllPassed.GetRawPacket()); + } +} + +// Send roll of targetGuid to the whole group (included targuetGuid) +void LootRoll::SendRoll(ObjectGuid const& targetGuid, int32 rollNumber, RollVote rollType, Optional<ObjectGuid> const& rollWinner) +{ + WorldPackets::Loot::LootRollBroadcast lootRoll; + lootRoll.LootObj = m_loot->GetGUID(); + lootRoll.Player = targetGuid; + lootRoll.Roll = rollNumber; + lootRoll.RollType = AsUnderlyingType(rollType); + lootRoll.Autopassed = false; + FillPacket(lootRoll.Item); + lootRoll.Item.UIType = LOOT_SLOT_TYPE_ROLL_ONGOING; + lootRoll.Write(); + + for (auto const& [playerGuid, roll] : m_rollVoteMap) + { + if (roll.Vote == RollVote::NotValid) + continue; + + if (playerGuid == rollWinner) + continue; + + Player* player = ObjectAccessor::GetPlayer(m_map, playerGuid); + if (!player) + continue; + + player->SendDirectMessage(lootRoll.GetRawPacket()); + } + + if (rollWinner) + { + if (Player* player = ObjectAccessor::GetPlayer(m_map, *rollWinner)) + { + lootRoll.Item.UIType = LOOT_SLOT_TYPE_ALLOW_LOOT; + lootRoll.Clear(); + player->SendDirectMessage(lootRoll.Write()); + } + } +} + +// Send roll 'value' of the whole group and the winner to the whole group +void LootRoll::SendLootRollWon(ObjectGuid const& targetGuid, int32 rollNumber, RollVote rollType) +{ + // Send roll values + for (auto const& [playerGuid, roll] : m_rollVoteMap) + { + switch (roll.Vote) + { + case RollVote::Pass: + break; + case RollVote::NotEmitedYet: + case RollVote::NotValid: + SendRoll(playerGuid, 0, RollVote::Pass, targetGuid); + break; + default: + SendRoll(playerGuid, roll.RollNumber, roll.Vote, targetGuid); + break; + } + } + + WorldPackets::Loot::LootRollWon lootRollWon; + lootRollWon.LootObj = m_loot->GetGUID(); + lootRollWon.Winner = targetGuid; + lootRollWon.Roll = rollNumber; + lootRollWon.RollType = AsUnderlyingType(rollType); + FillPacket(lootRollWon.Item); + lootRollWon.Item.UIType = LOOT_SLOT_TYPE_LOCKED; + lootRollWon.MainSpec = true; // offspec rolls not implemented + lootRollWon.Write(); + + for (auto const& [playerGuid, roll] : m_rollVoteMap) + { + if (roll.Vote == RollVote::NotValid) + continue; + + if (playerGuid == targetGuid) + continue; + + Player* player = ObjectAccessor::GetPlayer(m_map, playerGuid); + if (!player) + continue; + + player->SendDirectMessage(lootRollWon.GetRawPacket()); + } + + if (Player* player = ObjectAccessor::GetPlayer(m_map, targetGuid)) + { + lootRollWon.Item.UIType = LOOT_SLOT_TYPE_ALLOW_LOOT; + lootRollWon.Clear(); + player->SendDirectMessage(lootRollWon.Write()); + } +} + +void LootRoll::FillPacket(WorldPackets::Loot::LootItemData& lootItem) const +{ + lootItem.Quantity = m_lootItem->count; + lootItem.LootListID = m_lootListId + 1; + lootItem.CanTradeToTapList = m_lootItem->allowedGUIDs.size() > 1; + lootItem.Loot.Initialize(*m_lootItem); +} + +LootRoll::~LootRoll() +{ + if (m_isStarted) + SendAllPassed(); + + for (auto const& [playerGuid, roll] : m_rollVoteMap) + { + if (roll.Vote != RollVote::NotEmitedYet) + continue; + + Player* player = ObjectAccessor::GetPlayer(m_map, playerGuid); + if (!player) + continue; + + player->RemoveLootRoll(this); + } +} + +// Try to start the group roll for the specified item (it may fail for quest item or any condition +// If this method return false the roll have to be removed from the container to avoid any problem +bool LootRoll::TryToStart(Map* map, Loot& loot, uint32 lootListId, uint16 enchantingSkill) +{ + if (!m_isStarted) + { + if (lootListId >= loot.items.size()) + return false; + + m_map = map; + + // initialize the data needed for the roll + m_lootItem = &loot.items[lootListId]; + + m_loot = &loot; + m_lootListId = lootListId; + m_lootItem->is_blocked = true; // block the item while rolling + + uint32 playerCount = 0; + for (ObjectGuid allowedLooter : m_lootItem->GetAllowedLooters()) + { + Player* plr = ObjectAccessor::GetPlayer(m_map, allowedLooter); + if (!plr || !m_lootItem->AllowedForPlayer(plr)) // check if player meet the condition to be able to roll this item + { + m_rollVoteMap[allowedLooter].Vote = RollVote::NotValid; + continue; + } + // initialize player vote map + m_rollVoteMap[allowedLooter].Vote = plr->GetPassOnGroupLoot() ? RollVote::Pass : RollVote::NotEmitedYet; + if (!plr->GetPassOnGroupLoot()) + plr->AddLootRoll(this); + + ++playerCount; + } + + // initialize item prototype and check enchant possibilities for this group + ItemTemplate const* itemTemplate = ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(m_lootItem->itemid)); + m_voteMask = ROLL_ALL_TYPE_MASK; + if (itemTemplate->HasFlag(ITEM_FLAG2_CAN_ONLY_ROLL_GREED)) + m_voteMask = RollMask(m_voteMask & ~ROLL_FLAG_TYPE_NEED); + if (ItemDisenchantLootEntry const* disenchant = GetItemDisenchantLoot(); !disenchant || disenchant->SkillRequired > enchantingSkill) + m_voteMask = RollMask(m_voteMask & ~ROLL_FLAG_TYPE_DISENCHANT); + + if (playerCount > 1) // check if more than one player can loot this item + { + // start the roll + SendStartRoll(); + m_endTime = GameTime::Now() + LOOT_ROLL_TIMEOUT; + m_isStarted = true; + return true; + } + // no need to start roll if one or less player can loot this item so place it under threshold + m_lootItem->is_underthreshold = true; + m_lootItem->is_blocked = false; + } + return false; +} + +// Add vote from playerGuid +bool LootRoll::PlayerVote(Player* player, RollVote vote) +{ + ObjectGuid const& playerGuid = player->GetGUID(); + RollVoteMap::iterator voterItr = m_rollVoteMap.find(playerGuid); + if (voterItr == m_rollVoteMap.end()) + return false; + + voterItr->second.Vote = vote; + + if (vote != RollVote::Pass && vote != RollVote::NotValid) + voterItr->second.RollNumber = urand(1, 100); + + switch (vote) + { + case RollVote::Pass: // Player choose pass + { + SendRoll(playerGuid, -1, RollVote::Pass, {}); + break; + } + case RollVote::Need: // player choose Need + { + SendRoll(playerGuid, 0, RollVote::Need, {}); + player->UpdateCriteria(CriteriaType::RollAnyNeed, 1); + break; + } + case RollVote::Greed: // player choose Greed + { + SendRoll(playerGuid, -1, RollVote::Greed, {}); + player->UpdateCriteria(CriteriaType::RollAnyGreed, 1); + break; + } + case RollVote::Disenchant: // player choose Disenchant + { + SendRoll(playerGuid, -1, RollVote::Disenchant, {}); + player->UpdateCriteria(CriteriaType::RollAnyGreed, 1); + break; + } + default: // Roll removed case + return false; + } + return true; +} + +// check if we can found a winner for this roll or if timer is expired +bool LootRoll::UpdateRoll() +{ + RollVoteMap::const_iterator winnerItr = m_rollVoteMap.end(); + + if (AllPlayerVoted(winnerItr) || m_endTime <= GameTime::Now()) + { + Finish(winnerItr); + return true; + } + return false; +} + +bool LootRoll::IsLootItem(ObjectGuid const& lootObject, uint32 lootListId) const +{ + return m_loot->GetGUID() == lootObject && m_lootListId == lootListId; +} + +/** +* \brief Check if all player have voted and return true in that case. Also return current winner. +* \param winnerItr > will be different than m_rollCoteMap.end() if winner exist. (Someone voted greed or need) +* \returns true if all players voted +**/ +bool LootRoll::AllPlayerVoted(RollVoteMap::const_iterator& winnerItr) +{ + uint32 notVoted = 0; + bool isSomeoneNeed = false; + + winnerItr = m_rollVoteMap.end(); + for (RollVoteMap::const_iterator itr = m_rollVoteMap.begin(); itr != m_rollVoteMap.end(); ++itr) + { + switch (itr->second.Vote) + { + case RollVote::Need: + if (!isSomeoneNeed || winnerItr == m_rollVoteMap.end() || itr->second.RollNumber > winnerItr->second.RollNumber) + { + isSomeoneNeed = true; // first passage will force to set winner because need is prioritized + winnerItr = itr; + } + break; + case RollVote::Greed: + case RollVote::Disenchant: + if (!isSomeoneNeed) // if at least one need is detected then winner can't be a greed + { + if (winnerItr == m_rollVoteMap.end() || itr->second.RollNumber > winnerItr->second.RollNumber) + winnerItr = itr; + } + break; + // Explicitly passing excludes a player from winning loot, so no action required. + case RollVote::Pass: + break; + case RollVote::NotEmitedYet: + ++notVoted; + break; + default: + break; + } + } + + return notVoted == 0; +} + +ItemDisenchantLootEntry const* LootRoll::GetItemDisenchantLoot() const +{ + WorldPackets::Item::ItemInstance itemInstance; + itemInstance.Initialize(*m_lootItem); + + BonusData bonusData; + bonusData.Initialize(itemInstance); + if (!bonusData.CanDisenchant) + return nullptr; + + ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(m_lootItem->itemid); + uint32 itemLevel = Item::GetItemLevel(itemTemplate, bonusData, 1, 0, 0, 0, 0, false, 0); + return Item::GetDisenchantLoot(itemTemplate, bonusData.Quality, itemLevel); +} + +// terminate the roll +void LootRoll::Finish(RollVoteMap::const_iterator winnerItr) +{ + m_lootItem->is_blocked = false; + if (winnerItr == m_rollVoteMap.end()) + { + SendAllPassed(); + } + else + { + m_lootItem->rollWinnerGUID = winnerItr->first; + + SendLootRollWon(winnerItr->first, winnerItr->second.RollNumber, winnerItr->second.Vote); + + if (Player* player = ObjectAccessor::FindConnectedPlayer(winnerItr->first)) + { + if (winnerItr->second.Vote == RollVote::Need) + player->UpdateCriteria(CriteriaType::RollNeed, m_lootItem->itemid, winnerItr->second.RollNumber); + else if (winnerItr->second.Vote == RollVote::Disenchant) + player->UpdateCriteria(CriteriaType::CastSpell, 13262); + else + player->UpdateCriteria(CriteriaType::RollGreed, m_lootItem->itemid, winnerItr->second.RollNumber); + + if (winnerItr->second.Vote == RollVote::Disenchant) + { + ItemDisenchantLootEntry const* disenchant = ASSERT_NOTNULL(GetItemDisenchantLoot()); + Loot loot(m_map, m_loot->GetOwnerGUID(), LOOT_DISENCHANTING, nullptr); + loot.FillLoot(disenchant->ID, LootTemplates_Disenchant, player, true, false, LOOT_MODE_DEFAULT, ItemContext::NONE); + if (!loot.AutoStore(player, NULL_BAG, NULL_SLOT, true)) + { + uint32 maxSlot = loot.GetMaxSlotInLootFor(player); + for (uint32 i = 0; i < maxSlot; ++i) + if (LootItem* disenchantLoot = loot.LootItemInSlot(i, player)) + player->SendItemRetrievalMail(disenchantLoot->itemid, disenchantLoot->count, disenchantLoot->context); + } + else + m_loot->NotifyItemRemoved(m_lootItem->LootListId, m_map); + } + else + player->StoreLootItem(m_loot->GetOwnerGUID(), m_lootListId, m_loot); + } + } + m_isStarted = false; +} + +// // --------- Loot --------- // -Loot::Loot(Map* map, ObjectGuid owner, LootType type, LootMethod lootMethod) : gold(0), unlootedCount(0), loot_type(type), maxDuplicates(1), +Loot::Loot(Map* map, ObjectGuid owner, LootType type, Group const* group) : gold(0), unlootedCount(0), loot_type(type), maxDuplicates(1), _guid(map ? ObjectGuid::Create<HighGuid::LootObject>(map->GetId(), 0, map->GenerateLowGuid<HighGuid::LootObject>()) : ObjectGuid::Empty), - _owner(owner), _itemContext(ItemContext::NONE), _lootMethod(lootMethod) + _owner(owner), _itemContext(ItemContext::NONE), _lootMethod(group ? group->GetLootMethod() : FREE_FOR_ALL), + _lootMaster(group ? group->GetMasterLooterGuid() : ObjectGuid::Empty), _wasOpened(false) { } @@ -163,8 +574,28 @@ void Loot::clear() unlootedCount = 0; roundRobinPlayer.Clear(); loot_type = LOOT_NONE; - i_LootValidatorRefManager.clearReferences(); _itemContext = ItemContext::NONE; + _rolls.clear(); +} + +void Loot::NotifyLootList(Map const* map) const +{ + WorldPackets::Loot::LootList lootList; + + lootList.Owner = GetOwnerGUID(); + lootList.LootObj = GetGUID(); + + if (GetLootMethod() == MASTER_LOOT && hasOverThresholdItem()) + lootList.Master = GetLootMasterGUID(); + + if (!roundRobinPlayer.IsEmpty()) + lootList.RoundRobinWinner = roundRobinPlayer; + + lootList.Write(); + + for (ObjectGuid allowedLooterGuid : _allowedLooters) + if (Player* allowedLooter = ObjectAccessor::GetPlayer(map, allowedLooterGuid)) + allowedLooter->SendDirectMessage(lootList.GetRawPacket()); } void Loot::NotifyItemRemoved(uint8 lootIndex, Map const* map) @@ -230,6 +661,59 @@ void Loot::NotifyMoneyRemoved(Map const* map) } } +void Loot::OnLootOpened(Map* map, ObjectGuid looter) +{ + AddLooter(looter); + if (!_wasOpened) + { + _wasOpened = true; + + if (_lootMethod == GROUP_LOOT || _lootMethod == NEED_BEFORE_GREED) + { + uint16 maxEnchantingSkill = 0; + for (ObjectGuid allowedLooterGuid : _allowedLooters) + if (Player* allowedLooter = ObjectAccessor::GetPlayer(map, allowedLooterGuid)) + maxEnchantingSkill = std::max(maxEnchantingSkill, allowedLooter->GetSkillValue(SKILL_ENCHANTING)); + + uint32 lootListId = 0; + for (; lootListId < items.size(); ++lootListId) + { + LootItem& item = items[lootListId]; + if (!item.is_blocked) + continue; + + auto&& [itr, inserted] = _rolls.try_emplace(lootListId); + if (!itr->second.TryToStart(map, *this, lootListId, maxEnchantingSkill)) + _rolls.erase(itr); + } + + for (; lootListId - items.size() < quest_items.size(); ++lootListId) + { + LootItem& item = quest_items[lootListId - items.size()]; + if (!item.is_blocked) + continue; + + auto&& [itr, inserted] = _rolls.try_emplace(lootListId); + if (!itr->second.TryToStart(map, *this, lootListId, maxEnchantingSkill)) + _rolls.erase(itr); + } + } + else if (_lootMethod == MASTER_LOOT) + { + if (looter == _lootMaster) + { + if (Player* lootMaster = ObjectAccessor::GetPlayer(map, looter)) + { + WorldPackets::Loot::MasterLootCandidateList masterLootCandidateList; + masterLootCandidateList.LootObj = GetGUID(); + masterLootCandidateList.Players = _allowedLooters; + lootMaster->SendDirectMessage(masterLootCandidateList.Write()); + } + } + } + } +} + void Loot::generateMoneyLoot(uint32 minAmount, uint32 maxAmount) { if (maxAmount > 0) @@ -274,19 +758,52 @@ bool Loot::FillLoot(uint32 lootId, LootStore const& store, Player* lootOwner, bo for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) if (Player const* player = itr->GetSource()) // should actually be looted object instead of lootOwner but looter has to be really close so doesnt really matter - if (player->IsInMap(lootOwner)) - FillNotNormalLootFor(player, player->IsAtGroupRewardDistance(lootOwner)); + if (player->IsAtGroupRewardDistance(lootOwner)) + FillNotNormalLootFor(player); - for (uint8 i = 0; i < items.size(); ++i) + auto processLootItem = [&](LootItem& item) { - if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(items[i].itemid)) + if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item.itemid)) + { if (proto->GetQuality() < uint32(group->GetLootThreshold())) - items[i].is_underthreshold = true; + item.is_underthreshold = true; + else + { + switch (_lootMethod) + { + case MASTER_LOOT: + case GROUP_LOOT: + case NEED_BEFORE_GREED: + { + item.is_blocked = true; + break; + } + default: + break; + } + } + } + }; + + for (LootItem& item : items) + { + if (item.freeforall) + continue; + + processLootItem(item); + } + + for (LootItem& item : quest_items) + { + if (!item.follow_loot_rules) + continue; + + processLootItem(item); } } // ... for personal loot else - FillNotNormalLootFor(lootOwner, true); + FillNotNormalLootFor(lootOwner); return true; } @@ -309,7 +826,7 @@ void Loot::AddItem(LootStoreItem const& item, Player const* player) LootItem generatedLoot(item); generatedLoot.context = _itemContext; generatedLoot.count = std::min(count, proto->GetMaxStackSize()); - generatedLoot.itemIndex = lootItems.size(); + generatedLoot.LootListId = lootItems.size(); if (_itemContext != ItemContext::NONE) { std::set<uint32> bonusListIDs = sDB2Manager.GetDefaultItemBonusTree(generatedLoot.itemid, _itemContext); @@ -342,6 +859,63 @@ void Loot::AddItem(LootStoreItem const& item, Player const* player) } } +bool Loot::AutoStore(Player* player, uint8 bag, uint8 slot, bool broadcast, bool createdByPlayer) +{ + bool allLooted = true; + uint32 max_slot = GetMaxSlotInLootFor(player); + for (uint32 i = 0; i < max_slot; ++i) + { + NotNormalLootItem* qitem = nullptr; + NotNormalLootItem* ffaitem = nullptr; + NotNormalLootItem* conditem = nullptr; + + LootItem* lootItem = LootItemInSlot(i, player, &qitem, &ffaitem, &conditem); + if (!lootItem || lootItem->is_looted) + continue; + + if (!lootItem->AllowedForPlayer(player)) + continue; + + if (!qitem && lootItem->is_blocked) + continue; + + // dont allow protected item to be looted by someone else + if (!lootItem->rollWinnerGUID.IsEmpty() && lootItem->rollWinnerGUID != GetGUID()) + continue; + + ItemPosCountVec dest; + InventoryResult msg = player->CanStoreNewItem(bag, slot, dest, lootItem->itemid, lootItem->count); + if (msg != EQUIP_ERR_OK && slot != NULL_SLOT) + msg = player->CanStoreNewItem(bag, NULL_SLOT, dest, lootItem->itemid, lootItem->count); + if (msg != EQUIP_ERR_OK && bag != NULL_BAG) + msg = player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, lootItem->itemid, lootItem->count); + if (msg != EQUIP_ERR_OK) + { + player->SendEquipError(msg, nullptr, nullptr, lootItem->itemid); + allLooted = false; + continue; + } + + if (qitem) + qitem->is_looted = true; + else if (ffaitem) + ffaitem->is_looted = true; + else if (conditem) + conditem->is_looted = true; + + if (!lootItem->freeforall) + lootItem->is_looted = true; + + --unlootedCount; + + Item* pItem = player->StoreNewItem(dest, lootItem->itemid, true, lootItem->randomBonusListId, GuidSet(), lootItem->context, lootItem->BonusListIDs); + player->SendNewItem(pItem, lootItem->count, false, createdByPlayer, broadcast); + player->ApplyItemLootedSpell(pItem, true); + } + + return allLooted; +} + LootItem const* Loot::GetItemInSlot(uint32 lootSlot) const { if (lootSlot < items.size()) @@ -564,6 +1138,26 @@ void Loot::BuildLootResponse(WorldPackets::Loot::LootResponse& packet, Player* v } break; } + case ROUND_ROBIN_PERMISSION: + { + for (uint8 i = 0; i < items.size(); ++i) + { + if (!items[i].is_looted && !items[i].freeforall && items[i].conditions.empty() && items[i].AllowedForPlayer(viewer)) + { + if (!roundRobinPlayer.IsEmpty() && viewer->GetGUID() != roundRobinPlayer) + // item shall not be displayed. + continue; + + WorldPackets::Loot::LootItemData lootItem; + lootItem.LootListID = i + 1; + lootItem.UIType = LOOT_SLOT_TYPE_ALLOW_LOOT; + lootItem.Quantity = items[i].count; + lootItem.Loot.Initialize(items[i]); + packet.Items.push_back(lootItem); + } + } + break; + } case ALL_PERMISSION: case OWNER_PERMISSION: { @@ -613,6 +1207,7 @@ void Loot::BuildLootResponse(WorldPackets::Loot::LootResponse& packet, Player* v lootItem.UIType = item.is_blocked ? LOOT_SLOT_TYPE_LOCKED : LOOT_SLOT_TYPE_ALLOW_LOOT; break; case GROUP_PERMISSION: + case ROUND_ROBIN_PERMISSION: if (!item.is_blocked) lootItem.UIType = LOOT_SLOT_TYPE_ALLOW_LOOT; else @@ -675,6 +1270,7 @@ void Loot::BuildLootResponse(WorldPackets::Loot::LootResponse& packet, Player* v lootItem.UIType = item.is_blocked ? LOOT_SLOT_TYPE_LOCKED : LOOT_SLOT_TYPE_ALLOW_LOOT; break; case GROUP_PERMISSION: + case ROUND_ROBIN_PERMISSION: if (!item.is_blocked) lootItem.UIType = LOOT_SLOT_TYPE_ALLOW_LOOT; else @@ -691,9 +1287,21 @@ void Loot::BuildLootResponse(WorldPackets::Loot::LootResponse& packet, Player* v } } -void Loot::FillNotNormalLootFor(Player const* player, bool presentAtLooting) +void Loot::Update() +{ + for (auto itr = _rolls.begin(); itr != _rolls.end(); ) + { + if (itr->second.UpdateRoll()) + itr = _rolls.erase(itr); + else + ++itr; + } +} + +void Loot::FillNotNormalLootFor(Player const* player) { ObjectGuid plguid = player->GetGUID(); + _allowedLooters.insert(plguid); NotNormalLootItemMap::const_iterator qmapitr = PlayerQuestItems.find(plguid); if (qmapitr == PlayerQuestItems.end()) @@ -705,7 +1313,7 @@ void Loot::FillNotNormalLootFor(Player const* player, bool presentAtLooting) qmapitr = PlayerNonQuestNonFFAConditionalItems.find(plguid); if (qmapitr == PlayerNonQuestNonFFAConditionalItems.end()) - FillNonQuestNonFFAConditionalLoot(player, presentAtLooting); + FillNonQuestNonFFAConditionalLoot(player); } NotNormalLootItemList* Loot::FillFFALoot(Player const* player) @@ -744,6 +1352,8 @@ NotNormalLootItemList* Loot::FillQuestLoot(Player const* player) if (!item.is_looted && (item.AllowedForPlayer(player) || (item.follow_loot_rules && player->GetGroup() && ((GetLootMethod() == MASTER_LOOT && player->GetGroup()->GetMasterLooterGuid() == player->GetGUID()) || GetLootMethod() != MASTER_LOOT)))) { + item.AddAllowedLooter(player); + ql->push_back(NotNormalLootItem(i)); // quest items get blocked when they first appear in a @@ -752,7 +1362,7 @@ NotNormalLootItemList* Loot::FillQuestLoot(Player const* player) // increase once if one looter only, looter-times if free for all if (item.freeforall || !item.is_blocked) ++unlootedCount; - if (!player->GetGroup() || (GetLootMethod() != GROUP_LOOT)) + if (!player->GetGroup() || (GetLootMethod() != GROUP_LOOT && GetLootMethod() != ROUND_ROBIN)) item.is_blocked = true; if (items.size() + ql->size() == MAX_NR_LOOT_ITEMS) @@ -769,7 +1379,7 @@ NotNormalLootItemList* Loot::FillQuestLoot(Player const* player) return ql; } -NotNormalLootItemList* Loot::FillNonQuestNonFFAConditionalLoot(Player const* player, bool presentAtLooting) +NotNormalLootItemList* Loot::FillNonQuestNonFFAConditionalLoot(Player const* player) { NotNormalLootItemList* ql = new NotNormalLootItemList(); @@ -778,8 +1388,7 @@ NotNormalLootItemList* Loot::FillNonQuestNonFFAConditionalLoot(Player const* pla LootItem &item = items[i]; if (!item.is_looted && !item.freeforall && (item.AllowedForPlayer(player))) { - if (presentAtLooting) - item.AddAllowedLooter(player); + item.AddAllowedLooter(player); if (!item.conditions.empty()) { ql->push_back(NotNormalLootItem(i)); diff --git a/src/server/game/Loot/Loot.h b/src/server/game/Loot/Loot.h index 68007173919..a75862f05dc 100644 --- a/src/server/game/Loot/Loot.h +++ b/src/server/game/Loot/Loot.h @@ -18,20 +18,25 @@ #ifndef Loot_h__ #define Loot_h__ -#include "Define.h" #include "ConditionMgr.h" #include "DBCEnums.h" +#include "Define.h" +#include "Duration.h" #include "ItemEnchantmentMgr.h" #include "ObjectGuid.h" -#include "RefManager.h" +#include "Optional.h" #include "SharedDefines.h" #include <unordered_map> #include <vector> +constexpr Minutes LOOT_ROLL_TIMEOUT = 1min; + +class Group; class Item; class LootStore; class Map; class Player; +struct ItemDisenchantLootEntry; struct Loot; struct LootStoreItem; @@ -39,6 +44,7 @@ namespace WorldPackets { namespace Loot { + struct LootItemData; class LootResponse; } } @@ -52,6 +58,16 @@ enum RollType MAX_ROLL_TYPE = 4 }; +enum class RollVote +{ + Pass = 0, + Need = 1, + Greed = 2, + Disenchant = 3, + NotEmitedYet = 4, + NotValid = 5 +}; + enum RollMask { ROLL_FLAG_TYPE_PASS = 0x01, @@ -71,8 +87,10 @@ enum RollMask enum LootMethod : uint8 { FREE_FOR_ALL = 0, + ROUND_ROBIN = 1, MASTER_LOOT = 2, GROUP_LOOT = 3, + NEED_BEFORE_GREED = 4, PERSONAL_LOOT = 5 }; @@ -82,6 +100,7 @@ enum PermissionTypes GROUP_PERMISSION = 1, MASTER_PERMISSION = 2, RESTRICTED_PERMISSION = 3, + ROUND_ROBIN_PERMISSION = 4, OWNER_PERMISSION = 5, NONE_PERMISSION = 6 }; @@ -156,7 +175,7 @@ enum LootSlotType struct TC_GAME_API LootItem { uint32 itemid; - uint32 itemIndex; + uint32 LootListId; ItemRandomBonusListId randomBonusListId; std::vector<int32> BonusListIDs; ItemContext context; @@ -177,8 +196,8 @@ struct TC_GAME_API LootItem explicit LootItem(LootStoreItem const& li); // Empty constructor for creating an empty LootItem to be filled in with DB data - LootItem() : itemid(0), itemIndex(0), randomBonusListId(0), context(ItemContext::NONE), count(0), is_looted(false), is_blocked(false), - freeforall(false), is_underthreshold(false), is_counted(false), needs_quest(false), follow_loot_rules(false) { }; + LootItem() : itemid(0), LootListId(0), randomBonusListId(0), context(ItemContext::NONE), count(0), is_looted(false), is_blocked(false), + freeforall(false), is_underthreshold(false), is_counted(false), needs_quest(false), follow_loot_rules(false) { } // Basic checks for player/item compatibility - if false no chance to see the item in the loot bool AllowedForPlayer(Player const* player, bool isGivenByMasterLooter = false) const; @@ -204,28 +223,50 @@ typedef std::unordered_map<ObjectGuid, NotNormalLootItemList*> NotNormalLootItem //===================================================== -class LootValidatorRef : public Reference<Loot, LootValidatorRef> +struct PlayerRollVote { -public: - LootValidatorRef() { } - void targetObjectDestroyLink() override { } - void sourceObjectDestroyLink() override { } + PlayerRollVote() : Vote(RollVote::NotValid), RollNumber(0) { } + RollVote Vote; + uint8 RollNumber; }; -//===================================================== - -class LootValidatorRefManager : public RefManager<Loot, LootValidatorRef> +class LootRoll { public: - typedef LinkedListHead::Iterator<LootValidatorRef> iterator; + using RollVoteMap = std::unordered_map<ObjectGuid, PlayerRollVote>; - LootValidatorRef* getFirst() { return (LootValidatorRef*)RefManager<Loot, LootValidatorRef>::getFirst(); } + LootRoll() : m_map(nullptr), m_isStarted(false), m_lootItem(nullptr), m_loot(nullptr), m_lootListId(0), m_voteMask(), m_endTime(TimePoint::min()) { } + ~LootRoll(); - iterator begin() { return iterator(getFirst()); } - iterator end() { return iterator(nullptr); } -}; + LootRoll(LootRoll const&) = delete; + LootRoll(LootRoll&&) = delete; + LootRoll& operator=(LootRoll const&) = delete; + LootRoll& operator=(LootRoll&&) = delete; -//===================================================== + bool TryToStart(Map* map, Loot& loot, uint32 lootListId, uint16 enchantingSkill); + bool PlayerVote(Player* player, RollVote vote); + bool UpdateRoll(); + + bool IsLootItem(ObjectGuid const& lootObject, uint32 lootListId) const; + +private: + void SendStartRoll(); + void SendAllPassed(); + void SendRoll(ObjectGuid const& targetGuid, int32 rollNumber, RollVote rollType, Optional<ObjectGuid> const& rollWinner); + void SendLootRollWon(ObjectGuid const& targetGuid, int32 rollNumber, RollVote rollType); + void FillPacket(WorldPackets::Loot::LootItemData& lootItem) const; + void Finish(RollVoteMap::const_iterator winnerItr); + bool AllPlayerVoted(RollVoteMap::const_iterator& winnerItr); + ItemDisenchantLootEntry const* GetItemDisenchantLoot() const; + Map* m_map; + RollVoteMap m_rollVoteMap; + bool m_isStarted; + LootItem* m_lootItem; + Loot* m_loot; + uint32 m_lootListId; + RollMask m_voteMask; + TimePoint m_endTime; +}; struct TC_GAME_API Loot { @@ -241,27 +282,29 @@ struct TC_GAME_API Loot LootType loot_type; // required for achievement system uint8 maxDuplicates; // Max amount of items with the same entry that can drop (default is 1; on 25 man raid mode 3) - explicit Loot(Map* map, ObjectGuid owner, LootType type, LootMethod lootMethod); + explicit Loot(Map* map, ObjectGuid owner, LootType type, Group const* group); ~Loot(); + Loot(Loot const&) = delete; + Loot(Loot&&) = delete; + Loot& operator=(Loot const&) = delete; + Loot& operator=(Loot&&) = delete; + ObjectGuid const& GetGUID() const { return _guid; } ObjectGuid const& GetOwnerGUID() const { return _owner; } LootMethod GetLootMethod() const { return _lootMethod; } - - // if loot becomes invalid this reference is used to inform the listener - void addLootValidatorRef(LootValidatorRef* pLootValidatorRef) - { - i_LootValidatorRefManager.insertFirst(pLootValidatorRef); - } + ObjectGuid const& GetLootMasterGUID() const { return _lootMaster; } void clear(); bool empty() const { return items.empty() && gold == 0; } bool isLooted() const { return gold == 0 && unlootedCount == 0; } + void NotifyLootList(Map const* map) const; void NotifyItemRemoved(uint8 lootIndex, Map const* map); void NotifyQuestItemRemoved(uint8 questIndex, Map const* map); void NotifyMoneyRemoved(Map const* map); + void OnLootOpened(Map* map, ObjectGuid looter); void AddLooter(ObjectGuid GUID) { PlayersLooting.insert(GUID); } void RemoveLooter(ObjectGuid GUID) { PlayersLooting.erase(GUID); } @@ -271,6 +314,8 @@ struct TC_GAME_API Loot // Inserts the item into the loot (called by LootTemplate processors) void AddItem(LootStoreItem const& item, Player const* player); + bool AutoStore(Player* player, uint8 bag, uint8 slot, bool broadcast = false, bool createdByPlayer = false); + LootItem const* GetItemInSlot(uint32 lootSlot) const; LootItem* LootItemInSlot(uint32 lootslot, Player* player, NotNormalLootItem** qitem = nullptr, NotNormalLootItem** ffaitem = nullptr, NotNormalLootItem** conditem = nullptr); uint32 GetMaxSlotInLootFor(Player* player) const; @@ -281,26 +326,29 @@ struct TC_GAME_API Loot // Builds data for SMSG_LOOT_RESPONSE void BuildLootResponse(WorldPackets::Loot::LootResponse& packet, Player* viewer, PermissionTypes permission = ALL_PERMISSION) const; + void Update(); + private: - void FillNotNormalLootFor(Player const* player, bool presentAtLooting); + void FillNotNormalLootFor(Player const* player); NotNormalLootItemList* FillFFALoot(Player const* player); NotNormalLootItemList* FillQuestLoot(Player const* player); - NotNormalLootItemList* FillNonQuestNonFFAConditionalLoot(Player const* player, bool presentAtLooting); + NotNormalLootItemList* FillNonQuestNonFFAConditionalLoot(Player const* player); GuidSet PlayersLooting; NotNormalLootItemMap PlayerQuestItems; NotNormalLootItemMap PlayerFFAItems; NotNormalLootItemMap PlayerNonQuestNonFFAConditionalItems; - // All rolls are registered here. They need to know, when the loot is not valid anymore - LootValidatorRefManager i_LootValidatorRefManager; - // Loot GUID ObjectGuid _guid; ObjectGuid _owner; // The WorldObject that holds this loot ItemContext _itemContext; LootMethod _lootMethod; + std::unordered_map<uint32, LootRoll> _rolls; // used if an item is under rolling + ObjectGuid _lootMaster; + GuidUnorderedSet _allowedLooters; + bool _wasOpened; // true if at least one player received the loot content }; class TC_GAME_API AELootResult diff --git a/src/server/game/Loot/LootItemStorage.cpp b/src/server/game/Loot/LootItemStorage.cpp index 69b68acde70..605b2e6c2f9 100644 --- a/src/server/game/Loot/LootItemStorage.cpp +++ b/src/server/game/Loot/LootItemStorage.cpp @@ -33,7 +33,7 @@ namespace std::unordered_map<uint64, StoredLootContainer> _lootItemStore; } -StoredLootItem::StoredLootItem(LootItem const& lootItem) : ItemId(lootItem.itemid), Count(lootItem.count), ItemIndex(lootItem.itemIndex), FollowRules(lootItem.follow_loot_rules), +StoredLootItem::StoredLootItem(LootItem const& lootItem) : ItemId(lootItem.itemid), Count(lootItem.count), ItemIndex(lootItem.LootListId), FollowRules(lootItem.follow_loot_rules), FFA(lootItem.freeforall), Blocked(lootItem.is_blocked), Counted(lootItem.is_counted), UnderThreshold(lootItem.is_underthreshold), NeedsQuest(lootItem.needs_quest), RandomBonusListId(lootItem.randomBonusListId), Context(lootItem.context), BonusListIDs(lootItem.BonusListIDs) { @@ -81,7 +81,7 @@ void LootItemStorage::LoadStorageFromDB() LootItem lootItem; lootItem.itemid = fields[1].GetUInt32(); lootItem.count = fields[2].GetUInt32(); - lootItem.itemIndex = fields[3].GetUInt32(); + lootItem.LootListId = fields[3].GetUInt32(); lootItem.follow_loot_rules = fields[4].GetBool(); lootItem.freeforall = fields[5].GetBool(); lootItem.is_blocked = fields[6].GetBool(); @@ -161,7 +161,7 @@ bool LootItemStorage::LoadStoredLoot(Item* item, Player* player) LootItem li; li.itemid = storedItemPair.first; li.count = storedItemPair.second.Count; - li.itemIndex = storedItemPair.second.ItemIndex; + li.LootListId = storedItemPair.second.ItemIndex; li.follow_loot_rules = storedItemPair.second.FollowRules; li.freeforall = storedItemPair.second.FFA; li.is_blocked = storedItemPair.second.Blocked; @@ -303,7 +303,7 @@ void StoredLootContainer::AddLootItem(LootItem const& lootItem, CharacterDatabas stmt->setUInt64(0, _containerId); stmt->setUInt32(1, lootItem.itemid); stmt->setUInt32(2, lootItem.count); - stmt->setUInt32(3, lootItem.itemIndex); + stmt->setUInt32(3, lootItem.LootListId); stmt->setBool(4, lootItem.follow_loot_rules); stmt->setBool(5, lootItem.freeforall); stmt->setBool(6, lootItem.is_blocked); diff --git a/src/server/game/Mails/Mail.cpp b/src/server/game/Mails/Mail.cpp index dfacd99c768..fecbd220ce0 100644 --- a/src/server/game/Mails/Mail.cpp +++ b/src/server/game/Mails/Mail.cpp @@ -113,7 +113,7 @@ void MailDraft::prepareItems(Player* receiver, CharacterDatabaseTransaction tran if (m_mailTemplateId == 123) m_money = 1000000; - Loot mailLoot(nullptr, ObjectGuid::Empty, LOOT_NONE, FREE_FOR_ALL); + Loot mailLoot(nullptr, ObjectGuid::Empty, LOOT_NONE, nullptr); // can be empty mailLoot.FillLoot(m_mailTemplateId, LootTemplates_Mail, receiver, true, true, LOOT_MODE_DEFAULT, ItemContext::NONE); diff --git a/src/server/game/Server/Packets/LootPackets.cpp b/src/server/game/Server/Packets/LootPackets.cpp index 94f8cee783e..5c06b7bd41f 100644 --- a/src/server/game/Server/Packets/LootPackets.cpp +++ b/src/server/game/Server/Packets/LootPackets.cpp @@ -166,7 +166,7 @@ WorldPacket const* WorldPackets::Loot::StartLootRoll::Write() { _worldPacket << LootObj; _worldPacket << int32(MapID); - _worldPacket << uint32(RollTime); + _worldPacket << RollTime; _worldPacket << uint8(ValidRolls); _worldPacket << uint8(Method); _worldPacket << Item; diff --git a/src/server/game/Server/Packets/LootPackets.h b/src/server/game/Server/Packets/LootPackets.h index 1c8b4c20633..794e5ca3212 100644 --- a/src/server/game/Server/Packets/LootPackets.h +++ b/src/server/game/Server/Packets/LootPackets.h @@ -218,7 +218,7 @@ namespace WorldPackets ObjectGuid LootObj; int32 MapID = 0; - uint32 RollTime = 0; + Duration<Milliseconds, uint32> RollTime; uint8 Method = 0; uint8 ValidRolls = 0; LootItemData Item; @@ -283,7 +283,7 @@ namespace WorldPackets WorldPacket const* Write() override; - std::vector<ObjectGuid> Players; + GuidUnorderedSet Players; ObjectGuid LootObj; }; @@ -294,7 +294,7 @@ namespace WorldPackets WorldPacket const* Write() override; - uint32 Count; + uint32 Count = 0; }; class AELootTargetsAck final : public ServerPacket diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 91fddf7fc82..5c755d2c86d 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -640,7 +640,6 @@ void WorldSession::LogoutPlayer(bool save) if (Group* group = _player->GetGroup()) { group->SendUpdate(); - group->ResetMaxEnchantingLevel(); if (group->GetLeaderGUID() == _player->GetGUID()) group->StartLeaderOfflineTimer(); } diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index c75041d30d0..61a0e645188 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -49,6 +49,7 @@ #include "Item.h" #include "Language.h" #include "Log.h" +#include "Loot.h" #include "LootMgr.h" #include "Map.h" #include "MiscPackets.h" diff --git a/src/server/scripts/Commands/cs_group.cpp b/src/server/scripts/Commands/cs_group.cpp index 36db581a501..64d82dd47a8 100644 --- a/src/server/scripts/Commands/cs_group.cpp +++ b/src/server/scripts/Commands/cs_group.cpp @@ -22,6 +22,7 @@ #include "ChatCommand.h" #include "DatabaseEnv.h" #include "DB2Stores.h" +#include "Group.h" #include "GroupMgr.h" #include "Language.h" #include "LFG.h" |