aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Loot
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2022-09-16 16:58:03 +0200
committerShauren <shauren.trinity@gmail.com>2022-09-16 16:58:03 +0200
commit3ef5079feeedfdafc9d3c1d9f865e96dbc77ecc8 (patch)
treec88a3e2c1a8ae8459eb43fa63c66081c37393170 /src/server/game/Loot
parent9700b2a78680452d80025121a031da340af51348 (diff)
Core/Loot: Move loot rolls from Group to Loot
* Partial port of cmangos/mangos-wotlk@ffdf9a05d67a04c3c0304e9b021807fa5b867583
Diffstat (limited to 'src/server/game/Loot')
-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
3 files changed, 714 insertions, 57 deletions
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);