/* * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-GPL2 * Copyright (C) 2008-2016 TrinityCore * Copyright (C) 2005-2009 MaNGOS */ #include #include "GameObjectAI.h" #include "BattlegroundAV.h" #include "CellImpl.h" #include "CreatureAISelector.h" #include "DisableMgr.h" #include "DynamicTree.h" #include "GameObjectModel.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 "UpdateFieldFlags.h" #include "World.h" #include "Transport.h" #include "AccountMgr.h" #ifdef ELUNA #include "LuaEngine.h" #endif GameObject::GameObject() : WorldObject(false), 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_lootState = GO_NOT_READY; m_spawnedByDefault = true; m_allowModifyDestructibleBuilding = true; m_usetimes = 0; m_spellId = 0; m_cooldownTime = 0; m_goInfo = nullptr; m_ritualOwnerGUIDLow = 0; m_goData = nullptr; m_packedRotation = 0; m_DBTableGuid = 0; m_lootRecipient = 0; m_lootRecipientGroup = 0; m_groupLootTimer = 0; lootingGroupLowGUID = 0; m_lootGenerationTime = 0; ResetLootMode(); // restore default loot mode 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 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() { uint64 ownerGUID = GetOwnerGUID(); if (!ownerGUID) return; if (Unit* owner = ObjectAccessor::GetUnit(*this, ownerGUID)) { owner->RemoveGameObject(this, false); ASSERT(!GetOwnerGUID()); return; } // Xinef: not needed /*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); 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(); #ifdef ELUNA sEluna->OnAddToWorld(this); #endif } } void GameObject::RemoveFromWorld() { ///- Remove the gameobject from the accessor if (IsInWorld()) { #ifdef ELUNA sEluna->OnRemoveFromWorld(this); #endif 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); WorldObject::RemoveFromWorld(); sObjectAccessor->RemoveObject(this); } } void GameObject::CheckRitualList() { if (m_unique_users.empty()) return; for (std::set::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 == GetGOInfo()->summoningRitual.animSpell) erase = false; if (erase) m_unique_users.erase(itr++); else ++itr; } } void GameObject::ClearRitualList() { uint32 animSpell = GetGOInfo()->summoningRitual.animSpell; if (!animSpell || m_unique_users.empty()) return; for (std::set::iterator itr = m_unique_users.begin(); itr != m_unique_users.end(); ++itr) { if (Player* channeler = ObjectAccessor::GetPlayer(*this, *itr)) 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(uint32 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()) { 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; } GameObjectAddon const* addon = sObjectMgr->GetGameObjectAddon(guidlow); // 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 SetWorldRotation(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) SetWorldRotation(rotation); else SetWorldRotationAngles(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* addon = GetTemplateAddon()) { SetUInt32Value(GAMEOBJECT_FACTION, addon->faction); SetUInt32Value(GAMEOBJECT_FLAGS, addon->flags); } SetEntry(goinfo->entry); // set name for logs usage, doesn't affect anything ingame SetName(goinfo->name); SetDisplayId(goinfo->displayId); if (!m_model) m_model = GameObjectModel::Create(*this); // GAMEOBJECT_BYTES_1, index at 0, 1, 2 and 3 SetGoType(GameobjectTypes(goinfo->type)); SetGoState(go_state); SetGoArtKit(artKit); 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 = GetGOInfo()->ScriptId; AIM_Initialize(); // Check if GameObject is Large if (goinfo->IsLargeGameObject()) SetVisibilityDistanceOverride(true); return true; } void GameObject::Update(uint32 diff) { #ifdef ELUNA sEluna->UpdateAI(this, diff); #endif if (AI()) AI()->UpdateAI(diff); else if (!AIM_Initialize()) sLog->outError("Could not initialize GameObjectAI"); 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 = World::GetGameTimeMS()+10*IN_MILLISECONDS; // Hardcoded tooltip value else if (GetOwner()) m_cooldownTime = World::GetGameTimeMS()+goInfo->trap.startDelay*IN_MILLISECONDS; m_lootState = GO_READY; break; } case GAMEOBJECT_TYPE_FISHINGNODE: { // fishing code (bobber ready) if (time(nullptr) > 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; } case GAMEOBJECT_TYPE_SUMMONING_RITUAL: { if (World::GetGameTimeMS() < m_cooldownTime) return; GameObjectTemplate const* info = GetGOInfo(); if (info->summoningRitual.animSpell) { // xinef: if ritual requires animation, ensure that all users performs channel CheckRitualList(); } if (GetUniqueUseCount() < info->summoningRitual.reqParticipants) { SetLootState(GO_READY); return; } bool triggered = info->summoningRitual.animSpell; Unit* owner = GetOwner(); Unit* spellCaster = owner ? owner : ObjectAccessor::GetPlayer(*this, MAKE_NEW_GUID(m_ritualOwnerGUIDLow, 0, HIGHGUID_PLAYER)); 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; 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, 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, triggered); return; } default: m_lootState = GO_READY; // for other GOis same switched without delay to GO_READY break; } // NO BREAK for switch (m_lootState) [[fallthrough]]; } case GO_READY: { if (m_respawnTime > 0) // timer on { time_t now = time(nullptr); if (m_respawnTime <= now) // timer expired { uint64 dbtableHighGuid = MAKE_NEW_GUID(m_DBTableGuid, GetEntry(), HIGHGUID_GAMEOBJECT); time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid); 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->ToPlayer()->RemoveGameObject(this, false); WorldPacket data(SMSG_FISH_ESCAPED, 0); caster->ToPlayer()->GetSession()->SendPacket(&data); } // can be delete m_lootState = GO_JUST_DEACTIVATED; return; } case GAMEOBJECT_TYPE_DOOR: case GAMEOBJECT_TYPE_BUTTON: //we need to open doors if they are closed (add there another condition if this code breaks some usage, but it need to be here for battlegrounds) if (GetGoState() != GO_STATE_READY) ResetDoorOrButton(); break; case GAMEOBJECT_TYPE_FISHINGHOLE: // Initialize a new max fish count on respawn m_goValue.FishingHole.MaxOpens = urand(GetGOInfo()->fishinghole.minSuccessOpens, GetGOInfo()->fishinghole.maxSuccessOpens); break; default: break; } if (!m_spawnedByDefault) // despawn timer { // can be despawned or destroyed SetLootState(GO_JUST_DEACTIVATED); return; } // Xinef: Call AI Reset (required for example in SmartAI to clear one time events) if (AI()) AI()->Reset(); // respawn timer uint32 poolid = GetDBTableGUIDLow() ? sPoolMgr->IsPartOfAPool(GetDBTableGUIDLow()) : 0; if (poolid) sPoolMgr->UpdatePool(poolid, GetDBTableGUIDLow()); 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 (World::GetGameTimeMS() < m_cooldownTime) break; // Type 2 - Bomb (will go away after casting it's spell) 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); break; } // Type 0 despawns after being triggered, type 1 does not. bool isBattlegroundTrap = false; /// @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; // Battleground gameobjects have data2 == 0 && data5 == 3 isBattlegroundTrap = true; 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) // hunter trap { acore::AnyUnfriendlyNoTotemUnitInObjectRangeCheck checker(this, owner, radius); acore::UnitSearcher searcher(this, target, checker); VisitNearbyGridObject(radius, searcher); if (!target) 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 = nullptr; acore::AnyPlayerInObjectRangeCheck checker(this, radius, true, true); acore::PlayerSearcher searcher(this, player, checker); VisitNearbyWorldObject(radius, searcher); target = player; } if (target) { // some traps do not have spell but should be triggered if (goInfo->trap.spellId) CastSpell(target, goInfo->trap.spellId); m_cooldownTime = World::GetGameTimeMS()+(goInfo->trap.cooldown ? goInfo->trap.cooldown : uint32(4))*IN_MILLISECONDS; // template or 4 seconds if (goInfo->trap.type == 1) SetLootState(GO_JUST_DEACTIVATED); if (isBattlegroundTrap && target->GetTypeId() == TYPEID_PLAYER) { // battleground gameobjects case if (target->ToPlayer()->InBattleground()) if (Battleground* bg = target->ToPlayer()->GetBattleground()) bg->HandleTriggerBuff(this); } } } 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() && World::GetGameTimeMS() >= m_cooldownTime) ResetDoorOrButton(); break; case GAMEOBJECT_TYPE_GOOBER: if (World::GetGameTimeMS() >= m_cooldownTime) { RemoveFlag(GAMEOBJECT_FLAGS, 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; } 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) { 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(); //! 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 if (GameObjectTemplateAddon const* addon = GetTemplateAddon()) SetUInt32Value(GAMEOBJECT_FLAGS, addon->flags); } if (!m_respawnDelayTime) return; if (!m_spawnedByDefault) { m_respawnTime = 0; DestroyForNearbyPlayers(); // xinef: old UpdateObjectVisibility(); return; } m_respawnTime = time(nullptr) + m_respawnDelayTime; // if option not set then object will be saved at grid unload if (GetMap()->IsDungeon()) SaveRespawnTime(); DestroyForNearbyPlayers(); // 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::Delete() { SetLootState(GO_NOT_READY); RemoveFromOwner(); SendObjectDeSpawnAnim(GetGUID()); SetGoState(GO_STATE_READY); if (GameObjectTemplateAddon const* addon = GetTemplateAddon()) SetUInt32Value(GAMEOBJECT_FLAGS, addon->flags); // Xinef: if ritual gameobject is removed, clear anim spells if (GetGOInfo()->type == GAMEOBJECT_TYPE_SUMMONING_RITUAL) ClearRitualList(); uint32 poolid = GetDBTableGUIDLow() ? sPoolMgr->IsPartOfAPool(GetDBTableGUIDLow()) : 0; if (poolid) sPoolMgr->UpdatePool(poolid, GetDBTableGUIDLow()); else AddObjectToRemoveList(); } void GameObject::getFishLoot(Loot* fishloot, Player* loot_owner) { fishloot->clear(); uint32 zone, subzone; uint32 defaultzone = 1; GetZoneAndAreaId(zone, subzone); // if subzone loot exist use it fishloot->FillLoot(subzone, LootTemplates_Fishing, loot_owner, true, true); if (fishloot->empty()) //use this becase if zone or subzone has set LOOT_MODE_JUNK_FISH,Even if no normal drop, fishloot->FillLoot return true. it wrong. { //subzone no result,use zone loot fishloot->FillLoot(zone, LootTemplates_Fishing, loot_owner, true, true); //use zone 1 as default, somewhere fishing got nothing,becase subzone and zone not set, like Off the coast of Storm Peaks. if (fishloot->empty()) fishloot->FillLoot(defaultzone, LootTemplates_Fishing, loot_owner, true, true); } } void GameObject::getFishLootJunk(Loot* fishloot, Player* loot_owner) { fishloot->clear(); uint32 zone, subzone; uint32 defaultzone = 1; GetZoneAndAreaId(zone, subzone); // if subzone loot exist use it fishloot->FillLoot(subzone, LootTemplates_Fishing, loot_owner, true, true, LOOT_MODE_JUNK_FISH); if (fishloot->empty()) //use this becase if zone or subzone has normal mask drop, then fishloot->FillLoot return true. { //use zone loot fishloot->FillLoot(zone, LootTemplates_Fishing, loot_owner, true, true, LOOT_MODE_JUNK_FISH); if (fishloot->empty()) //use zone 1 as default fishloot->FillLoot(defaultzone, LootTemplates_Fishing, loot_owner, true, true, LOOT_MODE_JUNK_FISH); } } 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.rotation = m_worldRotation; 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 SQLTransaction trans = WorldDatabase.BeginTransaction(); uint8 index = 0; PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT); stmt->setUInt32(0, m_DBTableGuid); trans->Append(stmt); stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_GAMEOBJECT); stmt->setUInt32(index++, m_DBTableGuid); stmt->setUInt32(index++, GetEntry()); stmt->setUInt16(index++, uint16(mapid)); stmt->setUInt8(index++, spawnMask); stmt->setUInt32(index++, GetPhaseMask()); stmt->setFloat(index++, GetPositionX()); stmt->setFloat(index++, GetPositionY()); stmt->setFloat(index++, GetPositionZ()); stmt->setFloat(index++, GetOrientation()); stmt->setFloat(index++, m_worldRotation.x); stmt->setFloat(index++, m_worldRotation.y); stmt->setFloat(index++, m_worldRotation.z); stmt->setFloat(index++, m_worldRotation.w); stmt->setInt32(index++, int32(m_respawnDelayTime)); stmt->setUInt8(index++, GetGoAnimProgress()); stmt->setUInt8(index++, uint8(GetGoState())); trans->Append(stmt); 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; 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, data->rotation, 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 = GetMap()->GetGORespawnTime(m_DBTableGuid); // ready to respawn if (m_respawnTime && m_respawnTime <= time(nullptr)) { m_respawnTime = 0; GetMap()->RemoveGORespawnTime(m_DBTableGuid); } } } 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() { GetMap()->RemoveGORespawnTime(m_DBTableGuid); 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); } /*********************************************************/ /*** 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() { if (m_goData && m_goData->dbData && m_respawnTime > time(nullptr) && m_spawnedByDefault) GetMap()->SaveGORespawnTime(m_DBTableGuid, m_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 (uint64 guid = GetOwnerGUID()) { if (seer->GetGUID() == guid) return true; Unit* owner = GetOwner(); if (owner) { if (seer->isType(TYPEMASK_UNIT) && 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::Respawn() { if (m_spawnedByDefault && m_respawnTime > 0) { m_respawnTime = time(nullptr); GetMap()->RemoveGORespawnTime(m_DBTableGuid); } } 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 (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: wtf, many spells have range 0 but radius > 0 float range = float(target->GetSpellMaxRangeForTarget(GetOwner(), trapSpell)); if (range < 1.0f) range = 5.0f; // search nearest linked GO GameObject* trapGO = nullptr; { // using original GO distance CellCoord p(acore::ComputeCellCoord(GetPositionX(), GetPositionY())); Cell cell(p); acore::NearestGameObjectEntryInObjectRangeCheck go_check(*target, trapEntry, range); acore::GameObjectLastSearcher checker(this, trapGO, go_check); TypeContainerVisitor, GridTypeMapContainer > object_checker(checker); cell.Visit(p, object_checker, *GetMap(), *target, range); } // found correct GO // xinef: we should use the trap (checks for despawn type) if (trapGO) trapGO->Use(target); //trapGO->CastSpell(target, trapInfo->trap.spellId); } GameObject* GameObject::LookupFishingHoleAround(float range) { GameObject* ok = nullptr; CellCoord p(acore::ComputeCellCoord(GetPositionX(), GetPositionY())); Cell cell(p); acore::NearestGameObjectFishingHole u_check(*this, range); acore::GameObjectSearcher checker(this, ok, u_check); TypeContainerVisitor, 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 = World::GetGameTimeMS()+time_to_restore; } void GameObject::SetGoArtKit(uint8 kit) { SetByteValue(GAMEOBJECT_BYTES_1, 2, kit); GameObjectData* data = const_cast(sObjectMgr->GetGOData(m_DBTableGuid)); if (data) data->artKit = kit; } void GameObject::SetGoArtKit(uint8 artkit, GameObject* go, uint32 lowguid) { const GameObjectData* data = nullptr; if (go) { go->SetGoArtKit(artkit); data = go->GetGOData(); } else if (lowguid) data = sObjectMgr->GetGOData(lowguid); if (data) const_cast(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) { // Xinef: we cannot use go with not selectable flags if (HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE)) return; // by default spell caster is user Unit* spellCaster = user; uint32 spellId = 0; bool triggered = false; bool tmpfish = false; if (Player* playerUser = user->ToPlayer()) { #ifdef ELUNA if (sEluna->OnGossipHello(playerUser, this)) return; #endif if (sScriptMgr->OnGossipHello(playerUser, this)) return; if (AI()->GossipHello(playerUser, false)) return; } // If cooldown data present in template if (uint32 cooldown = GetGOInfo()->GetCooldown()) { if (World::GetGameTimeMS() < m_cooldownTime) return; m_cooldownTime = World::GetGameTimeMS()+cooldown*IN_MILLISECONDS; } switch (GetGoType()) { case GAMEOBJECT_TYPE_DOOR: //0 //doors/buttons never really despawn, only reset to default state/flags UseDoorOrButton(0, false, user); return; case GAMEOBJECT_TYPE_BUTTON: //1 //doors/buttons never really despawn, only reset to default state/flags UseDoorOrButton(0, false, user); // Xinef: properly link possible traps if (uint32 trapEntry = GetGOInfo()->button.linkedTrap) TriggeringLinkedGameObject(trapEntry, user); return; case GAMEOBJECT_TYPE_QUESTGIVER: //2 { if (user->GetTypeId() != TYPEID_PLAYER) 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 = World::GetGameTimeMS()+(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->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::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 = 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); 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 (HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE)) return; 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) { #if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS) sLog->outDebug(LOG_FILTER_MAPSCRIPTS, "Goober ScriptStart id %u for GO entry %u (GUID %u).", info->goober.eventId, GetEntry(), GetDBTableGUIDLow()); #endif 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->KillCreditGO(info->entry, GetGUID()); } if (uint32 trapEntry = info->goober.linkedTrapId) TriggeringLinkedGameObject(trapEntry, user); if (info->GetAutoCloseTime()) { SetFlag(GAMEOBJECT_FLAGS, 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 = World::GetGameTimeMS()+info->GetAutoCloseTime(); // cast this spell later if provided spellId = info->goober.spellId; spellCaster = user; tmpfish = true; 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); #if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS) sLog->outStaticDebug("Fishing check (skill: %i zone min skill: %i chance %i roll: %i", skill, zone_skill, chance, roll); #endif // 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()); SetSpellId(0); // prevent removing unintended auras at Unit::RemoveGameObject //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); } else // else: junk player->SendLoot(GetGUID(), LOOT_FISHING_JUNK); tmpfish = true; 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; } } if(tmpfish) player->FinishSpell(CURRENT_CHANNELED_SPELL, true); else player->InterruptSpell(CURRENT_CHANNELED_SPELL, true, true, true); 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_ritualOwnerGUIDLow == 0 && !owner) m_ritualOwnerGUIDLow = player->GetGUIDLow(); 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; } else { Player* ritualOwner = ObjectAccessor::GetPlayer(*this, MAKE_NEW_GUID(m_ritualOwnerGUIDLow, 0, HIGHGUID_PLAYER)); if (!ritualOwner) return; if (player != ritualOwner && (info->summoningRitual.castersGrouped && !player->IsInSameRaidWith(ritualOwner))) return; } if (info->summoningRitual.animSpell) { // xinef: if ritual requires animation, ensure that all users performs channel CheckRitualList(); // xinef: all participants found if (GetUniqueUseCount() == info->summoningRitual.reqParticipants) return; player->CastSpell(player, info->summoningRitual.animSpell, true); } AddUniqueUse(player); // full amount unique participants including original summoner if (GetUniqueUseCount() == info->summoningRitual.reqParticipants) { SetLootState(GO_NOT_READY); // can be deleted now, if if (!info->summoningRitual.animSpell) m_cooldownTime = 0; else // channel ready, maintain this m_cooldownTime = World::GetGameTimeMS()+5*IN_MILLISECONDS; } return; } case GAMEOBJECT_TYPE_SPELLCASTER: //22 { GameObjectTemplate const* info = GetGOInfo(); if (!info) return; if (info->spellcaster.partyOnly) { Player const* caster = ObjectAccessor::GetObjectInOrOutOfWorld(GetOwnerGUID(), (Player*)NULL); if (!caster || user->GetTypeId() != TYPEID_PLAYER || !user->ToPlayer()->IsInSameRaidWith(caster)) 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->GetTarget()); // 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(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->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(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->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().c_str(), 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 #if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS) sLog->outDebug(LOG_FILTER_OUTDOORPVP, "WORLD: %u non-dbc spell was handled by OutdoorPvP", spellId); #endif return; } if (Player* player = user->ToPlayer()) sOutdoorPvPMgr->HandleCustomSpell(player, spellId, this); 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() + 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->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE)) trigger->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE); if (owner->IsFFAPvP()) trigger->SetByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP); // xinef: Remove Immunity flags trigger->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC); // 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() : 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); //! 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 = GetFloatValue(OBJECT_FIELD_SCALE_X); 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; } // pussywizard! void GameObject::SendMessageToSetInRange(WorldPacket* data, float dist, bool /*self*/, bool includeMargin, Player const* skipped_rcvr) { dist += GetObjectSize(); if (includeMargin) dist += VISIBILITY_COMPENSATION * 2.0f; // pussywizard: to ensure everyone receives all important packets acore::MessageDistDeliverer notifier(this, data, dist, false, skipped_rcvr); VisitNearbyWorldObject(dist, notifier); } void GameObject::EventInform(uint32 eventId) { if (!eventId) return; if (AI()) AI()->EventInform(eventId); if (m_zoneScript) m_zoneScript->ProcessEvent(this, eventId); } // 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_worldRotation.w >= 0.f ? 1 : -1); int64 x = int32(m_worldRotation.x * PACK_X) * w_sign & PACK_X_MASK; int64 y = int32(m_worldRotation.y * PACK_YZ) * w_sign & PACK_YZ_MASK; int64 z = int32(m_worldRotation.z * PACK_YZ) * w_sign & PACK_YZ_MASK; m_packedRotation = z | (y << 21) | (x << 42); } void GameObject::SetWorldRotation(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_worldRotation = 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::SetWorldRotationAngles(float z_rot, float y_rot, float x_rot) { SetWorldRotation(G3D::Quat(G3D::Matrix3::fromEulerAnglesZYX(z_rot, y_rot, x_rot))); } void GameObject::ModifyHealth(int32 change, Unit* attackerOrHealer /*= NULL*/, uint32 spellId /*= 0*/) { if (!IsDestructibleBuilding()) return; if (!m_goValue.Building.MaxHealth || !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.appendPackGUID(GetGUID()); data.appendPackGUID(attackerOrHealer->GetGUID()); data.appendPackGUID(player->GetGUID()); data << uint32(-change); // change < 0 triggers SPELL_BUILDING_HEAL combat log event // change >= 0 triggers SPELL_BUILDING_DAMAGE event data << uint32(spellId); player->GetSession()->SendPacket(&data); } GameObjectDestructibleState newState = GetDestructibleState(); if (!m_goValue.Building.Health) newState = GO_DESTRUCTIBLE_DESTROYED; else if (m_goValue.Building.Health <= GetGOInfo()->building.damagedNumHits) newState = GO_DESTRUCTIBLE_DAMAGED; else if (m_goValue.Building.Health == m_goValue.Building.MaxHealth) newState = GO_DESTRUCTIBLE_INTACT; if (newState == GetDestructibleState()) return; SetDestructibleState(newState, player, false); } void GameObject::SetDestructibleState(GameObjectDestructibleState state, Player* eventInvoker /*= 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); } EnableCollision(true); break; case GO_DESTRUCTIBLE_DAMAGED: { #ifdef ELUNA sEluna->OnDamaged(this, eventInvoker); #endif 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); 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: { #ifdef ELUNA sEluna->OnDestroyed(this, eventInvoker); #endif 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); } } 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); } EnableCollision(false); 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); } EnableCollision(true); break; } } } void GameObject::SetLootState(LootState state, Unit* unit) { m_lootState = state; #ifdef ELUNA sEluna->OnLootStateChanged(this, state); #endif AI()->OnStateChanged(state, unit); sScriptMgr->OnGameObjectLootStateChanged(this, state, unit); // 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); #ifdef ELUNA sEluna->OnGameObjectStateChanged(this, state); #endif 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 (GetGOData() && GetGOData()->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);*/ } } 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 && !DisableMgr::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 = GameObjectModel::Create(*this); if (m_model) GetMap()->InsertGameObjectModel(*m_model); } Player* GameObject::GetLootRecipient() const { if (!m_lootRecipient) return nullptr; return ObjectAccessor::FindPlayerInOrOutOfWorld(m_lootRecipient); } Group* GameObject::GetLootRecipientGroup() const { if (!m_lootRecipientGroup) return nullptr; return sGroupMgr->GetGroupByGUID(m_lootRecipientGroup); } void GameObject::SetLootRecipient(Unit* unit) { // set the player whose group should receive the right // to loot the creature after it dies // should be set to NULL after the loot disappears if (!unit) { m_lootRecipient = 0; m_lootRecipientGroup = 0; return; } if (unit->GetTypeId() != TYPEID_PLAYER && !unit->IsVehicle()) return; Player* player = unit->GetCharmerOrOwnerPlayerOrPlayerItself(); if (!player) // normal creature, no player involved return; m_lootRecipient = player->GetGUID(); if (Group* group = player->GetGroup()) m_lootRecipientGroup = group->GetLowGUID(); } 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; Group const* playerGroup = player->GetGroup(); if (!playerGroup || playerGroup != GetLootRecipientGroup()) // if we dont have a group we arent the recipient return false; // if go doesnt have group bound it means it was solo killed by someone else return true; } void GameObject::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target) const { if (!target) return; bool forcedFlags = GetGoType() == GAMEOBJECT_TYPE_CHEST && GetGOInfo()->chest.groupLootRules && HasLootRecipient(); bool targetIsGM = target->IsGameMaster() && AccountMgr::IsGMAccount(target->GetSession()->GetSecurity()); 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 | 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)) 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 flags = m_uint32Values[GAMEOBJECT_FLAGS]; if (GetGoType() == GAMEOBJECT_TYPE_CHEST) if (GetGOInfo()->chest.groupLootRules && !IsLootAllowedFor(target)) flags |= GO_FLAG_LOCKED | GO_FLAG_NOT_SELECTABLE; fieldBuffer << flags; } 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 /* = NULL*/) const { if (m_DBTableGuid) { if (GameObjectData const* data = sObjectMgr->GetGOData(GetDBTableGUIDLow())) { 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() { switch (GetGoType()) { /// @todo find out how the client calculates the maximal usage distance to spellless working // gameobjects like guildbanks and mailboxes - 10.0 is a just an abitrary choosen number case GAMEOBJECT_TYPE_GUILD_BANK: case GAMEOBJECT_TYPE_MAILBOX: return 10.0f; case GAMEOBJECT_TYPE_FISHINGHOLE: case GAMEOBJECT_TYPE_FISHINGNODE: return 20.0f + CONTACT_DISTANCE; // max spell range 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); } } std::unordered_map GameObject::gameObjectToEventFlag = {};