mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-15 23:20:36 +01:00
1266 lines
42 KiB
C++
1266 lines
42 KiB
C++
/*
|
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "Loot.h"
|
|
#include "DB2Stores.h"
|
|
#include "DatabaseEnv.h"
|
|
#include "GameTime.h"
|
|
#include "Group.h"
|
|
#include "Item.h"
|
|
#include "ItemBonusMgr.h"
|
|
#include "ItemTemplate.h"
|
|
#include "Log.h"
|
|
#include "LootMgr.h"
|
|
#include "LootPackets.h"
|
|
#include "Map.h"
|
|
#include "MapUtils.h"
|
|
#include "ObjectAccessor.h"
|
|
#include "ObjectMgr.h"
|
|
#include "Player.h"
|
|
#include "Random.h"
|
|
#include "World.h"
|
|
#include "WorldSession.h"
|
|
|
|
//
|
|
// --------- LootItem ---------
|
|
//
|
|
|
|
// Constructor, copies most fields from LootStoreItem and generates random count
|
|
LootItem::LootItem(LootStoreItem const& li) : itemid(li.itemid), conditions(li.conditions), needs_quest(li.needs_quest)
|
|
{
|
|
switch (li.type)
|
|
{
|
|
case LootStoreItem::Type::Item:
|
|
{
|
|
randomBonusListId = GenerateItemRandomBonusListId(itemid);
|
|
type = LootItemType::Item;
|
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemid);
|
|
freeforall = proto && proto->HasFlag(ITEM_FLAG_MULTI_DROP);
|
|
follow_loot_rules = !li.needs_quest || (proto && proto->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES));
|
|
break;
|
|
}
|
|
case LootStoreItem::Type::Currency:
|
|
type = LootItemType::Currency;
|
|
freeforall = true;
|
|
break;
|
|
case LootStoreItem::Type::TrackingQuest:
|
|
type = LootItemType::TrackingQuest;
|
|
freeforall = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
LootItem::LootItem(LootItem const&) = default;
|
|
LootItem::LootItem(LootItem&&) noexcept = default;
|
|
LootItem& LootItem::operator=(LootItem const&) = default;
|
|
LootItem& LootItem::operator=(LootItem&&) noexcept = default;
|
|
LootItem::~LootItem() = default;
|
|
|
|
// Basic checks for player/item compatibility - if false no chance to see the item in the loot
|
|
bool LootItem::AllowedForPlayer(Player const* player, Loot const* loot) const
|
|
{
|
|
switch (type)
|
|
{
|
|
case LootItemType::Item:
|
|
return ItemAllowedForPlayer(player, loot, itemid, needs_quest, follow_loot_rules, false, conditions);
|
|
case LootItemType::Currency:
|
|
return CurrencyAllowedForPlayer(player, itemid, needs_quest, conditions);
|
|
case LootItemType::TrackingQuest:
|
|
return TrackingQuestAllowedForPlayer(player, itemid, conditions);
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LootItem::AllowedForPlayer(Player const* player, LootStoreItem const& lootStoreItem, bool strictUsabilityCheck)
|
|
{
|
|
switch (lootStoreItem.type)
|
|
{
|
|
case LootStoreItem::Type::Item:
|
|
return ItemAllowedForPlayer(player, nullptr, lootStoreItem.itemid, lootStoreItem.needs_quest,
|
|
!lootStoreItem.needs_quest || ASSERT_NOTNULL(sObjectMgr->GetItemTemplate(lootStoreItem.itemid))->HasFlag(ITEM_FLAGS_CU_FOLLOW_LOOT_RULES),
|
|
strictUsabilityCheck, lootStoreItem.conditions);
|
|
case LootStoreItem::Type::Currency:
|
|
return CurrencyAllowedForPlayer(player, lootStoreItem.itemid, lootStoreItem.needs_quest, lootStoreItem.conditions);
|
|
case LootStoreItem::Type::TrackingQuest:
|
|
return TrackingQuestAllowedForPlayer(player, lootStoreItem.itemid, lootStoreItem.conditions);
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LootItem::ItemAllowedForPlayer(Player const* player, Loot const* loot, uint32 itemid, bool needs_quest, bool follow_loot_rules, bool strictUsabilityCheck,
|
|
ConditionsReference const& conditions)
|
|
{
|
|
// DB conditions check
|
|
if (!conditions.Meets(player))
|
|
return false;
|
|
|
|
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(itemid);
|
|
if (!pProto)
|
|
return false;
|
|
|
|
// not show loot for not own team
|
|
if (pProto->HasFlag(ITEM_FLAG2_FACTION_HORDE) && player->GetTeam() != HORDE)
|
|
return false;
|
|
|
|
if (pProto->HasFlag(ITEM_FLAG2_FACTION_ALLIANCE) && player->GetTeam() != ALLIANCE)
|
|
return false;
|
|
|
|
// Master looter can see all items even if the character can't loot them
|
|
if (loot && loot->GetLootMethod() == MASTER_LOOT && follow_loot_rules && loot->GetLootMasterGUID() == player->GetGUID())
|
|
return true;
|
|
|
|
// Don't allow loot for players without profession or those who already know the recipe
|
|
if (pProto->HasFlag(ITEM_FLAG_HIDE_UNUSABLE_RECIPE))
|
|
{
|
|
if (!player->HasSkill(pProto->GetRequiredSkill()))
|
|
return false;
|
|
|
|
for (ItemEffectEntry const* itemEffect : pProto->Effects)
|
|
{
|
|
if (itemEffect->TriggerType != ITEM_SPELLTRIGGER_ON_LEARN)
|
|
continue;
|
|
|
|
if (player->HasSpell(itemEffect->SpellID))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// check quest requirements
|
|
if (!pProto->HasFlag(ITEM_FLAGS_CU_IGNORE_QUEST_STATUS) && ((needs_quest || (pProto->GetStartQuest() && player->GetQuestStatus(pProto->GetStartQuest()) != QUEST_STATUS_NONE)) && !player->HasQuestForItem(itemid)))
|
|
return false;
|
|
|
|
if (strictUsabilityCheck)
|
|
{
|
|
if ((pProto->IsWeapon() || pProto->IsArmor()) && !pProto->IsUsableByLootSpecialization(player, true))
|
|
return false;
|
|
|
|
if (player->CanRollNeedForItem(pProto, nullptr, false) != EQUIP_ERR_OK)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LootItem::CurrencyAllowedForPlayer(Player const* player, uint32 currencyId, bool needs_quest, ConditionsReference const& conditions)
|
|
{
|
|
// DB conditions check
|
|
if (!conditions.Meets(player))
|
|
return false;
|
|
|
|
CurrencyTypesEntry const* currency = sCurrencyTypesStore.LookupEntry(currencyId);
|
|
if (!currency)
|
|
return false;
|
|
|
|
// not show loot for not own team
|
|
if (currency->GetFlags().HasFlag(CurrencyTypesFlags::IsHordeOnly) && player->GetTeam() != HORDE)
|
|
return false;
|
|
|
|
if (currency->GetFlags().HasFlag(CurrencyTypesFlags::IsAllianceOnly) && player->GetTeam() != ALLIANCE)
|
|
return false;
|
|
|
|
// check quest requirements
|
|
if (needs_quest && !player->HasQuestForCurrency(currencyId))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LootItem::TrackingQuestAllowedForPlayer(Player const* player, uint32 questId, ConditionsReference const& conditions)
|
|
{
|
|
// DB conditions check
|
|
if (!conditions.Meets(player))
|
|
return false;
|
|
|
|
if (player->IsQuestCompletedBitSet(questId))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void LootItem::AddAllowedLooter(Player const* player)
|
|
{
|
|
allowedGUIDs.insert(player->GetGUID());
|
|
}
|
|
|
|
bool LootItem::HasAllowedLooter(ObjectGuid const& looter) const
|
|
{
|
|
return allowedGUIDs.contains(looter);
|
|
}
|
|
|
|
Optional<LootSlotType> LootItem::GetUiTypeForPlayer(Player const* player, Loot const& loot) const
|
|
{
|
|
if (is_looted)
|
|
return {};
|
|
|
|
if (!allowedGUIDs.contains(player->GetGUID()))
|
|
return {};
|
|
|
|
if (freeforall)
|
|
{
|
|
if (NotNormalLootItemList const* ffaItems = Trinity::Containers::MapGetValuePtr(loot.GetPlayerFFAItems(), player->GetGUID()))
|
|
{
|
|
auto ffaItemItr = std::ranges::find(*ffaItems, LootListId, &NotNormalLootItem::LootListId);
|
|
if (ffaItemItr != ffaItems->end() && !ffaItemItr->is_looted)
|
|
return loot.GetLootMethod() == FREE_FOR_ALL ? LOOT_SLOT_TYPE_OWNER : LOOT_SLOT_TYPE_ALLOW_LOOT;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
if (needs_quest && !follow_loot_rules)
|
|
return loot.GetLootMethod() == FREE_FOR_ALL ? LOOT_SLOT_TYPE_OWNER : LOOT_SLOT_TYPE_ALLOW_LOOT;
|
|
|
|
switch (loot.GetLootMethod())
|
|
{
|
|
case FREE_FOR_ALL:
|
|
return LOOT_SLOT_TYPE_OWNER;
|
|
case ROUND_ROBIN:
|
|
if (!loot.roundRobinPlayer.IsEmpty() && loot.roundRobinPlayer != player->GetGUID())
|
|
return {};
|
|
|
|
return LOOT_SLOT_TYPE_ALLOW_LOOT;
|
|
case MASTER_LOOT:
|
|
if (is_underthreshold)
|
|
{
|
|
if (!loot.roundRobinPlayer.IsEmpty() && loot.roundRobinPlayer != player->GetGUID())
|
|
return {};
|
|
|
|
return LOOT_SLOT_TYPE_ALLOW_LOOT;
|
|
}
|
|
|
|
return loot.GetLootMasterGUID() == player->GetGUID() ? LOOT_SLOT_TYPE_MASTER : LOOT_SLOT_TYPE_LOCKED;
|
|
case GROUP_LOOT:
|
|
case NEED_BEFORE_GREED:
|
|
if (is_underthreshold)
|
|
if (!loot.roundRobinPlayer.IsEmpty() && loot.roundRobinPlayer != player->GetGUID())
|
|
return {};
|
|
|
|
if (is_blocked)
|
|
return LOOT_SLOT_TYPE_ROLL_ONGOING;
|
|
|
|
if (rollWinnerGUID.IsEmpty()) // all passed
|
|
return LOOT_SLOT_TYPE_ALLOW_LOOT;
|
|
|
|
if (rollWinnerGUID == player->GetGUID())
|
|
return LOOT_SLOT_TYPE_OWNER;
|
|
|
|
return {};
|
|
case PERSONAL_LOOT:
|
|
return LOOT_SLOT_TYPE_OWNER;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
//
|
|
// ------- 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->CanRollNeedForItem(itemTemplate, m_map, true) != EQUIP_ERR_OK)
|
|
startLootRoll.ValidRolls &= ~ROLL_FLAG_TYPE_NEED;
|
|
|
|
FillPacket(startLootRoll.Item);
|
|
startLootRoll.Item.UIType = LOOT_SLOT_TYPE_ROLL_ONGOING;
|
|
startLootRoll.DungeonEncounterID = m_loot->GetDungeonEncounterId();
|
|
|
|
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.DungeonEncounterID = m_loot->GetDungeonEncounterId();
|
|
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.DungeonEncounterID = m_loot->GetDungeonEncounterId();
|
|
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.DungeonEncounterID = m_loot->GetDungeonEncounterId();
|
|
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_lootItem->LootListId;
|
|
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_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->HasAllowedLooter(plr->GetGUID())) // 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 (Optional<uint16> disenchantSkillRequired = GetItemDisenchantSkillRequired(); !disenchantSkillRequired || disenchantSkillRequired > 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_lootItem->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;
|
|
}
|
|
|
|
Optional<uint32> LootRoll::GetItemDisenchantLootId() const
|
|
{
|
|
WorldPackets::Item::ItemInstance itemInstance;
|
|
itemInstance.Initialize(*m_lootItem);
|
|
|
|
BonusData bonusData;
|
|
bonusData.Initialize(itemInstance);
|
|
if (!bonusData.CanDisenchant)
|
|
return {};
|
|
|
|
if (bonusData.DisenchantLootId)
|
|
return bonusData.DisenchantLootId;
|
|
|
|
ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(m_lootItem->itemid);
|
|
|
|
// ignore temporary item level scaling (pvp or timewalking)
|
|
uint32 itemLevel = Item::GetItemLevel(itemTemplate, bonusData, bonusData.RequiredLevel, 0, 0, 0, 0, false, 0);
|
|
|
|
ItemDisenchantLootEntry const* disenchantLoot = Item::GetBaseDisenchantLoot(itemTemplate, bonusData.Quality, itemLevel);
|
|
if (!disenchantLoot)
|
|
return {};
|
|
|
|
return disenchantLoot->ID;
|
|
}
|
|
|
|
Optional<uint16> LootRoll::GetItemDisenchantSkillRequired() const
|
|
{
|
|
WorldPackets::Item::ItemInstance itemInstance;
|
|
itemInstance.Initialize(*m_lootItem);
|
|
|
|
BonusData bonusData;
|
|
bonusData.Initialize(itemInstance);
|
|
if (!bonusData.CanDisenchant)
|
|
return {};
|
|
|
|
ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(m_lootItem->itemid);
|
|
|
|
// ignore temporary item level scaling (pvp or timewalking)
|
|
uint32 itemLevel = Item::GetItemLevel(itemTemplate, bonusData, bonusData.RequiredLevel, 0, 0, 0, 0, false, 0);
|
|
|
|
ItemDisenchantLootEntry const* disenchantLoot = Item::GetBaseDisenchantLoot(itemTemplate, bonusData.Quality, itemLevel);
|
|
if (!disenchantLoot)
|
|
return {};
|
|
|
|
return disenchantLoot->SkillRequired;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
Loot loot(m_map, m_loot->GetOwnerGUID(), LOOT_DISENCHANTING, nullptr);
|
|
loot.FillLoot(*GetItemDisenchantLootId(), LootTemplates_Disenchant, player, true, false, LOOT_MODE_DEFAULT, ItemContext::NONE);
|
|
if (!loot.AutoStore(player, NULL_BAG, NULL_SLOT, true))
|
|
{
|
|
for (uint32 i = 0; i < loot.items.size(); ++i)
|
|
if (LootItem* disenchantLoot = loot.LootItemInSlot(i, player))
|
|
if (disenchantLoot->type == LootItemType::Item)
|
|
player->SendItemRetrievalMail(disenchantLoot->itemid, disenchantLoot->count, disenchantLoot->context);
|
|
}
|
|
else
|
|
m_loot->NotifyItemRemoved(m_lootItem->LootListId, m_map);
|
|
}
|
|
else
|
|
player->StoreLootItem(m_loot->GetOwnerGUID(), m_lootItem->LootListId, m_loot);
|
|
}
|
|
}
|
|
m_isStarted = false;
|
|
}
|
|
|
|
//
|
|
// --------- Loot ---------
|
|
//
|
|
|
|
Loot::Loot(Map* map, ObjectGuid owner, LootType type, Group const* group) : gold(0), unlootedCount(0), loot_type(type),
|
|
_guid(map ? ObjectGuid::Create<HighGuid::LootObject>(map->GetId(), 0, map->GenerateLowGuid<HighGuid::LootObject>()) : ObjectGuid::Empty),
|
|
_owner(owner), _itemContext(ItemContext::NONE), _lootMethod(group ? group->GetLootMethod() : FREE_FOR_ALL),
|
|
_lootMaster(group ? group->GetMasterLooterGuid() : ObjectGuid::Empty), _wasOpened(false), _changed(false), _dungeonEncounterId(0)
|
|
{
|
|
}
|
|
|
|
Loot::~Loot()
|
|
{
|
|
GuidSet activeLooters = std::move(PlayersLooting);
|
|
for (ObjectGuid playerGuid : activeLooters)
|
|
if (Player* player = ObjectAccessor::FindConnectedPlayer(playerGuid))
|
|
player->GetSession()->DoLootRelease(this);
|
|
}
|
|
|
|
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 lootListId, Map const* map)
|
|
{
|
|
// notify all players that are looting this that the item was removed
|
|
// convert the index to the slot the player sees
|
|
for (auto itr = PlayersLooting.begin(); itr != PlayersLooting.end();)
|
|
{
|
|
LootItem const& item = items[lootListId];
|
|
if (item.GetAllowedLooters().find(*itr) == item.GetAllowedLooters().end())
|
|
{
|
|
++itr;
|
|
continue;
|
|
}
|
|
|
|
if (Player* player = ObjectAccessor::GetPlayer(map, *itr))
|
|
{
|
|
player->SendNotifyLootItemRemoved(GetGUID(), GetOwnerGUID(), lootListId);
|
|
++itr;
|
|
}
|
|
else
|
|
itr = PlayersLooting.erase(itr);
|
|
}
|
|
}
|
|
|
|
void Loot::NotifyMoneyRemoved(Map const* map)
|
|
{
|
|
// notify all players that are looting this that the money was removed
|
|
for (auto itr = PlayersLooting.begin(); itr != PlayersLooting.end();)
|
|
{
|
|
if (Player* player = ObjectAccessor::GetPlayer(map, *itr))
|
|
{
|
|
player->SendNotifyLootMoneyRemoved(GetGUID());
|
|
++itr;
|
|
}
|
|
else
|
|
itr = PlayersLooting.erase(itr);
|
|
}
|
|
}
|
|
|
|
void Loot::OnLootOpened(Map* map, Player* looter)
|
|
{
|
|
AddLooter(looter->GetGUID());
|
|
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));
|
|
|
|
for (uint32 lootListId = 0; 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);
|
|
}
|
|
|
|
if (!_rolls.empty())
|
|
_changed = true;
|
|
}
|
|
else if (_lootMethod == MASTER_LOOT)
|
|
{
|
|
if (looter->GetGUID() == _lootMaster)
|
|
{
|
|
WorldPackets::Loot::MasterLootCandidateList masterLootCandidateList;
|
|
masterLootCandidateList.LootObj = GetGUID();
|
|
masterLootCandidateList.Players = _allowedLooters;
|
|
looter->SendDirectMessage(masterLootCandidateList.Write());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Flag tracking quests as completed after all items were scanned for this player (some might depend on this quest not being completed)
|
|
//if (!_mailUnlootedItems)
|
|
if (std::vector<NotNormalLootItem>* ffaItems = Trinity::Containers::MapGetValuePtr(PlayerFFAItems, looter->GetGUID()))
|
|
AutoStoreTrackingQuests(looter, *ffaItems);
|
|
}
|
|
|
|
bool Loot::HasAllowedLooter(ObjectGuid const& looter) const
|
|
{
|
|
return _allowedLooters.find(looter) != _allowedLooters.end();
|
|
}
|
|
|
|
void Loot::generateMoneyLoot(uint32 minAmount, uint32 maxAmount)
|
|
{
|
|
if (maxAmount > 0)
|
|
{
|
|
if (maxAmount <= minAmount)
|
|
gold = uint32(maxAmount * sWorld->getRate(RATE_DROP_MONEY));
|
|
else if ((maxAmount - minAmount) < 32700)
|
|
gold = uint32(urand(minAmount, maxAmount) * sWorld->getRate(RATE_DROP_MONEY));
|
|
else
|
|
gold = uint32(urand(minAmount >> 8, maxAmount >> 8) * sWorld->getRate(RATE_DROP_MONEY)) << 8;
|
|
}
|
|
}
|
|
|
|
// Calls processor of corresponding LootTemplate (which handles everything including references)
|
|
bool Loot::FillLoot(uint32 lootId, LootStore const& store, Player* lootOwner, bool personal, bool noEmptyError, uint16 lootMode /*= LOOT_MODE_DEFAULT*/, ItemContext context /*= ItemContext::NONE*/)
|
|
{
|
|
// Must be provided
|
|
if (!lootOwner)
|
|
return false;
|
|
|
|
LootTemplate const* tab = store.GetLootFor(lootId);
|
|
|
|
if (!tab)
|
|
{
|
|
if (!noEmptyError)
|
|
TC_LOG_ERROR("sql.sql", "Table '{}' loot id #{} used but it doesn't have records.", store.GetName(), lootId);
|
|
return false;
|
|
}
|
|
|
|
_itemContext = context;
|
|
|
|
items.reserve(MAX_NR_LOOT_ITEMS);
|
|
|
|
tab->Process(*this, store.IsRatesAllowed(), lootMode, 0); // Processing is done there, callback via Loot::AddItem()
|
|
|
|
// Setting access rights for group loot case
|
|
Group const* group = lootOwner->GetGroup();
|
|
if (!personal && group)
|
|
{
|
|
if (loot_type == LOOT_CORPSE)
|
|
roundRobinPlayer = lootOwner->GetGUID();
|
|
|
|
for (GroupReference const& itr : group->GetMembers())
|
|
{
|
|
Player* member = itr.GetSource(); // should actually be looted object instead of lootOwner but looter has to be really close so doesnt really matter
|
|
if (member->IsAtGroupRewardDistance(lootOwner))
|
|
FillNotNormalLootFor(member);
|
|
}
|
|
|
|
for (LootItem& item : items)
|
|
{
|
|
if (!item.follow_loot_rules || item.freeforall || item.type != LootItemType::Item)
|
|
continue;
|
|
|
|
if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item.itemid))
|
|
{
|
|
if (proto->GetQuality() < uint32(group->GetLootThreshold()))
|
|
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 personal loot
|
|
else
|
|
FillNotNormalLootFor(lootOwner);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Inserts the item into the loot (called by LootTemplate processors)
|
|
void Loot::AddItem(LootStoreItem const& item)
|
|
{
|
|
switch (item.type)
|
|
{
|
|
case LootStoreItem::Type::Item:
|
|
{
|
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item.itemid);
|
|
if (!proto)
|
|
return;
|
|
|
|
uint32 count = urand(item.mincount, item.maxcount);
|
|
uint32 stacks = count / proto->GetMaxStackSize() + ((count % proto->GetMaxStackSize()) ? 1 : 0);
|
|
|
|
for (uint32 i = 0; i < stacks && items.size() < MAX_NR_LOOT_ITEMS; ++i)
|
|
{
|
|
LootItem generatedLoot(item);
|
|
generatedLoot.context = _itemContext;
|
|
generatedLoot.count = std::min(count, proto->GetMaxStackSize());
|
|
generatedLoot.LootListId = items.size();
|
|
generatedLoot.BonusListIDs = ItemBonusMgr::GetBonusListsForItem(generatedLoot.itemid, _itemContext);
|
|
|
|
items.push_back(generatedLoot);
|
|
count -= proto->GetMaxStackSize();
|
|
}
|
|
break;
|
|
}
|
|
case LootStoreItem::Type::Currency:
|
|
{
|
|
LootItem generatedLoot(item);
|
|
generatedLoot.count = urand(item.mincount, item.maxcount);
|
|
generatedLoot.LootListId = items.size();
|
|
items.push_back(generatedLoot);
|
|
break;
|
|
}
|
|
case LootStoreItem::Type::TrackingQuest:
|
|
{
|
|
LootItem generatedLoot(item);
|
|
generatedLoot.count = 1;
|
|
generatedLoot.LootListId = items.size();
|
|
items.push_back(generatedLoot);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool Loot::AutoStore(Player* player, uint8 bag, uint8 slot, bool broadcast, bool createdByPlayer)
|
|
{
|
|
bool allLooted = true;
|
|
for (uint32 i = 0; i < items.size(); ++i)
|
|
{
|
|
NotNormalLootItem* ffaitem = nullptr;
|
|
|
|
LootItem* lootItem = LootItemInSlot(i, player, &ffaitem);
|
|
if (!lootItem || lootItem->is_looted)
|
|
continue;
|
|
|
|
if (!lootItem->HasAllowedLooter(player->GetGUID()))
|
|
continue;
|
|
|
|
if (lootItem->is_blocked)
|
|
continue;
|
|
|
|
// dont allow protected item to be looted by someone else
|
|
if (!lootItem->rollWinnerGUID.IsEmpty() && lootItem->rollWinnerGUID != GetGUID())
|
|
continue;
|
|
|
|
switch (lootItem->type)
|
|
{
|
|
case LootItemType::Item:
|
|
{
|
|
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 (Item* pItem = player->StoreNewItem(dest, lootItem->itemid, true, lootItem->randomBonusListId, GuidSet(), lootItem->context, &lootItem->BonusListIDs))
|
|
{
|
|
player->SendNewItem(pItem, lootItem->count, false, createdByPlayer, broadcast, GetDungeonEncounterId());
|
|
player->ApplyItemLootedSpell(pItem, true);
|
|
}
|
|
else
|
|
player->ApplyItemLootedSpell(sObjectMgr->GetItemTemplate(lootItem->itemid));
|
|
|
|
break;
|
|
}
|
|
case LootItemType::Currency:
|
|
player->ModifyCurrency(lootItem->itemid, lootItem->count, CurrencyGainSource::Loot);
|
|
break;
|
|
case LootItemType::TrackingQuest:
|
|
|
|
if (Quest const* quest = sObjectMgr->GetQuestTemplate(lootItem->itemid))
|
|
player->RewardQuest(quest, LootItemType::Item, 0, player, false);
|
|
break;
|
|
}
|
|
|
|
if (ffaitem)
|
|
ffaitem->is_looted = true;
|
|
|
|
if (!lootItem->freeforall)
|
|
lootItem->is_looted = true;
|
|
|
|
--unlootedCount;
|
|
}
|
|
|
|
return allLooted;
|
|
}
|
|
|
|
void Loot::AutoStoreTrackingQuests(Player* player, NotNormalLootItemList& ffaItems)
|
|
{
|
|
for (NotNormalLootItem& ffaItem : ffaItems)
|
|
{
|
|
if (items[ffaItem.LootListId].type != LootItemType::TrackingQuest)
|
|
continue;
|
|
|
|
--unlootedCount;
|
|
ffaItem.is_looted = true;
|
|
if (Quest const* quest = sObjectMgr->GetQuestTemplate(items[ffaItem.LootListId].itemid))
|
|
player->RewardQuest(quest, LootItemType::Item, 0, player, false);
|
|
}
|
|
}
|
|
|
|
void Loot::LootMoney()
|
|
{
|
|
gold = 0;
|
|
_changed = true;
|
|
}
|
|
|
|
LootItem const* Loot::GetItemInSlot(uint32 lootListId) const
|
|
{
|
|
if (lootListId < items.size())
|
|
return &items[lootListId];
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
LootItem* Loot::LootItemInSlot(uint32 lootListId, Player const* player, NotNormalLootItem** ffaItem)
|
|
{
|
|
if (lootListId >= items.size())
|
|
return nullptr;
|
|
|
|
LootItem* item = &items[lootListId];
|
|
bool is_looted = item->is_looted;
|
|
|
|
if (item->freeforall)
|
|
{
|
|
auto itr = PlayerFFAItems.find(player->GetGUID());
|
|
if (itr != PlayerFFAItems.end())
|
|
{
|
|
for (NotNormalLootItem& notNormalLootItem : *itr->second)
|
|
{
|
|
if (notNormalLootItem.LootListId == lootListId)
|
|
{
|
|
is_looted = notNormalLootItem.is_looted;
|
|
if (ffaItem)
|
|
*ffaItem = ¬NormalLootItem;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_looted)
|
|
return nullptr;
|
|
|
|
_changed = true;
|
|
return item;
|
|
}
|
|
|
|
// return true if there is any item that is lootable for any player (not quest item, FFA or conditional)
|
|
bool Loot::hasItemForAll() const
|
|
{
|
|
// Gold is always lootable
|
|
if (gold)
|
|
return true;
|
|
|
|
for (LootItem const& item : items)
|
|
if (!item.is_looted && item.follow_loot_rules && !item.freeforall && item.conditions.IsEmpty())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// return true if there is any FFA, quest or conditional item for the player.
|
|
bool Loot::hasItemFor(Player const* player) const
|
|
{
|
|
// quest items
|
|
for (LootItem const& lootItem : items)
|
|
if (!lootItem.is_looted && !lootItem.follow_loot_rules && lootItem.GetAllowedLooters().contains(player->GetGUID()))
|
|
return true;
|
|
|
|
if (NotNormalLootItemList const* ffaItems = Trinity::Containers::MapGetValuePtr(GetPlayerFFAItems(), player->GetGUID()))
|
|
if (std::ranges::any_of(*ffaItems, &NotNormalLootItem::is_looted))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// return true if there is any item over the group threshold (i.e. not underthreshold).
|
|
bool Loot::hasOverThresholdItem() const
|
|
{
|
|
for (uint8 i = 0; i < items.size(); ++i)
|
|
{
|
|
if (!items[i].is_looted && !items[i].is_underthreshold && !items[i].freeforall)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Loot::BuildLootResponse(WorldPackets::Loot::LootResponse& packet, Player const* viewer) const
|
|
{
|
|
packet.Coins = gold;
|
|
|
|
for (LootItem const& item : items)
|
|
{
|
|
Optional<LootSlotType> uiType = item.GetUiTypeForPlayer(viewer, *this);
|
|
if (!uiType)
|
|
continue;
|
|
|
|
switch (item.type)
|
|
{
|
|
case LootItemType::Item:
|
|
{
|
|
WorldPackets::Loot::LootItemData& lootItem = packet.Items.emplace_back();
|
|
lootItem.LootListID = item.LootListId;
|
|
lootItem.Type = item.type;
|
|
lootItem.UIType = *uiType;
|
|
lootItem.Quantity = item.count;
|
|
lootItem.Loot.Initialize(item);
|
|
break;
|
|
}
|
|
case LootItemType::Currency:
|
|
{
|
|
WorldPackets::Loot::LootCurrency& lootCurrency = packet.Currencies.emplace_back();
|
|
lootCurrency.CurrencyID = item.itemid;
|
|
lootCurrency.Quantity = item.count;
|
|
lootCurrency.LootListID = item.LootListId;
|
|
lootCurrency.UIType = *uiType;
|
|
|
|
// fake visible quantity for SPELL_AURA_MOD_CURRENCY_CATEGORY_GAIN_PCT - handled in Player::ModifyCurrency
|
|
lootCurrency.Quantity = float(lootCurrency.Quantity) * viewer->GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_CURRENCY_CATEGORY_GAIN_PCT, sCurrencyTypesStore.AssertEntry(item.itemid)->CategoryID);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
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* player)
|
|
{
|
|
ObjectGuid plguid = player->GetGUID();
|
|
_allowedLooters.insert(plguid);
|
|
|
|
std::unique_ptr<NotNormalLootItemList> ffaItems = std::make_unique<NotNormalLootItemList>();
|
|
|
|
for (LootItem& item : items)
|
|
{
|
|
if (!item.AllowedForPlayer(player, this))
|
|
continue;
|
|
|
|
item.AddAllowedLooter(player);
|
|
|
|
if (item.freeforall)
|
|
{
|
|
ffaItems->emplace_back(item.LootListId);
|
|
++unlootedCount;
|
|
}
|
|
else if (!item.is_counted)
|
|
{
|
|
item.is_counted = true;
|
|
++unlootedCount;
|
|
}
|
|
}
|
|
|
|
if (!ffaItems->empty())
|
|
{
|
|
// TODO: flag immediately for loot that is supposed to be mailed if unlooted, otherwise flag when sending SMSG_LOOT_RESPONSE
|
|
//if (_mailUnlootedItems)
|
|
// AutoStoreTrackingQuests(player, *ffaItems);
|
|
|
|
PlayerFFAItems[player->GetGUID()] = std::move(ffaItems);
|
|
}
|
|
}
|
|
|
|
//
|
|
// --------- AELootResult ---------
|
|
//
|
|
|
|
void AELootResult::Add(Item* item, uint8 count, LootType lootType, uint32 dungeonEncounterId)
|
|
{
|
|
auto itr = _byItem.find(item);
|
|
if (itr != _byItem.end())
|
|
_byOrder[itr->second].count += count;
|
|
else
|
|
{
|
|
_byItem[item] = _byOrder.size();
|
|
ResultValue value;
|
|
value.item = item;
|
|
value.count = count;
|
|
value.lootType = lootType;
|
|
value.dungeonEncounterId = dungeonEncounterId;
|
|
_byOrder.push_back(value);
|
|
}
|
|
}
|
|
|
|
AELootResult::OrderedStorage::const_iterator AELootResult::begin() const
|
|
{
|
|
return _byOrder.begin();
|
|
}
|
|
|
|
AELootResult::OrderedStorage::const_iterator AELootResult::end() const
|
|
{
|
|
return _byOrder.end();
|
|
}
|