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