aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2022-09-18 16:33:38 +0200
committerShauren <shauren.trinity@gmail.com>2022-09-18 16:33:38 +0200
commit8c20f620d7b070b13b40803095e5fa97f9349512 (patch)
tree0e5d718500cf8139945234cb1f36a53986e05dea /src
parentc00e2e4851498aa04ac1d8ff13a0759b245df72b (diff)
Core/Loot: Move loot generation out of Player::SendLoot
Diffstat (limited to 'src')
-rw-r--r--src/server/game/Entities/GameObject/GameObject.cpp67
-rw-r--r--src/server/game/Entities/GameObject/GameObject.h7
-rw-r--r--src/server/game/Entities/Player/Player.cpp178
-rw-r--r--src/server/game/Handlers/LootHandler.cpp54
-rw-r--r--src/server/game/Handlers/SpellHandler.cpp23
-rw-r--r--src/server/game/Loot/Loot.cpp6
-rw-r--r--src/server/game/Spells/SpellEffects.cpp82
7 files changed, 186 insertions, 231 deletions
diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp
index 5e506dd8be0..ed518b5cf7a 100644
--- a/src/server/game/Entities/GameObject/GameObject.cpp
+++ b/src/server/game/Entities/GameObject/GameObject.cpp
@@ -956,6 +956,8 @@ void GameObject::Update(uint32 diff)
// If there is no restock timer, or if the restock timer passed, the chest becomes ready to loot
m_restockTime = 0;
m_lootState = GO_READY;
+ m_loot = nullptr;
+ m_personalLoot.clear();
AddToObjectUpdateIfNeeded();
break;
default:
@@ -1176,11 +1178,16 @@ void GameObject::Update(uint32 diff)
if (m_loot)
m_loot->Update();
+ for (auto&& [playerOwner, loot] : m_personalLoot)
+ loot->Update();
+
// Non-consumable chest was partially looted and restock time passed, restock all loot now
- if (GetGOInfo()->chest.consumable == 0 && GameTime::GetGameTime() >= m_restockTime)
+ if (GetGOInfo()->chest.consumable == 0 && GetGOInfo()->chest.chestRestockTime && GameTime::GetGameTime() >= m_restockTime)
{
m_restockTime = 0;
m_lootState = GO_READY;
+ m_loot = nullptr;
+ m_personalLoot.clear();
AddToObjectUpdateIfNeeded();
}
break;
@@ -1255,6 +1262,7 @@ void GameObject::Update(uint32 diff)
}
m_loot = nullptr;
+ m_personalLoot.clear();
// Do not delete chests or goobers that are not consumed on loot, while still allowing them to despawn when they expire if summoned
bool isSummonedAndExpired = (GetOwner() || GetSpellId()) && m_respawnTime == 0;
@@ -1405,44 +1413,48 @@ void GameObject::SendGameObjectDespawn()
SendMessageToSet(packet.Write(), true);
}
-void GameObject::getFishLoot(Loot* fishloot, Player* loot_owner)
+Loot* GameObject::GetFishLoot(Player* lootOwner)
{
- fishloot->clear();
-
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, loot_owner, 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.
+ 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.
{
//subzone no result,use zone loot
- fishloot->FillLoot(zone, LootTemplates_Fishing, loot_owner, true, true);
+ 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, loot_owner, true, true);
+ if (fishLoot->empty())
+ fishLoot->FillLoot(defaultzone, LootTemplates_Fishing, lootOwner, true, true);
}
+
+ return fishLoot;
}
-void GameObject::getFishLootJunk(Loot* fishloot, Player* loot_owner)
+Loot* GameObject::GetFishLootJunk(Player* lootOwner)
{
- fishloot->clear();
-
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, loot_owner, 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.
+ 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.
{
//use zone loot
- fishloot->FillLoot(zone, LootTemplates_Fishing, loot_owner, true, true, LOOT_MODE_JUNK_FISH);
- if (fishloot->empty())
+ 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, loot_owner, true, true, LOOT_MODE_JUNK_FISH);
+ fishLoot->FillLoot(defaultzone, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_JUNK_FISH);
}
+
+ return fishLoot;
}
void GameObject::SaveToDB()
@@ -2402,10 +2414,16 @@ void GameObject::Use(Unit* user)
SetLootState(GO_JUST_DEACTIVATED);
}
else
+ {
+ m_loot.reset(GetFishLoot(player));
player->SendLoot(GetGUID(), LOOT_FISHING);
+ }
}
else // If fishing skill is too low, send junk loot.
+ {
+ m_loot.reset(GetFishLootJunk(player));
player->SendLoot(GetGUID(), LOOT_FISHING_JUNK);
+ }
break;
}
case GO_JUST_DEACTIVATED: // nothing to do, will be deleted at next update
@@ -2617,6 +2635,10 @@ void GameObject::Use(Unit* user)
Player* player = user->ToPlayer();
+ Loot* loot = new Loot(GetMap(), GetGUID(), LOOT_FISHINGHOLE, nullptr);
+ loot->FillLoot(GetGOInfo()->GetLootId(), LootTemplates_Gameobject, player, true);
+ m_personalLoot[player->GetGUID()].reset(loot);
+
player->SendLoot(GetGUID(), LOOT_FISHINGHOLE);
player->UpdateCriteria(CriteriaType::CatchFishInFishingHole, GetGOInfo()->entry);
return;
@@ -3224,6 +3246,17 @@ bool GameObject::IsLootAllowedFor(Player const* player) const
return true;
}
+Loot* GameObject::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;
+}
+
GameObject* GameObject::GetLinkedTrap()
{
return ObjectAccessor::GetGameObject(*this, m_linkedTrap);
diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h
index fba6febb163..886b28b4cdb 100644
--- a/src/server/game/Entities/GameObject/GameObject.h
+++ b/src/server/game/Entities/GameObject/GameObject.h
@@ -225,8 +225,8 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject>
void DespawnOrUnsummon(Milliseconds delay = 0ms, Seconds forceRespawnTime = 0s);
void Delete();
void SendGameObjectDespawn();
- void getFishLoot(Loot* loot, Player* loot_owner);
- void getFishLootJunk(Loot* loot, Player* loot_owner);
+ Loot* GetFishLoot(Player* lootOwner);
+ Loot* GetFishLootJunk(Player* lootOwner);
bool HasFlag(GameObjectFlags flags) const { return (*m_gameObjectData->Flags & flags) != 0; }
void SetFlag(GameObjectFlags flags) { SetUpdateFieldFlagValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::Flags), flags); }
@@ -280,13 +280,14 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject>
void SaveRespawnTime(uint32 forceDelay = 0);
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);
bool IsLootAllowedFor(Player const* player) const;
bool HasLootRecipient() const { return !m_lootRecipient.IsEmpty() || !m_lootRecipientGroup.IsEmpty(); }
- Loot* GetLootForPlayer(Player const* /*player*/) const override { return m_loot.get(); }
+ Loot* GetLootForPlayer(Player const* /*player*/) const override;
GameObject* GetLinkedTrap();
void SetLinkedTrap(GameObject* linkedTrap) { m_linkedTrap = linkedTrap->GetGUID(); }
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index b56b414981d..e149b1a0bc3 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -8800,89 +8800,14 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type, bool aeLooting/* = fa
if (guid.IsGameObject())
{
GameObject* go = GetMap()->GetGameObject(guid);
- auto shouldLootRelease = [this](GameObject* go, LootType lootType) -> bool
- {
- // not check distance for GO in case owned GO (fishing bobber case, for example)
- // And permit out of range GO with no owner in case fishing hole
- if (!go)
- return true;
-
- switch (lootType)
- {
- case LOOT_FISHING:
- case LOOT_FISHING_JUNK:
- if (go->GetOwnerGUID() != GetGUID())
- return true;
- break;
- case LOOT_FISHINGHOLE:
- break;
- default:
- if (!go->IsWithinDistInMap(this))
- return true;
- break;
- }
- return false;
- };
-
- if (shouldLootRelease(go, loot_type))
+ if (!go)
{
SendLootRelease(guid);
return;
}
loot = go->GetLootForPlayer(this);
-
- // loot was generated and respawntime has passed since then, allow to recreate loot
- // to avoid bugs, this rule covers spawned gameobjects only
- // Don't allow to regenerate chest loot inside instances and raids, to avoid exploits with duplicate boss loot being given for some encounters
- if (go->isSpawnedByDefault() && go->getLootState() == GO_ACTIVATED && (!loot || !loot->isLooted()) && !go->GetMap()->Instanceable() && go->GetLootGenerationTime() + go->GetRespawnDelay() < GameTime::GetGameTime())
- go->SetLootState(GO_READY);
-
- if (go->getLootState() == GO_READY)
- {
- uint32 lootid = go->GetGOInfo()->GetLootId();
- if (Battleground* bg = GetBattleground())
- if (!bg->CanActivateGO(go->GetEntry(), bg->GetPlayerTeam(GetGUID())))
- {
- SendLootRelease(guid);
- return;
- }
-
- Group* group = GetGroup();
- bool groupRules = (group && go->GetGOInfo()->type == GAMEOBJECT_TYPE_CHEST && go->GetGOInfo()->chest.usegrouplootrules);
-
- loot = new Loot(GetMap(), guid, loot_type, groupRules ? group : nullptr);
- if (go->GetMap()->Is25ManRaid())
- loot->maxDuplicates = 3;
-
- go->m_loot.reset(loot);
-
- if (lootid)
- {
- // check current RR player and get next if necessary
- if (groupRules)
- group->UpdateLooterGuid(go, true);
-
- loot->FillLoot(lootid, LootTemplates_Gameobject, this, !groupRules, false, go->GetLootMode(), GetMap()->GetDifficultyLootItemContext());
- go->SetLootGenerationTime();
-
- // get next RR player (for next loot)
- if (groupRules && !loot->empty())
- group->UpdateLooterGuid(go);
- }
-
- if (go->GetLootMode() > 0)
- if (GameObjectTemplateAddon const* addon = go->GetTemplateAddon())
- loot->generateMoneyLoot(addon->Mingold, addon->Maxgold);
-
- if (loot_type == LOOT_FISHING)
- go->getFishLoot(loot, this);
- else if (loot_type == LOOT_FISHING_JUNK)
- go->getFishLootJunk(loot, this);
-
- go->SetLootState(GO_ACTIVATED, this);
- }
}
else if (guid.IsItem())
{
@@ -8895,44 +8820,12 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type, bool aeLooting/* = fa
}
loot = item->GetLootForPlayer(this);
-
- // If item doesn't already have loot, attempt to load it. If that
- // fails then this is first time opening, generate loot
- if (!item->m_lootGenerated && !sLootItemStorage->LoadStoredLoot(item, this))
- {
- item->m_lootGenerated = true;
- loot = new Loot(GetMap(), guid, loot_type, nullptr);
- item->m_loot.reset(loot);
-
- switch (loot_type)
- {
- case LOOT_DISENCHANTING:
- loot->FillLoot(ASSERT_NOTNULL(item->GetDisenchantLoot(this))->ID, LootTemplates_Disenchant, this, true);
- break;
- case LOOT_PROSPECTING:
- loot->FillLoot(item->GetEntry(), LootTemplates_Prospecting, this, true);
- break;
- case LOOT_MILLING:
- loot->FillLoot(item->GetEntry(), LootTemplates_Milling, this, true);
- break;
- default:
- loot->generateMoneyLoot(item->GetTemplate()->MinMoneyLoot, item->GetTemplate()->MaxMoneyLoot);
- loot->FillLoot(item->GetEntry(), LootTemplates_Item, this, true, loot->gold != 0);
-
- // Force save the loot and money items that were just rolled
- // Also saves the container item ID in Loot struct (not to DB)
- if (loot->gold > 0 || loot->unlootedCount > 0)
- sLootItemStorage->AddNewStoredLoot(item->GetGUID().GetCounter(), loot, this);
-
- break;
- }
- }
}
else if (guid.IsCorpse()) // remove insignia
{
Corpse* bones = ObjectAccessor::GetCorpse(*this, guid);
- if (!bones || !(loot_type == LOOT_CORPSE || loot_type == LOOT_INSIGNIA) || bones->GetType() != CORPSE_BONES)
+ if (!bones)
{
SendLootRelease(guid);
return;
@@ -8944,78 +8837,13 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type, bool aeLooting/* = fa
{
Creature* creature = GetMap()->GetCreature(guid);
- // must be in range and creature must be alive for pickpocket and must be dead for another loot
- if (!creature || creature->IsAlive() != (loot_type == LOOT_PICKPOCKETING) || (!aeLooting && !creature->IsWithinDistInMap(this, INTERACTION_DISTANCE)))
- {
- SendLootRelease(guid);
- return;
- }
-
- if (loot_type == LOOT_PICKPOCKETING && IsFriendlyTo(creature))
+ if (!creature)
{
SendLootRelease(guid);
return;
}
loot = creature->GetLootForPlayer(this);
-
- if (loot_type == LOOT_PICKPOCKETING)
- {
- if (!loot || loot->loot_type != LOOT_PICKPOCKETING)
- {
- if (creature->CanGeneratePickPocketLoot())
- {
- creature->StartPickPocketRefillTimer();
-
- loot = new Loot(GetMap(), creature->GetGUID(), LOOT_PICKPOCKETING, nullptr);
- creature->m_loot.reset(loot);
- if (uint32 lootid = creature->GetCreatureTemplate()->pickpocketLootId)
- loot->FillLoot(lootid, LootTemplates_Pickpocketing, this, true);
-
- // Generate extra money for pick pocket loot
- const uint32 a = urand(0, creature->GetLevel() / 2);
- const uint32 b = urand(0, GetLevel() / 2);
- loot->gold = uint32(10 * (a + b) * sWorld->getRate(RATE_DROP_MONEY));
- }
- else
- {
- SendLootError(loot->GetGUID(), guid, LOOT_ERROR_ALREADY_PICKPOCKETED);
- return;
- }
- } // else - still has pickpocket loot generated & not fully taken
- }
- else
- {
- // exploit fix
- if (!creature->HasDynamicFlag(UNIT_DYNFLAG_LOOTABLE))
- {
- SendLootError(loot->GetGUID(), guid, LOOT_ERROR_DIDNT_KILL);
- return;
- }
-
- // the player whose group may loot the corpse
- Player* recipient = creature->GetLootRecipient();
- Group* recipientGroup = creature->GetLootRecipientGroup();
- if (!recipient && !recipientGroup)
- {
- SendLootError(loot->GetGUID(), guid, LOOT_ERROR_DIDNT_KILL);
- return;
- }
-
- // if loot is already skinning loot then don't do anything else
- if (loot->loot_type == LOOT_SKINNING)
- {
- loot_type = LOOT_SKINNING;
- }
- else if (loot_type == LOOT_SKINNING)
- {
- loot->clear();
- loot->FillLoot(creature->GetCreatureTemplate()->SkinLootId, LootTemplates_Skinning, this, true);
-
- // Set new loot recipient
- creature->SetLootRecipient(this, false);
- }
- }
}
if (!guid.IsItem() && !aeLooting)
diff --git a/src/server/game/Handlers/LootHandler.cpp b/src/server/game/Handlers/LootHandler.cpp
index a367e18da8e..3d32e72e66e 100644
--- a/src/server/game/Handlers/LootHandler.cpp
+++ b/src/server/game/Handlers/LootHandler.cpp
@@ -44,12 +44,14 @@ public:
AELootCreatureCheck(Player* looter, ObjectGuid mainLootTarget) : _looter(looter), _mainLootTarget(mainLootTarget) { }
- bool operator()(Creature* creature) const
+ bool operator()(Creature const* creature) const
{
- if (creature->IsAlive())
- return false;
+ return IsValidAELootTarget(creature);
+ }
- if (creature->GetGUID() == _mainLootTarget)
+ bool IsValidLootTarget(Creature const* creature) const
+ {
+ if (creature->IsAlive())
return false;
if (!_looter->IsWithinDist(creature, LootDistance))
@@ -58,6 +60,15 @@ public:
return _looter->isAllowedToLoot(creature);
}
+ bool IsValidAELootTarget(Creature const* creature) const
+ {
+ if (creature->GetGUID() == _mainLootTarget)
+ return false;
+
+ return IsValidLootTarget(creature);
+ }
+
+private:
Player* _looter;
ObjectGuid _mainLootTarget;
};
@@ -201,14 +212,21 @@ void WorldSession::HandleLootOpcode(WorldPackets::Loot::LootUnit& packet)
if (!GetPlayer()->IsAlive() || !packet.Unit.IsCreatureOrVehicle())
return;
+ Creature* lootTarget = ObjectAccessor::GetCreature(*GetPlayer(), packet.Unit);
+ if (!lootTarget)
+ return;
+
+ AELootCreatureCheck check(_player, packet.Unit);
+ if (!check.IsValidLootTarget(lootTarget))
+ return;
+
// interrupt cast
if (GetPlayer()->IsNonMeleeSpellCast(false))
GetPlayer()->InterruptNonMeleeSpells(false);
GetPlayer()->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Looting);
- std::list<Creature*> corpses;
- AELootCreatureCheck check(_player, packet.Unit);
+ std::vector<Creature*> corpses;
Trinity::CreatureListSearcher<AELootCreatureCheck> searcher(_player, corpses, check);
Cell::VisitGridObjects(_player, searcher, AELootCreatureCheck::LootDistance);
@@ -246,6 +264,8 @@ void WorldSession::DoLootRelease(Loot* loot)
if (player->GetLootGUID() == lguid)
player->SetLootGUID(ObjectGuid::Empty);
+ //Player is not looking at loot list, he doesn't need to see updates on the loot list
+ loot->RemoveLooter(player->GetGUID());
player->SendLootRelease(lguid);
player->m_AELootView.erase(loot->GetGUID());
@@ -263,12 +283,7 @@ void WorldSession::DoLootRelease(Loot* loot)
if (!go || ((go->GetOwnerGUID() != player->GetGUID() && go->GetGoType() != GAMEOBJECT_TYPE_FISHINGHOLE) && !go->IsWithinDistInMap(player)))
return;
- if (go->GetGoType() == GAMEOBJECT_TYPE_DOOR)
- {
- // locked doors are opened with spelleffect openlock, prevent remove its as looted
- go->UseDoorOrButton();
- }
- else if (loot->isLooted() || go->GetGoType() == GAMEOBJECT_TYPE_FISHINGNODE)
+ if (loot->isLooted() || go->GetGoType() == GAMEOBJECT_TYPE_FISHINGNODE || go->GetGoType() == GAMEOBJECT_TYPE_FISHINGHOLE)
{
if (go->GetGoType() == GAMEOBJECT_TYPE_FISHINGHOLE)
{ // The fishing hole used once more
@@ -287,10 +302,6 @@ void WorldSession::DoLootRelease(Loot* loot)
{
// not fully looted object
go->SetLootState(GO_ACTIVATED, player);
-
- // if the round robin player release, reset it.
- if (player->GetGUID() == loot->roundRobinPlayer)
- loot->roundRobinPlayer.Clear();
}
}
else if (lguid.IsCorpse()) // ONLY remove insignia at BG
@@ -317,7 +328,7 @@ void WorldSession::DoLootRelease(Loot* loot)
if (loot->loot_type == LOOT_PROSPECTING || loot->loot_type == LOOT_MILLING)
{
pItem->m_lootGenerated = false;
- pItem->m_loot.reset();
+ pItem->m_loot = nullptr;
uint32 count = pItem->GetCount();
@@ -341,12 +352,6 @@ void WorldSession::DoLootRelease(Loot* loot)
if (!creature)
return;
- if (!creature->IsWithinDistInMap(player, AELootCreatureCheck::LootDistance))
- return;
-
- if (creature->IsAlive() != (loot && loot->loot_type == LOOT_PICKPOCKETING))
- return;
-
if (loot->isLooted())
{
creature->RemoveDynamicFlag(UNIT_DYNFLAG_LOOTABLE);
@@ -367,9 +372,6 @@ void WorldSession::DoLootRelease(Loot* loot)
creature->ForceUpdateFieldChange(creature->m_values.ModifyValue(&Object::m_objectData).ModifyValue(&UF::ObjectData::DynamicFlags));
}
}
-
- //Player is not looking at loot list, he doesn't need to see updates on the loot list
- loot->RemoveLooter(player->GetGUID());
}
void WorldSession::DoLootReleaseAll()
diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp
index f52c02a32f9..4b1559fc2a4 100644
--- a/src/server/game/Handlers/SpellHandler.cpp
+++ b/src/server/game/Handlers/SpellHandler.cpp
@@ -27,6 +27,8 @@
#include "Item.h"
#include "Log.h"
#include "Loot.h"
+#include "LootItemStorage.h"
+#include "LootMgr.h"
#include "Map.h"
#include "Player.h"
#include "ObjectAccessor.h"
@@ -199,10 +201,29 @@ void WorldSession::HandleOpenItemOpcode(WorldPackets::Spells::OpenItem& packet)
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_GIFT_BY_ITEM);
stmt->setUInt64(0, item->GetGUID().GetCounter());
_queryProcessor.AddCallback(CharacterDatabase.AsyncQuery(stmt)
- .WithPreparedCallback(std::bind(&WorldSession::HandleOpenWrappedItemCallback, this, item->GetPos(), item->GetGUID(), std::placeholders::_1)));
+ .WithPreparedCallback([this, pos = item->GetPos(), itemGuid = item->GetGUID()](PreparedQueryResult result)
+ {
+ HandleOpenWrappedItemCallback(pos, itemGuid, std::move(result));
+ }));
}
else
+ {
+ // If item doesn't already have loot, attempt to load it. If that
+ // fails then this is first time opening, generate loot
+ if (!item->m_lootGenerated && !sLootItemStorage->LoadStoredLoot(item, player))
+ {
+ Loot* loot = new Loot(player->GetMap(), item->GetGUID(), LOOT_ITEM, nullptr);
+ item->m_loot.reset(loot);
+ loot->generateMoneyLoot(item->GetTemplate()->MinMoneyLoot, item->GetTemplate()->MaxMoneyLoot);
+ loot->FillLoot(item->GetEntry(), LootTemplates_Item, player, true, loot->gold != 0);
+
+ // Force save the loot and money items that were just rolled
+ // Also saves the container item ID in Loot struct (not to DB)
+ if (loot->gold > 0 || loot->unlootedCount > 0)
+ sLootItemStorage->AddNewStoredLoot(item->GetGUID().GetCounter(), loot, player);
+ }
player->SendLoot(item->GetGUID(), LOOT_ITEM);
+ }
}
void WorldSession::HandleOpenWrappedItemCallback(uint16 pos, ObjectGuid itemGuid, PreparedQueryResult result)
diff --git a/src/server/game/Loot/Loot.cpp b/src/server/game/Loot/Loot.cpp
index 3c5e976245e..62d86b7ce0c 100644
--- a/src/server/game/Loot/Loot.cpp
+++ b/src/server/game/Loot/Loot.cpp
@@ -662,7 +662,10 @@ void Loot::NotifyItemRemoved(uint8 lootListId, Map const* map)
{
LootItem const& item = items[lootListId];
if (item.GetAllowedLooters().find(*itr) == item.GetAllowedLooters().end())
+ {
+ ++itr;
continue;
+ }
if (Player* player = ObjectAccessor::GetPlayer(map, *itr))
{
@@ -769,7 +772,8 @@ bool Loot::FillLoot(uint32 lootId, LootStore const& store, Player* lootOwner, bo
Group const* group = lootOwner->GetGroup();
if (!personal && group)
{
- roundRobinPlayer = lootOwner->GetGUID();
+ if (loot_type == LOOT_CORPSE)
+ roundRobinPlayer = lootOwner->GetGUID();
for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
if (Player const* player = itr->GetSource()) // should actually be looted object instead of lootOwner but looter has to be really close so doesnt really matter
diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp
index 61a0e645188..af66302df7f 100644
--- a/src/server/game/Spells/SpellEffects.cpp
+++ b/src/server/game/Spells/SpellEffects.cpp
@@ -1547,16 +1547,47 @@ void Spell::SendLoot(ObjectGuid guid, LootType loottype)
return;
case GAMEOBJECT_TYPE_CHEST:
- /// @todo possible must be moved to loot release (in different from linked triggering)
- if (gameObjTarget->GetGOInfo()->chest.triggeredEvent)
+ if (Battleground* bg = player->GetBattleground())
{
- TC_LOG_DEBUG("spells", "Chest ScriptStart id %u for GO " UI64FMTD, gameObjTarget->GetGOInfo()->chest.triggeredEvent, gameObjTarget->GetSpawnId());
- GameEvents::Trigger(gameObjTarget->GetGOInfo()->chest.triggeredEvent, player, gameObjTarget);
+ if (!bg->CanActivateGO(gameObjTarget->GetEntry(), bg->GetPlayerTeam(player->GetGUID())))
+ {
+ player->SendLootRelease(guid);
+ return;
+ }
}
- // triggering linked GO
- if (uint32 trapEntry = gameObjTarget->GetGOInfo()->chest.linkedTrap)
- gameObjTarget->TriggeringLinkedGameObject(trapEntry, player);
+ if (gameObjTarget->getLootState() == GO_READY)
+ {
+ if (uint32 lootId = gameObjTarget->GetGOInfo()->GetLootId())
+ {
+ gameObjTarget->SetLootGenerationTime();
+
+ Group const* group = player->GetGroup();
+ bool groupRules = group && gameObjTarget->GetGOInfo()->chest.usegrouplootrules;
+
+ Loot* loot = new Loot(gameObjTarget->GetMap(), guid, loottype, groupRules ? group : nullptr);
+ gameObjTarget->m_loot.reset(loot);
+
+ loot->FillLoot(lootId, LootTemplates_Gameobject, player, !groupRules, false, gameObjTarget->GetLootMode(), gameObjTarget->GetMap()->GetDifficultyLootItemContext());
+
+ if (gameObjTarget->GetLootMode() > 0)
+ if (GameObjectTemplateAddon const* addon = gameObjTarget->GetTemplateAddon())
+ loot->generateMoneyLoot(addon->Mingold, addon->Maxgold);
+ }
+
+ /// @todo possible must be moved to loot release (in different from linked triggering)
+ if (gameObjTarget->GetGOInfo()->chest.triggeredEvent)
+ {
+ TC_LOG_DEBUG("spells", "Chest ScriptStart id %u for GO " UI64FMTD, gameObjTarget->GetGOInfo()->chest.triggeredEvent, gameObjTarget->GetSpawnId());
+ GameEvents::Trigger(gameObjTarget->GetGOInfo()->chest.triggeredEvent, player, gameObjTarget);
+ }
+
+ // triggering linked GO
+ if (uint32 trapEntry = gameObjTarget->GetGOInfo()->chest.linkedTrap)
+ gameObjTarget->TriggeringLinkedGameObject(trapEntry, player);
+
+ gameObjTarget->SetLootState(GO_ACTIVATED, player);
+ }
// Don't return, let loots been taken
break;
@@ -2221,10 +2252,36 @@ void Spell::EffectPickPocket()
if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET)
return;
+ Player* player = m_caster->ToPlayer();
if (m_caster->GetTypeId() != TYPEID_PLAYER)
return;
- m_caster->ToPlayer()->SendLoot(unitTarget->GetGUID(), LOOT_PICKPOCKETING);
+ Creature* creature = Object::ToCreature(unitTarget);
+ if (!creature)
+ return;
+
+ if (creature->CanGeneratePickPocketLoot())
+ {
+ creature->StartPickPocketRefillTimer();
+
+ creature->m_loot.reset(new Loot(creature->GetMap(), creature->GetGUID(), LOOT_PICKPOCKETING, nullptr));
+ if (uint32 lootid = creature->GetCreatureTemplate()->pickpocketLootId)
+ creature->m_loot->FillLoot(lootid, LootTemplates_Pickpocketing, player, true);
+
+ // Generate extra money for pick pocket loot
+ const uint32 a = urand(0, creature->GetLevel() / 2);
+ const uint32 b = urand(0, player->GetLevel() / 2);
+ creature->m_loot->gold = uint32(10 * (a + b) * sWorld->getRate(RATE_DROP_MONEY));
+ }
+ else if (creature->m_loot)
+ {
+ if (creature->m_loot->loot_type == LOOT_PICKPOCKETING && creature->m_loot->isLooted())
+ player->SendLootError(creature->m_loot->GetGUID(), creature->GetGUID(), LOOT_ERROR_ALREADY_PICKPOCKETED);
+
+ return;
+ }
+
+ player->SendLoot(unitTarget->GetGUID(), LOOT_PICKPOCKETING);
}
void Spell::EffectAddFarsight()
@@ -3422,6 +3479,8 @@ void Spell::EffectDisEnchant()
if (Player* caster = m_caster->ToPlayer())
{
caster->UpdateCraftSkill(m_spellInfo);
+ itemTarget->m_loot.reset(new Loot(caster->GetMap(), itemTarget->GetGUID(), LOOT_DISENCHANTING, nullptr));
+ itemTarget->m_loot->FillLoot(ASSERT_NOTNULL(itemTarget->GetDisenchantLoot(caster))->ID, LootTemplates_Disenchant, caster, true);
caster->SendLoot(itemTarget->GetGUID(), LOOT_DISENCHANTING);
}
@@ -3781,6 +3840,9 @@ void Spell::EffectSkinning()
creature->RemoveUnitFlag(UNIT_FLAG_SKINNABLE);
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->GetGUID(), LOOT_SKINNING);
if (skill == SKILL_SKINNING)
@@ -4480,6 +4542,8 @@ void Spell::EffectProspecting()
player->UpdateGatherSkill(SKILL_JEWELCRAFTING, SkillValue, reqSkillValue);
}
+ itemTarget->m_loot.reset(new Loot(player->GetMap(), itemTarget->GetGUID(), LOOT_PROSPECTING, nullptr));
+ itemTarget->m_loot->FillLoot(itemTarget->GetEntry(), LootTemplates_Prospecting, player, true);
player->SendLoot(itemTarget->GetGUID(), LOOT_PROSPECTING);
}
@@ -4505,6 +4569,8 @@ void Spell::EffectMilling()
player->UpdateGatherSkill(SKILL_INSCRIPTION, SkillValue, reqSkillValue);
}
+ itemTarget->m_loot.reset(new Loot(player->GetMap(), itemTarget->GetGUID(), LOOT_MILLING, nullptr));
+ itemTarget->m_loot->FillLoot(itemTarget->GetEntry(), LootTemplates_Milling, player, true);
player->SendLoot(itemTarget->GetGUID(), LOOT_MILLING);
}