/* * 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 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 . */ #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 #include #include 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(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(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(), 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::iterator itr = m_SkillupList.begin(); itr != m_SkillupList.end();) { if (itr->second > 0) { if (itr->second > static_cast(diff)) { itr->second -= static_cast(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()->SendDirectMessage(&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(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()->SendDirectMessage(&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(m_spawnId) : 0; if (poolid) sPoolMgr->UpdatePool(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 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 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 /*= 0ms*/, Seconds forceRespawnTime /*= 0s*/) { 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(m_spawnId) : 0; if (poolid) { sPoolMgr->UpdatePool(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(m_spawnId) : 0; if (poolid) sPoolMgr->UpdatePool(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(), 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(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 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(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(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; uint32 triggeredFlags = TRIGGERED_NONE; 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; } if (user->IsPlayer() && GetGoType() != GAMEOBJECT_TYPE_TRAP) // workaround for GO casting if (!m_goInfo->IsUsableMounted()) user->RemoveAurasByType(SPELL_AURA_MOUNTED); 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->SendDirectMessage(&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->SendDirectMessage(&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; } } } 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->SendDirectMessage(&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 (m_goInfo->IsUsableMounted()) triggeredFlags |= TRIGGERED_IGNORE_CASTER_MOUNTED_OR_ON_VEHICLE; if (Player* player = user->ToPlayer()) sOutdoorPvPMgr->HandleCustomSpell(player, spellId, this); if (spellCaster) { if ((spellCaster->CastSpell(user, spellInfo, TriggerCastFlags(triggeredFlags)) == 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->SendDirectMessage(&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 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(_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(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 const& playerGuid) { int32 timer = GetMap()->IsDungeon() ? -1 : 10 * MINUTE * IN_MILLISECONDS; m_SkillupList[playerGuid] = timer; } bool GameObject::IsInSkillupList(ObjectGuid const& 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; }