aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/server/game/AI/CreatureAISelector.h2
-rw-r--r--src/server/game/AI/SmartScripts/SmartAI.cpp1
-rw-r--r--src/server/game/Entities/Corpse/Corpse.cpp8
-rw-r--r--src/server/game/Entities/Corpse/Corpse.h2
-rw-r--r--src/server/game/Entities/Creature/Creature.cpp26
-rw-r--r--src/server/game/Entities/Creature/Creature.h3
-rw-r--r--src/server/game/Entities/GameObject/GameObject.cpp17
-rw-r--r--src/server/game/Entities/GameObject/GameObject.h2
-rw-r--r--src/server/game/Entities/Player/Player.cpp110
-rw-r--r--src/server/game/Entities/Player/Player.h7
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp28
-rw-r--r--src/server/game/Groups/Group.cpp694
-rw-r--r--src/server/game/Groups/Group.h68
-rw-r--r--src/server/game/Groups/GroupMgr.cpp1
-rw-r--r--src/server/game/Groups/GroupMgr.h5
-rw-r--r--src/server/game/Handlers/CharacterHandler.cpp1
-rw-r--r--src/server/game/Handlers/GroupHandler.cpp21
-rw-r--r--src/server/game/Handlers/LootHandler.cpp14
-rw-r--r--src/server/game/Loot/Loot.cpp653
-rw-r--r--src/server/game/Loot/Loot.h110
-rw-r--r--src/server/game/Loot/LootItemStorage.cpp8
-rw-r--r--src/server/game/Mails/Mail.cpp2
-rw-r--r--src/server/game/Server/Packets/LootPackets.cpp2
-rw-r--r--src/server/game/Server/Packets/LootPackets.h6
-rw-r--r--src/server/game/Server/WorldSession.cpp1
-rw-r--r--src/server/game/Spells/SpellEffects.cpp1
-rw-r--r--src/server/scripts/Commands/cs_group.cpp1
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"