diff options
author | Shauren <shauren.trinity@gmail.com> | 2022-10-17 23:11:46 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2022-10-17 23:11:46 +0200 |
commit | 133334a902b705dae6f7e92bb1009b84cf1c51d2 (patch) | |
tree | 5a48c30a00441d36c285b78da396b5549eabbb87 | |
parent | af76b41ace2917ece0aa3f97e4f46e095a7c815f (diff) |
Core/Loot: Implemented personal loot and tag sharing for non-boss loot
36 files changed, 321 insertions, 350 deletions
diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index 4d5be9811b9..d5d9cf7406e 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -311,7 +311,7 @@ bool CreatureAI::_EnterEvadeMode(EvadeReason /*why*/) me->RemoveAurasOnEvade(); me->CombatStop(true); - me->SetLootRecipient(nullptr); + me->SetTappedBy(nullptr); me->ResetPlayerDamageReq(); me->SetLastDamagedTime(0); me->SetCannotReachTarget(false); diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp index 95c23c38904..c3da81c6d30 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp @@ -250,7 +250,7 @@ void ScriptedAI::ForceCombatStop(Creature* who, bool reset /*= true*/) if (reset) { who->LoadCreaturesAddon(); - who->SetLootRecipient(nullptr); + who->SetTappedBy(nullptr); who->ResetPlayerDamageReq(); who->SetLastDamagedTime(0); who->SetCannotReachTarget(false); diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp index c219d459cb1..0db25b9498e 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp @@ -94,7 +94,7 @@ void EscortAI::EnterEvadeMode(EvadeReason /*why*/) { me->RemoveAllAuras(); me->CombatStop(true); - me->SetLootRecipient(nullptr); + me->SetTappedBy(nullptr); EngagementOver(); diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index 014475d0d4e..c045eda5288 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -933,11 +933,14 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (!me) break; - if (Player* player = me->GetLootRecipient()) + for (ObjectGuid tapperGuid : me->GetTapList()) { - player->RewardPlayerAndGroupAtEvent(e.action.killedMonster.creature, player); - TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_CALL_KILLEDMONSTER: Player %s, Killcredit: %u", - player->GetGUID().ToString().c_str(), e.action.killedMonster.creature); + if (Player* tapper = ObjectAccessor::GetPlayer(*me, tapperGuid)) + { + tapper->KilledMonsterCredit(e.action.killedMonster.creature, me->GetGUID()); + TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_CALL_KILLEDMONSTER: Player %s, Killcredit: %u", + tapper->GetGUID().ToString().c_str(), e.action.killedMonster.creature); + } } } else // Specific target type @@ -2947,20 +2950,10 @@ void SmartScript::GetTargets(ObjectVector& targets, SmartScriptHolder const& e, case SMART_TARGET_LOOT_RECIPIENTS: { if (me) - { - if (Group* lootGroup = me->GetLootRecipientGroup()) - { - for (GroupReference* it = lootGroup->GetFirstMember(); it != nullptr; it = it->next()) - if (Player* recipient = it->GetSource()) - if (recipient->IsInMap(me)) - targets.push_back(recipient); - } - else - { - if (Player* recipient = me->GetLootRecipient()) - targets.push_back(recipient); - } - } + for (ObjectGuid tapperGuid : me->GetTapList()) + if (Player* tapper = ObjectAccessor::GetPlayer(*me, tapperGuid)) + targets.push_back(tapper); + break; } case SMART_TARGET_VEHICLE_PASSENGER: diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp index 1e84e026689..93db259b128 100644 --- a/src/server/game/Battlegrounds/Battleground.cpp +++ b/src/server/game/Battlegrounds/Battleground.cpp @@ -31,6 +31,7 @@ #include "Group.h" #include "Guild.h" #include "GuildMgr.h" +#include "KillRewarder.h" #include "Language.h" #include "Log.h" #include "Map.h" @@ -1944,7 +1945,10 @@ void Battleground::SetBracket(PVPDifficultyEntry const* bracketEntry) void Battleground::RewardXPAtKill(Player* killer, Player* victim) { if (sWorld->getBoolConfig(CONFIG_BG_XP_FOR_KILL) && killer && victim) - killer->RewardPlayerAndGroupAtKill(victim, true); + { + Player* killers[] = { killer }; + KillRewarder(Trinity::IteratorPair(std::begin(killers), std::end(killers)), victim, true).Reward(); + } } uint32 Battleground::GetTeamScore(uint32 teamId) const diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 34302dcbce9..a30c7d1129f 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -30,7 +30,6 @@ #include "GameTime.h" #include "GridNotifiersImpl.h" #include "Group.h" -#include "GroupMgr.h" #include "ItemTemplate.h" #include "Log.h" #include "Loot.h" @@ -778,6 +777,9 @@ void Creature::Update(uint32 diff) if (m_loot) m_loot->Update(); + for (auto&& [playerOwner, loot] : m_personalLoot) + loot->Update(); + if (m_corpseRemoveTime <= GameTime::GetGameTime()) { RemoveCorpse(false); @@ -1299,23 +1301,7 @@ bool Creature::CanResetTalents(Player* player) const && player->GetClass() == GetCreatureTemplate()->trainer_class; } -Player* Creature::GetLootRecipient() const -{ - if (!m_lootRecipient) - return nullptr; - - return ObjectAccessor::FindConnectedPlayer(m_lootRecipient); -} - -Group* Creature::GetLootRecipientGroup() const -{ - if (m_lootRecipientGroup.IsEmpty()) - return nullptr; - - return sGroupMgr->GetGroupByGUID(m_lootRecipientGroup); -} - -void Creature::SetLootRecipient(Unit* unit, bool withGroup) +void Creature::SetTappedBy(Unit const* unit, bool withGroup) { // set the player whose group should receive the right // to loot the creature after it dies @@ -1323,12 +1309,14 @@ void Creature::SetLootRecipient(Unit* unit, bool withGroup) if (!unit) { - m_lootRecipient.Clear(); - m_lootRecipientGroup.Clear(); + m_tapList.clear(); RemoveDynamicFlag(UNIT_DYNFLAG_LOOTABLE | UNIT_DYNFLAG_TAPPED); return; } + if (m_tapList.size() >= CREATURE_TAPPERS_SOFT_CAP) + return; + if (unit->GetTypeId() != TYPEID_PLAYER && !unit->IsVehicle()) return; @@ -1336,31 +1324,54 @@ void Creature::SetLootRecipient(Unit* unit, bool withGroup) if (!player) // normal creature, no player involved return; - m_lootRecipient = player->GetGUID(); + m_tapList.insert(player->GetGUID()); if (withGroup) - { - if (Group* group = player->GetGroup()) - m_lootRecipientGroup = group->GetGUID(); - } - else - m_lootRecipientGroup = ObjectGuid::Empty; + if (Group const* group = player->GetGroup()) + for (auto const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + if (GetMap()->IsRaid() || group->SameSubGroup(player, itr->GetSource())) + m_tapList.insert(itr->GetSource()->GetGUID()); - SetDynamicFlag(UNIT_DYNFLAG_TAPPED); + if (m_tapList.size() >= CREATURE_TAPPERS_SOFT_CAP) + SetDynamicFlag(UNIT_DYNFLAG_TAPPED); } // return true if this creature is tapped by the player or by a member of his group. bool Creature::isTappedBy(Player const* player) const { - if (player->GetGUID() == m_lootRecipient) - return true; + return m_tapList.find(player->GetGUID()) != m_tapList.end(); +} - Group const* playerGroup = player->GetGroup(); - if (!playerGroup || playerGroup != GetLootRecipientGroup()) // if we dont have a group we arent the recipient - return false; // if creature doesnt have group bound it means it was solo killed by someone else +Loot* Creature::GetLootForPlayer(Player const* player) const +{ + if (m_personalLoot.empty()) + return m_loot.get(); + + if (std::unique_ptr<Loot> const* loot = Trinity::Containers::MapGetValuePtr(m_personalLoot, player->GetGUID())) + return loot->get(); + + return nullptr; +} + +bool Creature::IsFullyLooted() const +{ + if (m_loot && !m_loot->isLooted()) + return false; + + for (auto const& [_, loot] : m_personalLoot) + if (!loot->isLooted()) + return false; return true; } +bool Creature::IsSkinnedBy(Player const* player) const +{ + if (Loot* loot = GetLootForPlayer(player)) + return loot->loot_type == LOOT_SKINNING; + + return false; +} + void Creature::SaveToDB() { // this should only be used when the creature has already been loaded @@ -2136,7 +2147,7 @@ void Creature::setDeathState(DeathState s) else SetSpawnHealth(); - SetLootRecipient(nullptr); + SetTappedBy(nullptr); ResetPlayerDamageReq(); SetCannotReachTarget(false); @@ -2836,10 +2847,6 @@ void Creature::RefreshCanSwimFlag(bool recheck) void Creature::AllLootRemovedFromCorpse() { - if ((!m_loot || m_loot->loot_type != LOOT_SKINNING) && !IsPet() && GetCreatureTemplate()->SkinLootId && hasLootRecipient()) - if (LootTemplates_Skinning.HaveLootFor(GetCreatureTemplate()->SkinLootId)) - SetUnitFlag(UNIT_FLAG_SKINNABLE); - time_t now = GameTime::GetGameTime(); // Do not reset corpse remove time if corpse is already removed if (m_corpseRemoveTime <= now) @@ -2849,7 +2856,19 @@ void Creature::AllLootRemovedFromCorpse() float decayRate = m_ignoreCorpseDecayRatio ? 1.f : sWorld->getRate(RATE_CORPSE_DECAY_LOOTED); // corpse skinnable, but without skinning flag, and then skinned, corpse will despawn next update - if (m_loot && m_loot->loot_type == LOOT_SKINNING) + bool isFullySkinned = [&]() -> bool + { + if (m_loot && m_loot->loot_type == LOOT_SKINNING && m_loot->isLooted()) + return true; + + for (auto const& [_, loot] : m_personalLoot) + if (loot->loot_type != LOOT_SKINNING || !loot->isLooted()) + return false; + + return true; + }(); + + if (isFullySkinned) m_corpseRemoveTime = now; else m_corpseRemoveTime = now + uint32(m_corpseDelay * decayRate); diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 4b14899c0f7..beb74bb7526 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -29,7 +29,6 @@ class CreatureAI; class CreatureGroup; -class Group; class Quest; class Player; class SpellInfo; @@ -61,6 +60,7 @@ enum class VendorInventoryReason : uint8 }; static constexpr uint8 WILD_BATTLE_PET_DEFAULT_LEVEL = 1; +static constexpr size_t CREATURE_TAPPERS_SOFT_CAP = 5; //used for handling non-repeatable random texts typedef std::vector<uint8> CreatureTextRepeatIds; @@ -225,17 +225,19 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma static bool DeleteFromDB(ObjectGuid::LowType spawnId); std::unique_ptr<Loot> m_loot; + std::unordered_map<ObjectGuid, std::unique_ptr<Loot>> m_personalLoot; void StartPickPocketRefillTimer(); void ResetPickPocketRefillTimer() { _pickpocketLootRestore = 0; } bool CanGeneratePickPocketLoot() const; - ObjectGuid GetLootRecipientGUID() const { return m_lootRecipient; } - Player* GetLootRecipient() const; - Group* GetLootRecipientGroup() const; - bool hasLootRecipient() const { return !m_lootRecipient.IsEmpty() || !m_lootRecipientGroup.IsEmpty(); } + GuidUnorderedSet const& GetTapList() const { return m_tapList; } + void SetTapList(GuidUnorderedSet tapList) { m_tapList = std::move(tapList); } + bool hasLootRecipient() const { return !m_tapList.empty(); } bool isTappedBy(Player const* player) const; // return true if the creature is tapped by the player or a member of his party. - Loot* GetLootForPlayer(Player const* /*player*/) const override { return m_loot.get(); } + Loot* GetLootForPlayer(Player const* player) const override; + bool IsFullyLooted() const; + bool IsSkinnedBy(Player const* player) const; - void SetLootRecipient (Unit* unit, bool withGroup = true); + void SetTappedBy(Unit const* unit, bool withGroup = true); void AllLootRemovedFromCorpse(); uint16 GetLootMode() const { return m_LootMode; } @@ -334,7 +336,6 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma void SetDisableReputationGain(bool disable) { DisableReputationGain = disable; } bool IsReputationGainDisabled() const { return DisableReputationGain; } - bool IsDamageEnoughForLootingAndReward() const { return (m_creatureInfo->flags_extra & CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ) || (m_PlayerDamageReq == 0); } void LowerPlayerDamageReq(uint64 unDamage); void ResetPlayerDamageReq() { m_PlayerDamageReq = GetHealth() / 2; } uint64 m_PlayerDamageReq; @@ -395,8 +396,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma static float _GetHealthMod(int32 Rank); - ObjectGuid m_lootRecipient; - ObjectGuid m_lootRecipientGroup; + GuidUnorderedSet m_tapList; /// Timers time_t _pickpocketLootRestore; diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index ed2dc526fba..4dffcf2652e 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -34,7 +34,6 @@ #include "GossipDef.h" #include "GridNotifiersImpl.h" #include "Group.h" -#include "GroupMgr.h" #include "Item.h" #include "Log.h" #include "Loot.h" @@ -1419,45 +1418,45 @@ void GameObject::SendGameObjectDespawn() Loot* GameObject::GetFishLoot(Player* lootOwner) { - uint32 zone, subzone; uint32 defaultzone = 1; - GetZoneAndAreaId(zone, subzone); Loot* fishLoot = new Loot(GetMap(), GetGUID(), LOOT_FISHING, nullptr); - // if subzone loot exist use it - fishLoot->FillLoot(subzone, LootTemplates_Fishing, lootOwner, true, true); - if (fishLoot->empty()) //use this becase if zone or subzone has set LOOT_MODE_JUNK_FISH,Even if no normal drop, fishloot->FillLoot return true. it wrong. + uint32 areaId = GetAreaId(); + while (AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(areaId)) { - //subzone no result,use zone loot - fishLoot->FillLoot(zone, LootTemplates_Fishing, lootOwner, true, true); - //use zone 1 as default, somewhere fishing got nothing,becase subzone and zone not set, like Off the coast of Storm Peaks. - if (fishLoot->empty()) - fishLoot->FillLoot(defaultzone, LootTemplates_Fishing, lootOwner, true, true); + fishLoot->FillLoot(areaId, LootTemplates_Fishing, lootOwner, true, true); + if (!fishLoot->isLooted()) + break; + + areaId = areaEntry->ParentAreaID; } + if (fishLoot->isLooted()) + fishLoot->FillLoot(defaultzone, LootTemplates_Fishing, lootOwner, true, true); + return fishLoot; } Loot* GameObject::GetFishLootJunk(Player* lootOwner) { - uint32 zone, subzone; uint32 defaultzone = 1; - GetZoneAndAreaId(zone, subzone); Loot* fishLoot = new Loot(GetMap(), GetGUID(), LOOT_FISHING_JUNK, nullptr); - // if subzone loot exist use it - fishLoot->FillLoot(subzone, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_JUNK_FISH); - if (fishLoot->empty()) //use this becase if zone or subzone has normal mask drop, then fishloot->FillLoot return true. + uint32 areaId = GetAreaId(); + while (AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(areaId)) { - //use zone loot - fishLoot->FillLoot(zone, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_JUNK_FISH); - if (fishLoot->empty()) - //use zone 1 as default - fishLoot->FillLoot(defaultzone, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_JUNK_FISH); + fishLoot->FillLoot(areaId, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_JUNK_FISH); + if (!fishLoot->isLooted()) + break; + + areaId = areaEntry->ParentAreaID; } + if (fishLoot->isLooted()) + fishLoot->FillLoot(defaultzone, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_JUNK_FISH); + return fishLoot; } @@ -3279,49 +3278,6 @@ void GameObject::UpdateModel() GetMap()->InsertGameObjectModel(*m_model); } -Player* GameObject::GetLootRecipient() const -{ - if (!m_lootRecipient) - return nullptr; - return ObjectAccessor::FindConnectedPlayer(m_lootRecipient); -} - -Group* GameObject::GetLootRecipientGroup() const -{ - if (!m_lootRecipientGroup) - return nullptr; - return sGroupMgr->GetGroupByGUID(m_lootRecipientGroup); -} - -void GameObject::SetLootRecipient(Unit* unit, Group* group) -{ - // set the player whose group should receive the right - // to loot the creature after it dies - // should be set to nullptr after the loot disappears - - if (!unit) - { - m_lootRecipient.Clear(); - m_lootRecipientGroup = group ? group->GetGUID() : ObjectGuid::Empty; - return; - } - - if (unit->GetTypeId() != TYPEID_PLAYER && !unit->IsVehicle()) - return; - - Player* player = unit->GetCharmerOrOwnerPlayerOrPlayerItself(); - if (!player) // normal creature, no player involved - return; - - m_lootRecipient = player->GetGUID(); - - // either get the group from the passed parameter or from unit's one - if (group) - m_lootRecipientGroup = group->GetGUID(); - else if (Group* unitGroup = player->GetGroup()) - m_lootRecipientGroup = unitGroup->GetGUID(); -} - bool GameObject::IsLootAllowedFor(Player const* player) const { if (Loot const* loot = GetLootForPlayer(player)) // check only if loot was already generated @@ -3332,15 +3288,8 @@ bool GameObject::IsLootAllowedFor(Player const* player) const return false; } - if (!m_lootRecipient && !m_lootRecipientGroup) - return true; - - if (player->GetGUID() == m_lootRecipient) - return true; - - Group const* playerGroup = player->GetGroup(); - if (!playerGroup || playerGroup != GetLootRecipientGroup()) // if we dont have a group we arent the recipient - return false; // if go doesnt have group bound it means it was solo killed by someone else + if (HasLootRecipient()) + return m_tapList.find(player->GetGUID()) != m_tapList.end(); return true; } diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index 55d86f1e5f8..7d7d0563532 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -27,7 +27,6 @@ class GameObject; class GameObjectAI; class GameObjectModel; -class Group; class OPvPCapturePoint; class Transport; class TransportBase; @@ -281,11 +280,10 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> std::unique_ptr<Loot> m_loot; std::unordered_map<ObjectGuid, std::unique_ptr<Loot>> m_personalLoot; - Player* GetLootRecipient() const; - Group* GetLootRecipientGroup() const; - void SetLootRecipient(Unit* unit, Group* group = nullptr); + GuidUnorderedSet const& GetTapList() const { return m_tapList; } + void SetTapList(GuidUnorderedSet tapList) { m_tapList = std::move(tapList); } bool IsLootAllowedFor(Player const* player) const; - bool HasLootRecipient() const { return !m_lootRecipient.IsEmpty() || !m_lootRecipientGroup.IsEmpty(); } + bool HasLootRecipient() const { return !m_tapList.empty(); } Loot* GetLootForPlayer(Player const* /*player*/) const override; GameObject* GetLinkedTrap(); @@ -427,8 +425,7 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> QuaternionData m_localRotation; Position m_stationaryPosition; - ObjectGuid m_lootRecipient; - ObjectGuid m_lootRecipientGroup; + GuidUnorderedSet m_tapList; uint16 m_LootMode; // bitmask, default LOOT_MODE_DEFAULT, determines what loot will be lootable ObjectGuid m_linkedTrap; diff --git a/src/server/game/Entities/Object/Updates/UpdateFields.cpp b/src/server/game/Entities/Object/Updates/UpdateFields.cpp index b12c585a788..c9f7d991d67 100644 --- a/src/server/game/Entities/Object/Updates/UpdateFields.cpp +++ b/src/server/game/Entities/Object/Updates/UpdateFields.cpp @@ -1006,7 +1006,7 @@ void UnitData::WriteCreate(ByteBuffer& data, EnumFlag<UpdateFieldFlag> fieldVisi } data << uint32(ViewerDependentValue<FlagsTag>::GetValue(this, owner, receiver)); data << uint32(Flags2); - data << uint32(Flags3); + data << uint32(ViewerDependentValue<Flags3Tag>::GetValue(this, owner, receiver)); data << uint32(ViewerDependentValue<AuraStateTag>::GetValue(this, owner, receiver)); for (uint32 i = 0; i < 2; ++i) { @@ -1397,7 +1397,7 @@ void UnitData::WriteUpdate(ByteBuffer& data, Mask const& changesMask, bool ignor } if (changesMask[45]) { - data << uint32(Flags3); + data << uint32(ViewerDependentValue<Flags3Tag>::GetValue(this, owner, receiver)); } if (changesMask[46]) { diff --git a/src/server/game/Entities/Object/Updates/UpdateFields.h b/src/server/game/Entities/Object/Updates/UpdateFields.h index b19fdebafb0..41f47ffa22f 100644 --- a/src/server/game/Entities/Object/Updates/UpdateFields.h +++ b/src/server/game/Entities/Object/Updates/UpdateFields.h @@ -303,6 +303,7 @@ struct UnitData : public IsUpdateFieldStructureTag, public HasChangesMask<194> struct FlagsTag : ViewerDependentValueTag<uint32> {}; UpdateField<uint32, 32, 44> Flags2; UpdateField<uint32, 32, 45> Flags3; + struct Flags3Tag : ViewerDependentValueTag<uint32> {}; UpdateField<uint32, 32, 46> AuraState; struct AuraStateTag : ViewerDependentValueTag<uint32> {}; UpdateField<uint32, 32, 47> RangedAttackRoundBaseTime; diff --git a/src/server/game/Entities/Object/Updates/ViewerDependentValues.h b/src/server/game/Entities/Object/Updates/ViewerDependentValues.h index 028fdd91a26..3ddef2b2db8 100644 --- a/src/server/game/Entities/Object/Updates/ViewerDependentValues.h +++ b/src/server/game/Entities/Object/Updates/ViewerDependentValues.h @@ -67,8 +67,6 @@ public: value_type dynamicFlags = objectData->DynamicFlags; if (Unit const* unit = object->ToUnit()) { - dynamicFlags &= ~UNIT_DYNFLAG_TAPPED; - if (Creature const* creature = object->ToCreature()) { if (creature->hasLootRecipient() && !creature->isTappedBy(receiver)) @@ -76,6 +74,9 @@ public: if (!receiver->isAllowedToLoot(creature)) dynamicFlags &= ~UNIT_DYNFLAG_LOOTABLE; + + if (dynamicFlags & UNIT_DYNFLAG_CAN_SKIN && creature->IsSkinnedBy(receiver)) + dynamicFlags &= ~UNIT_DYNFLAG_CAN_SKIN; } // unit UNIT_DYNFLAG_TRACK_UNIT should only be sent to caster of SPELL_AURA_MOD_STALKED auras @@ -218,6 +219,22 @@ public: }; template<> +class ViewerDependentValue<UF::UnitData::Flags3Tag> +{ +public: + using value_type = UF::UnitData::Flags3Tag::value_type; + + static value_type GetValue(UF::UnitData const* unitData, Unit const* unit, Player const* receiver) + { + value_type flags = unitData->Flags3; + if (flags & UNIT_FLAG3_ALREADY_SKINNED && unit->IsCreature() && !unit->ToCreature()->IsSkinnedBy(receiver)) + flags &= ~UNIT_FLAG3_ALREADY_SKINNED; + + return flags; + } +}; + +template<> class ViewerDependentValue<UF::UnitData::AuraStateTag> { public: diff --git a/src/server/game/Entities/Player/KillRewarder.cpp b/src/server/game/Entities/Player/KillRewarder.cpp index cc6d813b50d..e2acc7eb515 100644 --- a/src/server/game/Entities/Player/KillRewarder.cpp +++ b/src/server/game/Entities/Player/KillRewarder.cpp @@ -27,6 +27,8 @@ #include "Player.h" #include "Scenario.h" #include "SpellAuraEffects.h" +#include <boost/container/flat_set.hpp> +#include <boost/container/small_vector.hpp> // == KillRewarder ==================================================== // KillRewarder encapsulates logic of rewarding player upon kill with: @@ -71,9 +73,9 @@ // 6. Update guild achievements. // 7. Scenario credit -KillRewarder::KillRewarder(Player* killer, Unit* victim, bool isBattleGround) : +KillRewarder::KillRewarder(Trinity::IteratorPair<Player**> killers, Unit* victim, bool isBattleGround) : // 1. Initialize internal variables to default values. - _killer(killer), _victim(victim), _group(killer->GetGroup()), + _killers(killers), _victim(victim), _groupRate(1.0f), _maxNotGrayMember(nullptr), _count(0), _sumLevel(0), _xp(0), _isFullXP(false), _maxLevel(0), _isBattleGround(isBattleGround), _isPvP(false) { @@ -83,18 +85,18 @@ KillRewarder::KillRewarder(Player* killer, Unit* victim, bool isBattleGround) : // or if its owned by player and its not a vehicle else if (victim->GetCharmerOrOwnerGUID().IsPlayer()) _isPvP = !victim->IsVehicle(); - - _InitGroupData(); } -inline void KillRewarder::_InitGroupData() +inline void KillRewarder::_InitGroupData(Player const* killer) { - if (_group) + if (Group const* group = killer->GetGroup()) { // 2. In case when player is in group, initialize variables necessary for group calculations: - for (GroupReference* itr = _group->GetFirstMember(); itr != nullptr; itr = itr->next()) + for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { if (Player* member = itr->GetSource()) - if (_killer == member || (member->IsAtGroupRewardDistance(_victim) && member->IsAlive())) + { + if (killer == member || (member->IsAtGroupRewardDistance(_victim) && member->IsAlive())) { const uint8 lvl = member->GetLevel(); // 2.1. _count - number of alive group members within reward distance; @@ -110,6 +112,8 @@ inline void KillRewarder::_InitGroupData() if (_victim->GetLevelForTarget(member) > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember->GetLevel() < lvl)) _maxNotGrayMember = member; } + } + } // 2.5. _isFullXP - flag identifying that for all group members victim is not gray, // so 100% XP will be rewarded (50% otherwise). _isFullXP = _maxNotGrayMember && (_maxLevel == _maxNotGrayMember->GetLevel()); @@ -118,14 +122,14 @@ inline void KillRewarder::_InitGroupData() _count = 1; } -inline void KillRewarder::_InitXP(Player* player) +inline void KillRewarder::_InitXP(Player* player, Player const* killer) { // Get initial value of XP for kill. // XP is given: // * on battlegrounds; // * otherwise, not in PvP; // * not if killer is on vehicle. - if (_isBattleGround || (!_isPvP && !_killer->GetVehicle())) + if (_isBattleGround || (!_isPvP && !killer->GetVehicle())) _xp = Trinity::XP::Gain(player, _victim, _isBattleGround); } @@ -139,7 +143,7 @@ inline void KillRewarder::_RewardHonor(Player* player) inline void KillRewarder::_RewardXP(Player* player, float rate) { uint32 xp(_xp); - if (_group) + if (player->GetGroup()) { // 4.2.1. If player is in group, adjust XP: // * set to 0 if player's level is more than maximum level of not gray member; @@ -162,7 +166,7 @@ inline void KillRewarder::_RewardXP(Player* player, float rate) player->GiveXP(xp, _victim, _groupRate); if (Pet* pet = player->GetPet()) // 4.2.4. If player has pet, reward pet with XP (100% for single player, 50% for group case). - pet->GivePetXP(_group ? xp / 2 : xp); + pet->GivePetXP(player->GetGroup() ? xp / 2 : xp); } } @@ -176,12 +180,14 @@ inline void KillRewarder::_RewardReputation(Player* player, float rate) inline void KillRewarder::_RewardKillCredit(Player* player) { // 4.4. Give kill credit (player must not be in group, or he must be alive or without corpse). - if (!_group || player->IsAlive() || !player->GetCorpse()) + if (!player->GetGroup() || player->IsAlive() || !player->GetCorpse()) + { if (Creature* target = _victim->ToCreature()) { player->KilledMonster(target->GetCreatureTemplate(), target->GetGUID()); player->UpdateCriteria(CriteriaType::KillAnyCreature, target->GetCreatureType(), 1, 0, target); } + } } void KillRewarder::_RewardPlayer(Player* player, bool isDungeon) @@ -199,7 +205,7 @@ void KillRewarder::_RewardPlayer(Player* player, bool isDungeon) // Give reputation and kill credit only in PvE. if (!_isPvP || _isBattleGround) { - float const rate = _group ? + float const rate = player->GetGroup() ? _groupRate * float(player->GetLevel()) / _sumLevel : // Group rate depends on summary level. 1.0f; // Personal rate is 100%. if (_xp) @@ -214,34 +220,34 @@ void KillRewarder::_RewardPlayer(Player* player, bool isDungeon) } } -void KillRewarder::_RewardGroup() +void KillRewarder::_RewardGroup(Group const* group, Player const* killer) { if (_maxLevel) { if (_maxNotGrayMember) // 3.1.1. Initialize initial XP amount based on maximum level of group member, // for whom victim is not gray. - _InitXP(_maxNotGrayMember); + _InitXP(_maxNotGrayMember, killer); // To avoid unnecessary calculations and calls, // proceed only if XP is not ZERO or player is not on battleground // (battleground rewards only XP, that's why). if (!_isBattleGround || _xp) { - bool const isDungeon = !_isPvP && sMapStore.LookupEntry(_killer->GetMapId())->IsDungeon(); + bool const isDungeon = !_isPvP && sMapStore.LookupEntry(killer->GetMapId())->IsDungeon(); if (!_isBattleGround) { // 3.1.2. Alter group rate if group is in raid (not for battlegrounds). - bool const isRaid = !_isPvP && sMapStore.LookupEntry(_killer->GetMapId())->IsRaid() && _group->isRaidGroup(); + bool const isRaid = !_isPvP && sMapStore.LookupEntry(killer->GetMapId())->IsRaid() && group->isRaidGroup(); _groupRate = Trinity::XP::xp_in_group_rate(_count, isRaid); } // 3.1.3. Reward each group member (even dead or corpse) within reward distance. - for (GroupReference* itr = _group->GetFirstMember(); itr != nullptr; itr = itr->next()) + for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { if (Player* member = itr->GetSource()) { // Killer may not be at reward distance, check directly - if (_killer == member || member->IsAtGroupRewardDistance(_victim)) + if (killer == member || member->IsAtGroupRewardDistance(_victim)) { _RewardPlayer(member, isDungeon); } @@ -253,21 +259,32 @@ void KillRewarder::_RewardGroup() void KillRewarder::Reward() { - // 3. Reward killer (and group, if necessary). - if (_group) - // 3.1. If killer is in group, reward group. - _RewardGroup(); - else + boost::container::flat_set<Group const*, std::less<>, boost::container::small_vector<Group const*, 3>> processedGroups; + for (Player* killer : _killers) { - // 3.2. Reward single killer (not group case). - // 3.2.1. Initialize initial XP amount based on killer's level. - _InitXP(_killer); - // To avoid unnecessary calculations and calls, - // proceed only if XP is not ZERO or player is not on battleground - // (battleground rewards only XP, that's why). - if (!_isBattleGround || _xp) - // 3.2.2. Reward killer. - _RewardPlayer(_killer, false); + _InitGroupData(killer); + + // 3. Reward killer (and group, if necessary). + if (Group* group = killer->GetGroup()) + { + if (!processedGroups.insert(group).second) + continue; + + // 3.1. If killer is in group, reward group. + _RewardGroup(group, killer); + } + else + { + // 3.2. Reward single killer (not group case). + // 3.2.1. Initialize initial XP amount based on killer's level. + _InitXP(killer, killer); + // To avoid unnecessary calculations and calls, + // proceed only if XP is not ZERO or player is not on battleground + // (battleground rewards only XP, that's why). + if (!_isBattleGround || _xp) + // 3.2.2. Reward killer. + _RewardPlayer(killer, false); + } } // 5. Credit instance encounter. @@ -281,9 +298,9 @@ void KillRewarder::Reward() if (ObjectGuid::LowType guildId = victim->GetMap()->GetOwnerGuildId()) if (Guild* guild = sGuildMgr->GetGuildById(guildId)) - guild->UpdateCriteria(CriteriaType::KillCreature, victim->GetEntry(), 1, 0, victim, _killer); + guild->UpdateCriteria(CriteriaType::KillCreature, victim->GetEntry(), 1, 0, victim, *_killers.begin()); if (Scenario* scenario = victim->GetScenario()) - scenario->UpdateCriteria(CriteriaType::KillCreature, victim->GetEntry(), 1, 0, victim, _killer); + scenario->UpdateCriteria(CriteriaType::KillCreature, victim->GetEntry(), 1, 0, victim, *_killers.begin()); } } diff --git a/src/server/game/Entities/Player/KillRewarder.h b/src/server/game/Entities/Player/KillRewarder.h index 662f8c6b0e1..6abf0aae464 100644 --- a/src/server/game/Entities/Player/KillRewarder.h +++ b/src/server/game/Entities/Player/KillRewarder.h @@ -19,6 +19,7 @@ #define KillRewarder_h__ #include "Define.h" +#include "IteratorPair.h" class Player; class Unit; @@ -27,24 +28,23 @@ class Group; class TC_GAME_API KillRewarder { public: - KillRewarder(Player* killer, Unit* victim, bool isBattleGround); + KillRewarder(Trinity::IteratorPair<Player**> killers, Unit* victim, bool isBattleGround); void Reward(); private: - void _InitXP(Player* player); - void _InitGroupData(); + void _InitXP(Player* player, Player const* killer); + void _InitGroupData(Player const* killer); void _RewardHonor(Player* player); void _RewardXP(Player* player, float rate); void _RewardReputation(Player* player, float rate); void _RewardKillCredit(Player* player); void _RewardPlayer(Player* player, bool isDungeon); - void _RewardGroup(); + void _RewardGroup(Group const* group, Player const* killer); - Player* _killer; + Trinity::IteratorPair<Player**> _killers; Unit* _victim; - Group* _group; float _groupRate; Player* _maxNotGrayMember; uint32 _count; diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 8dd3ad58eb9..b581bd07799 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -71,7 +71,6 @@ #include "InstancePackets.h" #include "InstanceScript.h" #include "ItemPackets.h" -#include "KillRewarder.h" #include "Language.h" #include "LanguageMgr.h" #include "LFGMgr.h" @@ -17853,7 +17852,7 @@ void Player::_LoadCUFProfiles(PreparedQueryResult result) bool Player::isAllowedToLoot(const Creature* creature) const { - if (!creature->isDead() || !creature->IsDamageEnoughForLootingAndReward()) + if (!creature->isDead()) return false; if (HasPendingBind()) @@ -17865,15 +17864,6 @@ bool Player::isAllowedToLoot(const Creature* creature) const if (!loot->HasAllowedLooter(GetGUID()) || (!loot->hasItemForAll() && !loot->hasItemFor(this))) // no loot in creature for this player return false; - if (loot->loot_type == LOOT_SKINNING) - return creature->GetLootRecipientGUID() == GetGUID(); - - Group const* thisGroup = GetGroup(); - if (!thisGroup) - return this == creature->GetLootRecipient(); - else if (thisGroup != creature->GetLootRecipientGroup()) - return false; - switch (loot->GetLootMethod()) { case PERSONAL_LOOT: /// @todo implement personal loot (http://wow.gamepedia.com/Loot#Personal_Loot) @@ -24636,11 +24626,6 @@ bool Player::GetsRecruitAFriendBonus(bool forXP) return recruitAFriend; } -void Player::RewardPlayerAndGroupAtKill(Unit* victim, bool isBattleGround) -{ - KillRewarder(this, victim, isBattleGround).Reward(); -} - void Player::RewardPlayerAndGroupAtEvent(uint32 creature_id, WorldObject* pRewardSource) { if (!pRewardSource) diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 221d9c08218..335da76cb5c 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -2167,7 +2167,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> bool IsAtGroupRewardDistance(WorldObject const* pRewardSource) const; bool IsAtRecruitAFriendDistance(WorldObject const* pOther) const; - void RewardPlayerAndGroupAtKill(Unit* victim, bool isBattleGround); void RewardPlayerAndGroupAtEvent(uint32 creature_id, WorldObject* pRewardSource); bool isHonorOrXPTarget(Unit const* victim) const; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index e029077780b..db3caadc21c 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -44,6 +44,7 @@ #include "Group.h" #include "InstanceScript.h" #include "Item.h" +#include "KillRewarder.h" #include "Log.h" #include "Loot.h" #include "LootMgr.h" @@ -873,8 +874,7 @@ bool Unit::HasBreakableByDamageCrowdControlAura(Unit* excludeCasterChannel) cons if (victim->GetTypeId() != TYPEID_PLAYER && (!victim->IsControlledByPlayer() || victim->IsVehicle())) { - if (!victim->ToCreature()->hasLootRecipient()) - victim->ToCreature()->SetLootRecipient(attacker); + victim->ToCreature()->SetTappedBy(attacker); if (!attacker || attacker->IsControlledByPlayer()) victim->ToCreature()->LowerPlayerDamageReq(health < damage ? health : damage); @@ -10585,36 +10585,13 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId) bool isRewardAllowed = true; if (creature) - { - isRewardAllowed = creature->IsDamageEnoughForLootingAndReward(); - if (!isRewardAllowed) - creature->SetLootRecipient(nullptr); - } + isRewardAllowed = !creature->GetTapList().empty(); + std::vector<Player*> tappers; if (isRewardAllowed && creature) - { - if (Player* lootRecipient = creature->GetLootRecipient()) - { - // Loot recipient can be in a different map - if (!creature->IsInMap(lootRecipient)) - { - if (Group* group = creature->GetLootRecipientGroup()) - { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (!member || !creature->IsInMap(member)) - continue; - - player = member; - break; - } - } - } - else - player = creature->GetLootRecipient(); - } - } + for (ObjectGuid tapperGuid : creature->GetTapList()) + if (Player* tapper = ObjectAccessor::GetPlayer(*creature, tapperGuid)) + tappers.push_back(tapper); // Exploit fix if (creature && creature->IsPet() && creature->GetOwnerGUID().IsPlayer()) @@ -10622,59 +10599,87 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId) // Reward player, his pets, and group/raid members // call kill spell proc event (before real die and combat stop to triggering auras removed at death/combat stop) - if (isRewardAllowed && player && player != victim) + if (isRewardAllowed) { WorldPackets::Party::PartyKillLog partyKillLog; partyKillLog.Player = player->GetGUID(); partyKillLog.Victim = victim->GetGUID(); + partyKillLog.Write(); - Player* looter = player; - Group* group = player->GetGroup(); - - if (group) + std::unordered_set<Group*> groups; + for (Player* tapper : tappers) { - group->BroadcastPacket(partyKillLog.Write(), group->GetMemberGroup(player->GetGUID()) != 0); - - if (creature) + if (Group* tapperGroup = tapper->GetGroup()) { - group->UpdateLooterGuid(creature, true); - if (!group->GetLooterGuid().IsEmpty()) + if (groups.insert(tapperGroup).second) { - looter = ObjectAccessor::FindPlayer(group->GetLooterGuid()); - if (looter) - creature->SetLootRecipient(looter); // update creature loot recipient to the allowed looter. + tapperGroup->BroadcastPacket(partyKillLog.GetRawPacket(), tapperGroup->GetMemberGroup(tapper->GetGUID()) != 0); + + if (creature) + tapperGroup->UpdateLooterGuid(creature, true); } } + else + tapper->SendDirectMessage(partyKillLog.GetRawPacket()); } - else - player->SendDirectMessage(partyKillLog.Write()); // Generate loot before updating looter if (creature) { - creature->m_loot.reset(new Loot(creature->GetMap(), creature->GetGUID(), LOOT_CORPSE, group)); - Loot* loot = creature->m_loot.get(); - if (creature->GetMap()->Is25ManRaid()) - loot->maxDuplicates = 3; - + DungeonEncounterEntry const* dungeonEncounter = nullptr; if (InstanceScript const* instance = creature->GetInstanceScript()) - if (DungeonEncounterEntry const* dungeonEncounter = instance->GetBossDungeonEncounter(creature)) + dungeonEncounter = instance->GetBossDungeonEncounter(creature); + + if (creature->GetMap()->IsDungeon()) + { + 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, false, false, creature->GetLootMode(), creature->GetMap()->GetDifficultyLootItemContext()); + 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 (creature->GetLootMode() > 0) - loot->generateMoneyLoot(creature->GetCreatureTemplate()->mingold, creature->GetCreatureTemplate()->maxgold); + if (group) + loot->NotifyLootList(creature->GetMap()); - loot->NotifyLootList(creature->GetMap()); + if (dungeonEncounter || groups.empty()) + creature->m_loot.reset(loot); // TODO: personal boss loot + else + 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); + } + else + { + for (Player* tapper : tappers) + { + Loot* loot = new Loot(creature->GetMap(), creature->GetGUID(), LOOT_CORPSE, nullptr); - // Update round robin looter only if the creature had loot - if (group && !loot->empty()) - group->UpdateLooterGuid(creature); + if (dungeonEncounter) + loot->SetDungeonEncounterId(dungeonEncounter->ID); + + if (uint32 lootid = creature->GetCreatureTemplate()->lootid) + loot->FillLoot(lootid, LootTemplates_Creature, tapper, true, false, creature->GetLootMode(), creature->GetMap()->GetDifficultyLootItemContext()); + + if (creature->GetLootMode() > 0) + loot->generateMoneyLoot(creature->GetCreatureTemplate()->mingold, creature->GetCreatureTemplate()->maxgold); + + creature->m_personalLoot[tapper->GetGUID()].reset(loot); + } + } } - player->RewardPlayerAndGroupAtKill(victim, false); + KillRewarder(Trinity::IteratorPair(tappers.data(), tappers.data() + tappers.size()), victim, false).Reward(); } // Do KILL and KILLED procs. KILL proc is called only for the unit who landed the killing blow (and its owner - for pets and totems) regardless of who tapped the victim @@ -10689,11 +10694,9 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId) { Unit::ProcSkillsAndAuras(attacker, victim, PROC_FLAG_KILL, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); - if (player && player->GetGroup()) - for (GroupReference* itr = player->GetGroup()->GetFirstMember(); itr != nullptr; itr = itr->next()) - if (Player* member = itr->GetSource()) - if (member->IsAtGroupRewardDistance(victim)) - Unit::ProcSkillsAndAuras(member, victim, { PROC_FLAG_NONE, PROC_FLAG_2_TARGET_DIES }, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); + for (Player* tapper : tappers) + if (tapper->IsAtGroupRewardDistance(victim)) + Unit::ProcSkillsAndAuras(tapper, victim, { PROC_FLAG_NONE, PROC_FLAG_2_TARGET_DIES }, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr); } // Proc auras on death - must be before aura/combat remove @@ -10714,9 +10717,9 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId) // Inform pets (if any) when player kills target) // MUST come after victim->setDeathState(JUST_DIED); or pet next target // selection will get stuck on same target and break pet react state - if (player) + for (Player* tapper : tappers) { - Pet* pet = player->GetPet(); + Pet* pet = tapper->GetPet(); if (pet && pet->IsAlive() && pet->isControlled()) { if (pet->IsAIEnabled()) @@ -10764,10 +10767,16 @@ void Unit::SetMeleeAnimKitId(uint16 animKitId) if (!creature->IsPet()) { // must be after setDeathState which resets dynamic flags - if (creature->m_loot && !creature->m_loot->isLooted()) + if (!creature->IsFullyLooted()) creature->SetDynamicFlag(UNIT_DYNFLAG_LOOTABLE); else creature->AllLootRemovedFromCorpse(); + + if (LootTemplates_Skinning.HaveLootFor(creature->GetCreatureTemplate()->SkinLootId)) + { + creature->SetDynamicFlag(UNIT_DYNFLAG_CAN_SKIN); + creature->SetUnitFlag(UNIT_FLAG_SKINNABLE); + } } // Call KilledUnit for creatures, this needs to be called after the lootable flag is set diff --git a/src/server/game/Handlers/LootHandler.cpp b/src/server/game/Handlers/LootHandler.cpp index 8fe3ecff2b5..fdaf064dd74 100644 --- a/src/server/game/Handlers/LootHandler.cpp +++ b/src/server/game/Handlers/LootHandler.cpp @@ -354,11 +354,14 @@ void WorldSession::DoLootRelease(Loot* loot) if (loot->isLooted()) { - creature->RemoveDynamicFlag(UNIT_DYNFLAG_LOOTABLE); + if (creature->IsFullyLooted()) + { + creature->RemoveDynamicFlag(UNIT_DYNFLAG_LOOTABLE); - // skip pickpocketing loot for speed, skinning timer reduction is no-op in fact - if (!creature->IsAlive()) - creature->AllLootRemovedFromCorpse(); + // skip pickpocketing loot for speed, skinning timer reduction is no-op in fact + if (!creature->IsAlive()) + creature->AllLootRemovedFromCorpse(); + } } else { @@ -368,9 +371,9 @@ void WorldSession::DoLootRelease(Loot* loot) loot->roundRobinPlayer.Clear(); loot->NotifyLootList(creature->GetMap()); } - // force dynflag update to update looter and lootable info - creature->ForceUpdateFieldChange(creature->m_values.ModifyValue(&Object::m_objectData).ModifyValue(&UF::ObjectData::DynamicFlags)); } + // force dynflag update to update looter and lootable info + creature->ForceUpdateFieldChange(creature->m_values.ModifyValue(&Object::m_objectData).ModifyValue(&UF::ObjectData::DynamicFlags)); } } diff --git a/src/server/game/Loot/Loot.cpp b/src/server/game/Loot/Loot.cpp index d67f9ea55d9..6c5dd9286d0 100644 --- a/src/server/game/Loot/Loot.cpp +++ b/src/server/game/Loot/Loot.cpp @@ -613,7 +613,7 @@ void LootRoll::Finish(RollVoteMap::const_iterator winnerItr) // --------- Loot --------- // -Loot::Loot(Map* map, ObjectGuid owner, LootType type, Group const* group) : 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), _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), _dungeonEncounterId(0) @@ -622,25 +622,10 @@ Loot::Loot(Map* map, ObjectGuid owner, LootType type, Group const* group) : gold Loot::~Loot() { - clear(); -} - -void Loot::clear() -{ - PlayerFFAItems.clear(); - - for (ObjectGuid playerGuid : PlayersLooting) + GuidSet activeLooters = std::move(PlayersLooting); + for (ObjectGuid playerGuid : activeLooters) if (Player* player = ObjectAccessor::FindConnectedPlayer(playerGuid)) player->GetSession()->DoLootRelease(this); - PlayersLooting.clear(); - - items.clear(); - gold = 0; - unlootedCount = 0; - roundRobinPlayer.Clear(); - loot_type = LOOT_NONE; - _itemContext = ItemContext::NONE; - _rolls.clear(); } void Loot::NotifyLootList(Map const* map) const diff --git a/src/server/game/Loot/Loot.h b/src/server/game/Loot/Loot.h index 12f3d559f05..8f1e717d8bd 100644 --- a/src/server/game/Loot/Loot.h +++ b/src/server/game/Loot/Loot.h @@ -271,7 +271,6 @@ struct TC_GAME_API Loot uint8 unlootedCount; ObjectGuid roundRobinPlayer; // GUID of the player having the Round-Robin ownership for the loot. If 0, round robin owner has released. 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, Group const* group); ~Loot(); @@ -288,9 +287,6 @@ struct TC_GAME_API Loot uint32 GetDungeonEncounterId() const { return _dungeonEncounterId; } void SetDungeonEncounterId(uint32 dungeonEncounterId) { _dungeonEncounterId = dungeonEncounterId; } - void clear(); - - bool empty() const { return items.empty() && gold == 0; } bool isLooted() const { return gold == 0 && unlootedCount == 0; } void NotifyLootList(Map const* map) const; diff --git a/src/server/game/Loot/LootMgr.cpp b/src/server/game/Loot/LootMgr.cpp index 9e04a32edd4..7ea8bd2b1d0 100644 --- a/src/server/game/Loot/LootMgr.cpp +++ b/src/server/game/Loot/LootMgr.cpp @@ -56,24 +56,18 @@ 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(Loot const& /*loot*/, uint16 lootMode) : /*_loot(loot),*/ _lootMode(lootMode) { } bool operator()(LootStoreItem* item) const { if (!(item->lootmode & _lootMode)) return true; - uint8 foundDuplicates = 0; - for (std::vector<LootItem>::const_iterator itr = _loot.items.begin(); itr != _loot.items.end(); ++itr) - if (itr->itemid == item->itemid) - if (++foundDuplicates == _loot.maxDuplicates) - return true; - return false; } private: - Loot const& _loot; + //Loot const& _loot; uint16 _lootMode; }; diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index 85b7ff04715..651a88ddd43 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -5667,7 +5667,7 @@ enum UnitDynFlags UNIT_DYNFLAG_TRACK_UNIT = 0x0008, UNIT_DYNFLAG_TAPPED = 0x0010, // Lua_UnitIsTapped UNIT_DYNFLAG_SPECIALINFO = 0x0020, - UNIT_DYNFLAG_UNUSED = 0x0040, // previously UNIT_DYNFLAG_DEAD + UNIT_DYNFLAG_CAN_SKIN = 0x0040, UNIT_DYNFLAG_REFER_A_FRIEND = 0x0080 }; diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index d6f252695c3..fe7a1e12e06 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -1659,7 +1659,8 @@ void Spell::SelectImplicitCasterObjectTargets(SpellEffectInfo const& spellEffect break; case TARGET_UNIT_TARGET_TAP_LIST: if (Creature* creatureCaster = m_caster->ToCreature()) - target = creatureCaster->GetLootRecipient(); + if (!creatureCaster->GetTapList().empty()) + target = ObjectAccessor::GetWorldObject(*creatureCaster, Trinity::Containers::SelectRandomContainerElement(creatureCaster->GetTapList())); break; case TARGET_UNIT_OWN_CRITTER: if (Unit const* unitCaster = m_caster->ToUnit()) @@ -2857,8 +2858,8 @@ void Spell::TargetInfo::DoDamageAndTriggers(Spell* spell) if (spell->m_spellInfo->HasAttribute(SPELL_ATTR6_TAPS_IMMEDIATELY)) if (Creature* targetCreature = unit->ToCreature()) - if (!targetCreature->hasLootRecipient() && unitCaster->IsPlayer()) - targetCreature->SetLootRecipient(unitCaster); + if (unitCaster->IsPlayer()) + targetCreature->SetTappedBy(unitCaster); } if (!spell->m_spellInfo->HasAttribute(SPELL_ATTR3_DO_NOT_TRIGGER_TARGET_STAND) && !unit->IsStandState()) @@ -5998,7 +5999,7 @@ SpellCastResult Spell::CheckCast(bool strict, int32* param1 /*= nullptr*/, int32 Creature* creature = m_targets.GetUnitTarget()->ToCreature(); Loot* loot = creature->GetLootForPlayer(m_caster->ToPlayer()); - if (loot && !loot->isLooted()) + if (loot && (!loot->isLooted() || loot->loot_type == LOOT_SKINNING)) return SPELL_FAILED_TARGET_NOT_LOOTED; uint32 skill = creature->GetCreatureTemplate()->GetRequiredLootSkill(); diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index e4e9179dc37..b09841cac2a 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -3733,12 +3733,12 @@ void Spell::EffectSkinning() uint32 skill = creature->GetCreatureTemplate()->GetRequiredLootSkill(); - creature->RemoveUnitFlag(UNIT_FLAG_SKINNABLE); + creature->SetUnitFlag3(UNIT_FLAG3_ALREADY_SKINNED); creature->SetDynamicFlag(UNIT_DYNFLAG_LOOTABLE); - creature->m_loot.reset(new Loot(creature->GetMap(), creature->GetGUID(), LOOT_SKINNING, nullptr)); - creature->m_loot->FillLoot(creature->GetCreatureTemplate()->SkinLootId, LootTemplates_Skinning, player, true); - creature->SetLootRecipient(player, false); - player->SendLoot(*creature->m_loot); + Loot* loot = new Loot(creature->GetMap(), creature->GetGUID(), LOOT_SKINNING, nullptr); + creature->m_personalLoot[player->GetGUID()].reset(loot); + loot->FillLoot(creature->GetCreatureTemplate()->SkinLootId, LootTemplates_Skinning, player, true); + player->SendLoot(*loot); if (skill == SKILL_SKINNING) { diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index c242f96ae94..26e2baa9c81 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -546,9 +546,15 @@ public: if (!target) return false; - handler->PSendSysMessage("Loot recipient for creature %s (%s, SpawnID " UI64FMTD ") is %s", - target->GetName().c_str(), target->GetGUID().ToString().c_str(), target->GetSpawnId(), - target->hasLootRecipient() ? (target->GetLootRecipient() ? target->GetLootRecipient()->GetName().c_str() : "offline") : "no loot recipient"); + handler->PSendSysMessage("Loot recipients for creature %s (%s, SpawnID " UI64FMTD ") are:", + target->GetName().c_str(), target->GetGUID().ToString().c_str(), target->GetSpawnId()); + + for (ObjectGuid tapperGuid : target->GetTapList()) + { + Player* tapper = ObjectAccessor::GetPlayer(*target, tapperGuid); + handler->PSendSysMessage("* %s", tapper ? tapper->GetName().c_str() : "offline"); + } + return true; } diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index 9340b0f292f..4305ba8b10e 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -1200,7 +1200,7 @@ public: } Loot const* loot = creatureTarget->m_loot.get(); - if (!creatureTarget->isDead() || !loot || loot->empty()) + if (!creatureTarget->isDead() || !loot || loot->isLooted()) { handler->PSendSysMessage(LANG_COMMAND_NOT_DEAD_OR_NO_LOOT, creatureTarget->GetName().c_str()); handler->SetSentErrorMessage(true); diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjalAI.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjalAI.cpp index a0e404c3d09..6247dbf990b 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjalAI.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjalAI.cpp @@ -433,7 +433,7 @@ void hyjalAI::EnterEvadeMode(EvadeReason /*why*/) if (me->IsAlive()) me->GetMotionMaster()->MoveTargetedHome(); - me->SetLootRecipient(nullptr); + me->SetTappedBy(nullptr); } void hyjalAI::JustEngagedWith(Unit* /*who*/) diff --git a/src/server/scripts/Kalimdor/zone_bloodmyst_isle.cpp b/src/server/scripts/Kalimdor/zone_bloodmyst_isle.cpp index 0b8f48a26e0..670f503a717 100644 --- a/src/server/scripts/Kalimdor/zone_bloodmyst_isle.cpp +++ b/src/server/scripts/Kalimdor/zone_bloodmyst_isle.cpp @@ -200,10 +200,8 @@ public: if (Creature* legoso = me->FindNearestCreature(NPC_LEGOSO, SIZE_OF_GRIDS)) { - Group* group = me->GetLootRecipientGroup(); - if (killer->GetGUID() == legoso->GetGUID() || - (group && group->IsMember(killer->GetGUID())) || + (killer->IsPlayer() && me->isTappedBy(killer->ToPlayer())) || killer->GetGUID() == legoso->AI()->GetGUID(DATA_EVENT_STARTER_GUID)) legoso->AI()->DoAction(ACTION_LEGOSO_SIRONAS_KILLED); } diff --git a/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/obsidian_sanctum.cpp b/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/obsidian_sanctum.cpp index 7b791385242..c5f211c538b 100644 --- a/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/obsidian_sanctum.cpp +++ b/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/obsidian_sanctum.cpp @@ -323,7 +323,7 @@ struct dummy_dragonAI : public ScriptedAI void JustDied(Unit* /*killer*/) override { if (!_canLoot) - me->SetLootRecipient(nullptr); + me->SetTappedBy(nullptr); uint32 spellId = 0; diff --git a/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_halion.cpp b/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_halion.cpp index 347a874d2d4..e0223f674be 100644 --- a/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_halion.cpp +++ b/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_halion.cpp @@ -452,8 +452,7 @@ class boss_twilight_halion : public CreatureScript if (Creature* halion = instance->GetCreature(DATA_HALION)) { // Ensure looting - if (me->IsDamageEnoughForLootingAndReward()) - halion->LowerPlayerDamageReq(halion->GetMaxHealth()); + halion->LowerPlayerDamageReq(halion->GetMaxHealth()); if (halion->IsAlive()) Unit::Kill(killer, halion); diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp index a4567be2607..c4043240376 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp @@ -327,8 +327,7 @@ struct boss_blood_council_controller : public BossAI if (Creature* prince = ObjectAccessor::GetCreature(*me, _invocationOrder[_invocationStage].guid)) { // Make sure looting is allowed - if (me->IsDamageEnoughForLootingAndReward()) - prince->LowerPlayerDamageReq(prince->GetMaxHealth()); + prince->LowerPlayerDamageReq(prince->GetMaxHealth()); Unit::Kill(killer, prince); } } diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp index 4e428ab041c..5211071b1ef 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp @@ -523,7 +523,7 @@ struct boss_the_lich_king : public BossAI me->SummonCreature(NPC_HIGHLORD_TIRION_FORDRING_LK, TirionSpawn, TEMPSUMMON_MANUAL_DESPAWN); } - void JustDied(Unit* /*killer*/) override + void JustDied(Unit* killer) override { _JustDied(); DoCastAOE(SPELL_PLAY_MOVIE, false); @@ -535,7 +535,7 @@ struct boss_the_lich_king : public BossAI me->GetMap()->SetZoneWeather(AREA_ICECROWN_CITADEL, WEATHER_STATE_FOG, 0.0f); if (Is25ManRaid()) - if (Player* player = me->GetLootRecipient()) + if (Player* player = Object::ToPlayer(killer)) player->RewardPlayerAndGroupAtEvent(NPC_THE_LICH_KING_QUEST, player); } diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp index 6b08bf1717d..a1dd29b48d1 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp @@ -318,7 +318,7 @@ struct boss_valithria_dreamwalker : public ScriptedAI void HealReceived(Unit* healer, uint32& heal) override { if (!me->hasLootRecipient()) - me->SetLootRecipient(healer); + me->SetTappedBy(healer); me->LowerPlayerDamageReq(heal); diff --git a/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp b/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp index 132980fae3f..2c243261b3d 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp @@ -609,7 +609,7 @@ class instance_icecrown_citadel : public InstanceMapScript case GO_CACHE_OF_THE_DREAMWALKER_10H: case GO_CACHE_OF_THE_DREAMWALKER_25H: if (Creature* valithria = instance->GetCreature(ValithriaDreamwalkerGUID)) - go->SetLootRecipient(valithria->GetLootRecipient(), valithria->GetLootRecipientGroup()); + go->SetTapList(valithria->GetTapList()); go->RemoveFlag(GO_FLAG_LOCKED | GO_FLAG_NOT_SELECTABLE | GO_FLAG_NODESPAWN); break; case GO_ARTHAS_PLATFORM: @@ -843,7 +843,7 @@ class instance_icecrown_citadel : public InstanceMapScript if (GameObject* loot = instance->GetGameObject(DeathbringersCacheGUID)) { if (Creature* deathbringer = instance->GetCreature(DeathbringerSaurfangGUID)) - loot->SetLootRecipient(deathbringer->GetLootRecipient(), deathbringer->GetLootRecipientGroup()); + loot->SetTapList(deathbringer->GetTapList()); loot->RemoveFlag(GO_FLAG_LOCKED | GO_FLAG_NOT_SELECTABLE | GO_FLAG_NODESPAWN); } diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_assembly_of_iron.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_assembly_of_iron.cpp index b4dbadd51f9..3e4451164c3 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_assembly_of_iron.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_assembly_of_iron.cpp @@ -207,7 +207,7 @@ class boss_steelbreaker : public CreatureScript } else { - me->SetLootRecipient(nullptr); + me->SetTappedBy(nullptr); Talk(SAY_STEELBREAKER_DEATH); //DoCastAOE(SPELL_SUPERCHARGE, true); @@ -355,7 +355,7 @@ class boss_runemaster_molgeim : public CreatureScript } else { - me->SetLootRecipient(nullptr); + me->SetTappedBy(nullptr); Talk(SAY_MOLGEIM_DEATH); //DoCastAOE(SPELL_SUPERCHARGE, true); @@ -534,7 +534,7 @@ class boss_stormcaller_brundir : public CreatureScript } else { - me->SetLootRecipient(nullptr); + me->SetTappedBy(nullptr); Talk(SAY_BRUNDIR_DEATH); //DoCastAOE(SPELL_SUPERCHARGE, true); diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp index e7dd5c1cf2f..02825349ad5 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/instance_ulduar.cpp @@ -674,7 +674,7 @@ class instance_ulduar : public InstanceMapScript { if (GameObject* cache = instance->GetGameObject(thorim->AI()->GetData(DATA_THORIM_HARDMODE) ? CacheOfStormsHardmodeGUID : CacheOfStormsGUID)) { - cache->SetLootRecipient(thorim->GetLootRecipient()); + cache->SetTapList(thorim->GetTapList()); cache->SetRespawnTime(cache->GetRespawnDelay()); cache->RemoveFlag(GO_FLAG_LOCKED); cache->RemoveFlag(GO_FLAG_NOT_SELECTABLE); |