/*
* This file is part of the TrinityCore 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 "AreaTrigger.h"
#include "AreaTriggerAI.h"
#include "AreaTriggerDataStore.h"
#include "AreaTriggerPackets.h"
#include "CellImpl.h"
#include "Chat.h"
#include "Containers.h"
#include "CreatureAISelector.h"
#include "DB2Stores.h"
#include "G3DPosition.hpp"
#include "GameTime.h"
#include "GridNotifiersImpl.h"
#include "Language.h"
#include "Log.h"
#include "MapUtils.h"
#include "Object.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "PhasingHandler.h"
#include "Player.h"
#include "RestMgr.h"
#include "ScriptMgr.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "Spline.h"
#include "Transport.h"
#include "Unit.h"
#include "UpdateData.h"
#include "ZoneScript.h"
#include "advstd.h"
#include
AreaTrigger::AreaTrigger() : WorldObject(false), MapObject(), _spawnId(0), _aurEff(nullptr),
_duration(0), _totalDuration(0), _verticesUpdatePreviousOrientation(std::numeric_limits::infinity()),
_isRemoved(false), _reachedDestination(true), _lastSplineIndex(0),
_areaTriggerCreateProperties(nullptr), _areaTriggerTemplate(nullptr)
{
m_objectType |= TYPEMASK_AREATRIGGER;
m_objectTypeId = TYPEID_AREATRIGGER;
m_updateFlag.Stationary = true;
m_entityFragments.Add(WowCS::EntityFragment::Tag_AreaTrigger, false);
}
AreaTrigger::~AreaTrigger()
{
}
void AreaTrigger::AddToWorld()
{
///- Register the AreaTrigger for guid lookup and for caster
if (!IsInWorld())
{
if (m_zoneScript)
m_zoneScript->OnAreaTriggerCreate(this);
GetMap()->GetObjectsStore().Insert(this);
if (_spawnId)
GetMap()->GetAreaTriggerBySpawnIdStore().insert(std::make_pair(_spawnId, this));
WorldObject::AddToWorld();
}
}
void AreaTrigger::RemoveFromWorld()
{
///- Remove the AreaTrigger from the accessor and from all lists of objects in world
if (IsInWorld())
{
if (m_zoneScript)
m_zoneScript->OnAreaTriggerRemove(this);
_isRemoved = true;
if (Unit* caster = GetCaster())
caster->_UnregisterAreaTrigger(this);
_ai->OnRemove();
// Handle removal of all units, calling OnUnitExit & deleting auras if needed
HandleUnitEnterExit({}, AreaTriggerExitReason::ByExpire);
WorldObject::RemoveFromWorld();
if (IsStaticSpawn())
Trinity::Containers::MultimapErasePair(GetMap()->GetAreaTriggerBySpawnIdStore(), _spawnId, this);
GetMap()->GetObjectsStore().Remove(this);
}
}
void AreaTrigger::PlaySpellVisual(uint32 spellVisualId) const
{
WorldPackets::AreaTrigger::AreaTriggerPlaySpellVisual packet;
packet.AreaTriggerGUID = GetGUID();
packet.SpellVisualID = spellVisualId;
SendMessageToSet(packet.Write(), false);
}
bool AreaTrigger::Create(AreaTriggerCreatePropertiesId areaTriggerCreatePropertiesId, Map* map, Position const& pos, int32 duration, AreaTriggerSpawn const* spawnData /*= nullptr*/, Unit* caster /*= nullptr*/, Unit* target /*= nullptr*/, SpellCastVisual spellVisual /*= { 0, 0 }*/, SpellInfo const* spellInfo /*= nullptr*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/)
{
_targetGuid = target ? target->GetGUID() : ObjectGuid::Empty;
_aurEff = aurEff;
SetMap(map);
Relocate(pos);
RelocateStationaryPosition(pos);
if (!IsPositionValid())
{
TC_LOG_ERROR("entities.areatrigger", "AreaTrigger (AreaTriggerCreatePropertiesId: (Id: {}, IsCustom: {})) not created. Invalid coordinates (X: {} Y: {})", areaTriggerCreatePropertiesId.Id, uint32(areaTriggerCreatePropertiesId.IsCustom), GetPositionX(), GetPositionY());
return false;
}
_areaTriggerCreateProperties = sAreaTriggerDataStore->GetAreaTriggerCreateProperties(areaTriggerCreatePropertiesId);
if (!_areaTriggerCreateProperties)
{
TC_LOG_ERROR("entities.areatrigger", "AreaTrigger (AreaTriggerCreatePropertiesId: (Id: {}, IsCustom: {})) not created. Invalid areatrigger create properties id", areaTriggerCreatePropertiesId.Id, uint32(areaTriggerCreatePropertiesId.IsCustom));
return false;
}
SetZoneScript();
_areaTriggerTemplate = _areaTriggerCreateProperties->Template;
Object::_Create(ObjectGuid::Create(GetMapId(), GetTemplate() ? GetTemplate()->Id.Id : 0, GetMap()->GenerateLowGuid()));
if (GetTemplate())
SetEntry(GetTemplate()->Id.Id);
SetObjectScale(1.0f);
SetDuration(duration);
SetShape(GetCreateProperties()->Shape);
auto areaTriggerData = m_values.ModifyValue(&AreaTrigger::m_areaTriggerData);
if (caster)
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::Caster), caster->GetGUID());
if (spell)
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::CreatingEffectGUID), spell->m_castId);
if (spellInfo && !IsStaticSpawn())
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::SpellID), spellInfo->Id);
SpellInfo const* spellForVisuals = spellInfo;
if (GetCreateProperties()->SpellForVisuals)
{
spellForVisuals = sSpellMgr->GetSpellInfo(*GetCreateProperties()->SpellForVisuals, DIFFICULTY_NONE);
if (spellForVisuals)
spellVisual.SpellXSpellVisualID = caster ? caster->GetCastSpellXSpellVisualId(spellForVisuals) : spellForVisuals->GetSpellXSpellVisualId();
}
if (spellForVisuals)
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::SpellForVisuals), spellForVisuals->Id);
SetSpellVisual(spellVisual);
if (!IsStaticSpawn())
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::TimeToTargetScale), GetCreateProperties()->TimeToTargetScale != 0 ? GetCreateProperties()->TimeToTargetScale : *m_areaTriggerData->Duration);
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::BoundsRadius2D), GetCreateProperties()->Shape.GetMaxSearchRadius());
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::DecalPropertiesID), GetCreateProperties()->DecalPropertiesId);
if (IsServerSide())
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::DecalPropertiesID), 24); // Blue decal, for .debug areatrigger visibility
SetScaleCurve(areaTriggerData.ModifyValue(&UF::AreaTriggerData::ExtraScaleCurve), 1.0f);
if (caster)
{
if (Player const* modOwner = caster->GetSpellModOwner())
{
float multiplier = 1.0f;
int32 flat = 0;
modOwner->GetSpellModValues(spellInfo, SpellModOp::Radius, spell, *m_areaTriggerData->BoundsRadius2D, &flat, &multiplier);
if (multiplier != 1.0f)
{
ScaleCurveData overrideScale;
overrideScale.Curve = multiplier;
SetScaleCurve(areaTriggerData.ModifyValue(&UF::AreaTriggerData::OverrideScaleCurve), overrideScale);
}
}
}
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::VisualAnim).ModifyValue(&UF::VisualAnim::AnimationDataID), GetCreateProperties()->AnimId);
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::VisualAnim).ModifyValue(&UF::VisualAnim::AnimKitID), GetCreateProperties()->AnimKitId);
if (GetCreateProperties()->Flags.HasFlag(AreaTriggerCreatePropertiesFlag::VisualAnimIsDecay))
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::VisualAnim).ModifyValue(&UF::VisualAnim::IsDecay), true);
AreaTriggerFieldFlags fieldFlags = [flags = GetCreateProperties()->Flags]()
{
AreaTriggerFieldFlags fieldFlags = AreaTriggerFieldFlags::None;
if (flags.HasFlag(AreaTriggerCreatePropertiesFlag::HasAbsoluteOrientation))
fieldFlags |= AreaTriggerFieldFlags::AbsoluteOrientation;
if (flags.HasFlag(AreaTriggerCreatePropertiesFlag::HasDynamicShape))
fieldFlags |= AreaTriggerFieldFlags::DynamicShape;
if (flags.HasFlag(AreaTriggerCreatePropertiesFlag::HasAttached))
fieldFlags |= AreaTriggerFieldFlags::Attached;
if (flags.HasFlag(AreaTriggerCreatePropertiesFlag::HasFaceMovementDir))
fieldFlags |= AreaTriggerFieldFlags::FaceMovementDir;
if (flags.HasFlag(AreaTriggerCreatePropertiesFlag::HasFollowsTerrain))
fieldFlags |= AreaTriggerFieldFlags::FollowsTerrain;
if (flags.HasFlag(AreaTriggerCreatePropertiesFlag::AlwaysExterior))
fieldFlags |= AreaTriggerFieldFlags::AlwaysExterior;
return fieldFlags;
}();
ReplaceAllAreaTriggerFlags(fieldFlags);
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::MovementStartTime), GameTime::GetGameTimeMS());
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::CreationTime), GameTime::GetGameTimeMS());
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::ScaleCurveId), GetCreateProperties()->ScaleCurveId);
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::FacingCurveId), GetCreateProperties()->FacingCurveId);
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::MorphCurveId), GetCreateProperties()->MorphCurveId);
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::MoveCurveId), GetCreateProperties()->MoveCurveId);
if (caster)
PhasingHandler::InheritPhaseShift(this, caster);
else if (IsStaticSpawn() && spawnData)
{
if (spawnData->phaseUseFlags || spawnData->phaseId || spawnData->phaseGroup)
PhasingHandler::InitDbPhaseShift(GetPhaseShift(), spawnData->phaseUseFlags, spawnData->phaseId, spawnData->phaseGroup);
}
if (target && HasAreaTriggerFlag(AreaTriggerFieldFlags::Attached))
m_movementInfo.transport.guid = target->GetGUID();
if (!IsStaticSpawn())
UpdatePositionData();
UpdateShape();
std::visit([&](MovementType const& movement)
{
if constexpr (std::is_same_v)
{
AreaTriggerOrbitInfo orbit = movement;
if (target && HasAreaTriggerFlag(AreaTriggerFieldFlags::Attached))
orbit.PathTarget = target->GetGUID();
else
orbit.Center = pos;
this->InitOrbit(orbit);
}
else if constexpr (std::is_same_v)
this->InitSplineOffsets(movement);
else if constexpr (std::is_same_v)
this->SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::PathType), int32(AreaTriggerPathType::None));
else
static_assert(Trinity::dependant_false_v, "Unsupported movement type");
}, GetCreateProperties()->Movement);
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::Facing), _stationaryPosition.GetOrientation());
// movement on transport of areatriggers on unit is handled by themself
TransportBase* transport = nullptr;
if (caster)
{
transport = m_movementInfo.transport.guid.IsEmpty() ? caster->GetTransport() : nullptr;
if (transport)
{
float x, y, z, o;
pos.GetPosition(x, y, z, o);
transport->CalculatePassengerOffset(x, y, z, &o);
m_movementInfo.transport.pos.Relocate(x, y, z, o);
// This object must be added to transport before adding to map for the client to properly display it
transport->AddPassenger(this);
}
}
AI_Initialize();
// Relocate areatriggers with circular movement again
if (HasOrbit())
Relocate(CalculateOrbitPosition());
if (!IsStaticSpawn())
{
if (!GetMap()->AddToMap(this))
{
// Returning false will cause the object to be deleted - remove from transport
if (transport)
transport->RemovePassenger(this);
return false;
}
}
if (caster)
caster->_RegisterAreaTrigger(this);
_ai->OnCreate(spell ? spell : nullptr);
return true;
}
AreaTrigger* AreaTrigger::CreateAreaTrigger(AreaTriggerCreatePropertiesId areaTriggerCreatePropertiesId, Position const& pos, int32 duration, Unit * caster, Unit * target, SpellCastVisual spellVisual /*= { 0, 0 }*/, SpellInfo const* spellInfo /*= nullptr*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/)
{
AreaTrigger* at = new AreaTrigger();
if (!at->Create(areaTriggerCreatePropertiesId, caster->GetMap(), pos, duration, nullptr, caster, target, spellVisual, spellInfo, spell, aurEff))
{
delete at;
return nullptr;
}
return at;
}
ObjectGuid AreaTrigger::CreateNewMovementForceId(Map* map, uint32 areaTriggerId)
{
return ObjectGuid::Create(map->GetId(), areaTriggerId, map->GenerateLowGuid());
}
bool AreaTrigger::LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool /*addToMap*/, bool /*allowDuplicate*/)
{
_spawnId = spawnId;
AreaTriggerSpawn const* spawnData = sAreaTriggerDataStore->GetAreaTriggerSpawn(spawnId);
if (!spawnData)
return false;
AreaTriggerCreateProperties const* createProperties = sAreaTriggerDataStore->GetAreaTriggerCreateProperties(spawnData->Id);
if (!createProperties)
return false;
return Create(spawnData->Id, map, spawnData->spawnPoint, -1, spawnData);
}
void AreaTrigger::Update(uint32 diff)
{
WorldObject::Update(diff);
if (!IsStaticSpawn())
{
// "If" order matter here, Orbit > Attached > Splines
if (HasOverridePosition())
{
UpdateOverridePosition();
}
else if (HasOrbit())
{
UpdateOrbitPosition();
}
else if (HasAreaTriggerFlag(AreaTriggerFieldFlags::Attached))
{
if (Unit* target = GetTarget())
{
float orientation = 0.0f;
if (m_areaTriggerData->FacingCurveId)
orientation = sDB2Manager.GetCurveValueAt(m_areaTriggerData->FacingCurveId, GetProgress());
if (!HasAreaTriggerFlag(AreaTriggerFieldFlags::AbsoluteOrientation))
orientation += target->GetOrientation();
GetMap()->AreaTriggerRelocation(this, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), orientation);
}
}
else if (HasSplines())
{
UpdateSplinePosition(*_spline);
}
else
{
if (m_areaTriggerData->FacingCurveId)
{
float orientation = sDB2Manager.GetCurveValueAt(m_areaTriggerData->FacingCurveId, GetProgress());
if (!HasAreaTriggerFlag(AreaTriggerFieldFlags::AbsoluteOrientation))
orientation += m_areaTriggerData->Facing;
SetOrientation(orientation);
}
UpdateShape();
}
}
if (GetDuration() != -1)
{
if (GetDuration() > int32(diff))
_UpdateDuration(_duration - diff);
else
{
Remove(); // expired
return;
}
}
_ai->OnUpdate(diff);
UpdateTargetList();
}
void AreaTrigger::Remove()
{
if (IsInWorld())
{
AddObjectToRemoveList(); // calls RemoveFromWorld
}
}
uint32 AreaTrigger::GetTimeSinceCreated() const
{
uint32 now = GameTime::GetGameTimeMS();
if (now >= *m_areaTriggerData->CreationTime)
return now - *m_areaTriggerData->CreationTime;
return 0;
}
void AreaTrigger::SetOverrideScaleCurve(float overrideScale)
{
SetScaleCurve(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::OverrideScaleCurve), overrideScale);
}
void AreaTrigger::SetOverrideScaleCurve(std::array const& points, Optional startTimeOffset, CurveInterpolationMode interpolation)
{
SetScaleCurve(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::OverrideScaleCurve), points, startTimeOffset, interpolation);
}
void AreaTrigger::ClearOverrideScaleCurve()
{
ClearScaleCurve(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::OverrideScaleCurve));
}
void AreaTrigger::SetExtraScaleCurve(float extraScale)
{
SetScaleCurve(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::ExtraScaleCurve), extraScale);
}
void AreaTrigger::SetExtraScaleCurve(std::array const& points, Optional startTimeOffset, CurveInterpolationMode interpolation)
{
SetScaleCurve(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::ExtraScaleCurve), points, startTimeOffset, interpolation);
}
void AreaTrigger::ClearExtraScaleCurve()
{
ClearScaleCurve(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::ExtraScaleCurve));
}
void AreaTrigger::SetOverrideMoveCurve(float x, float y, float z)
{
SetScaleCurve(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::OverrideMoveCurveX), x);
SetScaleCurve(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::OverrideMoveCurveY), y);
SetScaleCurve(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::OverrideMoveCurveZ), z);
}
void AreaTrigger::SetOverrideMoveCurve(std::array const& xCurvePoints, std::array const& yCurvePoints,
std::array const& zCurvePoints, Optional startTimeOffset, CurveInterpolationMode interpolation)
{
SetScaleCurve(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::OverrideMoveCurveX), xCurvePoints, startTimeOffset, interpolation);
SetScaleCurve(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::OverrideMoveCurveY), yCurvePoints, startTimeOffset, interpolation);
SetScaleCurve(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::OverrideMoveCurveZ), zCurvePoints, startTimeOffset, interpolation);
}
void AreaTrigger::ClearOverrideMoveCurve()
{
ClearScaleCurve(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::OverrideMoveCurveX));
ClearScaleCurve(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::OverrideMoveCurveY));
ClearScaleCurve(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::OverrideMoveCurveZ));
}
void AreaTrigger::SetSpellVisual(SpellCastVisual const& visual)
{
auto spellVisualMutator = m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::SpellVisual);
SetUpdateFieldValue(spellVisualMutator.ModifyValue(&UF::SpellCastVisual::SpellXSpellVisualID), visual.SpellXSpellVisualID);
SetUpdateFieldValue(spellVisualMutator.ModifyValue(&UF::SpellCastVisual::ScriptVisualID), visual.ScriptVisualID);
}
void AreaTrigger::SetDuration(int32 newDuration)
{
_duration = newDuration;
_totalDuration = newDuration;
// negative duration (permanent areatrigger) sent as 0
SetUpdateFieldValue(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::Duration), std::max(newDuration, 0));
}
void AreaTrigger::_UpdateDuration(int32 newDuration)
{
_duration = newDuration;
// should be sent in object create packets only
DoWithSuppressingObjectUpdates([&]()
{
SetUpdateFieldValue(m_values.ModifyValue(&AreaTrigger::m_areaTriggerData).ModifyValue(&UF::AreaTriggerData::Duration), _duration);
const_cast(*m_areaTriggerData).ClearChanged(&UF::AreaTriggerData::Duration);
});
}
float AreaTrigger::CalcCurrentScale() const
{
float scale = 1.0f;
if (m_areaTriggerData->OverrideScaleCurve->OverrideActive)
scale *= std::max(GetScaleCurveValue(*m_areaTriggerData->OverrideScaleCurve, m_areaTriggerData->TimeToTargetScale), 0.000001f);
else if (m_areaTriggerData->ScaleCurveId)
scale *= std::max(sDB2Manager.GetCurveValueAt(m_areaTriggerData->ScaleCurveId, GetScaleCurveProgress(*m_areaTriggerData->OverrideScaleCurve, m_areaTriggerData->TimeToTargetScale)), 0.000001f);
scale *= std::max(GetScaleCurveValue(*m_areaTriggerData->ExtraScaleCurve, m_areaTriggerData->TimeToTargetExtraScale), 0.000001f);
return scale;
}
float AreaTrigger::GetProgress() const
{
if (_totalDuration <= 0)
return 1.0f;
return std::clamp(float(GetTimeSinceCreated()) / float(GetTotalDuration()), 0.0f, 1.0f);
}
float AreaTrigger::GetScaleCurveProgress(UF::ScaleCurve const& scaleCurve, uint32 timeTo) const
{
if (!timeTo)
return 0.0f;
return std::clamp(float(GetTimeSinceCreated() - scaleCurve.StartTimeOffset) / float(timeTo), 0.0f, 1.0f);
}
float AreaTrigger::GetScaleCurveValueAtProgress(UF::ScaleCurve const& scaleCurve, float x) const
{
ASSERT(*scaleCurve.OverrideActive, "ScaleCurve must be active to evaluate it");
// unpack ParameterCurve
if (*scaleCurve.ParameterCurve & 1u)
return advstd::bit_cast(*scaleCurve.ParameterCurve & ~1u);
std::array points;
for (std::size_t i = 0; i < scaleCurve.Points.size(); ++i)
points[i] = { .X = scaleCurve.Points[i].Pos.GetPositionX(), .Y = scaleCurve.Points[i].Pos.GetPositionY() };
CurveInterpolationMode mode = CurveInterpolationMode(*scaleCurve.ParameterCurve >> 1 & 0x7);
std::size_t pointCount = *scaleCurve.ParameterCurve >> 24 & 0xFF;
return sDB2Manager.GetCurveValueAt(mode, std::span(points.begin(), pointCount), x);
}
float AreaTrigger::GetScaleCurveValue(UF::ScaleCurve const& scaleCurve, uint32 timeTo) const
{
return GetScaleCurveValueAtProgress(scaleCurve, GetScaleCurveProgress(scaleCurve, timeTo));
}
void AreaTrigger::SetScaleCurve(UF::MutableFieldReference scaleCurveMutator, float constantValue)
{
ScaleCurveData curveTemplate;
curveTemplate.Curve = constantValue;
SetScaleCurve(scaleCurveMutator, curveTemplate);
}
void AreaTrigger::SetScaleCurve(UF::MutableFieldReference scaleCurveMutator, std::array const& points,
Optional startTimeOffset, CurveInterpolationMode interpolation)
{
ScaleCurveData curveTemplate;
curveTemplate.StartTimeOffset = startTimeOffset.value_or(GetTimeSinceCreated());
curveTemplate.Mode = interpolation;
curveTemplate.Curve = points;
SetScaleCurve(scaleCurveMutator, curveTemplate);
}
void AreaTrigger::ClearScaleCurve(UF::MutableFieldReference scaleCurveMutator)
{
SetScaleCurve(scaleCurveMutator, {});
}
void AreaTrigger::SetScaleCurve(UF::MutableFieldReference scaleCurveMutator, Optional const& curve)
{
if (!curve)
{
SetUpdateFieldValue(scaleCurveMutator.ModifyValue(&UF::ScaleCurve::OverrideActive), false);
return;
}
SetUpdateFieldValue(scaleCurveMutator.ModifyValue(&UF::ScaleCurve::OverrideActive), true);
SetUpdateFieldValue(scaleCurveMutator.ModifyValue(&UF::ScaleCurve::StartTimeOffset), curve->StartTimeOffset);
Position point;
// ParameterCurve packing information
// (not_using_points & 1) | ((interpolation_mode & 0x7) << 1) | ((first_point_offset & 0xFFFFF) << 4) | ((point_count & 0xFF) << 24)
// if not_using_points is set then the entire field is simply read as a float (ignoring that lowest bit)
if (float const* simpleFloat = std::get_if(&curve->Curve))
{
uint32 packedCurve = advstd::bit_cast(*simpleFloat);
packedCurve |= 1;
SetUpdateFieldValue(scaleCurveMutator.ModifyValue(&UF::ScaleCurve::ParameterCurve), packedCurve);
// clear points
for (std::size_t i = 0; i < UF::size(); ++i)
SetUpdateFieldValue(scaleCurveMutator.ModifyValue(&UF::ScaleCurve::Points, i), point);
}
else if (ScaleCurveData::Points const* curvePoints = std::get_if(&curve->Curve))
{
CurveInterpolationMode mode = curve->Mode;
if ((*curvePoints)[1].X < (*curvePoints)[0].X)
mode = CurveInterpolationMode::Constant;
switch (mode)
{
case CurveInterpolationMode::CatmullRom:
// catmullrom requires at least 4 points, impossible here
mode = CurveInterpolationMode::Cosine;
break;
case CurveInterpolationMode::Bezier3:
case CurveInterpolationMode::Bezier4:
case CurveInterpolationMode::Bezier:
// bezier requires more than 2 points, impossible here
mode = CurveInterpolationMode::Linear;
break;
default:
break;
}
uint32 pointCount = 2;
if (mode == CurveInterpolationMode::Constant)
pointCount = 1;
uint32 packedCurve = (uint32(mode) << 1) | (pointCount << 24);
SetUpdateFieldValue(scaleCurveMutator.ModifyValue(&UF::ScaleCurve::ParameterCurve), packedCurve);
for (std::size_t i = 0; i < curvePoints->size(); ++i)
{
point.Relocate((*curvePoints)[i].X, (*curvePoints)[i].Y);
SetUpdateFieldValue(scaleCurveMutator.ModifyValue(&UF::ScaleCurve::Points, i), point);
}
}
}
void AreaTrigger::UpdateTargetList()
{
std::vector targetList;
m_areaTriggerData->ShapeData.Visit([&](ShapeType const& shape)
{
if constexpr (std::is_same_v)
this->SearchUnitInSphere(shape, targetList);
else if constexpr (std::is_same_v)
this->SearchUnitInBox(shape, targetList);
else if constexpr (std::is_same_v)
this->SearchUnitInPolygon(shape, targetList);
else if constexpr (std::is_same_v)
this->SearchUnitInCylinder(shape, targetList);
else if constexpr (std::is_same_v)
this->SearchUnitInDisk(shape, targetList);
else if constexpr (std::is_same_v)
this->SearchUnitInBoundedPlane(shape, targetList);
});
if (GetTemplate())
{
ConditionContainer const* conditions = sConditionMgr->GetConditionsForAreaTrigger(GetTemplate()->Id.Id, GetTemplate()->Id.IsCustom);
Trinity::Containers::EraseIf(targetList, [this, conditions](Unit const* target)
{
if (GetCasterGuid() == target->GetGUID())
{
if (HasActionSetFlag(AreaTriggerActionSetFlag::NotTriggeredbyCaster))
return true;
}
else
{
if (HasActionSetFlag(AreaTriggerActionSetFlag::OnlyTriggeredByCaster))
return true;
if (HasActionSetFlag(AreaTriggerActionSetFlag::CreatorsPartyOnly))
{
Unit* caster = GetCaster();
if (!caster)
return true;
if (!caster->IsInRaidWith(target))
return true;
}
}
if (Player const* player = target->ToPlayer())
{
switch (player->getDeathState())
{
case DEAD:
if (!HasActionSetFlag(AreaTriggerActionSetFlag::AllowWhileGhost))
return true;
break;
case CORPSE:
if (!HasActionSetFlag(AreaTriggerActionSetFlag::AllowWhileDead))
return true;
break;
default:
break;
}
}
if (!HasActionSetFlag(AreaTriggerActionSetFlag::CanAffectUninteractible) && target->IsUninteractible())
return true;
if (conditions)
return !sConditionMgr->IsObjectMeetToConditions(target, *conditions);
return false;
});
}
HandleUnitEnterExit(targetList);
}
void AreaTrigger::SearchUnits(std::vector& targetList, float radius, bool check3D)
{
Trinity::AnyUnitInObjectRangeCheck check(this, radius, check3D, false);
if (IsStaticSpawn())
{
Trinity::PlayerListSearcher searcher(this, targetList, check);
Cell::VisitWorldObjects(this, searcher, GetMaxSearchRadius());
}
else
{
Trinity::UnitListSearcher searcher(this, targetList, check);
Cell::VisitAllObjects(this, searcher, GetMaxSearchRadius());
}
}
void AreaTrigger::SearchUnitInSphere(UF::AreaTriggerSphere const& sphere, std::vector& targetList)
{
float progress = GetProgress();
if (m_areaTriggerData->MorphCurveId)
progress = sDB2Manager.GetCurveValueAt(m_areaTriggerData->MorphCurveId, progress);
float scale = CalcCurrentScale();
float radius = G3D::lerp(sphere.Radius, sphere.RadiusTarget, progress) * scale;
SearchUnits(targetList, radius, true);
}
void AreaTrigger::SearchUnitInBox(UF::AreaTriggerBox const& box, std::vector& targetList)
{
float progress = GetProgress();
if (m_areaTriggerData->MorphCurveId)
progress = sDB2Manager.GetCurveValueAt(m_areaTriggerData->MorphCurveId, progress);
float scale = CalcCurrentScale();
float extentsX = G3D::lerp(box.Extents->Pos.GetPositionX(), box.ExtentsTarget->Pos.GetPositionX(), progress) * scale;
float extentsY = G3D::lerp(box.Extents->Pos.GetPositionY(), box.ExtentsTarget->Pos.GetPositionY(), progress) * scale;
float extentsZ = G3D::lerp(box.Extents->Pos.GetPositionZ(), box.ExtentsTarget->Pos.GetPositionZ(), progress) * scale;
float radius = std::sqrt(extentsX * extentsX + extentsY * extentsY);
SearchUnits(targetList, radius, false);
Position const& boxCenter = GetPosition();
Trinity::Containers::EraseIf(targetList, [boxCenter, extentsX, extentsY, extentsZ](Unit const* unit) -> bool
{
return !unit->IsWithinBox(boxCenter, extentsX, extentsY, extentsZ / 2);
});
}
void AreaTrigger::SearchUnitInPolygon(UF::AreaTriggerPolygon const& polygon, std::vector& targetList)
{
float progress = GetProgress();
if (m_areaTriggerData->MorphCurveId)
progress = sDB2Manager.GetCurveValueAt(m_areaTriggerData->MorphCurveId, progress);
float height = G3D::lerp(polygon.Height, polygon.HeightTarget, progress);
float minZ = GetPositionZ() - height;
float maxZ = GetPositionZ() + height;
SearchUnits(targetList, GetMaxSearchRadius(), false);
Trinity::Containers::EraseIf(targetList, [this, minZ, maxZ](Unit const* unit) -> bool
{
return unit->GetPositionZ() < minZ
|| unit->GetPositionZ() > maxZ
|| !unit->IsInPolygon2D(*this, _polygonVertices);
});
}
void AreaTrigger::SearchUnitInCylinder(UF::AreaTriggerCylinder const& cylinder, std::vector& targetList)
{
float progress = GetProgress();
if (m_areaTriggerData->MorphCurveId)
progress = sDB2Manager.GetCurveValueAt(m_areaTriggerData->MorphCurveId, progress);
float scale = CalcCurrentScale();
float radius = G3D::lerp(cylinder.Radius, cylinder.RadiusTarget, progress) * scale;
float height = G3D::lerp(cylinder.Height, cylinder.HeightTarget, progress);
if (!HasAreaTriggerFlag(AreaTriggerFieldFlags::HeightIgnoresScale))
height *= scale;
float minZ = GetPositionZ() - height;
float maxZ = GetPositionZ() + height;
SearchUnits(targetList, radius, false);
Trinity::Containers::EraseIf(targetList, [minZ, maxZ](Unit const* unit) -> bool
{
return unit->GetPositionZ() < minZ
|| unit->GetPositionZ() > maxZ;
});
}
void AreaTrigger::SearchUnitInDisk(UF::AreaTriggerDisk const& disk, std::vector& targetList)
{
float progress = GetProgress();
if (m_areaTriggerData->MorphCurveId)
progress = sDB2Manager.GetCurveValueAt(m_areaTriggerData->MorphCurveId, progress);
float scale = CalcCurrentScale();
float innerRadius = G3D::lerp(disk.InnerRadius, disk.InnerRadiusTarget, progress) * scale;
float outerRadius = G3D::lerp(disk.OuterRadius, disk.OuterRadiusTarget, progress) * scale;
float height = G3D::lerp(disk.Height, disk.HeightTarget, progress);
if (!HasAreaTriggerFlag(AreaTriggerFieldFlags::HeightIgnoresScale))
height *= scale;
float minZ = GetPositionZ() - height;
float maxZ = GetPositionZ() + height;
SearchUnits(targetList, outerRadius, false);
Trinity::Containers::EraseIf(targetList, [this, innerRadius, minZ, maxZ](Unit const* unit) -> bool
{
return unit->IsInDist2d(this, innerRadius) || unit->GetPositionZ() < minZ || unit->GetPositionZ() > maxZ;
});
}
void AreaTrigger::SearchUnitInBoundedPlane(UF::AreaTriggerBoundedPlane const& boundedPlane, std::vector& targetList)
{
float progress = GetProgress();
if (m_areaTriggerData->MorphCurveId)
progress = sDB2Manager.GetCurveValueAt(m_areaTriggerData->MorphCurveId, progress);
float scale = CalcCurrentScale();
float extentsX = G3D::lerp(boundedPlane.Extents->Pos.GetPositionX(), boundedPlane.ExtentsTarget->Pos.GetPositionX(), progress) * scale;
float extentsY = G3D::lerp(boundedPlane.Extents->Pos.GetPositionY(), boundedPlane.ExtentsTarget->Pos.GetPositionY(), progress) * scale;
float radius = std::sqrt(extentsX * extentsX + extentsY * extentsY);
SearchUnits(targetList, radius, false);
Position const& boxCenter = GetPosition();
Trinity::Containers::EraseIf(targetList, [boxCenter, extentsX, extentsY](Unit const* unit) -> bool
{
return !unit->IsWithinBox(boxCenter, extentsX, extentsY, MAP_SIZE);
});
}
void AreaTrigger::HandleUnitEnterExit(std::vector const& newTargetList, AreaTriggerExitReason exitMode)
{
GuidUnorderedSet exitUnits(std::move(_insideUnits));
std::vector enteringUnits;
for (Unit* unit : newTargetList)
{
if (exitUnits.erase(unit->GetGUID()) == 0) // erase(key_type) returns number of elements erased
enteringUnits.push_back(unit);
_insideUnits.insert(unit->GetGUID());
}
// Handle after _insideUnits have been reinserted so we can use GetInsideUnits() in hooks
for (Unit* unit : enteringUnits)
HandleUnitEnter(unit);
for (ObjectGuid const& exitUnitGuid : exitUnits)
if (Unit* leavingUnit = ObjectAccessor::GetUnit(*this, exitUnitGuid))
HandleUnitExitInternal(leavingUnit, exitMode);
UpdateHasPlayersFlag();
if (IsStaticSpawn())
setActive(!_insideUnits.empty());
}
void AreaTrigger::HandleUnitEnter(Unit* unit)
{
if (Player* player = unit->ToPlayer())
{
if (player->isDebugAreaTriggers)
ChatHandler(player->GetSession()).PSendSysMessage(LANG_DEBUG_AREATRIGGER_ENTITY_ENTERED, GetEntry(), IsCustom(), IsStaticSpawn(), _spawnId);
player->UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_AREA_TRIGGER_ENTER, GetEntry(), 1);
if (GetTemplate()->ActionSetId)
player->UpdateCriteria(CriteriaType::EnterAreaTriggerWithActionSet, GetTemplate()->ActionSetId);
}
DoActions(unit);
_ai->OnUnitEnter(unit);
unit->EnterAreaTrigger(this);
}
void AreaTrigger::HandleUnitExitInternal(Unit* unit, AreaTriggerExitReason exitMode)
{
bool canTriggerOnExit = exitMode != AreaTriggerExitReason::ByExpire || !HasActionSetFlag(AreaTriggerActionSetFlag::DontRunOnLeaveWhenExpiring);
if (Player* player = unit->ToPlayer())
{
if (player->isDebugAreaTriggers)
ChatHandler(player->GetSession()).PSendSysMessage(LANG_DEBUG_AREATRIGGER_ENTITY_LEFT, GetEntry(), IsCustom(), IsStaticSpawn(), _spawnId);
if (canTriggerOnExit)
{
player->UpdateQuestObjectiveProgress(QUEST_OBJECTIVE_AREA_TRIGGER_EXIT, GetEntry(), 1);
if (GetTemplate()->ActionSetId)
player->UpdateCriteria(CriteriaType::LeaveAreaTriggerWithActionSet, GetTemplate()->ActionSetId);
}
}
UndoActions(unit);
if (canTriggerOnExit)
_ai->OnUnitExit(unit, exitMode);
unit->ExitAreaTrigger(this);
}
void AreaTrigger::HandleUnitExit(Unit* unit)
{
_insideUnits.erase(unit->GetGUID());
HandleUnitExitInternal(unit);
UpdateHasPlayersFlag();
}
uint32 AreaTrigger::GetScriptId() const
{
if (_spawnId)
{
if (AreaTriggerSpawn const* spawn = ASSERT_NOTNULL(sAreaTriggerDataStore->GetAreaTriggerSpawn(_spawnId)))
{
if (spawn->scriptId)
return spawn->scriptId;
}
}
if (AreaTriggerCreateProperties const* createProperties = GetCreateProperties())
return createProperties->ScriptId;
return 0;
}
Unit* AreaTrigger::GetCaster() const
{
return ObjectAccessor::GetUnit(*this, GetCasterGuid());
}
Unit* AreaTrigger::GetTarget() const
{
return ObjectAccessor::GetUnit(*this, _targetGuid);
}
uint32 AreaTrigger::GetFaction() const
{
if (Unit const* caster = GetCaster())
return caster->GetFaction();
return 0;
}
void AreaTrigger::SetShape(AreaTriggerShapeInfo const& shape)
{
std::visit([this](ShapeType const& shapeData)
{
auto areaTriggerData = m_values.ModifyValue(&AreaTrigger::m_areaTriggerData);
if constexpr (std::is_same_v)
{
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::ShapeType), 0);
auto sphere = areaTriggerData.ModifyValue(&UF::AreaTriggerData::ShapeData, UF::VariantCase);
SetUpdateFieldValue(sphere.ModifyValue(&UF::AreaTriggerSphere::Radius), shapeData.Radius);
SetUpdateFieldValue(sphere.ModifyValue(&UF::AreaTriggerSphere::RadiusTarget), shapeData.RadiusTarget);
}
else if constexpr (std::is_same_v)
{
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::ShapeType), 1);
auto box = areaTriggerData.ModifyValue(&UF::AreaTriggerData::ShapeData, UF::VariantCase);
SetUpdateFieldValue(box.ModifyValue(&UF::AreaTriggerBox::Extents), shapeData.Extents);
SetUpdateFieldValue(box.ModifyValue(&UF::AreaTriggerBox::ExtentsTarget), shapeData.ExtentsTarget);
}
else if constexpr (std::is_same_v)
{
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::ShapeType), 3);
auto polygon = areaTriggerData.ModifyValue(&UF::AreaTriggerData::ShapeData, UF::VariantCase);
auto vertices = polygon.ModifyValue(&UF::AreaTriggerPolygon::Vertices);
ClearDynamicUpdateFieldValues(vertices);
for (TaggedPosition const& vertex : shapeData.PolygonVertices)
AddDynamicUpdateFieldValue(vertices) = vertex;
auto verticesTarget = polygon.ModifyValue(&UF::AreaTriggerPolygon::VerticesTarget);
ClearDynamicUpdateFieldValues(verticesTarget);
for (TaggedPosition const& vertex : shapeData.PolygonVerticesTarget)
AddDynamicUpdateFieldValue(verticesTarget) = vertex;
SetUpdateFieldValue(polygon.ModifyValue(&UF::AreaTriggerPolygon::Height), shapeData.Height);
SetUpdateFieldValue(polygon.ModifyValue(&UF::AreaTriggerPolygon::HeightTarget), shapeData.HeightTarget);
}
else if constexpr (std::is_same_v)
{
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::ShapeType), 4);
auto cylinder = areaTriggerData.ModifyValue(&UF::AreaTriggerData::ShapeData, UF::VariantCase);
SetUpdateFieldValue(cylinder.ModifyValue(&UF::AreaTriggerCylinder::Radius), shapeData.Radius);
SetUpdateFieldValue(cylinder.ModifyValue(&UF::AreaTriggerCylinder::RadiusTarget), shapeData.RadiusTarget);
SetUpdateFieldValue(cylinder.ModifyValue(&UF::AreaTriggerCylinder::Height), shapeData.Height);
SetUpdateFieldValue(cylinder.ModifyValue(&UF::AreaTriggerCylinder::HeightTarget), shapeData.HeightTarget);
SetUpdateFieldValue(cylinder.ModifyValue(&UF::AreaTriggerCylinder::LocationZOffset), shapeData.LocationZOffset);
SetUpdateFieldValue(cylinder.ModifyValue(&UF::AreaTriggerCylinder::LocationZOffsetTarget), shapeData.LocationZOffsetTarget);
}
else if constexpr (std::is_same_v)
{
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::ShapeType), 7);
auto disk = areaTriggerData.ModifyValue(&UF::AreaTriggerData::ShapeData, UF::VariantCase);
SetUpdateFieldValue(disk.ModifyValue(&UF::AreaTriggerDisk::InnerRadius), shapeData.InnerRadius);
SetUpdateFieldValue(disk.ModifyValue(&UF::AreaTriggerDisk::InnerRadiusTarget), shapeData.InnerRadiusTarget);
SetUpdateFieldValue(disk.ModifyValue(&UF::AreaTriggerDisk::OuterRadius), shapeData.OuterRadius);
SetUpdateFieldValue(disk.ModifyValue(&UF::AreaTriggerDisk::OuterRadiusTarget), shapeData.OuterRadiusTarget);
SetUpdateFieldValue(disk.ModifyValue(&UF::AreaTriggerDisk::Height), shapeData.Height);
SetUpdateFieldValue(disk.ModifyValue(&UF::AreaTriggerDisk::HeightTarget), shapeData.HeightTarget);
SetUpdateFieldValue(disk.ModifyValue(&UF::AreaTriggerDisk::LocationZOffset), shapeData.LocationZOffset);
SetUpdateFieldValue(disk.ModifyValue(&UF::AreaTriggerDisk::LocationZOffsetTarget), shapeData.LocationZOffsetTarget);
}
else if constexpr (std::is_same_v)
{
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::ShapeType), 8);
auto boundedPlane = areaTriggerData.ModifyValue(&UF::AreaTriggerData::ShapeData, UF::VariantCase);
SetUpdateFieldValue(boundedPlane.ModifyValue(&UF::AreaTriggerBoundedPlane::Extents), shapeData.Extents);
SetUpdateFieldValue(boundedPlane.ModifyValue(&UF::AreaTriggerBoundedPlane::ExtentsTarget), shapeData.ExtentsTarget);
}
else
static_assert(Trinity::dependant_false_v, "Unsupported shape type");
}, shape.Data);
}
float AreaTrigger::GetMaxSearchRadius() const
{
return *m_areaTriggerData->BoundsRadius2D * CalcCurrentScale();
}
void AreaTrigger::UpdatePolygonVertices()
{
UF::AreaTriggerPolygon const* shape = m_areaTriggerData->ShapeData.Get();
float newOrientation = GetOrientation();
// No need to recalculate, orientation didn't change
if (G3D::fuzzyEq(_verticesUpdatePreviousOrientation, newOrientation) && shape->VerticesTarget.empty())
return;
_polygonVertices.assign(shape->Vertices.begin(), shape->Vertices.end());
if (!shape->VerticesTarget.empty())
{
float progress = GetProgress();
if (m_areaTriggerData->MorphCurveId)
progress = sDB2Manager.GetCurveValueAt(m_areaTriggerData->MorphCurveId, progress);
for (std::size_t i = 0; i < _polygonVertices.size(); ++i)
{
Position& vertex = _polygonVertices[i];
Position const& vertexTarget = shape->VerticesTarget[i].Pos;
vertex.m_positionX = G3D::lerp(vertex.GetPositionX(), vertexTarget.GetPositionX(), progress);
vertex.m_positionY = G3D::lerp(vertex.GetPositionY(), vertexTarget.GetPositionY(), progress);
}
}
float angleSin = std::sin(newOrientation);
float angleCos = std::cos(newOrientation);
// This is needed to rotate the vertices, following orientation
for (Position& vertice : _polygonVertices)
{
float x = vertice.GetPositionX() * angleCos - vertice.GetPositionY() * angleSin;
float y = vertice.GetPositionY() * angleCos + vertice.GetPositionX() * angleSin;
vertice.Relocate(x, y);
}
_verticesUpdatePreviousOrientation = newOrientation;
}
bool AreaTrigger::HasOverridePosition() const
{
return m_areaTriggerData->OverrideMoveCurveX->OverrideActive
&& m_areaTriggerData->OverrideMoveCurveY->OverrideActive
&& m_areaTriggerData->OverrideMoveCurveZ->OverrideActive;
}
void AreaTrigger::UpdateShape()
{
if (m_areaTriggerData->ShapeData.Is())
UpdatePolygonVertices();
}
bool UnitFitToActionRequirement(Unit* unit, Unit* caster, AreaTriggerAction const& action)
{
switch (action.TargetType)
{
case AREATRIGGER_ACTION_USER_FRIEND:
{
return caster->IsValidAssistTarget(unit, sSpellMgr->GetSpellInfo(action.Param, caster->GetMap()->GetDifficultyID()));
}
case AREATRIGGER_ACTION_USER_ENEMY:
{
return caster->IsValidAttackTarget(unit, sSpellMgr->GetSpellInfo(action.Param, caster->GetMap()->GetDifficultyID()));
}
case AREATRIGGER_ACTION_USER_RAID:
{
return caster->IsInRaidWith(unit);
}
case AREATRIGGER_ACTION_USER_PARTY:
{
return caster->IsInPartyWith(unit);
}
case AREATRIGGER_ACTION_USER_CASTER:
{
return unit->GetGUID() == caster->GetGUID();
}
case AREATRIGGER_ACTION_USER_ANY:
default:
break;
}
return true;
}
void AreaTrigger::DoActions(Unit* unit)
{
Unit* caster = IsStaticSpawn() ? unit : GetCaster();
if (caster && GetTemplate())
{
for (AreaTriggerAction const& action : GetTemplate()->Actions)
{
if (IsStaticSpawn() || UnitFitToActionRequirement(unit, caster, action))
{
switch (action.ActionType)
{
case AREATRIGGER_ACTION_CAST:
caster->CastSpell(unit, action.Param, CastSpellExtraArgs(TRIGGERED_FULL_MASK)
.SetOriginalCastId(m_areaTriggerData->CreatingEffectGUID->IsCast() ? *m_areaTriggerData->CreatingEffectGUID : ObjectGuid::Empty));
break;
case AREATRIGGER_ACTION_ADDAURA:
caster->AddAura(action.Param, unit);
break;
case AREATRIGGER_ACTION_TELEPORT:
{
if (WorldSafeLocsEntry const* safeLoc = sObjectMgr->GetWorldSafeLoc(action.Param))
{
if (Player* player = caster->ToPlayer())
{
if (player->GetMapId() != safeLoc->Loc.GetMapId())
{
if (WorldSafeLocsEntry const* instanceEntrance = player->GetInstanceEntrance(safeLoc->Loc.GetMapId()))
safeLoc = instanceEntrance;
}
player->TeleportTo(safeLoc->Loc);
}
}
break;
}
case AREATRIGGER_ACTION_TAVERN:
if (Player* player = caster->ToPlayer())
{
player->GetRestMgr().SetInnTrigger(InnAreaTrigger{ .IsDBC = false });
player->GetRestMgr().SetRestFlag(REST_FLAG_IN_TAVERN);
}
break;
default:
break;
}
}
}
}
}
void AreaTrigger::UndoActions(Unit* unit)
{
if (GetTemplate())
{
for (AreaTriggerAction const& action : GetTemplate()->Actions)
{
switch (action.ActionType)
{
case AREATRIGGER_ACTION_CAST:
[[fallthrough]];
case AREATRIGGER_ACTION_ADDAURA:
unit->RemoveAurasDueToSpell(action.Param, GetCasterGuid());
break;
case AREATRIGGER_ACTION_TAVERN:
if (Player* player = unit->ToPlayer())
player->GetRestMgr().SetInnTrigger(std::nullopt);
break;
default:
break;
}
}
}
}
void AreaTrigger::InitSplineOffsets(std::vector const& offsets, Optional overrideSpeed /*= {}*/, Optional speedIsTimeInSeconds /*= {}*/)
{
float angleSin = std::sin(GetOrientation());
float angleCos = std::cos(GetOrientation());
// This is needed to rotate the spline, following caster orientation
std::vector rotatedPoints;
rotatedPoints.resize(offsets.size());
for (std::size_t i = 0; i < offsets.size(); ++i)
{
Position const& offset = offsets[i];
rotatedPoints[i].x = GetPositionX() + (offset.GetPositionX() * angleCos - offset.GetPositionY() * angleSin);
rotatedPoints[i].y = GetPositionY() + (offset.GetPositionY() * angleCos + offset.GetPositionX() * angleSin);
rotatedPoints[i].z = GetPositionZ();
UpdateAllowedPositionZ(rotatedPoints[i].x, rotatedPoints[i].y, rotatedPoints[i].z);
rotatedPoints[i].z += offset.GetPositionZ();
}
InitSplines(rotatedPoints, overrideSpeed, speedIsTimeInSeconds);
}
void AreaTrigger::InitSplines(std::vector const& splinePoints, Optional overrideSpeed /*= {}*/, Optional speedIsTimeInSeconds /*= {}*/)
{
if (splinePoints.size() < 2)
return;
std::unique_ptr> spline = std::make_unique<::Movement::Spline>();
spline->init_spline(splinePoints.data(), splinePoints.size(), ::Movement::SplineBase::ModeLinear, _stationaryPosition.GetOrientation());
spline->initLengths();
float speed = overrideSpeed.value_or(GetCreateProperties()->Speed);
if (speed <= 0.0f)
speed = 1.0f;
uint32 timeToTarget = (speedIsTimeInSeconds.value_or(GetCreateProperties()->SpeedIsTime)
? speed
: spline->length() / speed) * static_cast(IN_MILLISECONDS);
auto areaTriggerData = m_values.ModifyValue(&AreaTrigger::m_areaTriggerData);
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::TimeToTarget), timeToTarget);
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::MovementStartTime), GameTime::GetGameTimeMS());
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::PathType), int32(AreaTriggerPathType::Spline));
auto pathData = areaTriggerData.ModifyValue(&UF::AreaTriggerData::PathData, UF::VariantCase);
SetUpdateFieldValue(pathData.ModifyValue(&UF::AreaTriggerSplineCalculator::Catmullrom), spline->getPointCount() >= 4);
auto points = pathData.ModifyValue(&UF::AreaTriggerSplineCalculator::Points);
ClearDynamicUpdateFieldValues(points);
for (G3D::Vector3 const& point : spline->getPoints())
AddDynamicUpdateFieldValue(points) = Vector3ToPosition(point);
_reachedDestination = false;
_spline = std::move(spline);
}
uint32 AreaTrigger::GetElapsedTimeForMovement() const
{
uint32 now = GameTime::GetGameTimeMS();
if (now >= *m_areaTriggerData->MovementStartTime)
return now - *m_areaTriggerData->MovementStartTime;
return 0;
}
void AreaTrigger::InitOrbit(AreaTriggerOrbitInfo const& orbit, Optional overrideSpeed /*= {}*/, Optional speedIsTimeInSeconds /*= {}*/)
{
// Circular movement requires either a center position or an attached unit
ASSERT(orbit.Center.has_value() || orbit.PathTarget.has_value());
float speed = overrideSpeed.value_or(GetCreateProperties()->Speed);
if (speed <= 0.0f)
speed = 1.0f;
uint32 timeToTarget = (speedIsTimeInSeconds.value_or(GetCreateProperties()->SpeedIsTime)
? speed
: static_cast(orbit.Radius * 2.0f * static_cast(M_PI) / speed)) * static_cast(IN_MILLISECONDS);
auto areaTriggerData = m_values.ModifyValue(&AreaTrigger::m_areaTriggerData);
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::TimeToTarget), timeToTarget);
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::MovementStartTime), GameTime::GetGameTimeMS());
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::OrbitPathTarget), orbit.PathTarget.value_or(ObjectGuid::Empty));
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::ZOffset), orbit.ZOffset);
if (orbit.CanLoop)
SetAreaTriggerFlag(AreaTriggerFieldFlags::CanLoop);
else
RemoveAreaTriggerFlag(AreaTriggerFieldFlags::CanLoop);
SetUpdateFieldValue(areaTriggerData.ModifyValue(&UF::AreaTriggerData::PathType), int32(AreaTriggerPathType::Orbit));
auto pathData = areaTriggerData.ModifyValue(&UF::AreaTriggerData::PathData, UF::VariantCase);
SetUpdateFieldValue(pathData.ModifyValue(&UF::AreaTriggerOrbit::CounterClockwise), orbit.CounterClockwise);
SetUpdateFieldValue(pathData.ModifyValue(&UF::AreaTriggerOrbit::Center), orbit.Center.value_or(Position()));
SetUpdateFieldValue(pathData.ModifyValue(&UF::AreaTriggerOrbit::Radius), orbit.Radius);
SetUpdateFieldValue(pathData.ModifyValue(&UF::AreaTriggerOrbit::InitialAngle), orbit.InitialAngle);
SetUpdateFieldValue(pathData.ModifyValue(&UF::AreaTriggerOrbit::BlendFromRadius), orbit.BlendFromRadius);
SetUpdateFieldValue(pathData.ModifyValue(&UF::AreaTriggerOrbit::ExtraTimeForBlending), orbit.ExtraTimeForBlending);
}
Position const* AreaTrigger::GetOrbitCenterPosition() const
{
UF::AreaTriggerOrbit const* orbit = m_areaTriggerData->PathData.Get();
if (!orbit)
return nullptr;
if (!m_areaTriggerData->OrbitPathTarget->IsEmpty())
if (WorldObject* center = ObjectAccessor::GetWorldObject(*this, *m_areaTriggerData->OrbitPathTarget))
return center;
return &orbit->Center->Pos;
}
Position AreaTrigger::CalculateOrbitPosition() const
{
Position const* centerPos = GetOrbitCenterPosition();
if (!centerPos)
return GetPosition();
UF::AreaTriggerOrbit const& cmi = *m_areaTriggerData->PathData.Get();
// AreaTrigger make exactly "Duration / TimeToTarget" loops during his life time
float pathProgress = float(GetElapsedTimeForMovement() + *cmi.ExtraTimeForBlending) / float(GetTimeToTarget());
if (m_areaTriggerData->MoveCurveId)
pathProgress = sDB2Manager.GetCurveValueAt(m_areaTriggerData->MoveCurveId, pathProgress);
// We already made one circle and can't loop
if (!HasAreaTriggerFlag(AreaTriggerFieldFlags::CanLoop))
pathProgress = std::min(1.f, pathProgress);
float radius = cmi.Radius;
if (pathProgress <= 1.0f && G3D::fuzzyNe(cmi.BlendFromRadius, radius))
{
float blendCurve = (cmi.BlendFromRadius - radius) / radius;
RoundToInterval(blendCurve, 1.f, 4.f);
float blendProgress = std::min(1.f, pathProgress / blendCurve * 0.63661975f);
radius = G3D::lerp(cmi.BlendFromRadius, radius, blendProgress);
}
// Adapt Path progress depending of circle direction
if (!cmi.CounterClockwise)
pathProgress *= -1;
float angle = cmi.InitialAngle + 2.f * float(M_PI) * pathProgress;
float x = centerPos->GetPositionX() + (radius * std::cos(angle));
float y = centerPos->GetPositionY() + (radius * std::sin(angle));
float z = centerPos->GetPositionZ() + *m_areaTriggerData->ZOffset;
float orientation = 0.0f;
if (m_areaTriggerData->FacingCurveId)
orientation = sDB2Manager.GetCurveValueAt(m_areaTriggerData->FacingCurveId, GetProgress());
if (!HasAreaTriggerFlag(AreaTriggerFieldFlags::AbsoluteOrientation))
{
orientation += angle;
orientation += cmi.CounterClockwise ? float(M_PI_4) : -float(M_PI_4);
}
return { x, y, z, orientation };
}
void AreaTrigger::UpdateOrbitPosition()
{
Position pos = CalculateOrbitPosition();
GetMap()->AreaTriggerRelocation(this, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation());
#ifdef TRINITY_DEBUG
DebugVisualizePosition();
#endif
}
void AreaTrigger::UpdateSplinePosition(Movement::Spline& spline)
{
if (_reachedDestination)
return;
if (GetElapsedTimeForMovement() >= GetTimeToTarget())
{
_reachedDestination = true;
_lastSplineIndex = int32(spline.last());
G3D::Vector3 lastSplinePosition = spline.getPoint(_lastSplineIndex);
GetMap()->AreaTriggerRelocation(this, lastSplinePosition.x, lastSplinePosition.y, lastSplinePosition.z, GetOrientation());
#ifdef TRINITY_DEBUG
DebugVisualizePosition();
#endif
_ai->OnSplineIndexReached(_lastSplineIndex);
_ai->OnDestinationReached();
return;
}
float currentTimePercent = float(GetElapsedTimeForMovement()) / float(GetTimeToTarget());
if (currentTimePercent <= 0.f)
return;
if (m_areaTriggerData->MoveCurveId)
{
float progress = sDB2Manager.GetCurveValueAt(m_areaTriggerData->MoveCurveId, currentTimePercent);
if (progress < 0.f || progress > 1.f)
{
AreaTriggerCreateProperties const* createProperties = GetCreateProperties();
TC_LOG_ERROR("entities.areatrigger", "AreaTrigger (Id: {}, AreaTriggerCreatePropertiesId: (Id: {}, IsCustom: {})) has wrong progress ({}) caused by curve calculation (MoveCurveId: {})",
GetEntry(), createProperties->Id.Id, uint32(createProperties->Id.IsCustom), progress, *m_areaTriggerData->MoveCurveId);
}
else
currentTimePercent = progress;
}
int32 lastPositionIndex = 0;
float percentFromLastPoint = 0;
spline.computeIndex(currentTimePercent, lastPositionIndex, percentFromLastPoint);
G3D::Vector3 currentPosition;
spline.evaluate_percent(lastPositionIndex, percentFromLastPoint, currentPosition);
float orientation = _stationaryPosition.GetOrientation();
if (m_areaTriggerData->FacingCurveId)
orientation += sDB2Manager.GetCurveValueAt(m_areaTriggerData->FacingCurveId, GetProgress());
if (!HasAreaTriggerFlag(AreaTriggerFieldFlags::AbsoluteOrientation) && HasAreaTriggerFlag(AreaTriggerFieldFlags::FaceMovementDir))
{
G3D::Vector3 derivative;
spline.evaluate_derivative(lastPositionIndex, percentFromLastPoint, derivative);
if (derivative.x != 0.0f || derivative.y != 0.0f)
orientation += std::atan2(derivative.y, derivative.x);
}
GetMap()->AreaTriggerRelocation(this, currentPosition.x, currentPosition.y, currentPosition.z, orientation);
#ifdef TRINITY_DEBUG
DebugVisualizePosition();
#endif
if (_lastSplineIndex != lastPositionIndex)
{
_lastSplineIndex = lastPositionIndex;
_ai->OnSplineIndexReached(_lastSplineIndex);
}
}
void AreaTrigger::UpdateOverridePosition()
{
float progress = GetScaleCurveProgress(*m_areaTriggerData->OverrideMoveCurveX, m_areaTriggerData->TimeToTargetPos);
float x = GetScaleCurveValueAtProgress(*m_areaTriggerData->OverrideMoveCurveX, progress);
float y = GetScaleCurveValueAtProgress(*m_areaTriggerData->OverrideMoveCurveY, progress);
float z = GetScaleCurveValueAtProgress(*m_areaTriggerData->OverrideMoveCurveZ, progress);
float orientation = GetOrientation();
if (m_areaTriggerData->FacingCurveId)
{
orientation = sDB2Manager.GetCurveValueAt(m_areaTriggerData->FacingCurveId, GetProgress());
if (HasAreaTriggerFlag(AreaTriggerFieldFlags::AbsoluteOrientation))
orientation += m_areaTriggerData->Facing;
}
GetMap()->AreaTriggerRelocation(this, x, y, z, orientation);
}
void AreaTrigger::UpdateHasPlayersFlag()
{
if (std::ranges::any_of(_insideUnits, [](ObjectGuid const& guid) { return guid.IsPlayer(); }))
SetAreaTriggerFlag(AreaTriggerFieldFlags::HasPlayers);
else
RemoveAreaTriggerFlag(AreaTriggerFieldFlags::HasPlayers);
}
void AreaTrigger::DebugVisualizePosition()
{
if (Unit* caster = GetCaster())
if (Player* player = caster->ToPlayer())
if (player->isDebugAreaTriggers)
player->SummonCreature(1, *this, TEMPSUMMON_TIMED_DESPAWN, Milliseconds(GetTimeToTarget()));
}
void AreaTrigger::AI_Initialize()
{
AI_Destroy();
_ai.reset(FactorySelector::SelectAreaTriggerAI(this));
_ai->OnInitialize();
}
void AreaTrigger::AI_Destroy()
{
_ai.reset();
}
bool AreaTrigger::IsNeverVisibleFor(WorldObject const* seer, bool allowServersideObjects) const
{
if (WorldObject::IsNeverVisibleFor(seer, allowServersideObjects))
return true;
if (IsServerSide() && !allowServersideObjects)
{
if (Player const* seerPlayer = seer->ToPlayer())
return !seerPlayer->isDebugAreaTriggers;
return true;
}
return false;
}
void AreaTrigger::BuildValuesCreate(ByteBuffer* data, UF::UpdateFieldFlag flags, Player const* target) const
{
m_objectData->WriteCreate(*data, flags, this, target);
m_areaTriggerData->WriteCreate(*data, flags, this, target);
}
void AreaTrigger::BuildValuesUpdate(ByteBuffer* data, UF::UpdateFieldFlag flags, Player const* target) const
{
*data << uint32(m_values.GetChangedObjectTypeMask());
if (m_values.HasChanged(TYPEID_OBJECT))
m_objectData->WriteUpdate(*data, flags, this, target);
if (m_values.HasChanged(TYPEID_AREATRIGGER))
m_areaTriggerData->WriteUpdate(*data, flags, this, target);
}
void AreaTrigger::BuildValuesUpdateForPlayerWithMask(UpdateData* data, UF::ObjectData::Mask const& requestedObjectMask,
UF::AreaTriggerData::Mask const& requestedAreaTriggerMask, Player const* target) const
{
UF::UpdateFieldFlag flags = GetUpdateFieldFlagsFor(target);
UpdateMask valuesMask;
if (requestedObjectMask.IsAnySet())
valuesMask.Set(TYPEID_OBJECT);
if (requestedAreaTriggerMask.IsAnySet())
valuesMask.Set(TYPEID_AREATRIGGER);
ByteBuffer& buffer = PrepareValuesUpdateBuffer(data);
std::size_t sizePos = buffer.wpos();
buffer << uint32(0);
BuildEntityFragmentsForValuesUpdateForPlayerWithMask(&buffer, flags);
buffer << uint32(valuesMask.GetBlock(0));
if (valuesMask[TYPEID_OBJECT])
m_objectData->WriteUpdate(buffer, requestedObjectMask, true, this, target);
if (valuesMask[TYPEID_AREATRIGGER])
m_areaTriggerData->WriteUpdate(buffer, requestedAreaTriggerMask, true, this, target);
buffer.put(sizePos, buffer.wpos() - sizePos - 4);
data->AddUpdateBlock();
}
void AreaTrigger::ValuesUpdateForPlayerWithMaskSender::operator()(Player const* player) const
{
UpdateData udata(Owner->GetMapId());
WorldPacket packet;
Owner->BuildValuesUpdateForPlayerWithMask(&udata, ObjectMask.GetChangesMask(), AreaTriggerMask.GetChangesMask(), player);
udata.BuildPacket(&packet);
player->SendDirectMessage(&packet);
}
void AreaTrigger::ClearUpdateMask(bool remove)
{
m_values.ClearChangesMask(&AreaTrigger::m_areaTriggerData);
Object::ClearUpdateMask(remove);
}