mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2025-11-29 17:38:24 +08:00
3098 lines
110 KiB
C++
3098 lines
110 KiB
C++
/*
|
|
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Affero General Public License as published by the
|
|
* Free Software Foundation; either version 3 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "AccountMgr.h"
|
|
#include "BattlegroundAV.h"
|
|
#include "CellImpl.h"
|
|
#include "CreatureAISelector.h"
|
|
#include "DisableMgr.h"
|
|
#include "GameObjectAI.h"
|
|
#include "GameObjectModel.h"
|
|
#include "GameTime.h"
|
|
#include "GridNotifiersImpl.h"
|
|
#include "Group.h"
|
|
#include "GroupMgr.h"
|
|
#include "ObjectMgr.h"
|
|
#include "OutdoorPvPMgr.h"
|
|
#include "PoolMgr.h"
|
|
#include "ScriptMgr.h"
|
|
#include "SpellMgr.h"
|
|
#include "Transport.h"
|
|
#include "UpdateFieldFlags.h"
|
|
#include "World.h"
|
|
#include <G3D/Box.h>
|
|
#include <G3D/CoordinateFrame.h>
|
|
#include <G3D/Quat.h>
|
|
|
|
GameObject::GameObject() : WorldObject(), MovableMapObject(),
|
|
m_model(nullptr), m_goValue(), m_AI(nullptr)
|
|
{
|
|
m_objectType |= TYPEMASK_GAMEOBJECT;
|
|
m_objectTypeId = TYPEID_GAMEOBJECT;
|
|
|
|
m_updateFlag = (UPDATEFLAG_LOWGUID | UPDATEFLAG_STATIONARY_POSITION | UPDATEFLAG_POSITION | UPDATEFLAG_ROTATION);
|
|
|
|
m_valuesCount = GAMEOBJECT_END;
|
|
m_respawnTime = 0;
|
|
m_respawnDelayTime = 300;
|
|
m_despawnDelay = 0;
|
|
m_despawnRespawnTime = 0s;
|
|
m_restockTime = 0s;
|
|
m_lootState = GO_NOT_READY;
|
|
m_spawnedByDefault = true;
|
|
m_allowModifyDestructibleBuilding = true;
|
|
m_usetimes = 0;
|
|
m_spellId = 0;
|
|
m_cooldownTime = 0;
|
|
m_goInfo = nullptr;
|
|
m_goData = nullptr;
|
|
m_packedRotation = 0;
|
|
|
|
m_spawnId = 0;
|
|
|
|
m_lootRecipientGroup = 0;
|
|
m_groupLootTimer = 0;
|
|
lootingGroupLowGUID = 0;
|
|
m_lootGenerationTime = 0;
|
|
|
|
ResetLootMode(); // restore default loot mode
|
|
loot.sourceGameObject = this;
|
|
m_stationaryPosition.Relocate(0.0f, 0.0f, 0.0f, 0.0f);
|
|
}
|
|
|
|
GameObject::~GameObject()
|
|
{
|
|
delete m_AI;
|
|
delete m_model;
|
|
//if (m_uint32Values) // field array can be not exist if GameOBject not loaded
|
|
// CleanupsBeforeDelete();
|
|
}
|
|
|
|
bool GameObject::AIM_Initialize()
|
|
{
|
|
if (m_AI)
|
|
delete m_AI;
|
|
|
|
m_AI = FactorySelector::SelectGameObjectAI(this);
|
|
|
|
if (!m_AI)
|
|
return false;
|
|
|
|
m_AI->InitializeAI();
|
|
return true;
|
|
}
|
|
|
|
std::string const& GameObject::GetAIName() const
|
|
{
|
|
return sObjectMgr->GetGameObjectTemplate(GetEntry())->AIName;
|
|
}
|
|
|
|
void GameObject::CleanupsBeforeDelete(bool /*finalCleanup*/)
|
|
{
|
|
if (GetTransport() && !ToTransport())
|
|
{
|
|
GetTransport()->RemovePassenger(this);
|
|
SetTransport(nullptr);
|
|
m_movementInfo.transport.Reset();
|
|
m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_ONTRANSPORT);
|
|
}
|
|
|
|
if (IsInWorld())
|
|
RemoveFromWorld();
|
|
|
|
if (m_uint32Values) // field array can be not exist if GameOBject not loaded
|
|
RemoveFromOwner();
|
|
}
|
|
|
|
void GameObject::RemoveFromOwner()
|
|
{
|
|
ObjectGuid ownerGUID = GetOwnerGUID();
|
|
if (!ownerGUID)
|
|
return;
|
|
|
|
if (Unit* owner = ObjectAccessor::GetUnit(*this, ownerGUID))
|
|
{
|
|
owner->RemoveGameObject(this, false);
|
|
ASSERT(!GetOwnerGUID());
|
|
return;
|
|
}
|
|
|
|
LOG_DEBUG("entities.gameobject", "Delete GameObject ({} Entry: {} SpellId {} LinkedGO {}) that lost references to owner {} GO list.",
|
|
GetGUID().ToString(), GetGOInfo()->entry, m_spellId, GetGOInfo()->GetLinkedGameObjectEntry(), ownerGUID.ToString());
|
|
|
|
SetOwnerGUID(ObjectGuid::Empty);
|
|
}
|
|
|
|
void GameObject::AddToWorld()
|
|
{
|
|
///- Register the gameobject for guid lookup
|
|
if (!IsInWorld())
|
|
{
|
|
if (m_zoneScript)
|
|
m_zoneScript->OnGameObjectCreate(this);
|
|
|
|
GetMap()->GetObjectsStore().Insert<GameObject>(GetGUID(), this);
|
|
if (m_spawnId)
|
|
GetMap()->GetGameObjectBySpawnIdStore().insert(std::make_pair(m_spawnId, this));
|
|
|
|
if (m_model)
|
|
{
|
|
m_model->UpdatePosition();
|
|
GetMap()->InsertGameObjectModel(*m_model);
|
|
}
|
|
|
|
EnableCollision(GetGoState() == GO_STATE_READY || IsTransport()); // pussywizard: this startOpen is unneeded here, collision depends entirely on GOState
|
|
|
|
WorldObject::AddToWorld();
|
|
|
|
loot.sourceWorldObjectGUID = GetGUID();
|
|
|
|
sScriptMgr->OnGameObjectAddWorld(this);
|
|
}
|
|
}
|
|
|
|
void GameObject::RemoveFromWorld()
|
|
{
|
|
///- Remove the gameobject from the accessor
|
|
if (IsInWorld())
|
|
{
|
|
sScriptMgr->OnGameObjectRemoveWorld(this);
|
|
|
|
if (m_zoneScript)
|
|
m_zoneScript->OnGameObjectRemove(this);
|
|
|
|
RemoveFromOwner();
|
|
|
|
if (m_model)
|
|
if (GetMap()->ContainsGameObjectModel(*m_model))
|
|
GetMap()->RemoveGameObjectModel(*m_model);
|
|
|
|
if (Transport* transport = GetTransport())
|
|
transport->RemovePassenger(this, true);
|
|
|
|
// If linked trap exists, despawn it
|
|
if (GameObject* linkedTrap = GetLinkedTrap())
|
|
{
|
|
linkedTrap->Delete();
|
|
}
|
|
|
|
WorldObject::RemoveFromWorld();
|
|
|
|
if (m_spawnId)
|
|
Acore::Containers::MultimapErasePair(GetMap()->GetGameObjectBySpawnIdStore(), m_spawnId, this);
|
|
GetMap()->GetObjectsStore().Remove<GameObject>(GetGUID());
|
|
}
|
|
}
|
|
|
|
void GameObject::CheckRitualList()
|
|
{
|
|
if (m_unique_users.empty())
|
|
return;
|
|
|
|
uint32 animSpell = GetGOInfo()->summoningRitual.animSpell;
|
|
if (!animSpell)
|
|
animSpell = GetSpellId();
|
|
|
|
for (GuidSet::iterator itr = m_unique_users.begin(); itr != m_unique_users.end();)
|
|
{
|
|
if (*itr == GetOwnerGUID())
|
|
{
|
|
++itr;
|
|
continue;
|
|
}
|
|
|
|
bool erase = true;
|
|
if (Player* channeler = ObjectAccessor::GetPlayer(*this, *itr))
|
|
if (Spell* spell = channeler->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
|
|
if (spell->m_spellInfo->Id == animSpell)
|
|
erase = false;
|
|
|
|
if (erase)
|
|
m_unique_users.erase(itr++);
|
|
else
|
|
++itr;
|
|
}
|
|
}
|
|
|
|
void GameObject::ClearRitualList()
|
|
{
|
|
if (m_unique_users.empty())
|
|
return;
|
|
|
|
uint32 animSpell = GetGOInfo()->summoningRitual.animSpell;
|
|
if (!animSpell)
|
|
animSpell = GetSpellId();
|
|
|
|
for (ObjectGuid const& guid : m_unique_users)
|
|
{
|
|
if (Player* channeler = ObjectAccessor::GetPlayer(*this, guid))
|
|
if (Spell* spell = channeler->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
|
|
if (spell->m_spellInfo->Id == animSpell)
|
|
{
|
|
spell->SendChannelUpdate(0);
|
|
spell->finish();
|
|
}
|
|
}
|
|
|
|
m_unique_users.clear();
|
|
}
|
|
|
|
bool GameObject::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, uint32 phaseMask, float x, float y, float z, float ang, G3D::Quat const& rotation, uint32 animprogress, GOState go_state, uint32 artKit)
|
|
{
|
|
ASSERT(map);
|
|
SetMap(map);
|
|
|
|
Relocate(x, y, z, ang);
|
|
m_stationaryPosition.Relocate(x, y, z, ang);
|
|
if (!IsPositionValid())
|
|
{
|
|
LOG_ERROR("entities.gameobject", "Gameobject (GUID: {} Entry: {}) not created. Suggested coordinates isn't valid (X: {} Y: {})", guidlow, name_id, x, y);
|
|
return false;
|
|
}
|
|
|
|
SetPhaseMask(phaseMask, false);
|
|
|
|
UpdatePositionData();
|
|
|
|
SetZoneScript();
|
|
if (m_zoneScript)
|
|
{
|
|
name_id = m_zoneScript->GetGameObjectEntry(guidlow, name_id);
|
|
if (!name_id)
|
|
return false;
|
|
}
|
|
|
|
GameObjectTemplate const* goinfo = sObjectMgr->GetGameObjectTemplate(name_id);
|
|
if (!goinfo)
|
|
{
|
|
LOG_ERROR("sql.sql", "Gameobject (GUID: {} Entry: {}) not created: non-existing entry in `gameobject_template`. Map: {} (X: {} Y: {} Z: {})", guidlow, name_id, map->GetId(), x, y, z);
|
|
return false;
|
|
}
|
|
|
|
Object::_Create(guidlow, goinfo->entry, HighGuid::GameObject);
|
|
|
|
m_goInfo = goinfo;
|
|
|
|
if (goinfo->type >= MAX_GAMEOBJECT_TYPE)
|
|
{
|
|
LOG_ERROR("sql.sql", "Gameobject (GUID: {} Entry: {}) not created: non-existing GO type '{}' in `gameobject_template`. It will crash client if created.", guidlow, name_id, goinfo->type);
|
|
return false;
|
|
}
|
|
|
|
GameObjectAddon const* addon = sObjectMgr->GetGameObjectAddon(GetSpawnId());
|
|
|
|
// hackfix for the hackfix down below
|
|
switch (goinfo->entry)
|
|
{
|
|
// excluded ids from the hackfix below
|
|
// used switch since there should be more
|
|
case 181233: // maexxna portal effect
|
|
case 181575: // maexxna portal
|
|
case 20992: // theramore black shield
|
|
case 21042: // theramore guard badge
|
|
SetLocalRotation(rotation);
|
|
break;
|
|
default:
|
|
// xinef: hackfix - but make it possible to use original WorldRotation (using special gameobject addon data)
|
|
// pussywizard: temporarily calculate WorldRotation from orientation, do so until values in db are correct
|
|
if (addon && addon->invisibilityType == INVISIBILITY_GENERAL && addon->InvisibilityValue == 0)
|
|
{
|
|
SetLocalRotation(rotation);
|
|
}
|
|
else
|
|
{
|
|
SetLocalRotationAngles(NormalizeOrientation(GetOrientation()), 0.0f, 0.0f);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// pussywizard: no PathRotation for normal gameobjects
|
|
SetTransportPathRotation(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
SetObjectScale(goinfo->size);
|
|
|
|
if (GameObjectTemplateAddon const* templateAddon = GetTemplateAddon())
|
|
{
|
|
SetUInt32Value(GAMEOBJECT_FACTION, templateAddon->faction);
|
|
ReplaceAllGameObjectFlags((GameObjectFlags)templateAddon->flags);
|
|
}
|
|
|
|
SetEntry(goinfo->entry);
|
|
|
|
// set name for logs usage, doesn't affect anything ingame
|
|
SetName(goinfo->name);
|
|
|
|
// GAMEOBJECT_BYTES_1, index at 0, 1, 2 and 3
|
|
SetGoType(GameobjectTypes(goinfo->type));
|
|
|
|
if (IsInstanceGameobject())
|
|
{
|
|
if (InstanceScript* instance = GetInstanceScript())
|
|
{
|
|
switch (uint8 state = instance->GetStoredGameObjectState(GetSpawnId()))
|
|
{
|
|
case 0:
|
|
SetGoState(GO_STATE_READY);
|
|
SwitchDoorOrButton(true);
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
SetGoState((GOState)state);
|
|
break;
|
|
default:
|
|
SetGoState(go_state);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetGoState(go_state);
|
|
}
|
|
|
|
SetGoArtKit(artKit);
|
|
|
|
SetDisplayId(goinfo->displayId);
|
|
|
|
if (!m_model)
|
|
m_model = CreateModel();
|
|
|
|
switch (goinfo->type)
|
|
{
|
|
case GAMEOBJECT_TYPE_FISHINGHOLE:
|
|
SetGoAnimProgress(animprogress);
|
|
m_goValue.FishingHole.MaxOpens = urand(GetGOInfo()->fishinghole.minSuccessOpens, GetGOInfo()->fishinghole.maxSuccessOpens);
|
|
break;
|
|
case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING:
|
|
m_goValue.Building.Health = goinfo->building.intactNumHits + goinfo->building.damagedNumHits;
|
|
m_goValue.Building.MaxHealth = m_goValue.Building.Health;
|
|
SetGoAnimProgress(255);
|
|
break;
|
|
case GAMEOBJECT_TYPE_FISHINGNODE:
|
|
SetGoAnimProgress(0);
|
|
break;
|
|
case GAMEOBJECT_TYPE_TRAP:
|
|
if (GetGOInfo()->trap.stealthed)
|
|
{
|
|
m_stealth.AddFlag(STEALTH_TRAP);
|
|
m_stealth.AddValue(STEALTH_TRAP, 70);
|
|
}
|
|
|
|
if (GetGOInfo()->trap.invisible)
|
|
{
|
|
m_invisibility.AddFlag(INVISIBILITY_TRAP);
|
|
m_invisibility.AddValue(INVISIBILITY_TRAP, 300);
|
|
}
|
|
break;
|
|
default:
|
|
SetGoAnimProgress(animprogress);
|
|
break;
|
|
}
|
|
|
|
if (addon)
|
|
{
|
|
if (addon->InvisibilityValue)
|
|
{
|
|
m_invisibility.AddFlag(addon->invisibilityType);
|
|
m_invisibility.AddValue(addon->invisibilityType, addon->InvisibilityValue);
|
|
}
|
|
}
|
|
|
|
LastUsedScriptID = GetScriptId();
|
|
AIM_Initialize();
|
|
|
|
if (uint32 linkedEntry = GetGOInfo()->GetLinkedGameObjectEntry())
|
|
{
|
|
GameObject* linkedGO = new GameObject();
|
|
if (linkedGO->Create(map->GenerateLowGuid<HighGuid::GameObject>(), linkedEntry, map, phaseMask, x, y, z, ang, rotation, 255, GO_STATE_READY))
|
|
{
|
|
SetLinkedTrap(linkedGO);
|
|
map->AddToMap(linkedGO);
|
|
}
|
|
else
|
|
{
|
|
delete linkedGO;
|
|
}
|
|
}
|
|
|
|
// Check if GameObject is Large
|
|
if (goinfo->IsLargeGameObject())
|
|
SetVisibilityDistanceOverride(VisibilityDistanceType::Large);
|
|
|
|
// Check if GameObject is Infinite
|
|
if (goinfo->IsInfiniteGameObject())
|
|
SetVisibilityDistanceOverride(VisibilityDistanceType::Infinite);
|
|
|
|
return true;
|
|
}
|
|
|
|
void GameObject::Update(uint32 diff)
|
|
{
|
|
WorldObject::Update(diff);
|
|
|
|
if (AI())
|
|
AI()->UpdateAI(diff);
|
|
else if (!AIM_Initialize())
|
|
LOG_ERROR("entities.gameobject", "Could not initialize GameObjectAI");
|
|
|
|
if (m_despawnDelay)
|
|
{
|
|
if (m_despawnDelay > diff)
|
|
{
|
|
m_despawnDelay -= diff;
|
|
}
|
|
else
|
|
{
|
|
m_despawnDelay = 0;
|
|
DespawnOrUnsummon(0ms, m_despawnRespawnTime);
|
|
}
|
|
}
|
|
|
|
for (std::unordered_map<ObjectGuid, int32>::iterator itr = m_SkillupList.begin(); itr != m_SkillupList.end();)
|
|
{
|
|
if (itr->second > 0)
|
|
{
|
|
if (itr->second > static_cast<int32>(diff))
|
|
{
|
|
itr->second -= static_cast<int32>(diff);
|
|
++itr;
|
|
}
|
|
else
|
|
{
|
|
itr = m_SkillupList.erase(itr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++itr;
|
|
}
|
|
}
|
|
|
|
switch (m_lootState)
|
|
{
|
|
case GO_NOT_READY:
|
|
{
|
|
switch (GetGoType())
|
|
{
|
|
case GAMEOBJECT_TYPE_TRAP:
|
|
{
|
|
// Arming Time for GAMEOBJECT_TYPE_TRAP (6)
|
|
GameObjectTemplate const* goInfo = GetGOInfo();
|
|
// Bombs
|
|
if (goInfo->trap.type == 2)
|
|
m_cooldownTime = GameTime::GetGameTimeMS().count() + 10 * IN_MILLISECONDS; // Hardcoded tooltip value
|
|
else if (GetOwner())
|
|
m_cooldownTime = GameTime::GetGameTimeMS().count() + goInfo->trap.startDelay * IN_MILLISECONDS;
|
|
|
|
m_lootState = GO_READY;
|
|
break;
|
|
}
|
|
case GAMEOBJECT_TYPE_FISHINGNODE:
|
|
{
|
|
// fishing code (bobber ready)
|
|
if (GameTime::GetGameTime().count() > m_respawnTime - FISHING_BOBBER_READY_TIME)
|
|
{
|
|
// splash bobber (bobber ready now)
|
|
Unit* caster = GetOwner();
|
|
if (caster && caster->IsPlayer())
|
|
{
|
|
SetGoState(GO_STATE_ACTIVE);
|
|
ReplaceAllGameObjectFlags(GO_FLAG_NODESPAWN);
|
|
|
|
UpdateData udata;
|
|
WorldPacket packet;
|
|
BuildValuesUpdateBlockForPlayer(&udata, caster->ToPlayer());
|
|
udata.BuildPacket(packet);
|
|
caster->ToPlayer()->GetSession()->SendPacket(&packet);
|
|
|
|
SendCustomAnim(GetGoAnimProgress());
|
|
}
|
|
|
|
m_lootState = GO_READY; // can be successfully open with some chance
|
|
}
|
|
return;
|
|
}
|
|
case GAMEOBJECT_TYPE_SUMMONING_RITUAL:
|
|
{
|
|
if (GameTime::GetGameTimeMS().count() < m_cooldownTime)
|
|
return;
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
|
|
CheckRitualList();
|
|
|
|
if (GetUniqueUseCount() < info->summoningRitual.reqParticipants)
|
|
{
|
|
SetLootState(GO_READY);
|
|
return;
|
|
}
|
|
|
|
Unit* owner = GetOwner();
|
|
Unit* spellCaster = owner ? owner : ObjectAccessor::GetPlayer(*this, m_ritualOwnerGUID);
|
|
if (!spellCaster)
|
|
{
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
return;
|
|
}
|
|
|
|
uint32 spellId = info->summoningRitual.spellId;
|
|
|
|
if (spellId == 62330) // GO store nonexistent spell, replace by expected
|
|
{
|
|
// spell have reagent and mana cost but it not expected use its
|
|
// it triggered spell in fact casted at currently channeled GO
|
|
spellId = 61993;
|
|
}
|
|
|
|
// Cast casterTargetSpell at a random GO user
|
|
// on the current DB there is only one gameobject that uses this (Ritual of Doom)
|
|
// and its required target number is 1 (outter for loop will run once)
|
|
if (info->summoningRitual.casterTargetSpell && info->summoningRitual.casterTargetSpell != 1) // No idea why this field is a bool in some cases
|
|
for (uint32 i = 0; i < info->summoningRitual.casterTargetSpellTargets; i++)
|
|
// m_unique_users can contain only player GUIDs
|
|
if (Player* target = ObjectAccessor::GetPlayer(*this, Acore::Containers::SelectRandomContainerElement(m_unique_users)))
|
|
spellCaster->CastSpell(target, info->summoningRitual.casterTargetSpell, true);
|
|
|
|
// finish owners spell
|
|
// xinef: properly process event cooldowns
|
|
if (owner)
|
|
{
|
|
if (Spell* spell = owner->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
|
|
{
|
|
spell->SendChannelUpdate(0);
|
|
spell->finish(false);
|
|
}
|
|
}
|
|
|
|
// can be deleted now
|
|
if (!info->summoningRitual.ritualPersistent)
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
else
|
|
SetLootState(GO_READY);
|
|
|
|
ClearRitualList();
|
|
spellCaster->CastSpell(spellCaster, spellId, true);
|
|
return;
|
|
}
|
|
case GAMEOBJECT_TYPE_CHEST:
|
|
if (m_restockTime > GameTime::GetGameTime())
|
|
{
|
|
return;
|
|
}
|
|
// If there is no restock timer, or if the restock timer passed, the chest becomes ready to loot
|
|
m_restockTime = 0s;
|
|
m_lootState = GO_READY;
|
|
AddToObjectUpdateIfNeeded();
|
|
break;
|
|
default:
|
|
m_lootState = GO_READY; // for other GOis same switched without delay to GO_READY
|
|
break;
|
|
}
|
|
[[fallthrough]];
|
|
}
|
|
case GO_READY:
|
|
{
|
|
if (m_respawnTime > 0) // timer on
|
|
{
|
|
time_t now = GameTime::GetGameTime().count();
|
|
if (m_respawnTime <= now) // timer expired
|
|
{
|
|
ObjectGuid dbtableHighGuid = ObjectGuid::Create<HighGuid::GameObject>(GetEntry(), m_spawnId);
|
|
time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid);
|
|
if (linkedRespawntime) // Can't respawn, the master is dead
|
|
{
|
|
ObjectGuid targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid);
|
|
if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day)
|
|
SetRespawnTime(DAY);
|
|
else
|
|
m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + urand(5, MINUTE); // else copy time from master and add a little
|
|
SaveRespawnTime(); // also save to DB immediately
|
|
return;
|
|
}
|
|
|
|
m_respawnTime = 0;
|
|
m_SkillupList.clear();
|
|
m_usetimes = 0;
|
|
|
|
switch (GetGoType())
|
|
{
|
|
case GAMEOBJECT_TYPE_FISHINGNODE: // can't fish now
|
|
{
|
|
Unit* caster = GetOwner();
|
|
if (caster && caster->IsPlayer())
|
|
{
|
|
caster->ToPlayer()->RemoveGameObject(this, false);
|
|
|
|
WorldPacket data(SMSG_FISH_ESCAPED, 0);
|
|
caster->ToPlayer()->GetSession()->SendPacket(&data);
|
|
}
|
|
// can be delete
|
|
m_lootState = GO_JUST_DEACTIVATED;
|
|
return;
|
|
}
|
|
case GAMEOBJECT_TYPE_DOOR:
|
|
case GAMEOBJECT_TYPE_BUTTON:
|
|
//we need to open doors if they are closed (add there another condition if this code breaks some usage, but it need to be here for battlegrounds)
|
|
if (GetGoState() != GO_STATE_READY)
|
|
ResetDoorOrButton();
|
|
break;
|
|
case GAMEOBJECT_TYPE_FISHINGHOLE:
|
|
// Initialize a new max fish count on respawn
|
|
m_goValue.FishingHole.MaxOpens = urand(GetGOInfo()->fishinghole.minSuccessOpens, GetGOInfo()->fishinghole.maxSuccessOpens);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!m_spawnedByDefault) // despawn timer
|
|
{
|
|
// can be despawned or destroyed
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
return;
|
|
}
|
|
|
|
// Xinef: Call AI Reset (required for example in SmartAI to clear one time events)
|
|
if (AI())
|
|
AI()->Reset();
|
|
|
|
// respawn timer
|
|
uint32 poolid = m_spawnId ? sPoolMgr->IsPartOfAPool<GameObject>(m_spawnId) : 0;
|
|
if (poolid)
|
|
sPoolMgr->UpdatePool<GameObject>(poolid, m_spawnId);
|
|
else
|
|
GetMap()->AddToMap(this);
|
|
}
|
|
}
|
|
|
|
if (isSpawned())
|
|
{
|
|
// traps can have time and can not have
|
|
GameObjectTemplate const* goInfo = GetGOInfo();
|
|
if (goInfo->type == GAMEOBJECT_TYPE_TRAP)
|
|
{
|
|
if (GameTime::GetGameTimeMS().count() < m_cooldownTime)
|
|
break;
|
|
|
|
// Type 2 - Bomb (will go away after casting it's spell)
|
|
if (goInfo->trap.type == 2)
|
|
{
|
|
SetLootState(GO_ACTIVATED);
|
|
break;
|
|
}
|
|
|
|
/// @todo This is activation radius. Casting radius must be selected from spell data.
|
|
/// @todo Move activated state code to GO_ACTIVATED, in this place just check for activation and set state.
|
|
float radius = float(goInfo->trap.diameter) * 0.5f;
|
|
if (!goInfo->trap.diameter)
|
|
{
|
|
// Cast in other case (at some triggering/linked go/etc explicit call)
|
|
if (goInfo->trap.cooldown != 3 || m_respawnTime > 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
radius = 3.f;
|
|
}
|
|
|
|
// Type 0 and 1 - trap (type 0 will not get removed after casting a spell)
|
|
Unit* owner = GetOwner();
|
|
Unit* target = nullptr; // pointer to appropriate target if found any
|
|
|
|
// Note: this hack with search required until GO casting not implemented
|
|
// search unfriendly creature
|
|
if (owner && goInfo->trap.autoCloseTime != -1) // hunter trap
|
|
{
|
|
Acore::NearestAttackableNoTotemUnitInObjectRangeCheck checker(this, owner, radius);
|
|
Acore::UnitSearcher<Acore::NearestAttackableNoTotemUnitInObjectRangeCheck> searcher(this, target, checker);
|
|
Cell::VisitObjects(this, searcher, radius);
|
|
}
|
|
else // environmental trap
|
|
{
|
|
// environmental damage spells already have around enemies targeting but this not help in case not existed GO casting support
|
|
// affect only players
|
|
Player* player = nullptr;
|
|
Acore::AnyPlayerInObjectRangeCheck checker(this, radius, true, true);
|
|
Acore::PlayerSearcher<Acore::AnyPlayerInObjectRangeCheck> searcher(this, player, checker);
|
|
Cell::VisitObjects(this, searcher, radius);
|
|
target = player;
|
|
}
|
|
|
|
if (target)
|
|
{
|
|
SetLootState(GO_ACTIVATED, target);
|
|
}
|
|
}
|
|
else if (uint32 max_charges = goInfo->GetCharges())
|
|
{
|
|
if (m_usetimes >= max_charges)
|
|
{
|
|
m_usetimes = 0;
|
|
SetLootState(GO_JUST_DEACTIVATED); // can be despawned or destroyed
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case GO_ACTIVATED:
|
|
{
|
|
switch (GetGoType())
|
|
{
|
|
case GAMEOBJECT_TYPE_DOOR:
|
|
case GAMEOBJECT_TYPE_BUTTON:
|
|
if (GetGOInfo()->GetAutoCloseTime() && GameTime::GetGameTimeMS().count() >= m_cooldownTime)
|
|
ResetDoorOrButton();
|
|
break;
|
|
case GAMEOBJECT_TYPE_GOOBER:
|
|
if (GameTime::GetGameTimeMS().count() >= m_cooldownTime)
|
|
{
|
|
RemoveGameObjectFlag(GO_FLAG_IN_USE);
|
|
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
}
|
|
break;
|
|
case GAMEOBJECT_TYPE_CHEST:
|
|
if (m_groupLootTimer)
|
|
{
|
|
if (m_groupLootTimer <= diff)
|
|
{
|
|
Group* group = sGroupMgr->GetGroupByGUID(lootingGroupLowGUID);
|
|
if (group)
|
|
group->EndRoll(&loot, GetMap());
|
|
m_groupLootTimer = 0;
|
|
lootingGroupLowGUID = 0;
|
|
}
|
|
else
|
|
{
|
|
m_groupLootTimer -= diff;
|
|
}
|
|
}
|
|
|
|
// Non-consumable chest was partially looted and restock time passed, restock all loot now
|
|
if (GetGOInfo()->chest.consumable == 0 && GameTime::GetGameTime() >= m_restockTime)
|
|
{
|
|
m_restockTime = 0s;
|
|
m_lootState = GO_READY;
|
|
AddToObjectUpdateIfNeeded();
|
|
}
|
|
break;
|
|
case GAMEOBJECT_TYPE_TRAP:
|
|
{
|
|
GameObjectTemplate const* goInfo = GetGOInfo();
|
|
if (goInfo->trap.type == 2)
|
|
{
|
|
if (goInfo->trap.spellId)
|
|
CastSpell(nullptr, goInfo->trap.spellId); // FIXME: null target won't work for target type 1
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
}
|
|
else if (Unit* target = ObjectAccessor::GetUnit(*this, _lootStateUnitGUID))
|
|
{
|
|
if (goInfo->trap.spellId)
|
|
CastSpell(target, goInfo->trap.spellId);
|
|
|
|
m_cooldownTime = GameTime::GetGameTimeMS().count() + (goInfo->trap.cooldown ? goInfo->trap.cooldown : uint32(4)) * IN_MILLISECONDS; // template or 4 seconds
|
|
|
|
if (goInfo->trap.type == 1)
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
else if (!goInfo->trap.type)
|
|
SetLootState(GO_READY);
|
|
|
|
// Battleground gameobjects have data2 == 0 && data5 == 3
|
|
if (!goInfo->trap.diameter && goInfo->trap.cooldown == 3)
|
|
if (Player* player = target->ToPlayer())
|
|
if (Battleground* bg = player->GetBattleground())
|
|
bg->HandleTriggerBuff(this);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case GO_JUST_DEACTIVATED:
|
|
{
|
|
// If nearby linked trap exists, despawn it
|
|
if (GameObject* linkedTrap = GetLinkedTrap())
|
|
{
|
|
linkedTrap->DespawnOrUnsummon();
|
|
}
|
|
|
|
//if Gameobject should cast spell, then this, but some GOs (type = 10) should be destroyed
|
|
if (GetGoType() == GAMEOBJECT_TYPE_GOOBER)
|
|
{
|
|
SetGoState(GO_STATE_READY);
|
|
|
|
//any return here in case battleground traps
|
|
// Xinef: Do not return here for summoned gos that should be deleted few lines below
|
|
// Xinef: Battleground objects are treated as spawned by default
|
|
if (GameObjectTemplateAddon const* addon = GetTemplateAddon())
|
|
if ((addon->flags & GO_FLAG_NODESPAWN) && isSpawnedByDefault())
|
|
return;
|
|
}
|
|
|
|
loot.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;
|
|
if ((GetGoType() == GAMEOBJECT_TYPE_CHEST || GetGoType() == GAMEOBJECT_TYPE_GOOBER) && !GetGOInfo()->IsDespawnAtAction() && !isSummonedAndExpired)
|
|
{
|
|
if (GetGoType() == GAMEOBJECT_TYPE_CHEST && GetGOInfo()->chest.chestRestockTime > 0)
|
|
{
|
|
// Start restock timer when the chest is fully looted
|
|
m_restockTime = GameTime::GetGameTime() + Seconds(GetGOInfo()->chest.chestRestockTime);
|
|
SetLootState(GO_NOT_READY);
|
|
AddToObjectUpdateIfNeeded();
|
|
}
|
|
else
|
|
{
|
|
SetLootState(GO_READY);
|
|
}
|
|
|
|
UpdateObjectVisibility();
|
|
return;
|
|
}
|
|
else if (GetOwnerGUID() || GetSpellId())
|
|
{
|
|
SetRespawnTime(0);
|
|
Delete();
|
|
return;
|
|
}
|
|
|
|
SetLootState(GO_READY);
|
|
|
|
//burning flags in some battlegrounds, if you find better condition, just add it
|
|
if (GetGOInfo()->IsDespawnAtAction() || GetGoAnimProgress() > 0)
|
|
{
|
|
SendObjectDeSpawnAnim(GetGUID());
|
|
//reset flags
|
|
if (GameObjectTemplateAddon const* addon = GetTemplateAddon())
|
|
ReplaceAllGameObjectFlags((GameObjectFlags)addon->flags);
|
|
}
|
|
|
|
if (!m_respawnDelayTime)
|
|
return;
|
|
|
|
if (!m_spawnedByDefault)
|
|
{
|
|
m_respawnTime = 0;
|
|
DestroyForVisiblePlayers(); // xinef: old UpdateObjectVisibility();
|
|
return;
|
|
}
|
|
|
|
uint32 dynamicRespawnDelay = GetMap()->ApplyDynamicModeRespawnScaling(this, m_respawnDelayTime);
|
|
m_respawnTime = GameTime::GetGameTime().count() + dynamicRespawnDelay;
|
|
|
|
// if option not set then object will be saved at grid unload
|
|
if (GetMap()->IsDungeon())
|
|
SaveRespawnTime();
|
|
|
|
DestroyForVisiblePlayers(); // xinef: old UpdateObjectVisibility();
|
|
break;
|
|
}
|
|
}
|
|
|
|
sScriptMgr->OnGameObjectUpdate(this, diff);
|
|
}
|
|
|
|
GameObjectTemplateAddon const* GameObject::GetTemplateAddon() const
|
|
{
|
|
return sObjectMgr->GetGameObjectTemplateAddon(GetGOInfo()->entry);
|
|
}
|
|
|
|
void GameObject::Refresh()
|
|
{
|
|
// not refresh despawned not casted GO (despawned casted GO destroyed in all cases anyway)
|
|
if (m_respawnTime > 0 && m_spawnedByDefault)
|
|
return;
|
|
|
|
if (isSpawned())
|
|
GetMap()->AddToMap(this);
|
|
}
|
|
|
|
void GameObject::AddUniqueUse(Player* player)
|
|
{
|
|
AddUse();
|
|
m_unique_users.insert(player->GetGUID());
|
|
}
|
|
|
|
void GameObject::DespawnOrUnsummon(Milliseconds delay, Seconds forceRespawnTime)
|
|
{
|
|
if (delay > 0ms)
|
|
{
|
|
if (!m_despawnDelay || m_despawnDelay > delay.count())
|
|
{
|
|
m_despawnDelay = delay.count();
|
|
m_despawnRespawnTime = forceRespawnTime;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_goData)
|
|
{
|
|
int32 const respawnDelay = (forceRespawnTime > 0s) ? forceRespawnTime.count() : m_goData->spawntimesecs;
|
|
SetRespawnTime(respawnDelay);
|
|
}
|
|
|
|
// Respawn is handled by the gameobject itself.
|
|
// If we delete it from world, it simply never respawns...
|
|
// Uncomment this and remove the following lines if dynamic spawn is implemented.
|
|
// Delete();
|
|
{
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
SendObjectDeSpawnAnim(GetGUID());
|
|
SetGoState(GO_STATE_READY);
|
|
|
|
if (GameObject* trap = GetLinkedTrap())
|
|
{
|
|
trap->DespawnOrUnsummon();
|
|
}
|
|
|
|
if (GameObjectTemplateAddon const* addon = GetTemplateAddon())
|
|
{
|
|
ReplaceAllGameObjectFlags((GameObjectFlags)addon->flags);
|
|
}
|
|
|
|
uint32 poolid = m_spawnId ? sPoolMgr->IsPartOfAPool<GameObject>(m_spawnId) : 0;
|
|
if (poolid)
|
|
{
|
|
sPoolMgr->UpdatePool<GameObject>(poolid, m_spawnId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GameObject::Delete()
|
|
{
|
|
SetLootState(GO_NOT_READY);
|
|
RemoveFromOwner();
|
|
|
|
SendObjectDeSpawnAnim(GetGUID());
|
|
|
|
SetGoState(GO_STATE_READY);
|
|
|
|
if (GameObjectTemplateAddon const* addon = GetTemplateAddon())
|
|
ReplaceAllGameObjectFlags((GameObjectFlags)addon->flags);
|
|
|
|
// Xinef: if ritual gameobject is removed, clear anim spells
|
|
if (GetGOInfo()->type == GAMEOBJECT_TYPE_SUMMONING_RITUAL)
|
|
ClearRitualList();
|
|
|
|
uint32 poolid = m_spawnId ? sPoolMgr->IsPartOfAPool<GameObject>(m_spawnId) : 0;
|
|
if (poolid)
|
|
sPoolMgr->UpdatePool<GameObject>(poolid, m_spawnId);
|
|
else
|
|
AddObjectToRemoveList();
|
|
}
|
|
|
|
void GameObject::GetFishLoot(Loot* fishLoot, Player* lootOwner, bool junk /*= false*/)
|
|
{
|
|
fishLoot->clear();
|
|
|
|
uint32 zone, area;
|
|
uint32 defaultZone = 1;
|
|
GetZoneAndAreaId(zone, area);
|
|
|
|
uint16 lootMode = junk ? LOOT_MODE_JUNK_FISH : LOOT_MODE_DEFAULT;
|
|
// Check to fill loot in the order area - zone - defaultZone.
|
|
// This is because area and zone is not set in some places, like Off the coast of Storm Peaks.
|
|
uint32 lootZones[] = { area, zone, defaultZone };
|
|
for (uint32 fillZone : lootZones)
|
|
{
|
|
fishLoot->FillLoot(fillZone, LootTemplates_Fishing, lootOwner, true, true, lootMode);
|
|
|
|
// If the loot is filled and the loot is eligible, then we break out of the loop.
|
|
if (!fishLoot->empty() && !fishLoot->isLooted())
|
|
break;
|
|
}
|
|
}
|
|
|
|
void GameObject::SaveToDB(bool saveAddon /*= false*/)
|
|
{
|
|
// this should only be used when the gameobject has already been loaded
|
|
// preferably after adding to map, because mapid may not be valid otherwise
|
|
GameObjectData const* data = sObjectMgr->GetGameObjectData(m_spawnId);
|
|
if (!data)
|
|
{
|
|
LOG_ERROR("entities.gameobject", "GameObject::SaveToDB failed, cannot get gameobject data!");
|
|
return;
|
|
}
|
|
|
|
SaveToDB(GetMapId(), data->spawnMask, data->phaseMask, saveAddon);
|
|
}
|
|
|
|
void GameObject::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask, bool saveAddon /*= false*/)
|
|
{
|
|
const GameObjectTemplate* goI = GetGOInfo();
|
|
|
|
if (!goI)
|
|
return;
|
|
|
|
if (!m_spawnId)
|
|
m_spawnId = sObjectMgr->GenerateGameObjectSpawnId();
|
|
|
|
// update in loaded data (changing data only in this place)
|
|
GameObjectData& data = sObjectMgr->NewGOData(m_spawnId);
|
|
|
|
data.id = GetEntry();
|
|
data.mapid = mapid;
|
|
data.phaseMask = phaseMask;
|
|
data.posX = GetPositionX();
|
|
data.posY = GetPositionY();
|
|
data.posZ = GetPositionZ();
|
|
data.orientation = GetOrientation();
|
|
data.rotation = m_localRotation;
|
|
data.spawntimesecs = m_spawnedByDefault ? m_respawnDelayTime : -(int32)m_respawnDelayTime;
|
|
data.animprogress = GetGoAnimProgress();
|
|
data.go_state = GetGoState();
|
|
data.spawnMask = spawnMask;
|
|
data.artKit = GetGoArtKit();
|
|
|
|
// Update in DB
|
|
WorldDatabaseTransaction trans = WorldDatabase.BeginTransaction();
|
|
|
|
uint8 index = 0;
|
|
|
|
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT);
|
|
stmt->SetData(0, m_spawnId);
|
|
trans->Append(stmt);
|
|
|
|
stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_GAMEOBJECT);
|
|
stmt->SetData(index++, m_spawnId);
|
|
stmt->SetData(index++, GetEntry());
|
|
stmt->SetData(index++, uint16(mapid));
|
|
stmt->SetData(index++, spawnMask);
|
|
stmt->SetData(index++, GetPhaseMask());
|
|
stmt->SetData(index++, GetPositionX());
|
|
stmt->SetData(index++, GetPositionY());
|
|
stmt->SetData(index++, GetPositionZ());
|
|
stmt->SetData(index++, GetOrientation());
|
|
stmt->SetData(index++, m_localRotation.x);
|
|
stmt->SetData(index++, m_localRotation.y);
|
|
stmt->SetData(index++, m_localRotation.z);
|
|
stmt->SetData(index++, m_localRotation.w);
|
|
stmt->SetData(index++, int32(m_respawnDelayTime));
|
|
stmt->SetData(index++, GetGoAnimProgress());
|
|
stmt->SetData(index++, uint8(GetGoState()));
|
|
trans->Append(stmt);
|
|
|
|
if (saveAddon && !sObjectMgr->GetGameObjectAddon(m_spawnId))
|
|
{
|
|
index = 0;
|
|
stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_GAMEOBJECT_ADDON);
|
|
stmt->SetData(index++, m_spawnId);
|
|
trans->Append(stmt);
|
|
}
|
|
|
|
WorldDatabase.CommitTransaction(trans);
|
|
sScriptMgr->OnGameObjectSaveToDB(this);
|
|
}
|
|
|
|
bool GameObject::LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap)
|
|
{
|
|
GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId);
|
|
|
|
if (!data)
|
|
{
|
|
LOG_ERROR("sql.sql", "Gameobject (GUID: {}) not found in table `gameobject`, can't load. ", spawnId);
|
|
return false;
|
|
}
|
|
|
|
uint32 entry = data->id;
|
|
//uint32 map_id = data->mapid; // already used before call
|
|
uint32 phaseMask = data->phaseMask;
|
|
float x = data->posX;
|
|
float y = data->posY;
|
|
float z = data->posZ;
|
|
float ang = data->orientation;
|
|
|
|
uint32 animprogress = data->animprogress;
|
|
GOState go_state = data->go_state;
|
|
uint32 artKit = data->artKit;
|
|
|
|
m_goData = data;
|
|
m_spawnId = spawnId;
|
|
|
|
if (!Create(map->GenerateLowGuid<HighGuid::GameObject>(), entry, map, phaseMask, x, y, z, ang, data->rotation, animprogress, go_state, artKit))
|
|
return false;
|
|
|
|
if (data->spawntimesecs >= 0)
|
|
{
|
|
m_spawnedByDefault = true;
|
|
|
|
if (!GetGOInfo()->GetDespawnPossibility() && !GetGOInfo()->IsDespawnAtAction())
|
|
{
|
|
SetGameObjectFlag(GO_FLAG_NODESPAWN);
|
|
m_respawnDelayTime = 0;
|
|
m_respawnTime = 0;
|
|
}
|
|
else
|
|
{
|
|
m_respawnDelayTime = data->spawntimesecs;
|
|
m_respawnTime = GetMap()->GetGORespawnTime(m_spawnId);
|
|
|
|
// ready to respawn
|
|
if (m_respawnTime && m_respawnTime <= GameTime::GetGameTime().count())
|
|
{
|
|
m_respawnTime = 0;
|
|
GetMap()->RemoveGORespawnTime(m_spawnId);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_spawnedByDefault = false;
|
|
m_respawnDelayTime = -data->spawntimesecs;
|
|
m_respawnTime = 0;
|
|
}
|
|
|
|
if (addToMap && !GetMap()->AddToMap(this))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void GameObject::DeleteFromDB()
|
|
{
|
|
GetMap()->RemoveGORespawnTime(m_spawnId);
|
|
sObjectMgr->DeleteGOData(m_spawnId);
|
|
|
|
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT);
|
|
stmt->SetData(0, m_spawnId);
|
|
WorldDatabase.Execute(stmt);
|
|
|
|
stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_EVENT_GAMEOBJECT);
|
|
stmt->SetData(0, m_spawnId);
|
|
WorldDatabase.Execute(stmt);
|
|
}
|
|
|
|
/*********************************************************/
|
|
/*** QUEST SYSTEM ***/
|
|
/*********************************************************/
|
|
bool GameObject::hasQuest(uint32 quest_id) const
|
|
{
|
|
QuestRelationBounds qr = sObjectMgr->GetGOQuestRelationBounds(GetEntry());
|
|
for (QuestRelations::const_iterator itr = qr.first; itr != qr.second; ++itr)
|
|
{
|
|
if (itr->second == quest_id)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool GameObject::hasInvolvedQuest(uint32 quest_id) const
|
|
{
|
|
QuestRelationBounds qir = sObjectMgr->GetGOQuestInvolvedRelationBounds(GetEntry());
|
|
for (QuestRelations::const_iterator itr = qir.first; itr != qir.second; ++itr)
|
|
{
|
|
if (itr->second == quest_id)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool GameObject::IsTransport() const
|
|
{
|
|
return GetGOInfo() && (GetGOInfo()->type == GAMEOBJECT_TYPE_TRANSPORT || GetGOInfo()->type == GAMEOBJECT_TYPE_MO_TRANSPORT);
|
|
}
|
|
|
|
bool GameObject::IsDestructibleBuilding() const
|
|
{
|
|
GameObjectTemplate const* gInfo = GetGOInfo();
|
|
if (!gInfo) return false;
|
|
return gInfo->type == GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING;
|
|
}
|
|
|
|
Unit* GameObject::GetOwner() const
|
|
{
|
|
return ObjectAccessor::GetUnit(*this, GetOwnerGUID());
|
|
}
|
|
|
|
void GameObject::SaveRespawnTime(uint32 forceDelay)
|
|
{
|
|
if (m_goData && m_goData->dbData && (forceDelay || m_respawnTime > GameTime::GetGameTime().count()) && m_spawnedByDefault)
|
|
{
|
|
time_t respawnTime = forceDelay ? GameTime::GetGameTime().count() + forceDelay : m_respawnTime;
|
|
GetMap()->SaveGORespawnTime(m_spawnId, respawnTime);
|
|
}
|
|
}
|
|
|
|
bool GameObject::IsNeverVisible() const
|
|
{
|
|
if (WorldObject::IsNeverVisible())
|
|
return true;
|
|
|
|
if (GetGoType() == GAMEOBJECT_TYPE_SPELL_FOCUS && GetGOInfo()->spellFocus.serverOnly == 1)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GameObject::IsAlwaysVisibleFor(WorldObject const* seer) const
|
|
{
|
|
if (WorldObject::IsAlwaysVisibleFor(seer))
|
|
return true;
|
|
|
|
if (IsTransport() || IsDestructibleBuilding())
|
|
return true;
|
|
|
|
if (!seer)
|
|
return false;
|
|
|
|
// Always seen by owner and friendly units
|
|
if (ObjectGuid guid = GetOwnerGUID())
|
|
{
|
|
if (seer->GetGUID() == guid)
|
|
return true;
|
|
|
|
Unit* owner = GetOwner();
|
|
if (owner)
|
|
{
|
|
if (seer->IsUnit() && owner->IsFriendlyTo(seer->ToUnit()))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GameObject::IsInvisibleDueToDespawn() const
|
|
{
|
|
if (WorldObject::IsInvisibleDueToDespawn())
|
|
return true;
|
|
|
|
// Despawned
|
|
if (!isSpawned())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void GameObject::SetRespawnTime(int32 respawn)
|
|
{
|
|
m_respawnTime = respawn > 0 ? GameTime::GetGameTime().count() + respawn : 0;
|
|
SetRespawnDelay(respawn);
|
|
if (respawn && !m_spawnedByDefault)
|
|
{
|
|
UpdateObjectVisibility(true);
|
|
}
|
|
}
|
|
|
|
void GameObject::SetRespawnDelay(int32 respawn)
|
|
{
|
|
m_respawnDelayTime = respawn > 0 ? respawn : 0;
|
|
}
|
|
|
|
void GameObject::Respawn()
|
|
{
|
|
if (m_spawnedByDefault && m_respawnTime > 0)
|
|
{
|
|
m_respawnTime = GameTime::GetGameTime().count();
|
|
GetMap()->RemoveGORespawnTime(m_spawnId);
|
|
}
|
|
}
|
|
|
|
bool GameObject::ActivateToQuest(Player* target) const
|
|
{
|
|
if (target->HasQuestForGO(GetEntry()))
|
|
return true;
|
|
|
|
if (!GetGOInfo()->IsGameObjectForQuests())
|
|
return false;
|
|
|
|
switch (GetGoType())
|
|
{
|
|
case GAMEOBJECT_TYPE_QUESTGIVER:
|
|
{
|
|
GameObject* go = const_cast<GameObject*>(this);
|
|
QuestGiverStatus questStatus = target->GetQuestDialogStatus(go);
|
|
if (questStatus > DIALOG_STATUS_UNAVAILABLE)
|
|
return true;
|
|
break;
|
|
}
|
|
case GAMEOBJECT_TYPE_CHEST:
|
|
{
|
|
// scan GO chest with loot including quest items
|
|
if (target->GetQuestStatus(GetGOInfo()->chest.questId) == QUEST_STATUS_INCOMPLETE || LootTemplates_Gameobject.HaveQuestLootForPlayer(GetGOInfo()->GetLootId(), target))
|
|
{
|
|
//TODO: fix this hack
|
|
//look for battlegroundAV for some objects which are only activated after mine gots captured by own team
|
|
if (GetEntry() == BG_AV_OBJECTID_MINE_N || GetEntry() == BG_AV_OBJECTID_MINE_S)
|
|
if (Battleground* bg = target->GetBattleground())
|
|
if (bg->GetBgTypeID(true) == BATTLEGROUND_AV && !bg->ToBattlegroundAV()->PlayerCanDoMineQuest(GetEntry(), target->GetTeamId()))
|
|
return false;
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case GAMEOBJECT_TYPE_GENERIC:
|
|
{
|
|
if (GetGOInfo()->_generic.questID == -1 || target->GetQuestStatus(GetGOInfo()->_generic.questID) == QUEST_STATUS_INCOMPLETE)
|
|
return true;
|
|
break;
|
|
}
|
|
case GAMEOBJECT_TYPE_SPELL_FOCUS:
|
|
{
|
|
if (GetGOInfo()->spellFocus.questID > 0 && target->GetQuestStatus(GetGOInfo()->spellFocus.questID) == QUEST_STATUS_INCOMPLETE)
|
|
return true;
|
|
break;
|
|
}
|
|
case GAMEOBJECT_TYPE_GOOBER:
|
|
{
|
|
if (GetGOInfo()->goober.questId == -1 || target->GetQuestStatus(GetGOInfo()->goober.questId) == QUEST_STATUS_INCOMPLETE)
|
|
return true;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void GameObject::TriggeringLinkedGameObject(uint32 trapEntry, Unit* target)
|
|
{
|
|
GameObjectTemplate const* trapInfo = sObjectMgr->GetGameObjectTemplate(trapEntry);
|
|
if (!trapInfo || trapInfo->type != GAMEOBJECT_TYPE_TRAP)
|
|
return;
|
|
|
|
SpellInfo const* trapSpell = sSpellMgr->GetSpellInfo(trapInfo->trap.spellId);
|
|
if (!trapSpell) // checked at load already
|
|
return;
|
|
|
|
// xinef: many spells have range 0 but radius > 0
|
|
float range = float(target->GetSpellMaxRangeForTarget(GetOwner(), trapSpell));
|
|
if (range < 1.0f)
|
|
range = 5.0f;
|
|
|
|
// found correct GO
|
|
// xinef: we should use the trap (checks for despawn type)
|
|
if (GameObject* trapGO = GetLinkedTrap())
|
|
{
|
|
trapGO->Use(target); // trapGO->CastSpell(target, trapInfo->trap.spellId);
|
|
}
|
|
}
|
|
|
|
GameObject* GameObject::LookupFishingHoleAround(float range)
|
|
{
|
|
GameObject* ok = nullptr;
|
|
|
|
Acore::NearestGameObjectFishingHole u_check(*this, range);
|
|
Acore::GameObjectSearcher<Acore::NearestGameObjectFishingHole> checker(this, ok, u_check);
|
|
|
|
Cell::VisitObjects(this, checker, range);
|
|
return ok;
|
|
}
|
|
|
|
void GameObject::ResetDoorOrButton()
|
|
{
|
|
if (m_lootState == GO_READY || m_lootState == GO_JUST_DEACTIVATED)
|
|
return;
|
|
|
|
SwitchDoorOrButton(false);
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
m_cooldownTime = 0;
|
|
}
|
|
|
|
void GameObject::UseDoorOrButton(uint32 time_to_restore, bool alternative /* = false */, Unit* user /*=nullptr*/)
|
|
{
|
|
if (m_lootState != GO_READY)
|
|
return;
|
|
|
|
if (!time_to_restore)
|
|
time_to_restore = GetGOInfo()->GetAutoCloseTime();
|
|
|
|
SwitchDoorOrButton(true, alternative);
|
|
SetLootState(GO_ACTIVATED, user);
|
|
|
|
m_cooldownTime = GameTime::GetGameTimeMS().count() + time_to_restore;
|
|
}
|
|
|
|
void GameObject::SetGoArtKit(uint8 kit)
|
|
{
|
|
SetByteValue(GAMEOBJECT_BYTES_1, 2, kit);
|
|
GameObjectData* data = const_cast<GameObjectData*>(sObjectMgr->GetGameObjectData(m_spawnId));
|
|
if (data)
|
|
data->artKit = kit;
|
|
}
|
|
|
|
void GameObject::SetGoArtKit(uint8 artkit, GameObject* go, ObjectGuid::LowType lowguid)
|
|
{
|
|
const GameObjectData* data = nullptr;
|
|
if (go)
|
|
{
|
|
go->SetGoArtKit(artkit);
|
|
data = go->GetGameObjectData();
|
|
}
|
|
else if (lowguid)
|
|
data = sObjectMgr->GetGameObjectData(lowguid);
|
|
|
|
if (data)
|
|
const_cast<GameObjectData*>(data)->artKit = artkit;
|
|
}
|
|
|
|
void GameObject::SwitchDoorOrButton(bool activate, bool alternative /* = false */)
|
|
{
|
|
if (activate)
|
|
SetGameObjectFlag(GO_FLAG_IN_USE);
|
|
else
|
|
RemoveGameObjectFlag(GO_FLAG_IN_USE);
|
|
|
|
if (GetGoState() == GO_STATE_READY) //if closed -> open
|
|
SetGoState(alternative ? GO_STATE_ACTIVE_ALTERNATIVE : GO_STATE_ACTIVE);
|
|
else //if open -> close
|
|
SetGoState(GO_STATE_READY);
|
|
}
|
|
|
|
void GameObject::Use(Unit* user)
|
|
{
|
|
// Xinef: we cannot use go with not selectable flags
|
|
if (HasGameObjectFlag(GO_FLAG_NOT_SELECTABLE))
|
|
return;
|
|
|
|
// by default spell caster is user
|
|
Unit* spellCaster = user;
|
|
uint32 spellId = 0;
|
|
bool triggered = false;
|
|
|
|
if (Player* playerUser = user->ToPlayer())
|
|
{
|
|
if (sScriptMgr->OnGossipHello(playerUser, this))
|
|
return;
|
|
|
|
if (AI()->GossipHello(playerUser, false))
|
|
return;
|
|
}
|
|
|
|
// If cooldown data present in template
|
|
if (uint32 cooldown = GetGOInfo()->GetCooldown())
|
|
{
|
|
if (GameTime::GetGameTimeMS().count() < m_cooldownTime)
|
|
return;
|
|
|
|
m_cooldownTime = GameTime::GetGameTimeMS().count() + cooldown * IN_MILLISECONDS;
|
|
}
|
|
|
|
switch (GetGoType())
|
|
{
|
|
case GAMEOBJECT_TYPE_DOOR: //0
|
|
//doors/buttons never really despawn, only reset to default state/flags
|
|
UseDoorOrButton(0, false, user);
|
|
return;
|
|
case GAMEOBJECT_TYPE_BUTTON: //1
|
|
//doors/buttons never really despawn, only reset to default state/flags
|
|
UseDoorOrButton(0, false, user);
|
|
|
|
// Xinef: properly link possible traps
|
|
if (uint32 trapEntry = GetGOInfo()->button.linkedTrap)
|
|
TriggeringLinkedGameObject(trapEntry, user);
|
|
return;
|
|
case GAMEOBJECT_TYPE_QUESTGIVER: //2
|
|
{
|
|
if (!user->IsPlayer())
|
|
return;
|
|
|
|
Player* player = user->ToPlayer();
|
|
|
|
player->PrepareGossipMenu(this, GetGOInfo()->questgiver.gossipID, true);
|
|
player->SendPreparedGossip(this);
|
|
return;
|
|
}
|
|
case GAMEOBJECT_TYPE_TRAP: //6
|
|
{
|
|
GameObjectTemplate const* goInfo = GetGOInfo();
|
|
if (goInfo->trap.spellId)
|
|
CastSpell(user, goInfo->trap.spellId);
|
|
|
|
m_cooldownTime = GameTime::GetGameTimeMS().count() + (goInfo->trap.cooldown ? goInfo->trap.cooldown : uint32(4)) * IN_MILLISECONDS; // template or 4 seconds
|
|
|
|
if (goInfo->trap.type == 1) // Deactivate after trigger
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
|
|
return;
|
|
}
|
|
//Sitting: Wooden bench, chairs enzz
|
|
case GAMEOBJECT_TYPE_CHAIR: //7
|
|
{
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
if (!info)
|
|
return;
|
|
|
|
if (!user->IsPlayer())
|
|
return;
|
|
|
|
if (ChairListSlots.empty()) // this is called once at first chair use to make list of available slots
|
|
{
|
|
if (info->chair.slots > 0) // sometimes chairs in DB have error in fields and we dont know number of slots
|
|
for (uint32 i = 0; i < info->chair.slots; ++i)
|
|
ChairListSlots[i].Clear(); // Last user of current slot set to 0 (none sit here yet)
|
|
else
|
|
ChairListSlots[0].Clear(); // error in DB, make one default slot
|
|
}
|
|
|
|
Player* player = user->ToPlayer();
|
|
|
|
// a chair may have n slots. we have to calculate their positions and teleport the player to the nearest one
|
|
|
|
float lowestDist = DEFAULT_VISIBILITY_DISTANCE;
|
|
|
|
uint32 nearest_slot = 0;
|
|
float x_lowest = GetPositionX();
|
|
float y_lowest = GetPositionY();
|
|
|
|
// the object orientation + 1/2 pi
|
|
// every slot will be on that straight line
|
|
float orthogonalOrientation = GetOrientation() + M_PI * 0.5f;
|
|
// find nearest slot
|
|
bool found_free_slot = false;
|
|
for (ChairSlotAndUser::iterator itr = ChairListSlots.begin(); itr != ChairListSlots.end(); ++itr)
|
|
{
|
|
// the distance between this slot and the center of the go - imagine a 1D space
|
|
float relativeDistance = (info->size * itr->first) - (info->size * (info->chair.slots - 1) / 2.0f);
|
|
|
|
float x_i = GetPositionX() + relativeDistance * cos(orthogonalOrientation);
|
|
float y_i = GetPositionY() + relativeDistance * std::sin(orthogonalOrientation);
|
|
|
|
if (itr->second)
|
|
{
|
|
if (Player* ChairUser = ObjectAccessor::GetPlayer(*this, itr->second))
|
|
{
|
|
if (ChairUser->IsSitState() && ChairUser->getStandState() != UNIT_STAND_STATE_SIT && ChairUser->GetExactDist2d(x_i, y_i) < 0.1f)
|
|
continue; // This seat is already occupied by ChairUser. NOTE: Not sure if the ChairUser->getStandState() != UNIT_STAND_STATE_SIT check is required.
|
|
else
|
|
itr->second.Clear(); // This seat is unoccupied.
|
|
}
|
|
else
|
|
itr->second.Clear(); // The seat may of had an occupant, but they're offline.
|
|
}
|
|
|
|
found_free_slot = true;
|
|
|
|
// calculate the distance between the player and this slot
|
|
float thisDistance = player->GetDistance2d(x_i, y_i);
|
|
|
|
if (thisDistance <= lowestDist)
|
|
{
|
|
nearest_slot = itr->first;
|
|
lowestDist = thisDistance;
|
|
x_lowest = x_i;
|
|
y_lowest = y_i;
|
|
}
|
|
}
|
|
|
|
if (found_free_slot)
|
|
{
|
|
ChairSlotAndUser::iterator itr = ChairListSlots.find(nearest_slot);
|
|
if (itr != ChairListSlots.end())
|
|
{
|
|
itr->second = player->GetGUID(); //this slot in now used by player
|
|
player->TeleportTo(GetMapId(), x_lowest, y_lowest, GetPositionZ(), GetOrientation(), TELE_TO_NOT_LEAVE_TRANSPORT | TELE_TO_NOT_LEAVE_COMBAT | TELE_TO_NOT_UNSUMMON_PET);
|
|
player->SetStandState(UNIT_STAND_STATE_SIT_LOW_CHAIR + info->chair.height);
|
|
return;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
//big gun, its a spell/aura
|
|
case GAMEOBJECT_TYPE_GOOBER: //10
|
|
{
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
|
|
// xinef: Goober cannot be used with this flag, skip
|
|
if (HasGameObjectFlag(GO_FLAG_IN_USE))
|
|
return;
|
|
|
|
if (user->IsPlayer())
|
|
{
|
|
Player* player = user->ToPlayer();
|
|
|
|
if (info->goober.pageId) // show page...
|
|
{
|
|
WorldPacket data(SMSG_GAMEOBJECT_PAGETEXT, 8);
|
|
data << GetGUID();
|
|
player->GetSession()->SendPacket(&data);
|
|
}
|
|
else if (info->goober.gossipID)
|
|
{
|
|
player->PrepareGossipMenu(this, info->goober.gossipID);
|
|
player->SendPreparedGossip(this);
|
|
}
|
|
|
|
if (info->goober.eventId)
|
|
{
|
|
LOG_DEBUG("maps.script", "Goober ScriptStart id {} for GO entry {} (spawnId {}).", info->goober.eventId, GetEntry(), m_spawnId);
|
|
GetMap()->ScriptsStart(sEventScripts, info->goober.eventId, player, this);
|
|
EventInform(info->goober.eventId);
|
|
}
|
|
|
|
// possible quest objective for active quests
|
|
if (info->goober.questId && sObjectMgr->GetQuestTemplate(info->goober.questId))
|
|
{
|
|
//Quest require to be active for GO using
|
|
if (player->GetQuestStatus(info->goober.questId) != QUEST_STATUS_INCOMPLETE)
|
|
break;
|
|
}
|
|
|
|
if (Battleground* bg = player->GetBattleground())
|
|
bg->EventPlayerUsedGO(player, this);
|
|
|
|
if (Group* group = player->GetGroup())
|
|
{
|
|
for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
|
{
|
|
if (Player* member = itr->GetSource())
|
|
{
|
|
if (member->IsAtGroupRewardDistance(this))
|
|
{
|
|
member->KillCreditGO(info->entry, GetGUID());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
player->KillCreditGO(info->entry, GetGUID());
|
|
}
|
|
}
|
|
|
|
if (uint32 trapEntry = info->goober.linkedTrapId)
|
|
TriggeringLinkedGameObject(trapEntry, user);
|
|
|
|
if (info->GetAutoCloseTime())
|
|
{
|
|
SetGameObjectFlag(GO_FLAG_IN_USE);
|
|
SetLootState(GO_ACTIVATED, user);
|
|
if (!info->goober.customAnim)
|
|
SetGoState(GO_STATE_ACTIVE);
|
|
}
|
|
|
|
// this appear to be ok, however others exist in addition to this that should have custom (ex: 190510, 188692, 187389)
|
|
if (info->goober.customAnim)
|
|
SendCustomAnim(GetGoAnimProgress());
|
|
|
|
m_cooldownTime = GameTime::GetGameTimeMS().count() + info->GetAutoCloseTime();
|
|
|
|
// cast this spell later if provided
|
|
spellId = info->goober.spellId;
|
|
spellCaster = user;
|
|
|
|
break;
|
|
}
|
|
case GAMEOBJECT_TYPE_CAMERA: //13
|
|
{
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
if (!info)
|
|
return;
|
|
|
|
if (!user->IsPlayer())
|
|
return;
|
|
|
|
Player* player = user->ToPlayer();
|
|
|
|
if (info->camera.cinematicId)
|
|
player->SendCinematicStart(info->camera.cinematicId);
|
|
|
|
if (info->camera.eventID)
|
|
{
|
|
GetMap()->ScriptsStart(sEventScripts, info->camera.eventID, player, this);
|
|
EventInform(info->camera.eventID);
|
|
}
|
|
|
|
return;
|
|
}
|
|
//fishing bobber
|
|
case GAMEOBJECT_TYPE_FISHINGNODE: //17
|
|
{
|
|
Player* player = user->ToPlayer();
|
|
if (!player)
|
|
return;
|
|
|
|
if (player->GetGUID() != GetOwnerGUID())
|
|
return;
|
|
|
|
switch (getLootState())
|
|
{
|
|
case GO_READY: // ready for loot
|
|
{
|
|
uint32 zone, subzone;
|
|
GetZoneAndAreaId(zone, subzone);
|
|
|
|
int32 zoneSkill = sObjectMgr->GetFishingBaseSkillLevel(subzone);
|
|
if (!zoneSkill)
|
|
zoneSkill = sObjectMgr->GetFishingBaseSkillLevel(zone);
|
|
|
|
//provide error, no fishable zone or area should be 0
|
|
if (!zoneSkill)
|
|
LOG_ERROR("sql.sql", "Fishable areaId {} are not properly defined in `skill_fishing_base_level`.", subzone);
|
|
|
|
// no miss skill is zone skill + 95 since at least patch 2.1
|
|
int32 const noMissSkill = zoneSkill + 95;
|
|
|
|
int32 const skill = player->GetSkillValue(SKILL_FISHING);
|
|
|
|
int32 chance;
|
|
// fishing pool catches are 100%
|
|
//TODO: find reasonable value for fishing hole search
|
|
GameObject* fishingHole = LookupFishingHoleAround(20.0f + CONTACT_DISTANCE);
|
|
if (fishingHole)
|
|
chance = 100;
|
|
else if (skill < noMissSkill)
|
|
{
|
|
chance = int32(pow((double)skill / noMissSkill, 2) * 100);
|
|
if (chance < 1)
|
|
chance = 1;
|
|
}
|
|
else
|
|
chance = 100;
|
|
|
|
int32 const roll = irand(1, 100);
|
|
|
|
LOG_DEBUG("entities.gameobject", "Fishing check (skill: {} zone min skill: {} no-miss skill: {} chance {} roll: {})", skill, zoneSkill, noMissSkill, chance, roll);
|
|
|
|
if (sScriptMgr->OnPlayerUpdateFishingSkill(player, skill, zoneSkill, chance, roll))
|
|
player->UpdateFishingSkill();
|
|
// but you will likely cause junk in areas that require a high fishing skill (not yet implemented)
|
|
if (chance >= roll)
|
|
{
|
|
//TODO: I do not understand this hack. Need some explanation.
|
|
// prevent removing GO at spell cancel
|
|
RemoveFromOwner();
|
|
SetOwnerGUID(player->GetGUID());
|
|
SetSpellId(0); // prevent removing unintended auras at Unit::RemoveGameObject
|
|
|
|
// fishing pool catch
|
|
if (fishingHole)
|
|
{
|
|
fishingHole->Use(player);
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
}
|
|
else
|
|
player->SendLoot(GetGUID(), LOOT_FISHING);
|
|
}
|
|
else // else: junk
|
|
player->SendLoot(GetGUID(), LOOT_FISHING_JUNK);
|
|
break;
|
|
}
|
|
case GO_JUST_DEACTIVATED: // nothing to do, will be deleted at next update
|
|
break;
|
|
default:
|
|
{
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
|
|
WorldPacket data(SMSG_FISH_NOT_HOOKED, 0);
|
|
player->GetSession()->SendPacket(&data);
|
|
break;
|
|
}
|
|
}
|
|
player->FinishSpell(CURRENT_CHANNELED_SPELL, true);
|
|
return;
|
|
}
|
|
|
|
case GAMEOBJECT_TYPE_SUMMONING_RITUAL: //18
|
|
{
|
|
if (!user->IsPlayer())
|
|
return;
|
|
|
|
Player* player = user->ToPlayer();
|
|
Unit* owner = GetOwner();
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
|
|
// ritual owner is set for GO's without owner (not summoned)
|
|
if (!m_ritualOwnerGUID && !owner)
|
|
m_ritualOwnerGUID = player->GetGUID();
|
|
|
|
if (owner)
|
|
{
|
|
if (!owner->IsPlayer())
|
|
return;
|
|
|
|
// accept only use by player from same group as owner, excluding owner itself (unique use already added in spell effect)
|
|
if (player == owner->ToPlayer() || (info->summoningRitual.castersGrouped && !player->IsInSameRaidWith(owner->ToPlayer())))
|
|
return;
|
|
|
|
// expect owner to already be channeling, so if not...
|
|
if (!owner->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Player* ritualOwner = ObjectAccessor::GetPlayer(*this, m_ritualOwnerGUID);
|
|
if (!ritualOwner)
|
|
return;
|
|
if (player != ritualOwner && (info->summoningRitual.castersGrouped && !player->IsInSameRaidWith(ritualOwner)))
|
|
return;
|
|
}
|
|
|
|
CheckRitualList();
|
|
|
|
if (GetUniqueUseCount() == info->summoningRitual.reqParticipants)
|
|
return;
|
|
|
|
if (info->summoningRitual.animSpell)
|
|
player->CastSpell(player, info->summoningRitual.animSpell, true);
|
|
else
|
|
player->CastSpell(player, GetSpellId(),
|
|
TriggerCastFlags(TRIGGERED_IGNORE_EFFECTS
|
|
| TRIGGERED_IGNORE_POWER_AND_REAGENT_COST
|
|
| TRIGGERED_CAST_DIRECTLY));
|
|
|
|
AddUniqueUse(player);
|
|
|
|
// full amount unique participants including original summoner
|
|
if (GetUniqueUseCount() == info->summoningRitual.reqParticipants)
|
|
{
|
|
SetLootState(GO_NOT_READY);
|
|
|
|
// channel ready, maintain this
|
|
m_cooldownTime = GameTime::GetGameTimeMS().count() + 5 * IN_MILLISECONDS;
|
|
}
|
|
|
|
return;
|
|
}
|
|
case GAMEOBJECT_TYPE_SPELLCASTER: //22
|
|
{
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
if (!info)
|
|
return;
|
|
|
|
if (info->spellcaster.partyOnly)
|
|
{
|
|
if (!user->IsPlayer())
|
|
return;
|
|
if (ObjectGuid ownerGuid = GetOwnerGUID())
|
|
{
|
|
if (user->GetGUID() != ownerGuid)
|
|
{
|
|
Group* group = user->ToPlayer()->GetGroup();
|
|
if (!group)
|
|
return;
|
|
if (!group->IsMember(ownerGuid))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
user->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
|
spellId = info->spellcaster.spellId;
|
|
break;
|
|
}
|
|
case GAMEOBJECT_TYPE_MEETINGSTONE: //23
|
|
{
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
|
|
if (!user->IsPlayer())
|
|
return;
|
|
|
|
Player* player = user->ToPlayer();
|
|
|
|
Player* targetPlayer = ObjectAccessor::FindPlayer(player->GetTarget());
|
|
|
|
// accept only use by player from same raid as caster
|
|
if (!targetPlayer || !targetPlayer->IsInSameRaidWith(player))
|
|
return;
|
|
|
|
//required lvl checks!
|
|
uint8 level = player->GetLevel();
|
|
if (level < info->meetingstone.minLevel)
|
|
return;
|
|
level = targetPlayer->GetLevel();
|
|
if (level < info->meetingstone.minLevel)
|
|
return;
|
|
|
|
spellId = 23598; // Meeting Stone Summon
|
|
|
|
break;
|
|
}
|
|
|
|
case GAMEOBJECT_TYPE_FLAGSTAND: // 24
|
|
{
|
|
if (!user->IsPlayer())
|
|
return;
|
|
|
|
Player* player = user->ToPlayer();
|
|
|
|
if (player->CanUseBattlegroundObject(this))
|
|
{
|
|
// in battleground check
|
|
Battleground* bg = player->GetBattleground();
|
|
if (!bg)
|
|
return;
|
|
|
|
if (player->GetVehicle())
|
|
return;
|
|
|
|
player->RemoveAurasByType(SPELL_AURA_MOD_STEALTH);
|
|
player->RemoveAurasByType(SPELL_AURA_MOD_INVISIBILITY);
|
|
// BG flag click
|
|
// AB:
|
|
// 15001
|
|
// 15002
|
|
// 15003
|
|
// 15004
|
|
// 15005
|
|
bg->EventPlayerClickedOnFlag(player, this);
|
|
return; //we don;t need to delete flag ... it is despawned!
|
|
}
|
|
break;
|
|
}
|
|
|
|
case GAMEOBJECT_TYPE_FISHINGHOLE: // 25
|
|
{
|
|
if (!user->IsPlayer())
|
|
return;
|
|
|
|
Player* player = user->ToPlayer();
|
|
|
|
player->SendLoot(GetGUID(), LOOT_FISHINGHOLE);
|
|
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_FISH_IN_GAMEOBJECT, GetGOInfo()->entry);
|
|
return;
|
|
}
|
|
|
|
case GAMEOBJECT_TYPE_FLAGDROP: // 26
|
|
{
|
|
if (!user->IsPlayer())
|
|
return;
|
|
|
|
Player* player = user->ToPlayer();
|
|
|
|
if (player->CanUseBattlegroundObject(this))
|
|
{
|
|
// in battleground check
|
|
Battleground* bg = player->GetBattleground();
|
|
if (!bg)
|
|
return;
|
|
|
|
if (player->GetVehicle())
|
|
return;
|
|
|
|
player->RemoveAurasByType(SPELL_AURA_MOD_STEALTH);
|
|
player->RemoveAurasByType(SPELL_AURA_MOD_INVISIBILITY);
|
|
// BG flag dropped
|
|
// WS:
|
|
// 179785 - Silverwing Flag
|
|
// 179786 - Warsong Flag
|
|
// EotS:
|
|
// 184142 - Netherstorm Flag
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
if (info)
|
|
{
|
|
if (GameObject::gameObjectToEventFlag.find(info->entry) != GameObject::gameObjectToEventFlag.end())
|
|
{
|
|
GameObject::gameObjectToEventFlag[info->entry](player, this, bg);
|
|
}
|
|
else
|
|
{
|
|
switch (info->entry)
|
|
{
|
|
case 179785: // Silverwing Flag
|
|
case 179786: // Warsong Flag
|
|
if (bg->GetBgTypeID(true) == BATTLEGROUND_WS)
|
|
bg->EventPlayerClickedOnFlag(player, this);
|
|
break;
|
|
case 184142: // Netherstorm Flag
|
|
if (bg->GetBgTypeID(true) == BATTLEGROUND_EY)
|
|
bg->EventPlayerClickedOnFlag(player, this);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//this cause to call return, all flags must be deleted here!!
|
|
spellId = 0;
|
|
Delete();
|
|
}
|
|
break;
|
|
}
|
|
case GAMEOBJECT_TYPE_BARBER_CHAIR: //32
|
|
{
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
if (!info)
|
|
return;
|
|
|
|
if (!user->IsPlayer())
|
|
return;
|
|
|
|
Player* player = user->ToPlayer();
|
|
|
|
// fallback, will always work
|
|
player->TeleportTo(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation(), TELE_TO_NOT_LEAVE_TRANSPORT | TELE_TO_NOT_LEAVE_COMBAT | TELE_TO_NOT_UNSUMMON_PET);
|
|
|
|
WorldPacket data(SMSG_ENABLE_BARBER_SHOP, 0);
|
|
player->GetSession()->SendPacket(&data);
|
|
|
|
player->SetStandState(UNIT_STAND_STATE_SIT_LOW_CHAIR + info->barberChair.chairheight);
|
|
return;
|
|
}
|
|
default:
|
|
if (GetGoType() >= MAX_GAMEOBJECT_TYPE)
|
|
LOG_ERROR("entities.gameobject", "GameObject::Use(): unit ({}, name: {}) tries to use object ({}, name: {}) of unknown type ({})",
|
|
user->GetGUID().ToString(), user->GetName(), GetGUID().ToString(), GetGOInfo()->name, GetGoType());
|
|
break;
|
|
}
|
|
|
|
if (!spellId)
|
|
return;
|
|
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
|
if (!spellInfo)
|
|
{
|
|
if (!user->IsPlayer() || !sOutdoorPvPMgr->HandleCustomSpell(user->ToPlayer(), spellId, this))
|
|
LOG_ERROR("entities.gameobject", "WORLD: unknown spell id {} at use action for gameobject (Entry: {} GoType: {})", spellId, GetEntry(), GetGoType());
|
|
else
|
|
LOG_DEBUG("outdoorpvp", "WORLD: {} non-dbc spell was handled by OutdoorPvP", spellId);
|
|
return;
|
|
}
|
|
|
|
if (Player* player = user->ToPlayer())
|
|
sOutdoorPvPMgr->HandleCustomSpell(player, spellId, this);
|
|
|
|
if (spellCaster)
|
|
{
|
|
if ((spellCaster->CastSpell(user, spellInfo, triggered) == SPELL_CAST_OK) && GetGoType() == GAMEOBJECT_TYPE_SPELLCASTER)
|
|
AddUse();
|
|
}
|
|
else
|
|
CastSpell(user, spellId);
|
|
}
|
|
|
|
void GameObject::CastSpell(Unit* target, uint32 spellId)
|
|
{
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
|
if (!spellInfo)
|
|
return;
|
|
|
|
bool self = true;
|
|
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
if (spellInfo->Effects[i].TargetA.GetReferenceType() != TARGET_REFERENCE_TYPE_CASTER || spellInfo->Effects[i].TargetB.GetTarget())
|
|
{
|
|
self = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (self && target && target->GetGUID() != GetGUID())
|
|
{
|
|
target->CastSpell(target, spellInfo, true);
|
|
return;
|
|
}
|
|
|
|
//summon world trigger
|
|
Creature* trigger = SummonTrigger(GetPositionX(), GetPositionY(), GetPositionZ(), 0, spellInfo->CalcCastTime() + 2000, true);
|
|
if (!trigger)
|
|
return;
|
|
|
|
if (Unit* owner = GetOwner())
|
|
{
|
|
trigger->SetLevel(owner->GetLevel(), false);
|
|
trigger->SetFaction(owner->GetFaction());
|
|
// needed for GO casts for proper target validation checks
|
|
trigger->SetOwnerGUID(owner->GetGUID());
|
|
// xinef: fixes some duel bugs with traps]
|
|
if (owner->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED))
|
|
trigger->SetUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED);
|
|
if (owner->IsFFAPvP())
|
|
{
|
|
if (!trigger->HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP))
|
|
{
|
|
sScriptMgr->OnFfaPvpStateUpdate(trigger, true);
|
|
trigger->SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP);
|
|
}
|
|
|
|
}
|
|
// xinef: Remove Immunity flags
|
|
trigger->SetImmuneToNPC(false);
|
|
// xinef: set proper orientation, fixes cast against stealthed targets
|
|
if (target)
|
|
trigger->SetInFront(target);
|
|
trigger->CastSpell(target ? target : trigger, spellInfo, true, 0, 0, owner->GetGUID());
|
|
}
|
|
else
|
|
{
|
|
// xinef: set faction of gameobject, if no faction - assume hostile
|
|
trigger->SetFaction(GetTemplateAddon() && GetTemplateAddon()->faction ? GetTemplateAddon()->faction : 14);
|
|
// Set owner guid for target if no owner availble - needed by trigger auras
|
|
// - trigger gets despawned and there's no caster avalible (see AuraEffect::TriggerSpell())
|
|
// xinef: set proper orientation, fixes cast against stealthed targets
|
|
if (target)
|
|
trigger->SetInFront(target);
|
|
trigger->CastSpell(target ? target : trigger, spellInfo, true, 0, 0, target ? target->GetGUID() : ObjectGuid::Empty);
|
|
}
|
|
}
|
|
|
|
void GameObject::SendCustomAnim(uint32 anim)
|
|
{
|
|
WorldPacket data(SMSG_GAMEOBJECT_CUSTOM_ANIM, 8 + 4);
|
|
data << GetGUID();
|
|
data << uint32(anim);
|
|
SendMessageToSet(&data, true);
|
|
}
|
|
|
|
bool GameObject::IsInRange(float x, float y, float z, float radius) const
|
|
{
|
|
GameObjectDisplayInfoEntry const* info = sGameObjectDisplayInfoStore.LookupEntry(m_goInfo->displayId);
|
|
if (!info)
|
|
return IsWithinDist3d(x, y, z, radius);
|
|
|
|
float sinA = std::sin(GetOrientation());
|
|
float cosA = cos(GetOrientation());
|
|
float dx = x - GetPositionX();
|
|
float dy = y - GetPositionY();
|
|
float dz = z - GetPositionZ();
|
|
float dist = std::sqrt(dx * dx + dy * dy);
|
|
//! Check if the distance between the 2 objects is 0, can happen if both objects are on the same position.
|
|
//! The code below this check wont crash if dist is 0 because 0/0 in float operations is valid, and returns infinite
|
|
if (G3D::fuzzyEq(dist, 0.0f))
|
|
return true;
|
|
|
|
float scale = GetObjectScale();
|
|
float sinB = dx / dist;
|
|
float cosB = dy / dist;
|
|
dx = dist * (cosA * cosB + sinA * sinB);
|
|
dy = dist * (cosA * sinB - sinA * cosB);
|
|
return dx < (info->maxX * scale) + radius && dx > (info->minX * scale) - radius
|
|
&& dy < (info->maxY * scale) + radius && dy > (info->minY * scale) - radius
|
|
&& dz < (info->maxZ * scale) + radius && dz > (info->minZ * scale) - radius;
|
|
}
|
|
|
|
void GameObject::EventInform(uint32 eventId)
|
|
{
|
|
if (!eventId)
|
|
return;
|
|
|
|
if (AI())
|
|
AI()->EventInform(eventId);
|
|
|
|
if (m_zoneScript)
|
|
m_zoneScript->ProcessEvent(this, eventId);
|
|
}
|
|
|
|
uint32 GameObject::GetScriptId() const
|
|
{
|
|
if (GameObjectData const* gameObjectData = GetGameObjectData())
|
|
if (uint32 scriptId = gameObjectData->ScriptId)
|
|
return scriptId;
|
|
|
|
return GetGOInfo()->ScriptId;
|
|
}
|
|
|
|
// overwrite WorldObject function for proper name localization
|
|
std::string const& GameObject::GetNameForLocaleIdx(LocaleConstant loc_idx) const
|
|
{
|
|
if (loc_idx != DEFAULT_LOCALE)
|
|
{
|
|
uint8 uloc_idx = uint8(loc_idx);
|
|
if (GameObjectLocale const* cl = sObjectMgr->GetGameObjectLocale(GetEntry()))
|
|
if (cl->Name.size() > uloc_idx && !cl->Name[uloc_idx].empty())
|
|
return cl->Name[uloc_idx];
|
|
}
|
|
|
|
return GetName();
|
|
}
|
|
|
|
void GameObject::UpdatePackedRotation()
|
|
{
|
|
static const int32 PACK_YZ = 1 << 20;
|
|
static const int32 PACK_X = PACK_YZ << 1;
|
|
static const int32 PACK_YZ_MASK = (PACK_YZ << 1) - 1;
|
|
static const int32 PACK_X_MASK = (PACK_X << 1) - 1;
|
|
int8 w_sign = (m_localRotation.w >= 0.f ? 1 : -1);
|
|
int64 x = int32(m_localRotation.x * PACK_X) * w_sign & PACK_X_MASK;
|
|
int64 y = int32(m_localRotation.y * PACK_YZ) * w_sign & PACK_YZ_MASK;
|
|
int64 z = int32(m_localRotation.z * PACK_YZ) * w_sign & PACK_YZ_MASK;
|
|
m_packedRotation = z | (y << 21) | (x << 42);
|
|
}
|
|
|
|
void GameObject::SetLocalRotation(G3D::Quat const& rot)
|
|
{
|
|
G3D::Quat rotation;
|
|
// Temporary solution for gameobjects that have no rotation data in DB:
|
|
if (G3D::fuzzyEq(rot.z, 0.f) && G3D::fuzzyEq(rot.w, 0.f))
|
|
rotation = G3D::Quat::fromAxisAngleRotation(G3D::Vector3::unitZ(), GetOrientation());
|
|
else
|
|
rotation = rot;
|
|
|
|
rotation.unitize();
|
|
m_localRotation = rotation;
|
|
UpdatePackedRotation();
|
|
}
|
|
|
|
void GameObject::SetTransportPathRotation(float qx, float qy, float qz, float qw)
|
|
{
|
|
SetFloatValue(GAMEOBJECT_PARENTROTATION + 0, qx);
|
|
SetFloatValue(GAMEOBJECT_PARENTROTATION + 1, qy);
|
|
SetFloatValue(GAMEOBJECT_PARENTROTATION + 2, qz);
|
|
SetFloatValue(GAMEOBJECT_PARENTROTATION + 3, qw);
|
|
}
|
|
|
|
void GameObject::SetLocalRotationAngles(float z_rot, float y_rot, float x_rot)
|
|
{
|
|
SetLocalRotation(G3D::Quat(G3D::Matrix3::fromEulerAnglesZYX(z_rot, y_rot, x_rot)));
|
|
}
|
|
|
|
G3D::Quat GameObject::GetWorldRotation() const
|
|
{
|
|
G3D::Quat localRotation = GetLocalRotation();
|
|
if (Transport* transport = GetTransport())
|
|
{
|
|
G3D::Quat worldRotation = transport->GetWorldRotation();
|
|
|
|
G3D::Quat worldRotationQuat(worldRotation.x, worldRotation.y, worldRotation.z, worldRotation.w);
|
|
G3D::Quat localRotationQuat(localRotation.x, localRotation.y, localRotation.z, localRotation.w);
|
|
|
|
G3D::Quat resultRotation = localRotationQuat * worldRotationQuat;
|
|
|
|
return G3D::Quat(resultRotation.x, resultRotation.y, resultRotation.z, resultRotation.w);
|
|
}
|
|
return localRotation;
|
|
}
|
|
|
|
void GameObject::ModifyHealth(int32 change, Unit* attackerOrHealer /*= nullptr*/, uint32 spellId /*= 0*/)
|
|
{
|
|
if (!IsDestructibleBuilding())
|
|
return;
|
|
|
|
// if this building doesn't have health, return
|
|
if (!m_goValue.Building.MaxHealth)
|
|
return;
|
|
|
|
sScriptMgr->OnGameObjectModifyHealth(this, attackerOrHealer, change, sSpellMgr->GetSpellInfo(spellId));
|
|
|
|
// if the health isn't being changed, return
|
|
if (!change)
|
|
return;
|
|
|
|
if (!m_allowModifyDestructibleBuilding)
|
|
change = 0;
|
|
|
|
// prevent double destructions of the same object
|
|
if (change < 0 && !m_goValue.Building.Health)
|
|
return;
|
|
|
|
if (int32(m_goValue.Building.Health) + change <= 0)
|
|
m_goValue.Building.Health = 0;
|
|
else if (int32(m_goValue.Building.Health) + change >= int32(m_goValue.Building.MaxHealth))
|
|
m_goValue.Building.Health = m_goValue.Building.MaxHealth;
|
|
else
|
|
m_goValue.Building.Health += change;
|
|
|
|
// Set the health bar, value = 255 * healthPct;
|
|
SetGoAnimProgress(m_goValue.Building.Health * 255 / m_goValue.Building.MaxHealth);
|
|
|
|
Player* player = attackerOrHealer->GetCharmerOrOwnerPlayerOrPlayerItself();
|
|
|
|
// dealing damage, send packet
|
|
/// @todo: is there any packet for healing?
|
|
if (player)
|
|
{
|
|
WorldPacket data(SMSG_DESTRUCTIBLE_BUILDING_DAMAGE, 8 + 8 + 8 + 4 + 4);
|
|
data << GetPackGUID();
|
|
data << attackerOrHealer->GetPackGUID();
|
|
data << player->GetPackGUID();
|
|
data << uint32(-change); // change < 0 triggers SPELL_BUILDING_HEAL combat log event
|
|
// change >= 0 triggers SPELL_BUILDING_DAMAGE event
|
|
data << uint32(spellId);
|
|
player->GetSession()->SendPacket(&data);
|
|
}
|
|
|
|
GameObjectDestructibleState newState = GetDestructibleState();
|
|
|
|
if (!m_goValue.Building.Health)
|
|
newState = GO_DESTRUCTIBLE_DESTROYED;
|
|
else if (m_goValue.Building.Health <= GetGOInfo()->building.damagedNumHits)
|
|
newState = GO_DESTRUCTIBLE_DAMAGED;
|
|
else if (m_goValue.Building.Health == m_goValue.Building.MaxHealth)
|
|
newState = GO_DESTRUCTIBLE_INTACT;
|
|
|
|
if (newState == GetDestructibleState())
|
|
return;
|
|
|
|
SetDestructibleState(newState, player, false);
|
|
}
|
|
|
|
void GameObject::SetDestructibleState(GameObjectDestructibleState state, Player* eventInvoker /*= nullptr*/, bool setHealth /*= false*/)
|
|
{
|
|
// the user calling this must know he is already operating on destructible gameobject
|
|
ASSERT(GetGoType() == GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING);
|
|
|
|
switch (state)
|
|
{
|
|
case GO_DESTRUCTIBLE_INTACT:
|
|
RemoveGameObjectFlag(GO_FLAG_DAMAGED | GO_FLAG_DESTROYED);
|
|
SetDisplayId(m_goInfo->displayId);
|
|
if (setHealth)
|
|
{
|
|
m_goValue.Building.Health = m_goValue.Building.MaxHealth;
|
|
SetGoAnimProgress(255);
|
|
}
|
|
EnableCollision(true);
|
|
break;
|
|
case GO_DESTRUCTIBLE_DAMAGED:
|
|
{
|
|
EventInform(m_goInfo->building.damagedEvent);
|
|
|
|
sScriptMgr->OnGameObjectDamaged(this, eventInvoker);
|
|
|
|
if (BattlegroundMap* bgMap = GetMap()->ToBattlegroundMap())
|
|
if (Battleground* bg = bgMap->GetBG())
|
|
bg->EventPlayerDamagedGO(eventInvoker, this, m_goInfo->building.damagedEvent);
|
|
|
|
RemoveGameObjectFlag(GO_FLAG_DESTROYED);
|
|
SetGameObjectFlag(GO_FLAG_DAMAGED);
|
|
|
|
uint32 modelId = m_goInfo->building.damagedDisplayId;
|
|
if (DestructibleModelDataEntry const* modelData = sDestructibleModelDataStore.LookupEntry(m_goInfo->building.destructibleData))
|
|
if (modelData->DamagedDisplayId)
|
|
modelId = modelData->DamagedDisplayId;
|
|
SetDisplayId(modelId);
|
|
|
|
if (setHealth)
|
|
{
|
|
m_goValue.Building.Health = m_goInfo->building.damagedNumHits;
|
|
uint32 maxHealth = m_goValue.Building.MaxHealth;
|
|
// in this case current health is 0 anyway so just prevent crashing here
|
|
if (!maxHealth)
|
|
maxHealth = 1;
|
|
SetGoAnimProgress(m_goValue.Building.Health * 255 / maxHealth);
|
|
}
|
|
break;
|
|
}
|
|
case GO_DESTRUCTIBLE_DESTROYED:
|
|
{
|
|
sScriptMgr->OnGameObjectDestroyed(this, eventInvoker);
|
|
|
|
EventInform(m_goInfo->building.destroyedEvent);
|
|
|
|
if (BattlegroundMap* bgMap = GetMap()->ToBattlegroundMap())
|
|
{
|
|
if (Battleground* bg = bgMap->GetBG())
|
|
{
|
|
bg->EventPlayerDamagedGO(eventInvoker, this, m_goInfo->building.destroyedEvent);
|
|
bg->DestroyGate(eventInvoker, this);
|
|
}
|
|
}
|
|
|
|
RemoveGameObjectFlag(GO_FLAG_DAMAGED);
|
|
SetGameObjectFlag(GO_FLAG_DESTROYED);
|
|
|
|
uint32 modelId = m_goInfo->building.destroyedDisplayId;
|
|
if (DestructibleModelDataEntry const* modelData = sDestructibleModelDataStore.LookupEntry(m_goInfo->building.destructibleData))
|
|
if (modelData->DestroyedDisplayId)
|
|
modelId = modelData->DestroyedDisplayId;
|
|
SetDisplayId(modelId);
|
|
|
|
if (setHealth)
|
|
{
|
|
m_goValue.Building.Health = 0;
|
|
SetGoAnimProgress(0);
|
|
}
|
|
EnableCollision(false);
|
|
break;
|
|
}
|
|
case GO_DESTRUCTIBLE_REBUILDING:
|
|
{
|
|
EventInform(m_goInfo->building.rebuildingEvent);
|
|
RemoveGameObjectFlag(GO_FLAG_DAMAGED | GO_FLAG_DESTROYED);
|
|
|
|
uint32 modelId = m_goInfo->displayId;
|
|
if (DestructibleModelDataEntry const* modelData = sDestructibleModelDataStore.LookupEntry(m_goInfo->building.destructibleData))
|
|
if (modelData->RebuildingDisplayId)
|
|
modelId = modelData->RebuildingDisplayId;
|
|
SetDisplayId(modelId);
|
|
|
|
// restores to full health
|
|
if (setHealth)
|
|
{
|
|
m_goValue.Building.Health = m_goValue.Building.MaxHealth;
|
|
SetGoAnimProgress(255);
|
|
}
|
|
EnableCollision(true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GameObject::SetLootState(LootState state, Unit* unit)
|
|
{
|
|
m_lootState = state;
|
|
|
|
if (unit)
|
|
_lootStateUnitGUID = unit->GetGUID();
|
|
else
|
|
_lootStateUnitGUID.Clear();
|
|
|
|
AI()->OnStateChanged(state, unit);
|
|
sScriptMgr->OnGameObjectLootStateChanged(this, state, unit);
|
|
|
|
// Start restock timer if the chest is partially looted or not looted at all
|
|
if (GetGoType() == GAMEOBJECT_TYPE_CHEST && state == GO_ACTIVATED && GetGOInfo()->chest.chestRestockTime > 0 && m_restockTime == 0s)
|
|
{
|
|
m_restockTime = GameTime::GetGameTime() + Seconds(GetGOInfo()->chest.chestRestockTime);
|
|
}
|
|
|
|
// pussywizard: lootState has nothing to do with collision, it depends entirely on GOState. Loot state is for timed close/open door and respawning, which then sets GOState
|
|
/*if (m_model)
|
|
{
|
|
// startOpen determines whether we are going to add or remove the LoS on activation
|
|
bool startOpen = (GetGoType() == GAMEOBJECT_TYPE_DOOR || GetGoType() == GAMEOBJECT_TYPE_BUTTON ? GetGOInfo()->door.startOpen : false);
|
|
|
|
// Use the current go state
|
|
if (GetGoState() == GO_STATE_ACTIVE)
|
|
startOpen = !startOpen;
|
|
|
|
if (state == GO_ACTIVATED || state == GO_JUST_DEACTIVATED)
|
|
EnableCollision(startOpen);
|
|
else if (state == GO_READY)
|
|
EnableCollision(!startOpen);
|
|
}*/
|
|
}
|
|
|
|
void GameObject::SetGoState(GOState state)
|
|
{
|
|
SetByteValue(GAMEOBJECT_BYTES_1, 0, state);
|
|
|
|
sScriptMgr->OnGameObjectStateChanged(this, state);
|
|
|
|
if (m_model)
|
|
{
|
|
if (!IsInWorld())
|
|
return;
|
|
|
|
// pussywizard: this startOpen is unneeded here, collision depends entirely on current GOState
|
|
EnableCollision(state == GO_STATE_READY || IsTransport());
|
|
// pussywizard: commented out everything below
|
|
|
|
// startOpen determines whether we are going to add or remove the LoS on activation
|
|
/*bool startOpen = (GetGoType() == GAMEOBJECT_TYPE_DOOR || GetGoType() == GAMEOBJECT_TYPE_BUTTON ? GetGOInfo()->door.startOpen : false);
|
|
|
|
if (GetGameObjectData() && GetGameObjectData()->go_state == GO_STATE_READY)
|
|
startOpen = !startOpen;
|
|
|
|
if (state == GO_STATE_ACTIVE || state == GO_STATE_ACTIVE_ALTERNATIVE)
|
|
EnableCollision(startOpen);
|
|
else if (state == GO_STATE_READY)
|
|
EnableCollision(!startOpen);*/
|
|
}
|
|
/* Whenever a gameobject inside an instance changes
|
|
* save it's state on the database to be loaded properly
|
|
* on server restart or crash.
|
|
*/
|
|
if (IsInstanceGameobject() && IsAllowedToSaveToDB())
|
|
{
|
|
SaveStateToDB();
|
|
}
|
|
}
|
|
|
|
bool GameObject::IsInstanceGameobject() const
|
|
{
|
|
// Avoid checking for unecessary gameobjects whose
|
|
// states don't matter for the dungeon progression
|
|
if (!ValidateGameobjectType())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (auto* map = FindMap())
|
|
{
|
|
if (map->IsDungeon() || map->IsRaid())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool GameObject::ValidateGameobjectType() const
|
|
{
|
|
switch (m_goInfo->type)
|
|
{
|
|
case GAMEOBJECT_TYPE_DOOR:
|
|
case GAMEOBJECT_TYPE_BUTTON:
|
|
case GAMEOBJECT_TYPE_TRAP:
|
|
case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING:
|
|
case GAMEOBJECT_TYPE_TRAPDOOR:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
uint8 GameObject::GameobjectStateToInt(GOState* state) const
|
|
{
|
|
uint8 m_state = 3;
|
|
|
|
if (state)
|
|
{
|
|
switch (*state)
|
|
{
|
|
case GO_STATE_ACTIVE:
|
|
m_state = 0;
|
|
return m_state;
|
|
case GO_STATE_READY:
|
|
m_state = 1;
|
|
return m_state;
|
|
case GO_STATE_ACTIVE_ALTERNATIVE:
|
|
m_state = 2;
|
|
return m_state;
|
|
}
|
|
}
|
|
|
|
// Returning any value that is not one of the specified ones
|
|
// Which will default into the invalid part of the switch
|
|
return m_state;
|
|
}
|
|
|
|
void GameObject::SaveStateToDB()
|
|
{
|
|
if (IsInstanceGameobject())
|
|
{
|
|
if (InstanceScript* instance = GetInstanceScript())
|
|
{
|
|
GOState param = GetGoState();
|
|
instance->StoreGameObjectState(GetSpawnId(), GameobjectStateToInt(¶m));
|
|
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INSERT_INSTANCE_SAVED_DATA);
|
|
stmt->SetData(0, GetInstanceId());
|
|
stmt->SetData(1, GetSpawnId());
|
|
stmt->SetData(2, GameobjectStateToInt(¶m));
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GameObject::SetDisplayId(uint32 displayid)
|
|
{
|
|
SetUInt32Value(GAMEOBJECT_DISPLAYID, displayid);
|
|
UpdateModel();
|
|
}
|
|
|
|
void GameObject::SetPhaseMask(uint32 newPhaseMask, bool update)
|
|
{
|
|
WorldObject::SetPhaseMask(newPhaseMask, update);
|
|
|
|
if (m_model && m_model->isEnabled())
|
|
EnableCollision(true);
|
|
}
|
|
|
|
void GameObject::EnableCollision(bool enable)
|
|
{
|
|
if (!m_model)
|
|
return;
|
|
|
|
/*if (enable && !GetMap()->ContainsGameObjectModel(*m_model))
|
|
GetMap()->InsertGameObjectModel(*m_model);*/
|
|
|
|
uint32 phaseMask = 0;
|
|
if (enable && !sDisableMgr->IsDisabledFor(DISABLE_TYPE_GO_LOS, GetEntry(), nullptr))
|
|
phaseMask = GetPhaseMask();
|
|
|
|
m_model->enable(phaseMask);
|
|
}
|
|
|
|
void GameObject::UpdateModel()
|
|
{
|
|
if (!IsInWorld())
|
|
return;
|
|
if (m_model)
|
|
if (GetMap()->ContainsGameObjectModel(*m_model))
|
|
GetMap()->RemoveGameObjectModel(*m_model);
|
|
delete m_model;
|
|
m_model = CreateModel();
|
|
if (m_model)
|
|
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(Creature* creature)
|
|
{
|
|
// 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 (!creature)
|
|
{
|
|
m_lootRecipient.Clear();
|
|
m_lootRecipientGroup = 0;
|
|
ResetAllowedLooters();
|
|
return;
|
|
}
|
|
|
|
m_lootRecipient = creature->GetLootRecipientGUID();
|
|
m_lootRecipientGroup = creature->GetLootRecipientGroupGUID();
|
|
SetAllowedLooters(creature->GetAllowedLooters());
|
|
}
|
|
|
|
void GameObject::SetLootRecipient(Map* map)
|
|
{
|
|
Group* group = nullptr;
|
|
Map::PlayerList const& PlayerList = map->GetPlayers();
|
|
for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
|
|
{
|
|
if (Player* groupMember = i->GetSource())
|
|
{
|
|
if (groupMember->IsGameMaster() || groupMember->IsSpectator())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!m_lootRecipient)
|
|
{
|
|
m_lootRecipient = groupMember->GetGUID();
|
|
}
|
|
|
|
Group* memberGroup = groupMember->GetGroup();
|
|
if (memberGroup && !group)
|
|
{
|
|
group = memberGroup;
|
|
m_lootRecipientGroup = group->GetGUID().GetCounter();
|
|
}
|
|
|
|
if (memberGroup == group)
|
|
{
|
|
AddAllowedLooter(groupMember->GetGUID());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!group)
|
|
{
|
|
AddAllowedLooter(m_lootRecipient);
|
|
}
|
|
}
|
|
|
|
bool GameObject::IsLootAllowedFor(Player const* player) const
|
|
{
|
|
if (!m_lootRecipient && !m_lootRecipientGroup)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (player->GetGUID() == m_lootRecipient)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (player->HasPendingBind())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// if we dont have a group we arent the recipient
|
|
// if go doesnt have group bound it means it was solo killed by someone else
|
|
Group const* playerGroup = player->GetGroup();
|
|
if (!playerGroup || playerGroup != GetLootRecipientGroup())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!HasAllowedLooter(player->GetGUID()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
GameObject* GameObject::GetLinkedTrap()
|
|
{
|
|
return ObjectAccessor::GetGameObject(*this, m_linkedTrap);
|
|
}
|
|
|
|
void GameObject::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target)
|
|
{
|
|
if (!target)
|
|
return;
|
|
|
|
bool forcedFlags = GetGoType() == GAMEOBJECT_TYPE_CHEST && GetGOInfo()->chest.groupLootRules && HasLootRecipient();
|
|
bool targetIsGM = target->IsGameMaster() && target->GetSession()->IsGMAccount();
|
|
|
|
ByteBuffer fieldBuffer;
|
|
|
|
UpdateMask updateMask;
|
|
updateMask.SetCount(m_valuesCount);
|
|
|
|
uint32* flags = GameObjectUpdateFieldFlags;
|
|
uint32 visibleFlag = UF_FLAG_PUBLIC;
|
|
if (GetOwnerGUID() == target->GetGUID())
|
|
visibleFlag |= UF_FLAG_OWNER;
|
|
|
|
for (uint16 index = 0; index < m_valuesCount; ++index)
|
|
{
|
|
if (_fieldNotifyFlags & flags[index] ||
|
|
((updateType == UPDATETYPE_VALUES ? _changesMask.GetBit(index) : m_uint32Values[index]) && (flags[index] & visibleFlag)) ||
|
|
(index == GAMEOBJECT_FLAGS && forcedFlags))
|
|
{
|
|
updateMask.SetBit(index);
|
|
|
|
if (index == GAMEOBJECT_DYNAMIC)
|
|
{
|
|
uint16 dynFlags = 0;
|
|
int16 pathProgress = -1;
|
|
switch (GetGoType())
|
|
{
|
|
case GAMEOBJECT_TYPE_QUESTGIVER:
|
|
if (ActivateToQuest(target))
|
|
dynFlags |= GO_DYNFLAG_LO_ACTIVATE;
|
|
break;
|
|
case GAMEOBJECT_TYPE_CHEST:
|
|
case GAMEOBJECT_TYPE_GOOBER:
|
|
if (ActivateToQuest(target))
|
|
{
|
|
dynFlags |= GO_DYNFLAG_LO_ACTIVATE;
|
|
if (sWorld->getBoolConfig(CONFIG_OBJECT_SPARKLES))
|
|
dynFlags |= GO_DYNFLAG_LO_SPARKLE;
|
|
}
|
|
else if (targetIsGM)
|
|
dynFlags |= GO_DYNFLAG_LO_ACTIVATE;
|
|
break;
|
|
case GAMEOBJECT_TYPE_SPELL_FOCUS:
|
|
case GAMEOBJECT_TYPE_GENERIC:
|
|
if (ActivateToQuest(target) && sWorld->getBoolConfig(CONFIG_OBJECT_SPARKLES))
|
|
dynFlags |= GO_DYNFLAG_LO_SPARKLE;
|
|
break;
|
|
case GAMEOBJECT_TYPE_TRANSPORT:
|
|
if (const StaticTransport* t = ToStaticTransport())
|
|
if (t->GetPauseTime())
|
|
{
|
|
if (GetGoState() == GO_STATE_READY)
|
|
{
|
|
if (t->GetPathProgress() >= t->GetPauseTime()) // if not, send 100% progress
|
|
pathProgress = int16(float(t->GetPathProgress() - t->GetPauseTime()) / float(t->GetPeriod() - t->GetPauseTime()) * 65535.0f);
|
|
}
|
|
else
|
|
{
|
|
if (t->GetPathProgress() <= t->GetPauseTime()) // if not, send 100% progress
|
|
pathProgress = int16(float(t->GetPathProgress()) / float(t->GetPauseTime()) * 65535.0f);
|
|
}
|
|
}
|
|
// else it's ignored
|
|
break;
|
|
case GAMEOBJECT_TYPE_MO_TRANSPORT:
|
|
if (const MotionTransport* t = ToMotionTransport())
|
|
pathProgress = int16(float(t->GetPathProgress()) / float(t->GetPeriod()) * 65535.0f);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
fieldBuffer << uint16(dynFlags);
|
|
fieldBuffer << int16(pathProgress);
|
|
}
|
|
else if (index == GAMEOBJECT_FLAGS)
|
|
{
|
|
uint32 goFlags = m_uint32Values[GAMEOBJECT_FLAGS];
|
|
if (GetGoType() == GAMEOBJECT_TYPE_CHEST && GetGOInfo() && GetGOInfo()->chest.groupLootRules && !IsLootAllowedFor(target))
|
|
{
|
|
goFlags |= GO_FLAG_LOCKED | GO_FLAG_NOT_SELECTABLE;
|
|
}
|
|
|
|
fieldBuffer << goFlags;
|
|
}
|
|
else
|
|
fieldBuffer << m_uint32Values[index]; // other cases
|
|
}
|
|
}
|
|
|
|
*data << uint8(updateMask.GetBlockCount());
|
|
updateMask.AppendToPacket(data);
|
|
data->append(fieldBuffer);
|
|
}
|
|
|
|
void GameObject::GetRespawnPosition(float& x, float& y, float& z, float* ori /* = nullptr*/) const
|
|
{
|
|
if (m_spawnId)
|
|
{
|
|
if (GameObjectData const* data = sObjectMgr->GetGameObjectData(m_spawnId))
|
|
{
|
|
x = data->posX;
|
|
y = data->posY;
|
|
z = data->posZ;
|
|
if (ori)
|
|
*ori = data->orientation;
|
|
return;
|
|
}
|
|
}
|
|
|
|
x = GetPositionX();
|
|
y = GetPositionY();
|
|
z = GetPositionZ();
|
|
if (ori)
|
|
*ori = GetOrientation();
|
|
}
|
|
|
|
void GameObject::SetPosition(float x, float y, float z, float o)
|
|
{
|
|
// pussywizard: do not call for MotionTransport and other gobjects not in grid
|
|
|
|
if (!Acore::IsValidMapCoord(x, y, z, o))
|
|
return;
|
|
|
|
GetMap()->GameObjectRelocation(this, x, y, z, o);
|
|
}
|
|
|
|
float GameObject::GetInteractionDistance() const
|
|
{
|
|
switch (GetGoType())
|
|
{
|
|
case GAMEOBJECT_TYPE_AREADAMAGE:
|
|
return 0.0f;
|
|
case GAMEOBJECT_TYPE_QUESTGIVER:
|
|
case GAMEOBJECT_TYPE_TEXT:
|
|
case GAMEOBJECT_TYPE_FLAGSTAND:
|
|
case GAMEOBJECT_TYPE_FLAGDROP:
|
|
case GAMEOBJECT_TYPE_MINI_GAME:
|
|
return 5.5555553f;
|
|
case GAMEOBJECT_TYPE_BINDER:
|
|
return 10.0f;
|
|
case GAMEOBJECT_TYPE_CHAIR:
|
|
case GAMEOBJECT_TYPE_BARBER_CHAIR:
|
|
return 3.0f;
|
|
case GAMEOBJECT_TYPE_FISHINGNODE:
|
|
return 100.0f;
|
|
case GAMEOBJECT_TYPE_FISHINGHOLE:
|
|
return 20.0f + CONTACT_DISTANCE; // max spell range
|
|
case GAMEOBJECT_TYPE_CAMERA:
|
|
case GAMEOBJECT_TYPE_MAP_OBJECT:
|
|
case GAMEOBJECT_TYPE_DUNGEON_DIFFICULTY:
|
|
case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING:
|
|
case GAMEOBJECT_TYPE_DOOR:
|
|
return 5.0f;
|
|
// Following values are not blizzlike
|
|
case GAMEOBJECT_TYPE_GUILD_BANK:
|
|
case GAMEOBJECT_TYPE_MAILBOX:
|
|
// Successful mailbox interaction is rather critical to the client, failing it will start a minute-long cooldown until the next mail query may be executed.
|
|
// And since movement info update is not sent with mailbox interaction query, server may find the player outside of interaction range. Thus we increase it.
|
|
return 10.0f; // 5.0f is blizzlike
|
|
default:
|
|
return INTERACTION_DISTANCE;
|
|
}
|
|
}
|
|
|
|
void GameObject::UpdateModelPosition()
|
|
{
|
|
if (!m_model)
|
|
return;
|
|
|
|
if (GetMap()->ContainsGameObjectModel(*m_model))
|
|
{
|
|
GetMap()->RemoveGameObjectModel(*m_model);
|
|
m_model->UpdatePosition();
|
|
GetMap()->InsertGameObjectModel(*m_model);
|
|
}
|
|
}
|
|
|
|
time_t GameObject::GetRespawnTimeEx() const
|
|
{
|
|
time_t now = GameTime::GetGameTime().count();
|
|
if (m_respawnTime > now)
|
|
return m_respawnTime;
|
|
else
|
|
return now;
|
|
}
|
|
|
|
void GameObject::SetLootGenerationTime()
|
|
{
|
|
m_lootGenerationTime = GameTime::GetGameTime().count();
|
|
}
|
|
|
|
std::unordered_map<int, goEventFlag> GameObject::gameObjectToEventFlag = { };
|
|
|
|
class GameObjectModelOwnerImpl : public GameObjectModelOwnerBase
|
|
{
|
|
public:
|
|
explicit GameObjectModelOwnerImpl(GameObject* owner) : _owner(owner) { }
|
|
|
|
bool IsSpawned() const override { return _owner->isSpawned(); }
|
|
uint32 GetDisplayId() const override { return _owner->GetDisplayId(); }
|
|
uint32 GetPhaseMask() const override { return (_owner->GetGoState() == GO_STATE_READY || _owner->IsTransport()) ? _owner->GetPhaseMask() : 0; }
|
|
G3D::Vector3 GetPosition() const override { return G3D::Vector3(_owner->GetPositionX(), _owner->GetPositionY(), _owner->GetPositionZ()); }
|
|
float GetOrientation() const override { return _owner->GetOrientation(); }
|
|
float GetScale() const override { return _owner->GetObjectScale(); }
|
|
void DebugVisualizeCorner(G3D::Vector3 const& corner) const override { const_cast<GameObject*>(_owner)->SummonCreature(1, corner.x, corner.y, corner.z, 0.0f, TEMPSUMMON_TIMED_DESPAWN, 10000); }
|
|
|
|
private:
|
|
GameObject* _owner;
|
|
};
|
|
|
|
GameObjectModel* GameObject::CreateModel()
|
|
{
|
|
return GameObjectModel::Create(std::make_unique<GameObjectModelOwnerImpl>(this), sWorld->GetDataPath());
|
|
}
|
|
|
|
bool GameObject::IsAtInteractDistance(Player const* player, SpellInfo const* spell) const
|
|
{
|
|
if (spell || (spell = GetSpellForLock(player)))
|
|
{
|
|
float maxRange = spell->GetMaxRange(spell->IsPositive());
|
|
|
|
if (GetGoType() == GAMEOBJECT_TYPE_SPELL_FOCUS)
|
|
{
|
|
return maxRange * maxRange >= GetExactDistSq(player);
|
|
}
|
|
|
|
if (sGameObjectDisplayInfoStore.LookupEntry(GetGOInfo()->displayId))
|
|
{
|
|
return IsAtInteractDistance(*player, maxRange);
|
|
}
|
|
}
|
|
|
|
return IsAtInteractDistance(*player, GetInteractionDistance());
|
|
}
|
|
|
|
bool GameObject::IsAtInteractDistance(Position const& pos, float radius) const
|
|
{
|
|
if (GameObjectDisplayInfoEntry const* displayInfo = sGameObjectDisplayInfoStore.LookupEntry(GetGOInfo()->displayId))
|
|
{
|
|
float scale = GetObjectScale();
|
|
|
|
float minX = displayInfo->minX * scale - radius;
|
|
float minY = displayInfo->minY * scale - radius;
|
|
float minZ = displayInfo->minZ * scale - radius;
|
|
float maxX = displayInfo->maxX * scale + radius;
|
|
float maxY = displayInfo->maxY * scale + radius;
|
|
float maxZ = displayInfo->maxZ * scale + radius;
|
|
|
|
G3D::Quat worldRotation = GetWorldRotation();
|
|
G3D::Quat worldRotationQuat(worldRotation.x, worldRotation.y, worldRotation.z, worldRotation.w);
|
|
|
|
return G3D::CoordinateFrame {{worldRotationQuat}, {GetPositionX(), GetPositionY(), GetPositionZ()}}.toWorldSpace(G3D::Box {{minX, minY, minZ}, {maxX, maxY, maxZ}}).contains({pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()});
|
|
}
|
|
|
|
return GetExactDist(&pos) <= radius;
|
|
}
|
|
|
|
bool GameObject::IsWithinDistInMap(Player const* player) const
|
|
{
|
|
return IsInMap(player) && InSamePhase(player) && IsAtInteractDistance(player);
|
|
}
|
|
|
|
SpellInfo const* GameObject::GetSpellForLock(Player const* player) const
|
|
{
|
|
if (!player)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
uint32 lockId = GetGOInfo()->GetLockId();
|
|
if (!lockId)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
LockEntry const* lock = sLockStore.LookupEntry(lockId);
|
|
if (!lock)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
for (uint8 i = 0; i < MAX_LOCK_CASE; ++i)
|
|
{
|
|
if (!lock->Type[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (lock->Type[i] == LOCK_KEY_SPELL)
|
|
{
|
|
if (SpellInfo const* spell = sSpellMgr->GetSpellInfo(lock->Index[i]))
|
|
{
|
|
return spell;
|
|
}
|
|
}
|
|
|
|
if (lock->Type[i] != LOCK_KEY_SKILL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
for (auto&& playerSpell : player->GetSpellMap())
|
|
{
|
|
if (SpellInfo const* spell = sSpellMgr->GetSpellInfo(playerSpell.first))
|
|
{
|
|
for (auto&& effect : spell->Effects)
|
|
{
|
|
if (effect.Effect == SPELL_EFFECT_OPEN_LOCK && ((uint32) effect.MiscValue) == lock->Index[i])
|
|
{
|
|
if (effect.CalcValue(player) >= int32(lock->Skill[i]))
|
|
{
|
|
return spell;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void GameObject::AddToSkillupList(ObjectGuid playerGuid)
|
|
{
|
|
int32 timer = GetMap()->IsDungeon() ? -1 : 10 * MINUTE * IN_MILLISECONDS;
|
|
m_SkillupList[playerGuid] = timer;
|
|
}
|
|
|
|
bool GameObject::IsInSkillupList(ObjectGuid playerGuid) const
|
|
{
|
|
for (auto const& itr : m_SkillupList)
|
|
{
|
|
if (itr.first == playerGuid)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::string GameObject::GetDebugInfo() const
|
|
{
|
|
std::stringstream sstr;
|
|
sstr << WorldObject::GetDebugInfo() << "\n"
|
|
<< "SpawnId: " << GetSpawnId() << " GoState: " << std::to_string(GetGoState()) << " ScriptId: " << GetScriptId() << " AIName: " << GetAIName();
|
|
return sstr.str();
|
|
}
|
|
|
|
// Note: This is called in a tight (heavy) loop, is it critical that all checks are FAST and are hopefully only simple conditionals.
|
|
bool GameObject::IsUpdateNeeded()
|
|
{
|
|
if (WorldObject::IsUpdateNeeded())
|
|
return true;
|
|
|
|
if (GetMap()->isCellMarked(GetCurrentCell().GetCellCoord().GetId()))
|
|
return true;
|
|
|
|
if (!GetObjectVisibilityContainer().GetVisiblePlayersMap().empty())
|
|
return true;
|
|
|
|
if (IsTransport())
|
|
return true;
|
|
|
|
return false;
|
|
}
|