mirror of
https://github.com/araxiaonline/TrinityCore.git
synced 2026-06-15 12:42:43 -04:00
1973 lines
70 KiB
C++
Executable File
1973 lines
70 KiB
C++
Executable File
/*
|
|
* Copyright (C) 2008-2012 TrinityCore <http://www.trinitycore.org/>
|
|
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 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 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 "GameObjectAI.h"
|
|
#include "ObjectMgr.h"
|
|
#include "GroupMgr.h"
|
|
#include "PoolMgr.h"
|
|
#include "SpellMgr.h"
|
|
#include "World.h"
|
|
#include "GridNotifiersImpl.h"
|
|
#include "CellImpl.h"
|
|
#include "OutdoorPvPMgr.h"
|
|
#include "BattlegroundAV.h"
|
|
#include "ScriptMgr.h"
|
|
#include "CreatureAISelector.h"
|
|
#include "Group.h"
|
|
|
|
#include "GameObjectModel.h"
|
|
#include "DynamicTree.h"
|
|
|
|
GameObject::GameObject() : WorldObject(false), m_model(NULL), m_goValue(new GameObjectValue), m_AI(NULL)
|
|
{
|
|
m_objectType |= TYPEMASK_GAMEOBJECT;
|
|
m_objectTypeId = TYPEID_GAMEOBJECT;
|
|
|
|
m_updateFlag = (UPDATEFLAG_HIGHGUID | UPDATEFLAG_HAS_POSITION | UPDATEFLAG_POSITION | UPDATEFLAG_ROTATION);
|
|
|
|
m_valuesCount = GAMEOBJECT_END;
|
|
m_respawnTime = 0;
|
|
m_respawnDelayTime = 300;
|
|
m_lootState = GO_NOT_READY;
|
|
m_spawnedByDefault = true;
|
|
m_usetimes = 0;
|
|
m_spellId = 0;
|
|
m_cooldownTime = 0;
|
|
m_goInfo = NULL;
|
|
m_ritualOwner = NULL;
|
|
m_goData = NULL;
|
|
|
|
m_DBTableGuid = 0;
|
|
m_rotation = 0;
|
|
|
|
m_groupLootTimer = 0;
|
|
lootingGroupLowGUID = 0;
|
|
|
|
ResetLootMode(); // restore default loot mode
|
|
}
|
|
|
|
GameObject::~GameObject()
|
|
{
|
|
delete m_goValue;
|
|
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 GameObject::GetAIName() const
|
|
{
|
|
return sObjectMgr->GetGameObjectTemplate(GetEntry())->AIName;
|
|
}
|
|
|
|
void GameObject::CleanupsBeforeDelete(bool /*finalCleanup*/)
|
|
{
|
|
if (IsInWorld())
|
|
RemoveFromWorld();
|
|
|
|
if (m_uint32Values) // field array can be not exist if GameOBject not loaded
|
|
RemoveFromOwner();
|
|
}
|
|
|
|
void GameObject::RemoveFromOwner()
|
|
{
|
|
uint64 ownerGUID = GetOwnerGUID();
|
|
if (!ownerGUID)
|
|
return;
|
|
|
|
if (Unit* owner = ObjectAccessor::GetUnit(*this, ownerGUID))
|
|
{
|
|
owner->RemoveGameObject(this, false);
|
|
ASSERT(!GetOwnerGUID());
|
|
return;
|
|
}
|
|
|
|
const char * ownerType = "creature";
|
|
if (IS_PLAYER_GUID(ownerGUID))
|
|
ownerType = "player";
|
|
else if (IS_PET_GUID(ownerGUID))
|
|
ownerType = "pet";
|
|
|
|
sLog->outCrash("Delete GameObject (GUID: %u Entry: %u SpellId %u LinkedGO %u) that lost references to owner (GUID %u Type '%s') GO list. Crash possible later.",
|
|
GetGUIDLow(), GetGOInfo()->entry, m_spellId, GetGOInfo()->GetLinkedGameObjectEntry(), GUID_LOPART(ownerGUID), ownerType);
|
|
SetOwnerGUID(0);
|
|
}
|
|
|
|
void GameObject::AddToWorld()
|
|
{
|
|
///- Register the gameobject for guid lookup
|
|
if (!IsInWorld())
|
|
{
|
|
if (m_zoneScript)
|
|
m_zoneScript->OnGameObjectCreate(this);
|
|
|
|
sObjectAccessor->AddObject(this);
|
|
bool startOpen = (GetGoType() == GAMEOBJECT_TYPE_DOOR || GetGoType() == GAMEOBJECT_TYPE_BUTTON ? GetGOInfo()->door.startOpen : false);
|
|
bool toggledState = (GetGOData() ? GetGOData()->go_state == GO_STATE_ACTIVE : false);
|
|
if (m_model)
|
|
GetMap()->Insert(*m_model);
|
|
if ((startOpen && !toggledState) || (!startOpen && toggledState))
|
|
EnableCollision(false);
|
|
|
|
WorldObject::AddToWorld();
|
|
}
|
|
}
|
|
|
|
void GameObject::RemoveFromWorld()
|
|
{
|
|
///- Remove the gameobject from the accessor
|
|
if (IsInWorld())
|
|
{
|
|
if (m_zoneScript)
|
|
m_zoneScript->OnGameObjectRemove(this);
|
|
|
|
RemoveFromOwner();
|
|
if (m_model)
|
|
if (GetMap()->Contains(*m_model))
|
|
GetMap()->Remove(*m_model);
|
|
WorldObject::RemoveFromWorld();
|
|
sObjectAccessor->RemoveObject(this);
|
|
}
|
|
}
|
|
|
|
bool GameObject::Create(uint32 guidlow, uint32 name_id, Map* map, uint32 phaseMask, float x, float y, float z, float ang, float rotation0, float rotation1, float rotation2, float rotation3, uint32 animprogress, GOState go_state, uint32 artKit)
|
|
{
|
|
ASSERT(map);
|
|
SetMap(map);
|
|
|
|
Relocate(x, y, z, ang);
|
|
if (!IsPositionValid())
|
|
{
|
|
sLog->outError("Gameobject (GUID: %u Entry: %u) not created. Suggested coordinates isn't valid (X: %f Y: %f)", guidlow, name_id, x, y);
|
|
return false;
|
|
}
|
|
|
|
SetPhaseMask(phaseMask, false);
|
|
|
|
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)
|
|
{
|
|
sLog->outErrorDb("Gameobject (GUID: %u Entry: %u) not created: non-existing entry in `gameobject_template`. Map: %u (X: %f Y: %f Z: %f)", 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)
|
|
{
|
|
sLog->outErrorDb("Gameobject (GUID: %u Entry: %u) not created: non-existing GO type '%u' in `gameobject_template`. It will crash client if created.", guidlow, name_id, goinfo->type);
|
|
return false;
|
|
}
|
|
|
|
SetFloatValue(GAMEOBJECT_PARENTROTATION+0, rotation0);
|
|
SetFloatValue(GAMEOBJECT_PARENTROTATION+1, rotation1);
|
|
|
|
UpdateRotationFields(rotation2, rotation3); // GAMEOBJECT_FACING, GAMEOBJECT_ROTATION, GAMEOBJECT_PARENTROTATION+2/3
|
|
|
|
SetFloatValue(OBJECT_FIELD_SCALE_X, goinfo->size);
|
|
|
|
SetUInt32Value(GAMEOBJECT_FACTION, goinfo->faction);
|
|
SetUInt32Value(GAMEOBJECT_FLAGS, goinfo->flags);
|
|
|
|
SetEntry(goinfo->entry);
|
|
|
|
// set name for logs usage, doesn't affect anything ingame
|
|
SetName(goinfo->name);
|
|
|
|
SetDisplayId(goinfo->displayId);
|
|
|
|
m_model = GameObjectModel::Create(*this);
|
|
// GAMEOBJECT_BYTES_1, index at 0, 1, 2 and 3
|
|
SetGoType(GameobjectTypes(goinfo->type));
|
|
SetGoState(go_state);
|
|
|
|
SetGoArtKit(0); // unknown what this is
|
|
SetByteValue(GAMEOBJECT_BYTES_1, 2, artKit);
|
|
|
|
|
|
switch (goinfo->type)
|
|
{
|
|
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_TRANSPORT:
|
|
SetUInt32Value(GAMEOBJECT_LEVEL, goinfo->transport.pause);
|
|
if (goinfo->transport.startOpen)
|
|
SetGoState(GO_STATE_ACTIVE);
|
|
SetGoAnimProgress(animprogress);
|
|
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;
|
|
}
|
|
LastUsedScriptID = GetGOInfo()->ScriptId;
|
|
AIM_Initialize();
|
|
|
|
return true;
|
|
}
|
|
|
|
void GameObject::Update(uint32 diff)
|
|
{
|
|
if (!AI())
|
|
{
|
|
if (!AIM_Initialize())
|
|
sLog->outError("Could not initialize GameObjectAI");
|
|
} else
|
|
AI()->UpdateAI(diff);
|
|
|
|
if (IS_MO_TRANSPORT(GetGUID()))
|
|
{
|
|
//((Transport*)this)->Update(p_time);
|
|
return;
|
|
}
|
|
|
|
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 = time(NULL) + 10; // Hardcoded tooltip value
|
|
else if (Unit* owner = GetOwner())
|
|
{
|
|
if (owner->isInCombat())
|
|
m_cooldownTime = time(NULL) + goInfo->trap.cooldown;
|
|
}
|
|
m_lootState = GO_READY;
|
|
break;
|
|
}
|
|
case GAMEOBJECT_TYPE_FISHINGNODE:
|
|
{
|
|
// fishing code (bobber ready)
|
|
if (time(NULL) > m_respawnTime - FISHING_BOBBER_READY_TIME)
|
|
{
|
|
// splash bobber (bobber ready now)
|
|
Unit* caster = GetOwner();
|
|
if (caster && caster->GetTypeId() == TYPEID_PLAYER)
|
|
{
|
|
SetGoState(GO_STATE_ACTIVE);
|
|
SetUInt32Value(GAMEOBJECT_FLAGS, 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;
|
|
}
|
|
default:
|
|
m_lootState = GO_READY; // for other GOis same switched without delay to GO_READY
|
|
break;
|
|
}
|
|
// NO BREAK for switch (m_lootState)
|
|
}
|
|
case GO_READY:
|
|
{
|
|
if (m_respawnTime > 0) // timer on
|
|
{
|
|
time_t now = time(NULL);
|
|
if (m_respawnTime <= now) // timer expired
|
|
{
|
|
uint64 dbtableHighGuid = MAKE_NEW_GUID(m_DBTableGuid, GetEntry(), HIGHGUID_GAMEOBJECT);
|
|
time_t linkedRespawntime = sObjectMgr->GetLinkedRespawnTime(dbtableHighGuid, GetMap()->GetInstanceId());
|
|
if (linkedRespawntime) // Can't respawn, the master is dead
|
|
{
|
|
uint64 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->GetTypeId() == TYPEID_PLAYER)
|
|
{
|
|
caster->FinishSpell(CURRENT_CHANNELED_SPELL);
|
|
|
|
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();
|
|
//flags in AB are type_button and we need to add them here so no break!
|
|
default:
|
|
if (!m_spawnedByDefault) // despawn timer
|
|
{
|
|
// can be despawned or destroyed
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
return;
|
|
}
|
|
// respawn timer
|
|
uint32 poolid = GetDBTableGUIDLow() ? sPoolMgr->IsPartOfAPool<GameObject>(GetDBTableGUIDLow()) : 0;
|
|
if (poolid)
|
|
sPoolMgr->UpdatePool<GameObject>(poolid, GetDBTableGUIDLow());
|
|
else
|
|
GetMap()->AddToMap(this);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isSpawned())
|
|
{
|
|
// traps can have time and can not have
|
|
GameObjectTemplate const* goInfo = GetGOInfo();
|
|
if (goInfo->type == GAMEOBJECT_TYPE_TRAP)
|
|
{
|
|
if (m_cooldownTime >= time(NULL))
|
|
return;
|
|
|
|
// Type 2 - Bomb (will go away after casting it's spell)
|
|
if (goInfo->trap.type == 2)
|
|
{
|
|
if (goInfo->trap.spellId)
|
|
CastSpell(NULL, goInfo->trap.spellId); // FIXME: null target won't work for target type 1
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
break;
|
|
}
|
|
// Type 0 and 1 - trap (type 0 will not get removed after casting a spell)
|
|
Unit* owner = GetOwner();
|
|
Unit* ok = NULL; // pointer to appropriate target if found any
|
|
|
|
bool IsBattlegroundTrap = false;
|
|
//FIXME: this is activation radius (in different casting radius that must be selected from spell data)
|
|
//TODO: move activated state code (cast itself) to GO_ACTIVATED, in this place only check activating and set state
|
|
float radius = (float)(goInfo->trap.radius)/2; // TODO rename radius to diameter (goInfo->trap.radius) should be (goInfo->trap.diameter)
|
|
if (!radius)
|
|
{
|
|
if (goInfo->trap.cooldown != 3) // cast in other case (at some triggering/linked go/etc explicit call)
|
|
return;
|
|
else
|
|
{
|
|
if (m_respawnTime > 0)
|
|
break;
|
|
|
|
radius = (float)goInfo->trap.cooldown; // battlegrounds gameobjects has data2 == 0 && data5 == 3
|
|
IsBattlegroundTrap = true;
|
|
|
|
if (!radius)
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Note: this hack with search required until GO casting not implemented
|
|
// search unfriendly creature
|
|
if (owner) // hunter trap
|
|
{
|
|
Trinity::AnyUnfriendlyNoTotemUnitInObjectRangeCheck checker(this, owner, radius);
|
|
Trinity::UnitSearcher<Trinity::AnyUnfriendlyNoTotemUnitInObjectRangeCheck> searcher(this, ok, checker);
|
|
VisitNearbyGridObject(radius, searcher);
|
|
if (!ok) VisitNearbyWorldObject(radius, searcher);
|
|
}
|
|
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 = NULL;
|
|
Trinity::AnyPlayerInObjectRangeCheck checker(this, radius);
|
|
Trinity::PlayerSearcher<Trinity::AnyPlayerInObjectRangeCheck> searcher(this, player, checker);
|
|
VisitNearbyWorldObject(radius, searcher);
|
|
ok = player;
|
|
}
|
|
|
|
if (ok)
|
|
{
|
|
// some traps do not have spell but should be triggered
|
|
if (goInfo->trap.spellId)
|
|
CastSpell(ok, goInfo->trap.spellId);
|
|
|
|
m_cooldownTime = time(NULL) + (goInfo->trap.cooldown ? goInfo->trap.cooldown : uint32(4)); // template or 4 seconds
|
|
|
|
if (goInfo->trap.type == 1)
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
|
|
if (IsBattlegroundTrap && ok->GetTypeId() == TYPEID_PLAYER)
|
|
{
|
|
//Battleground gameobjects case
|
|
if (ok->ToPlayer()->InBattleground())
|
|
if (Battleground* bg = ok->ToPlayer()->GetBattleground())
|
|
bg->HandleTriggerBuff(GetGUID());
|
|
}
|
|
}
|
|
}
|
|
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() && (m_cooldownTime < time(NULL)))
|
|
ResetDoorOrButton();
|
|
break;
|
|
case GAMEOBJECT_TYPE_GOOBER:
|
|
if (m_cooldownTime < time(NULL))
|
|
{
|
|
RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE);
|
|
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
m_cooldownTime = 0;
|
|
}
|
|
break;
|
|
case GAMEOBJECT_TYPE_CHEST:
|
|
if (m_groupLootTimer)
|
|
{
|
|
if (m_groupLootTimer <= diff)
|
|
{
|
|
Group* group = sGroupMgr->GetGroupByGUID(lootingGroupLowGUID);
|
|
if (group)
|
|
group->EndRoll(&loot);
|
|
m_groupLootTimer = 0;
|
|
lootingGroupLowGUID = 0;
|
|
}
|
|
else m_groupLootTimer -= diff;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case GO_JUST_DEACTIVATED:
|
|
{
|
|
//if Gameobject should cast spell, then this, but some GOs (type = 10) should be destroyed
|
|
if (GetGoType() == GAMEOBJECT_TYPE_GOOBER)
|
|
{
|
|
uint32 spellId = GetGOInfo()->goober.spellId;
|
|
|
|
if (spellId)
|
|
{
|
|
for (std::set<uint64>::const_iterator it = m_unique_users.begin(); it != m_unique_users.end(); ++it)
|
|
// m_unique_users can contain only player GUIDs
|
|
if (Player* owner = ObjectAccessor::GetPlayer(*this, *it))
|
|
owner->CastSpell(owner, spellId, false);
|
|
|
|
m_unique_users.clear();
|
|
m_usetimes = 0;
|
|
}
|
|
|
|
SetGoState(GO_STATE_READY);
|
|
|
|
//any return here in case battleground traps
|
|
if (GetGOInfo()->flags & GO_FLAG_NODESPAWN)
|
|
return;
|
|
}
|
|
|
|
loot.clear();
|
|
|
|
//! If this is summoned by a spell with ie. SPELL_EFFECT_SUMMON_OBJECT_WILD, with or without owner, we check respawn criteria based on spell
|
|
//! The GetOwnerGUID() check is mostly for compatibility with hacky scripts - 99% of the time summoning should be done trough spells.
|
|
if (GetSpellId() || GetOwnerGUID())
|
|
{
|
|
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
|
|
SetUInt32Value(GAMEOBJECT_FLAGS, GetGOInfo()->flags);
|
|
}
|
|
|
|
if (!m_respawnDelayTime)
|
|
return;
|
|
|
|
if (!m_spawnedByDefault)
|
|
{
|
|
m_respawnTime = 0;
|
|
UpdateObjectVisibility();
|
|
return;
|
|
}
|
|
|
|
m_respawnTime = time(NULL) + m_respawnDelayTime;
|
|
|
|
// if option not set then object will be saved at grid unload
|
|
if (sWorld->getBoolConfig(CONFIG_SAVE_RESPAWN_TIME_IMMEDIATELY))
|
|
SaveRespawnTime();
|
|
|
|
UpdateObjectVisibility();
|
|
|
|
break;
|
|
}
|
|
}
|
|
sScriptMgr->OnGameObjectUpdate(this, diff);
|
|
}
|
|
|
|
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::Delete()
|
|
{
|
|
SetLootState(GO_NOT_READY);
|
|
RemoveFromOwner();
|
|
|
|
SendObjectDeSpawnAnim(GetGUID());
|
|
|
|
SetGoState(GO_STATE_READY);
|
|
SetUInt32Value(GAMEOBJECT_FLAGS, GetGOInfo()->flags);
|
|
|
|
uint32 poolid = GetDBTableGUIDLow() ? sPoolMgr->IsPartOfAPool<GameObject>(GetDBTableGUIDLow()) : 0;
|
|
if (poolid)
|
|
sPoolMgr->UpdatePool<GameObject>(poolid, GetDBTableGUIDLow());
|
|
else
|
|
AddObjectToRemoveList();
|
|
}
|
|
|
|
void GameObject::getFishLoot(Loot* fishloot, Player* loot_owner)
|
|
{
|
|
fishloot->clear();
|
|
|
|
uint32 zone, subzone;
|
|
GetZoneAndAreaId(zone, subzone);
|
|
|
|
// if subzone loot exist use it
|
|
if (!fishloot->FillLoot(subzone, LootTemplates_Fishing, loot_owner, true, true))
|
|
// else use zone loot (must exist in like case)
|
|
fishloot->FillLoot(zone, LootTemplates_Fishing, loot_owner, true);
|
|
}
|
|
|
|
void GameObject::SaveToDB()
|
|
{
|
|
// 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->GetGOData(m_DBTableGuid);
|
|
if (!data)
|
|
{
|
|
sLog->outError("GameObject::SaveToDB failed, cannot get gameobject data!");
|
|
return;
|
|
}
|
|
|
|
SaveToDB(GetMapId(), data->spawnMask, data->phaseMask);
|
|
}
|
|
|
|
void GameObject::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask)
|
|
{
|
|
const GameObjectTemplate* goI = GetGOInfo();
|
|
|
|
if (!goI)
|
|
return;
|
|
|
|
if (!m_DBTableGuid)
|
|
m_DBTableGuid = GetGUIDLow();
|
|
// update in loaded data (changing data only in this place)
|
|
GameObjectData& data = sObjectMgr->NewGOData(m_DBTableGuid);
|
|
|
|
// data->guid = guid must not be updated at save
|
|
data.id = GetEntry();
|
|
data.mapid = mapid;
|
|
data.phaseMask = phaseMask;
|
|
data.posX = GetPositionX();
|
|
data.posY = GetPositionY();
|
|
data.posZ = GetPositionZ();
|
|
data.orientation = GetOrientation();
|
|
data.rotation0 = GetFloatValue(GAMEOBJECT_PARENTROTATION+0);
|
|
data.rotation1 = GetFloatValue(GAMEOBJECT_PARENTROTATION+1);
|
|
data.rotation2 = GetFloatValue(GAMEOBJECT_PARENTROTATION+2);
|
|
data.rotation3 = GetFloatValue(GAMEOBJECT_PARENTROTATION+3);
|
|
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
|
|
std::ostringstream ss;
|
|
ss << "INSERT INTO gameobject VALUES ("
|
|
<< m_DBTableGuid << ','
|
|
<< GetEntry() << ','
|
|
<< mapid << ','
|
|
<< uint32(spawnMask) << ',' // cast to prevent save as symbol
|
|
<< uint16(GetPhaseMask()) << ',' // prevent out of range error
|
|
<< GetPositionX() << ','
|
|
<< GetPositionY() << ','
|
|
<< GetPositionZ() << ','
|
|
<< GetOrientation() << ','
|
|
<< GetFloatValue(GAMEOBJECT_PARENTROTATION) << ','
|
|
<< GetFloatValue(GAMEOBJECT_PARENTROTATION+1) << ','
|
|
<< GetFloatValue(GAMEOBJECT_PARENTROTATION+2) << ','
|
|
<< GetFloatValue(GAMEOBJECT_PARENTROTATION+3) << ','
|
|
<< m_respawnDelayTime << ','
|
|
<< uint32(GetGoAnimProgress()) << ','
|
|
<< uint32(GetGoState()) << ')';
|
|
|
|
SQLTransaction trans = WorldDatabase.BeginTransaction();
|
|
trans->PAppend("DELETE FROM gameobject WHERE guid = '%u'", m_DBTableGuid);
|
|
trans->Append(ss.str().c_str());
|
|
WorldDatabase.CommitTransaction(trans);
|
|
}
|
|
|
|
bool GameObject::LoadGameObjectFromDB(uint32 guid, Map* map, bool addToMap)
|
|
{
|
|
GameObjectData const* data = sObjectMgr->GetGOData(guid);
|
|
|
|
if (!data)
|
|
{
|
|
sLog->outErrorDb("Gameobject (GUID: %u) not found in table `gameobject`, can't load. ", guid);
|
|
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;
|
|
|
|
float rotation0 = data->rotation0;
|
|
float rotation1 = data->rotation1;
|
|
float rotation2 = data->rotation2;
|
|
float rotation3 = data->rotation3;
|
|
|
|
uint32 animprogress = data->animprogress;
|
|
GOState go_state = data->go_state;
|
|
uint32 artKit = data->artKit;
|
|
|
|
m_DBTableGuid = guid;
|
|
if (map->GetInstanceId() != 0) guid = sObjectMgr->GenerateLowGuid(HIGHGUID_GAMEOBJECT);
|
|
|
|
if (!Create(guid, entry, map, phaseMask, x, y, z, ang, rotation0, rotation1, rotation2, rotation3, animprogress, go_state, artKit))
|
|
return false;
|
|
|
|
if (data->spawntimesecs >= 0)
|
|
{
|
|
m_spawnedByDefault = true;
|
|
|
|
if (!GetGOInfo()->GetDespawnPossibility() && !GetGOInfo()->IsDespawnAtAction())
|
|
{
|
|
SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_NODESPAWN);
|
|
m_respawnDelayTime = 0;
|
|
m_respawnTime = 0;
|
|
}
|
|
else
|
|
{
|
|
m_respawnDelayTime = data->spawntimesecs;
|
|
m_respawnTime = sObjectMgr->GetGORespawnTime(m_DBTableGuid, map->GetInstanceId());
|
|
|
|
// ready to respawn
|
|
if (m_respawnTime && m_respawnTime <= time(NULL))
|
|
{
|
|
m_respawnTime = 0;
|
|
sObjectMgr->RemoveGORespawnTime(m_DBTableGuid, GetInstanceId());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_spawnedByDefault = false;
|
|
m_respawnDelayTime = -data->spawntimesecs;
|
|
m_respawnTime = 0;
|
|
}
|
|
|
|
m_goData = data;
|
|
|
|
if (addToMap && !GetMap()->AddToMap(this))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void GameObject::DeleteFromDB()
|
|
{
|
|
sObjectMgr->RemoveGORespawnTime(m_DBTableGuid, GetInstanceId());
|
|
sObjectMgr->DeleteGOData(m_DBTableGuid);
|
|
|
|
PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT);
|
|
|
|
stmt->setUInt32(0, m_DBTableGuid);
|
|
|
|
WorldDatabase.Execute(stmt);
|
|
|
|
stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_EVENT_GAMEOBJECT);
|
|
|
|
stmt->setUInt32(0, m_DBTableGuid);
|
|
|
|
WorldDatabase.Execute(stmt);
|
|
}
|
|
|
|
GameObject* GameObject::GetGameObject(WorldObject& object, uint64 guid)
|
|
{
|
|
return object.GetMap()->GetGameObject(guid);
|
|
}
|
|
|
|
/*********************************************************/
|
|
/*** 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
|
|
{
|
|
// If something is marked as a transport, don't transmit an out of range packet for it.
|
|
GameObjectTemplate const* gInfo = GetGOInfo();
|
|
if (!gInfo) return false;
|
|
return gInfo->type == GAMEOBJECT_TYPE_TRANSPORT || gInfo->type == GAMEOBJECT_TYPE_MO_TRANSPORT;
|
|
}
|
|
|
|
// is Dynamic transport = non-stop Transport
|
|
bool GameObject::IsDynTransport() const
|
|
{
|
|
// If something is marked as a transport, don't transmit an out of range packet for it.
|
|
GameObjectTemplate const* gInfo = GetGOInfo();
|
|
if (!gInfo) return false;
|
|
return gInfo->type == GAMEOBJECT_TYPE_MO_TRANSPORT || (gInfo->type == GAMEOBJECT_TYPE_TRANSPORT && !gInfo->transport.pause);
|
|
}
|
|
|
|
Unit* GameObject::GetOwner() const
|
|
{
|
|
return ObjectAccessor::GetUnit(*this, GetOwnerGUID());
|
|
}
|
|
|
|
void GameObject::SaveRespawnTime()
|
|
{
|
|
if (m_goData && m_goData->dbData && m_respawnTime > time(NULL) && m_spawnedByDefault)
|
|
sObjectMgr->SaveGORespawnTime(m_DBTableGuid, GetInstanceId(), m_respawnTime);
|
|
}
|
|
|
|
bool GameObject::IsAlwaysVisibleFor(WorldObject const* seer) const
|
|
{
|
|
if (WorldObject::IsAlwaysVisibleFor(seer))
|
|
return true;
|
|
|
|
if (IsTransport())
|
|
return true;
|
|
|
|
if (!seer)
|
|
return false;
|
|
|
|
// Always seen by owner and friendly units
|
|
if (uint64 guid = GetOwnerGUID())
|
|
{
|
|
if (seer->GetGUID() == guid)
|
|
return true;
|
|
|
|
Unit* owner = GetOwner();
|
|
if (owner && seer->isType(TYPEMASK_UNIT) && owner->IsFriendlyTo(((Unit*)seer)))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GameObject::IsInvisibleDueToDespawn() const
|
|
{
|
|
if (WorldObject::IsInvisibleDueToDespawn())
|
|
return true;
|
|
|
|
// Despawned
|
|
if (!isSpawned())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void GameObject::Respawn()
|
|
{
|
|
if (m_spawnedByDefault && m_respawnTime > 0)
|
|
{
|
|
m_respawnTime = time(NULL);
|
|
sObjectMgr->RemoveGORespawnTime(m_DBTableGuid, GetInstanceId());
|
|
}
|
|
}
|
|
|
|
bool GameObject::ActivateToQuest(Player* target) const
|
|
{
|
|
if (target->HasQuestForGO(GetEntry()))
|
|
return true;
|
|
|
|
if (!sObjectMgr->IsGameObjectForQuests(GetEntry()))
|
|
return false;
|
|
|
|
switch (GetGoType())
|
|
{
|
|
// scan GO chest with loot including quest items
|
|
case GAMEOBJECT_TYPE_CHEST:
|
|
{
|
|
if (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->GetTypeID(true) == BATTLEGROUND_AV && !(((BattlegroundAV*)bg)->PlayerCanDoMineQuest(GetEntry(), target->GetTeam())))
|
|
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_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;
|
|
|
|
float range = float(target->GetSpellMaxRangeForTarget(GetOwner(), trapSpell));
|
|
|
|
// search nearest linked GO
|
|
GameObject* trapGO = NULL;
|
|
{
|
|
// using original GO distance
|
|
CellCoord p(Trinity::ComputeCellCoord(GetPositionX(), GetPositionY()));
|
|
Cell cell(p);
|
|
|
|
Trinity::NearestGameObjectEntryInObjectRangeCheck go_check(*target, trapEntry, range);
|
|
Trinity::GameObjectLastSearcher<Trinity::NearestGameObjectEntryInObjectRangeCheck> checker(this, trapGO, go_check);
|
|
|
|
TypeContainerVisitor<Trinity::GameObjectLastSearcher<Trinity::NearestGameObjectEntryInObjectRangeCheck>, GridTypeMapContainer > object_checker(checker);
|
|
cell.Visit(p, object_checker, *GetMap(), *target, range);
|
|
}
|
|
|
|
// found correct GO
|
|
if (trapGO)
|
|
trapGO->CastSpell(target, trapInfo->trap.spellId);
|
|
}
|
|
|
|
GameObject* GameObject::LookupFishingHoleAround(float range)
|
|
{
|
|
GameObject* ok = NULL;
|
|
|
|
CellCoord p(Trinity::ComputeCellCoord(GetPositionX(), GetPositionY()));
|
|
Cell cell(p);
|
|
Trinity::NearestGameObjectFishingHole u_check(*this, range);
|
|
Trinity::GameObjectSearcher<Trinity::NearestGameObjectFishingHole> checker(this, ok, u_check);
|
|
|
|
TypeContainerVisitor<Trinity::GameObjectSearcher<Trinity::NearestGameObjectFishingHole>, GridTypeMapContainer > grid_object_checker(checker);
|
|
cell.Visit(p, grid_object_checker, *GetMap(), *this, 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 /*=NULL*/)
|
|
{
|
|
if (m_lootState != GO_READY)
|
|
return;
|
|
|
|
if (!time_to_restore)
|
|
time_to_restore = GetGOInfo()->GetAutoCloseTime();
|
|
|
|
SwitchDoorOrButton(true, alternative);
|
|
SetLootState(GO_ACTIVATED, user);
|
|
|
|
m_cooldownTime = time(NULL) + time_to_restore;
|
|
}
|
|
|
|
void GameObject::SetGoArtKit(uint8 kit)
|
|
{
|
|
SetByteValue(GAMEOBJECT_BYTES_1, 2, kit);
|
|
GameObjectData* data = const_cast<GameObjectData*>(sObjectMgr->GetGOData(m_DBTableGuid));
|
|
if (data)
|
|
data->artKit = kit;
|
|
}
|
|
|
|
void GameObject::SetGoArtKit(uint8 artkit, GameObject* go, uint32 lowguid)
|
|
{
|
|
const GameObjectData* data = NULL;
|
|
if (go)
|
|
{
|
|
go->SetGoArtKit(artkit);
|
|
data = go->GetGOData();
|
|
}
|
|
else if (lowguid)
|
|
data = sObjectMgr->GetGOData(lowguid);
|
|
|
|
if (data)
|
|
const_cast<GameObjectData*>(data)->artKit = artkit;
|
|
}
|
|
|
|
void GameObject::SwitchDoorOrButton(bool activate, bool alternative /* = false */)
|
|
{
|
|
if (activate)
|
|
SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE);
|
|
else
|
|
RemoveFlag(GAMEOBJECT_FLAGS, 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)
|
|
{
|
|
// 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;
|
|
|
|
AI()->GossipHello(playerUser);
|
|
}
|
|
|
|
// If cooldown data present in template
|
|
if (uint32 cooldown = GetGOInfo()->GetCooldown())
|
|
{
|
|
if (m_cooldownTime > sWorld->GetGameTime())
|
|
return;
|
|
|
|
m_cooldownTime = sWorld->GetGameTime() + cooldown;
|
|
}
|
|
|
|
switch (GetGoType())
|
|
{
|
|
case GAMEOBJECT_TYPE_DOOR: //0
|
|
case GAMEOBJECT_TYPE_BUTTON: //1
|
|
//doors/buttons never really despawn, only reset to default state/flags
|
|
UseDoorOrButton(0, false, user);
|
|
|
|
// activate script
|
|
GetMap()->ScriptsStart(sGameObjectScripts, GetDBTableGUIDLow(), spellCaster, this);
|
|
return;
|
|
|
|
case GAMEOBJECT_TYPE_QUESTGIVER: //2
|
|
{
|
|
if (user->GetTypeId() != TYPEID_PLAYER)
|
|
return;
|
|
|
|
Player* player = user->ToPlayer();
|
|
|
|
player->PrepareGossipMenu(this, GetGOInfo()->questgiver.gossipID);
|
|
player->SendPreparedGossip(this);
|
|
return;
|
|
}
|
|
//Sitting: Wooden bench, chairs enzz
|
|
case GAMEOBJECT_TYPE_CHAIR: //7
|
|
{
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
if (!info)
|
|
return;
|
|
|
|
if (user->GetTypeId() != TYPEID_PLAYER)
|
|
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] = 0; // Last user of current slot set to 0 (none sit here yet)
|
|
else
|
|
ChairListSlots[0] = 0; // 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 * sin(orthogonalOrientation);
|
|
|
|
if (itr->second)
|
|
{
|
|
if (Player* ChairUser = ObjectAccessor::FindPlayer(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 = 0; // This seat is unoccupied.
|
|
else
|
|
itr->second = 0; // 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);
|
|
|
|
/* debug code. It will spawn a npc on each slot to visualize them.
|
|
Creature* helper = player->SummonCreature(14496, x_i, y_i, GetPositionZ(), GetOrientation(), TEMPSUMMON_TIMED_OR_DEAD_DESPAWN, 10000);
|
|
std::ostringstream output;
|
|
output << i << ": thisDist: " << thisDistance;
|
|
helper->MonsterSay(output.str().c_str(), LANG_UNIVERSAL, 0);
|
|
*/
|
|
|
|
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;
|
|
}
|
|
}
|
|
//else
|
|
//player->GetSession()->SendNotification("There's nowhere left for you to sit.");
|
|
|
|
return;
|
|
}
|
|
//big gun, its a spell/aura
|
|
case GAMEOBJECT_TYPE_GOOBER: //10
|
|
{
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
|
|
if (user->GetTypeId() == TYPEID_PLAYER)
|
|
{
|
|
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)
|
|
{
|
|
sLog->outDebug(LOG_FILTER_MAPSCRIPTS, "Goober ScriptStart id %u for GO entry %u (GUID %u).", info->goober.eventId, GetEntry(), GetDBTableGUIDLow());
|
|
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);
|
|
|
|
player->CastedCreatureOrGO(info->entry, GetGUID(), 0);
|
|
}
|
|
|
|
if (uint32 trapEntry = info->goober.linkedTrapId)
|
|
TriggeringLinkedGameObject(trapEntry, user);
|
|
|
|
SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE);
|
|
SetLootState(GO_ACTIVATED, user);
|
|
|
|
// 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());
|
|
else
|
|
SetGoState(GO_STATE_ACTIVE);
|
|
|
|
m_cooldownTime = time(NULL) + info->GetAutoCloseTime();
|
|
|
|
// cast this spell later if provided
|
|
spellId = info->goober.spellId;
|
|
spellCaster = NULL;
|
|
|
|
break;
|
|
}
|
|
case GAMEOBJECT_TYPE_CAMERA: //13
|
|
{
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
if (!info)
|
|
return;
|
|
|
|
if (user->GetTypeId() != TYPEID_PLAYER)
|
|
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);
|
|
|
|
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 zone_skill = sObjectMgr->GetFishingBaseSkillLevel(subzone);
|
|
if (!zone_skill)
|
|
zone_skill = sObjectMgr->GetFishingBaseSkillLevel(zone);
|
|
|
|
//provide error, no fishable zone or area should be 0
|
|
if (!zone_skill)
|
|
sLog->outErrorDb("Fishable areaId %u are not properly defined in `skill_fishing_base_level`.", subzone);
|
|
|
|
int32 skill = player->GetSkillValue(SKILL_FISHING);
|
|
|
|
int32 chance;
|
|
if (skill < zone_skill)
|
|
{
|
|
chance = int32(pow((double)skill/zone_skill, 2) * 100);
|
|
if (chance < 1)
|
|
chance = 1;
|
|
}
|
|
else
|
|
chance = 100;
|
|
|
|
int32 roll = irand(1, 100);
|
|
|
|
sLog->outStaticDebug("Fishing check (skill: %i zone min skill: %i chance %i roll: %i", skill, zone_skill, chance, roll);
|
|
|
|
// but you will likely cause junk in areas that require a high fishing skill (not yet implemented)
|
|
if (chance >= roll)
|
|
{
|
|
player->UpdateFishingSkill();
|
|
|
|
//TODO: I do not understand this hack. Need some explanation.
|
|
// prevent removing GO at spell cancel
|
|
RemoveFromOwner();
|
|
SetOwnerGUID(player->GetGUID());
|
|
|
|
//TODO: find reasonable value for fishing hole search
|
|
GameObject* ok = LookupFishingHoleAround(20.0f + CONTACT_DISTANCE);
|
|
if (ok)
|
|
{
|
|
ok->Use(player);
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
}
|
|
else
|
|
player->SendLoot(GetGUID(), LOOT_FISHING);
|
|
}
|
|
// TODO: else: 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);
|
|
return;
|
|
}
|
|
|
|
case GAMEOBJECT_TYPE_SUMMONING_RITUAL: //18
|
|
{
|
|
if (user->GetTypeId() != TYPEID_PLAYER)
|
|
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_ritualOwner && !owner)
|
|
m_ritualOwner = player;
|
|
|
|
if (owner)
|
|
{
|
|
if (owner->GetTypeId() != TYPEID_PLAYER)
|
|
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;
|
|
|
|
// in case summoning ritual caster is GO creator
|
|
spellCaster = owner;
|
|
}
|
|
else
|
|
{
|
|
if (player != m_ritualOwner && (info->summoningRitual.castersGrouped && !player->IsInSameRaidWith(m_ritualOwner)))
|
|
return;
|
|
|
|
spellCaster = player;
|
|
}
|
|
|
|
AddUniqueUse(player);
|
|
|
|
if (info->summoningRitual.animSpell)
|
|
{
|
|
player->CastSpell(player, info->summoningRitual.animSpell, true);
|
|
|
|
// for this case, summoningRitual.spellId is always triggered
|
|
triggered = true;
|
|
}
|
|
|
|
// full amount unique participants including original summoner
|
|
if (GetUniqueUseCount() == info->summoningRitual.reqParticipants)
|
|
{
|
|
spellCaster = m_ritualOwner ? m_ritualOwner : spellCaster;
|
|
|
|
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;
|
|
triggered = true;
|
|
}
|
|
|
|
// 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, SelectRandomContainerElement(m_unique_users)))
|
|
spellCaster->CastSpell(target, info->summoningRitual.casterTargetSpell, true);
|
|
|
|
// finish owners spell
|
|
if (owner)
|
|
owner->FinishSpell(CURRENT_CHANNELED_SPELL);
|
|
|
|
// can be deleted now, if
|
|
if (!info->summoningRitual.ritualPersistent)
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
else
|
|
{
|
|
// reset ritual for this GO
|
|
m_ritualOwner = NULL;
|
|
m_unique_users.clear();
|
|
m_usetimes = 0;
|
|
}
|
|
}
|
|
else
|
|
return;
|
|
|
|
// go to end function to spell casting
|
|
break;
|
|
}
|
|
case GAMEOBJECT_TYPE_SPELLCASTER: //22
|
|
{
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
if (!info)
|
|
return;
|
|
|
|
if (info->spellcaster.partyOnly)
|
|
{
|
|
Unit* caster = GetOwner();
|
|
if (!caster || caster->GetTypeId() != TYPEID_PLAYER)
|
|
return;
|
|
|
|
if (user->GetTypeId() != TYPEID_PLAYER || !user->ToPlayer()->IsInSameRaidWith(caster->ToPlayer()))
|
|
return;
|
|
}
|
|
|
|
user->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
|
spellId = info->spellcaster.spellId;
|
|
|
|
AddUse();
|
|
break;
|
|
}
|
|
case GAMEOBJECT_TYPE_MEETINGSTONE: //23
|
|
{
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
|
|
if (user->GetTypeId() != TYPEID_PLAYER)
|
|
return;
|
|
|
|
Player* player = user->ToPlayer();
|
|
|
|
Player* targetPlayer = ObjectAccessor::FindPlayer(player->GetSelection());
|
|
|
|
// accept only use by player from same raid as caster, except caster itself
|
|
if (!targetPlayer || targetPlayer == player || !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;
|
|
|
|
if (info->entry == 194097)
|
|
spellId = 61994; // Ritual of Summoning
|
|
else
|
|
spellId = 59782; // Summoning Stone Effect
|
|
|
|
break;
|
|
}
|
|
|
|
case GAMEOBJECT_TYPE_FLAGSTAND: // 24
|
|
{
|
|
if (user->GetTypeId() != TYPEID_PLAYER)
|
|
return;
|
|
|
|
Player* player = user->ToPlayer();
|
|
|
|
if (player->CanUseBattlegroundObject())
|
|
{
|
|
// in battleground check
|
|
Battleground* bg = player->GetBattleground();
|
|
if (!bg)
|
|
return;
|
|
if (player->GetVehicle())
|
|
return;
|
|
// 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->GetTypeId() != TYPEID_PLAYER)
|
|
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->GetTypeId() != TYPEID_PLAYER)
|
|
return;
|
|
|
|
Player* player = user->ToPlayer();
|
|
|
|
if (player->CanUseBattlegroundObject())
|
|
{
|
|
// in battleground check
|
|
Battleground* bg = player->GetBattleground();
|
|
if (!bg)
|
|
return;
|
|
if (player->GetVehicle())
|
|
return;
|
|
// BG flag dropped
|
|
// WS:
|
|
// 179785 - Silverwing Flag
|
|
// 179786 - Warsong Flag
|
|
// EotS:
|
|
// 184142 - Netherstorm Flag
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
if (info)
|
|
{
|
|
switch (info->entry)
|
|
{
|
|
case 179785: // Silverwing Flag
|
|
case 179786: // Warsong Flag
|
|
if (bg->GetTypeID(true) == BATTLEGROUND_WS)
|
|
bg->EventPlayerClickedOnFlag(player, this);
|
|
break;
|
|
case 184142: // Netherstorm Flag
|
|
if (bg->GetTypeID(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->GetTypeId() != TYPEID_PLAYER)
|
|
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)
|
|
sLog->outError("GameObject::Use(): unit (type: %u, guid: %u, name: %s) tries to use object (guid: %u, entry: %u, name: %s) of unknown type (%u)",
|
|
user->GetTypeId(), user->GetGUIDLow(), user->GetName(), GetGUIDLow(), GetEntry(), GetGOInfo()->name.c_str(), GetGoType());
|
|
break;
|
|
}
|
|
|
|
if (!spellId)
|
|
return;
|
|
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
|
if (!spellInfo)
|
|
{
|
|
if (user->GetTypeId() != TYPEID_PLAYER || !sOutdoorPvPMgr->HandleCustomSpell(user->ToPlayer(), spellId, this))
|
|
sLog->outError("WORLD: unknown spell id %u at use action for gameobject (Entry: %u GoType: %u)", spellId, GetEntry(), GetGoType());
|
|
else
|
|
sLog->outDebug(LOG_FILTER_OUTDOORPVP, "WORLD: %u non-dbc spell was handled by OutdoorPvP", spellId);
|
|
return;
|
|
}
|
|
|
|
if (spellCaster)
|
|
spellCaster->CastSpell(user, spellInfo, triggered);
|
|
else
|
|
CastSpell(user, spellId);
|
|
}
|
|
|
|
void GameObject::CastSpell(Unit* target, uint32 spellId)
|
|
{
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
|
if (!spellInfo)
|
|
return;
|
|
|
|
bool self = false;
|
|
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
if (spellInfo->Effects[i].TargetA.GetTarget() == TARGET_UNIT_CASTER)
|
|
{
|
|
self = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (self)
|
|
{
|
|
if (target)
|
|
target->CastSpell(target, spellInfo, true);
|
|
return;
|
|
}
|
|
|
|
//summon world trigger
|
|
Creature* trigger = SummonTrigger(GetPositionX(), GetPositionY(), GetPositionZ(), 0, spellInfo->CalcCastTime() + 100);
|
|
if (!trigger)
|
|
return;
|
|
|
|
if (Unit* owner = GetOwner())
|
|
{
|
|
trigger->setFaction(owner->getFaction());
|
|
// needed for GO casts for proper target validation checks
|
|
trigger->SetUInt64Value(UNIT_FIELD_SUMMONEDBY, owner->GetGUID());
|
|
trigger->CastSpell(target ? target : trigger, spellInfo, true, 0, 0, owner->GetGUID());
|
|
}
|
|
else
|
|
{
|
|
trigger->setFaction(14);
|
|
// Set owner guid for target if no owner avalible - needed by trigger auras
|
|
// - trigger gets despawned and there's no caster avalible (see AuraEffect::TriggerSpell())
|
|
trigger->CastSpell(target ? target : trigger, spellInfo, true, 0, 0, target ? target->GetGUID() : 0);
|
|
}
|
|
}
|
|
|
|
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 = sin(GetOrientation());
|
|
float cosA = cos(GetOrientation());
|
|
float dx = x - GetPositionX();
|
|
float dy = y - GetPositionY();
|
|
float dz = z - GetPositionZ();
|
|
float dist = sqrt(dx*dx + dy*dy);
|
|
float sinB = dx / dist;
|
|
float cosB = dy / dist;
|
|
dx = dist * (cosA * cosB + sinA * sinB);
|
|
dy = dist * (cosA * sinB - sinA * cosB);
|
|
return dx < info->maxX + radius && dx > info->minX - radius
|
|
&& dy < info->maxY + radius && dy > info->minY - radius
|
|
&& dz < info->maxZ + radius && dz > info->minZ - radius;
|
|
}
|
|
|
|
void GameObject::EventInform(uint32 eventId)
|
|
{
|
|
if (eventId && m_zoneScript)
|
|
m_zoneScript->ProcessEvent(this, eventId);
|
|
}
|
|
|
|
// overwrite WorldObject function for proper name localization
|
|
const char* 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].c_str();
|
|
}
|
|
|
|
return GetName();
|
|
}
|
|
|
|
void GameObject::UpdateRotationFields(float rotation2 /*=0.0f*/, float rotation3 /*=0.0f*/)
|
|
{
|
|
static double const atan_pow = atan(pow(2.0f, -20.0f));
|
|
|
|
double f_rot1 = sin(GetOrientation() / 2.0f);
|
|
double f_rot2 = cos(GetOrientation() / 2.0f);
|
|
|
|
int64 i_rot1 = int64(f_rot1 / atan_pow *(f_rot2 >= 0 ? 1.0f : -1.0f));
|
|
int64 rotation = (i_rot1 << 43 >> 43) & 0x00000000001FFFFF;
|
|
|
|
//float f_rot2 = sin(0.0f / 2.0f);
|
|
//int64 i_rot2 = f_rot2 / atan(pow(2.0f, -20.0f));
|
|
//rotation |= (((i_rot2 << 22) >> 32) >> 11) & 0x000003FFFFE00000;
|
|
|
|
//float f_rot3 = sin(0.0f / 2.0f);
|
|
//int64 i_rot3 = f_rot3 / atan(pow(2.0f, -21.0f));
|
|
//rotation |= (i_rot3 >> 42) & 0x7FFFFC0000000000;
|
|
|
|
m_rotation = rotation;
|
|
|
|
if (rotation2 == 0.0f && rotation3 == 0.0f)
|
|
{
|
|
rotation2 = (float)f_rot1;
|
|
rotation3 = (float)f_rot2;
|
|
}
|
|
|
|
SetFloatValue(GAMEOBJECT_PARENTROTATION+2, rotation2);
|
|
SetFloatValue(GAMEOBJECT_PARENTROTATION+3, rotation3);
|
|
}
|
|
|
|
void GameObject::ModifyHealth(int32 change, Unit* attackerOrHealer /*= NULL*/, uint32 spellId /*= 0*/)
|
|
{
|
|
if (!GetGOValue()->Building.MaxHealth || !change)
|
|
return;
|
|
|
|
// prevent double destructions of the same object
|
|
if (change < 0 && !GetGOValue()->Building.Health)
|
|
return;
|
|
|
|
if (int32(GetGOValue()->Building.Health) + change <= 0)
|
|
GetGOValue()->Building.Health = 0;
|
|
else if (int32(GetGOValue()->Building.Health) + change >= int32(GetGOValue()->Building.MaxHealth))
|
|
GetGOValue()->Building.Health = GetGOValue()->Building.MaxHealth;
|
|
else
|
|
GetGOValue()->Building.Health += change;
|
|
|
|
// Set the health bar, value = 255 * healthPct;
|
|
SetGoAnimProgress(GetGOValue()->Building.Health * 255 / GetGOValue()->Building.MaxHealth);
|
|
|
|
Player* player = attackerOrHealer->GetCharmerOrOwnerPlayerOrPlayerItself();
|
|
|
|
// dealing damage, send packet
|
|
// TODO: is there any packet for healing?
|
|
if (change < 0 && player)
|
|
{
|
|
WorldPacket data(SMSG_DESTRUCTIBLE_BUILDING_DAMAGE, 8 + 8 + 8 + 4 + 4);
|
|
data.appendPackGUID(GetGUID());
|
|
data.appendPackGUID(attackerOrHealer->GetGUID());
|
|
data.appendPackGUID(player->GetGUID());
|
|
data << uint32(-change);
|
|
data << uint32(spellId);
|
|
player->GetSession()->SendPacket(&data);
|
|
}
|
|
|
|
GameObjectDestructibleState newState = GetDestructibleState();
|
|
|
|
if (!GetGOValue()->Building.Health)
|
|
newState = GO_DESTRUCTIBLE_DESTROYED;
|
|
else if (GetGOValue()->Building.Health <= GetGOInfo()->building.damagedNumHits)
|
|
newState = GO_DESTRUCTIBLE_DAMAGED;
|
|
else if (GetGOValue()->Building.Health == GetGOValue()->Building.MaxHealth)
|
|
newState = GO_DESTRUCTIBLE_INTACT;
|
|
|
|
if (newState == GetDestructibleState())
|
|
return;
|
|
|
|
SetDestructibleState(newState, player, false);
|
|
}
|
|
|
|
void GameObject::SetDestructibleState(GameObjectDestructibleState state, Player* eventInvoker /*= NULL*/, 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:
|
|
RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_DAMAGED | GO_FLAG_DESTROYED);
|
|
SetDisplayId(m_goInfo->displayId);
|
|
if (setHealth)
|
|
{
|
|
m_goValue->Building.Health = m_goValue->Building.MaxHealth;
|
|
SetGoAnimProgress(255);
|
|
}
|
|
break;
|
|
case GO_DESTRUCTIBLE_DAMAGED:
|
|
{
|
|
EventInform(m_goInfo->building.damagedEvent);
|
|
sScriptMgr->OnGameObjectDamaged(this, eventInvoker);
|
|
if (eventInvoker)
|
|
if (Battleground* bg = eventInvoker->GetBattleground())
|
|
bg->EventPlayerDamagedGO(eventInvoker, this, m_goInfo->building.damagedEvent);
|
|
|
|
RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_DESTROYED);
|
|
SetFlag(GAMEOBJECT_FLAGS, 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 (eventInvoker)
|
|
{
|
|
if (Battleground* bg = eventInvoker->GetBattleground())
|
|
{
|
|
bg->EventPlayerDamagedGO(eventInvoker, this, m_goInfo->building.destroyedEvent);
|
|
bg->DestroyGate(eventInvoker, this);
|
|
}
|
|
}
|
|
|
|
RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_DAMAGED);
|
|
SetFlag(GAMEOBJECT_FLAGS, 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);
|
|
}
|
|
break;
|
|
}
|
|
case GO_DESTRUCTIBLE_REBUILDING:
|
|
{
|
|
EventInform(m_goInfo->building.rebuildingEvent);
|
|
RemoveFlag(GAMEOBJECT_FLAGS, 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);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GameObject::SetLootState(LootState state, Unit* unit)
|
|
{
|
|
m_lootState = state;
|
|
AI()->OnStateChanged(state, unit);
|
|
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);
|
|
|
|
if (GetGOData() && GetGOData()->go_state == GO_NOT_READY)
|
|
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);
|
|
if (m_model)
|
|
{
|
|
if (!IsInWorld())
|
|
return;
|
|
|
|
// 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 (GetGOData() && GetGOData()->go_state == GO_NOT_READY)
|
|
startOpen = !startOpen;
|
|
|
|
if (state == GO_STATE_ACTIVE || state == GO_STATE_ACTIVE_ALTERNATIVE)
|
|
EnableCollision(startOpen);
|
|
else if (state == GO_STATE_READY)
|
|
EnableCollision(!startOpen);
|
|
}
|
|
}
|
|
|
|
void GameObject::SetDisplayId(uint32 displayid)
|
|
{
|
|
SetUInt32Value(GAMEOBJECT_DISPLAYID, displayid);
|
|
UpdateModel();
|
|
}
|
|
|
|
void GameObject::SetPhaseMask(uint32 newPhaseMask, bool update)
|
|
{
|
|
WorldObject::SetPhaseMask(newPhaseMask, update);
|
|
EnableCollision(true);
|
|
}
|
|
|
|
void GameObject::EnableCollision(bool enable)
|
|
{
|
|
if (!m_model)
|
|
return;
|
|
|
|
/*if (enable && !GetMap()->Contains(*m_model))
|
|
GetMap()->Insert(*m_model);*/
|
|
|
|
m_model->enable(enable ? GetPhaseMask() : 0);
|
|
}
|
|
|
|
void GameObject::UpdateModel()
|
|
{
|
|
if (!IsInWorld())
|
|
return;
|
|
if (m_model)
|
|
if (GetMap()->Contains(*m_model))
|
|
GetMap()->Remove(*m_model);
|
|
delete m_model;
|
|
m_model = GameObjectModel::Create(*this);
|
|
if (m_model)
|
|
GetMap()->Insert(*m_model);
|
|
}
|