diff --git a/src/server/game/Entities/DynamicObject/DynamicObject.cpp b/src/server/game/Entities/DynamicObject/DynamicObject.cpp index 4555f83c8..46902575f 100644 --- a/src/server/game/Entities/DynamicObject/DynamicObject.cpp +++ b/src/server/game/Entities/DynamicObject/DynamicObject.cpp @@ -15,6 +15,7 @@ * with this program. If not, see . */ +#include "SpellAuraEffects.h" #include "GameTime.h" #include "GridNotifiers.h" #include "GridNotifiersImpl.h" @@ -26,7 +27,7 @@ #include "World.h" DynamicObject::DynamicObject(bool isWorldObject) : WorldObject(isWorldObject), MovableMapObject(), - _aura(nullptr), _removedAura(nullptr), _caster(nullptr), _duration(0), _isViewpoint(false) + _aura(nullptr), _removedAura(nullptr), _caster(nullptr), _duration(0), _isViewpoint(false), _updateViewerVisibilityTimer(0) { m_objectType |= TYPEMASK_DYNAMICOBJECT; m_objectTypeId = TYPEID_DYNAMICOBJECT; @@ -125,15 +126,17 @@ bool DynamicObject::CreateDynamicObject(ObjectGuid::LowType guidlow, Unit* caste SetFloatValue(DYNAMICOBJECT_RADIUS, radius); SetUInt32Value(DYNAMICOBJECT_CASTTIME, GameTime::GetGameTimeMS().count()); - if (IsWorldObject()) - setActive(true); //must before add to map to be put in world container - if (!GetMap()->AddToMap(this, true)) { // Returning false will cause the object to be deleted - remove from transport return false; } + if (IsWorldObject()) + { + setActive(true); + } + return true; } @@ -165,7 +168,22 @@ void DynamicObject::Update(uint32 p_time) if (expired) Remove(); else + { + if (_updateViewerVisibilityTimer) + { + if (_updateViewerVisibilityTimer <= p_time) + { + _updateViewerVisibilityTimer = 0; + + if (Player* playerCaster = _caster->ToPlayer()) + playerCaster->UpdateVisibilityForPlayer(); + } + else + _updateViewerVisibilityTimer -= p_time; + } + sScriptMgr->OnDynamicObjectUpdate(this, p_time); + } } void DynamicObject::Remove() @@ -214,13 +232,22 @@ void DynamicObject::RemoveAura() _removedAura->_Remove(AURA_REMOVE_BY_DEFAULT); } -void DynamicObject::SetCasterViewpoint() +void DynamicObject::SetCasterViewpoint(bool updateViewerVisibility) { if (Player* caster = _caster->ToPlayer()) { + // Remove old farsight viewpoint + if (Unit* farsightObject = ObjectAccessor::GetUnit(*caster, caster->GetGuidValue(PLAYER_FARSIGHT))) + { + _oldFarsightGUID = caster->GetGuidValue(PLAYER_FARSIGHT); + caster->SetViewpoint(farsightObject, false); + } + caster->SetViewpoint(this, true); _isViewpoint = true; } + + _updateViewerVisibilityTimer = updateViewerVisibility ? 100 : 0; } void DynamicObject::RemoveCasterViewpoint() @@ -229,6 +256,13 @@ void DynamicObject::RemoveCasterViewpoint() { caster->SetViewpoint(this, false); _isViewpoint = false; + + // Restore prev farsight viewpoint + if (Unit* farsightObject = ObjectAccessor::GetUnit(*caster, _oldFarsightGUID)) + { + caster->SetViewpoint(farsightObject, true); + } + _oldFarsightGUID.Clear(); } } diff --git a/src/server/game/Entities/DynamicObject/DynamicObject.h b/src/server/game/Entities/DynamicObject/DynamicObject.h index 90d89c060..f7cef0326 100644 --- a/src/server/game/Entities/DynamicObject/DynamicObject.h +++ b/src/server/game/Entities/DynamicObject/DynamicObject.h @@ -50,7 +50,7 @@ public: void Delay(int32 delaytime); void SetAura(Aura* aura); void RemoveAura(); - void SetCasterViewpoint(); + void SetCasterViewpoint(bool updateViewerVisibility); void RemoveCasterViewpoint(); [[nodiscard]] Unit* GetCaster() const { return _caster; } void BindToCaster(); @@ -60,11 +60,15 @@ public: [[nodiscard]] float GetRadius() const { return GetFloatValue(DYNAMICOBJECT_RADIUS); } [[nodiscard]] bool IsViewpoint() const { return _isViewpoint; } + ObjectGuid const& GetOldFarsightGUID() const { return _oldFarsightGUID; } + protected: Aura* _aura; Aura* _removedAura; Unit* _caster; int32 _duration; // for non-aura dynobjects bool _isViewpoint; + uint32 _updateViewerVisibilityTimer; + ObjectGuid _oldFarsightGUID; }; #endif diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 5d07919a0..023632c0f 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -65,7 +65,7 @@ constexpr float VisibilityDistances[AsUnderlyingType(VisibilityDistanceType::Max VISIBILITY_DISTANCE_SMALL, VISIBILITY_DISTANCE_LARGE, VISIBILITY_DISTANCE_GIGANTIC, - MAX_VISIBILITY_DISTANCE + VISIBILITY_DISTANCE_INFINITE }; Object::Object() : m_PackGUID(sizeof(uint64) + 1) @@ -1631,7 +1631,7 @@ float WorldObject::GetGridActivationRange() const { return ToCreature()->m_SightDistance; } - else if (GetTypeId() == TYPEID_GAMEOBJECT && ToGameObject()->IsTransport() && isActiveObject()) + else if (((GetTypeId() == TYPEID_GAMEOBJECT && ToGameObject()->IsTransport()) || GetTypeId() == TYPEID_DYNAMICOBJECT) && isActiveObject()) { return GetMap()->GetVisibilityRange(); } @@ -1643,7 +1643,7 @@ float WorldObject::GetVisibilityRange() const { if (IsVisibilityOverridden() && GetTypeId() == TYPEID_UNIT) { - return MAX_VISIBILITY_DISTANCE; + return *m_visibilityDistanceOverride; } else if (GetTypeId() == TYPEID_GAMEOBJECT) { @@ -1654,7 +1654,7 @@ float WorldObject::GetVisibilityRange() const } else if (IsVisibilityOverridden()) { - return MAX_VISIBILITY_DISTANCE; + return *m_visibilityDistanceOverride; } else { @@ -1676,7 +1676,7 @@ float WorldObject::GetSightRange(WorldObject const* target) const { if (target->IsVisibilityOverridden() && target->GetTypeId() == TYPEID_UNIT) { - return MAX_VISIBILITY_DISTANCE; + return *target->m_visibilityDistanceOverride; } else if (target->GetTypeId() == TYPEID_GAMEOBJECT) { @@ -1686,7 +1686,7 @@ float WorldObject::GetSightRange(WorldObject const* target) const } else if (target->IsVisibilityOverridden()) { - return MAX_VISIBILITY_DISTANCE; + return *target->m_visibilityDistanceOverride; } else if (ToPlayer()->GetCinematicMgr()->IsOnCinematic()) { diff --git a/src/server/game/Entities/Object/ObjectDefines.h b/src/server/game/Entities/Object/ObjectDefines.h index b578b5e6a..2dbd40426 100644 --- a/src/server/game/Entities/Object/ObjectDefines.h +++ b/src/server/game/Entities/Object/ObjectDefines.h @@ -31,6 +31,7 @@ #define MAX_VISIBILITY_DISTANCE 250.0f // max distance for visible objects, experimental #define SIGHT_RANGE_UNIT 50.0f #define MAX_SEARCHER_DISTANCE 150.0f // pussywizard: replace the use of MAX_VISIBILITY_DISTANCE in searchers, because MAX_VISIBILITY_DISTANCE is quite too big for this purpose +#define VISIBILITY_DISTANCE_INFINITE 533.0f #define VISIBILITY_DISTANCE_GIGANTIC 400.0f #define VISIBILITY_DISTANCE_LARGE 200.0f #define VISIBILITY_DISTANCE_NORMAL 100.0f diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 603b3d508..b582ecdd5 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -12818,7 +12818,7 @@ void Player::SetViewpoint(WorldObject* target, bool apply) if (!AddGuidValue(PLAYER_FARSIGHT, target->GetGUID())) { - LOG_FATAL("entities.player", "Player::CreateViewpoint: Player {} cannot add new viewpoint!", GetName()); + LOG_DEBUG("entities.player", "Player::CreateViewpoint: Player {} cannot add new viewpoint!", GetName()); return; } @@ -12838,7 +12838,7 @@ void Player::SetViewpoint(WorldObject* target, bool apply) if (!RemoveGuidValue(PLAYER_FARSIGHT, target->GetGUID())) { - LOG_FATAL("entities.player", "Player::CreateViewpoint: Player {} cannot remove current viewpoint!", GetName()); + LOG_DEBUG("entities.player", "Player::CreateViewpoint: Player {} cannot remove current viewpoint!", GetName()); return; } diff --git a/src/server/game/Entities/Totem/Totem.cpp b/src/server/game/Entities/Totem/Totem.cpp index e487c3f50..b41974cc5 100644 --- a/src/server/game/Entities/Totem/Totem.cpp +++ b/src/server/game/Entities/Totem/Totem.cpp @@ -97,6 +97,10 @@ void Totem::InitSummon() { SetReactState(REACT_AGGRESSIVE); GetOwner()->CastSpell(this, 6277, true); + + // Farsight objects should be active + setActive(true); + SetVisibilityDistanceOverride(VisibilityDistanceType::Infinite); } if (!IsInWater()) diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 790544196..a07cc3f09 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -3604,8 +3604,10 @@ void Unit::SetCurrentCastedSpell(Spell* pSpell) if (pSpell == m_currentSpells[CSpellType]) // avoid breaking self return; + bool bySelf = m_currentSpells[CSpellType] && m_currentSpells[CSpellType]->m_spellInfo->Id == pSpell->m_spellInfo->Id; + // break same type spell if it is not delayed - InterruptSpell(CSpellType, false); + InterruptSpell(CSpellType, false, true, bySelf); // special breakage effects: switch (CSpellType) @@ -3634,7 +3636,7 @@ void Unit::SetCurrentCastedSpell(Spell* pSpell) { // channel spells always break generic non-delayed and any channeled spells InterruptSpell(CURRENT_GENERIC_SPELL, false); - InterruptSpell(CURRENT_CHANNELED_SPELL); + InterruptSpell(CURRENT_CHANNELED_SPELL, true, true, bySelf); // it also does break autorepeat if not Auto Shot if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL] && @@ -5728,10 +5730,12 @@ DynamicObject* Unit::GetDynObject(uint32 spellId) return nullptr; } -void Unit::RemoveDynObject(uint32 spellId) +bool Unit::RemoveDynObject(uint32 spellId) { if (m_dynObj.empty()) - return; + return false; + + bool result = false; for (DynObjectList::iterator i = m_dynObj.begin(); i != m_dynObj.end();) { DynamicObject* dynObj = *i; @@ -5739,10 +5743,13 @@ void Unit::RemoveDynObject(uint32 spellId) { dynObj->Remove(); i = m_dynObj.begin(); + result = true; } else ++i; } + + return result; } void Unit::RemoveAllDynObjects() @@ -9922,7 +9929,7 @@ bool Unit::Attack(Unit* victim, bool meleeAttack) } // switch target - InterruptSpell(CURRENT_MELEE_SPELL); + InterruptSpell(CURRENT_MELEE_SPELL, true, true, true); if (!meleeAttack) ClearUnitState(UNIT_STATE_MELEE_ATTACKING); } diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 082950ffc..8beeb55a5 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -2140,7 +2140,7 @@ public: void _RegisterDynObject(DynamicObject* dynObj); void _UnregisterDynObject(DynamicObject* dynObj); DynamicObject* GetDynObject(uint32 spellId); - void RemoveDynObject(uint32 spellId); + bool RemoveDynObject(uint32 spellId); void RemoveAllDynObjects(); [[nodiscard]] GameObject* GetGameObject(uint32 spellId) const; diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index 2719d2f88..973cee758 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -1225,14 +1225,37 @@ void WorldSession::HandleFarSightOpcode(WorldPacket& recvData) if (WorldObject* target = _player->GetViewpoint()) _player->SetSeer(target); else - { LOG_DEBUG("network.opcode", "Player {} requests non-existing seer {}", _player->GetName(), _player->GetGuidValue(PLAYER_FARSIGHT).ToString()); - } } else { - LOG_DEBUG("network", "Player {} set vision to self", _player->GetGUID().ToString()); - _player->SetSeer(_player); + WorldObject* newFarsightobject = nullptr; + if (WorldObject* viewpoint = _player->GetViewpoint()) + { + if (DynamicObject* viewpointDynamicObject = viewpoint->ToDynObject()) + { + newFarsightobject = ObjectAccessor::GetUnit(*viewpointDynamicObject, viewpointDynamicObject->GetOldFarsightGUID()); + } + else if (DynamicObject* viewpointDynamicObject = _player->GetDynObject(_player->GetUInt32Value(UNIT_CHANNEL_SPELL))) + { + if (viewpointDynamicObject->IsViewpoint() && viewpointDynamicObject->GetCasterGUID() == _player->GetGUID()) + { + newFarsightobject = viewpointDynamicObject; + } + } + } + + if (newFarsightobject) + { + LOG_DEBUG("network", "Player {} set vision to old farsight {}", _player->GetGUID().ToString(), newFarsightobject->GetGUID().ToString()); + _player->SetViewpoint(_player->GetViewpoint(), false); + _player->SetViewpoint(newFarsightobject, true); + } + else + { + LOG_DEBUG("network", "Player {} set vision to self", _player->GetGUID().ToString()); + _player->SetSeer(_player); + } } GetPlayer()->UpdateVisibilityForPlayer(); diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp index 5f0c3cc97..be3d6c522 100644 --- a/src/server/game/Handlers/SpellHandler.cpp +++ b/src/server/game/Handlers/SpellHandler.cpp @@ -576,7 +576,7 @@ void WorldSession::HandleCancelChanneling(WorldPacket& recvData) if (mover != _player && mover->GetTypeId() == TYPEID_PLAYER) return; - mover->InterruptSpell(CURRENT_CHANNELED_SPELL, true, true, true); + mover->InterruptSpell(CURRENT_CHANNELED_SPELL); } void WorldSession::HandleTotemDestroyed(WorldPackets::Totem::TotemDestroyed& totemDestroyed) diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index dbc1db87e..751033fe0 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -3639,8 +3639,15 @@ SpellCastResult Spell::prepare(SpellCastTargets const* targets, AuraEffect const // skip triggered spell (item equip spell casting and other not explicit character casts/item uses) if (!(_triggeredCastFlags & TRIGGERED_IGNORE_AURA_INTERRUPT_FLAGS) && m_spellInfo->IsBreakingStealth()) { - m_caster->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_CAST, 0, m_spellInfo->Id == 75); - m_caster->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_SPELL_ATTACK, 0, m_spellInfo->Id == 75); + // Farsight spells exception + uint32 exceptSpellId = 0; + if (m_spellInfo->HasEffect(SPELL_EFFECT_ADD_FARSIGHT)) + { + exceptSpellId = m_spellInfo->Id; + } + + m_caster->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_CAST, exceptSpellId, m_spellInfo->Id == 75); + m_caster->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_SPELL_ATTACK, exceptSpellId, m_spellInfo->Id == 75); } m_caster->SetCurrentCastedSpell(this); @@ -3700,14 +3707,17 @@ void Spell::cancel(bool bySelf) break; case SPELL_STATE_CASTING: - for (std::list::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) - if ((*ihit).missCondition == SPELL_MISS_NONE) - if (Unit* unit = m_caster->GetGUID() == ihit->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID)) - unit->RemoveOwnedAura(m_spellInfo->Id, m_originalCasterGUID, 0, AURA_REMOVE_BY_CANCEL); + if (!bySelf) + { + for (std::list::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + if ((*ihit).missCondition == SPELL_MISS_NONE) + if (Unit* unit = m_caster->GetGUID() == ihit->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID)) + unit->RemoveOwnedAura(m_spellInfo->Id, m_originalCasterGUID, 0, AURA_REMOVE_BY_CANCEL); - SendChannelUpdate(0); - SendInterrupted(0); - SendCastResult(SPELL_FAILED_INTERRUPTED); + SendChannelUpdate(0); + SendInterrupted(0); + SendCastResult(SPELL_FAILED_INTERRUPTED); + } if (m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->ToPlayer()->NeedSendSpectatorData()) ArenaSpectator::SendCommand_Spell(m_caster->FindMap(), m_caster->GetGUID(), "SPE", m_spellInfo->Id, bySelf ? 99998 : 99999); @@ -3718,7 +3728,6 @@ void Spell::cancel(bool bySelf) m_appliedMods.clear(); break; - default: break; } @@ -3727,7 +3736,12 @@ void Spell::cancel(bool bySelf) if (m_selfContainer && *m_selfContainer == this) *m_selfContainer = nullptr; - m_caster->RemoveDynObject(m_spellInfo->Id); + // Do not remove current far sight object (already done in Spell::EffectAddFarsight) to prevent from reset viewpoint to player + if (!(bySelf && m_spellInfo->HasEffect(SPELL_EFFECT_ADD_FARSIGHT))) + { + m_caster->RemoveDynObject(m_spellInfo->Id); + } + if (m_spellInfo->IsChanneled()) // if not channeled then the object for the current cast wasn't summoned yet m_caster->RemoveGameObject(m_spellInfo->Id, true); diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index aa16c4026..1e7865b2f 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -2719,6 +2719,9 @@ void Spell::EffectAddFarsight(SpellEffIndex effIndex) if (!m_caster->IsInWorld()) return; + // Remove old farsight if exist + bool updateViewerVisibility = m_caster->RemoveDynObject(m_spellInfo->Id); + DynamicObject* dynObj = new DynamicObject(true); if (!dynObj->CreateDynamicObject(m_caster->GetMap()->GenerateLowGuid(), m_caster, m_spellInfo->Id, *destTarget, radius, DYNAMIC_OBJECT_FARSIGHT_FOCUS)) { @@ -2727,9 +2730,7 @@ void Spell::EffectAddFarsight(SpellEffIndex effIndex) } dynObj->SetDuration(duration); - dynObj->SetCasterViewpoint(); - - m_caster->ToPlayer()->UpdateVisibilityForPlayer(); + dynObj->SetCasterViewpoint(updateViewerVisibility); } void Spell::EffectUntrainTalents(SpellEffIndex /*effIndex*/)