mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-15 23:20:36 +01:00
Core/Loot: Move loot generation out of Player::SendLoot
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user