Core/Loot: Move loot generation out of Player::SendLoot

This commit is contained in:
Shauren
2022-09-18 16:33:38 +02:00
parent c00e2e4851
commit 8c20f620d7
7 changed files with 186 additions and 231 deletions

View File

@@ -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);

View File

@@ -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(); }

View File

@@ -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)

View File

@@ -44,20 +44,31 @@ public:
AELootCreatureCheck(Player* looter, ObjectGuid mainLootTarget) : _looter(looter), _mainLootTarget(mainLootTarget) { }
bool operator()(Creature* creature) const
bool operator()(Creature const* creature) const
{
return IsValidAELootTarget(creature);
}
bool IsValidLootTarget(Creature const* creature) const
{
if (creature->IsAlive())
return false;
if (creature->GetGUID() == _mainLootTarget)
return false;
if (!_looter->IsWithinDist(creature, LootDistance))
return false;
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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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);
}