diff options
| author | Traesh <Traesh@users.noreply.github.com> | 2017-01-29 00:00:43 +0100 |
|---|---|---|
| committer | joschiwald <joschiwald.trinity@gmail.com> | 2017-01-29 00:00:43 +0100 |
| commit | 6ab3877c91c9440b81cf1c7c66a1275ee04ea26a (patch) | |
| tree | 9930d19512eef247d1c0a23284caa446ceef21c8 /src/server/game/Entities/AreaTrigger | |
| parent | 5818dd364ca6a8b0cb068710291adf26b571aa23 (diff) | |
Core/Entities: Basic AreaTrigger System (#18035)
* Implemented AreaTrigger Templates
* Implemented AreaTrigger Splines
* Implemented SPELL_AURA_AREA_TRIGGER
* and many more
Diffstat (limited to 'src/server/game/Entities/AreaTrigger')
4 files changed, 913 insertions, 19 deletions
diff --git a/src/server/game/Entities/AreaTrigger/AreaTrigger.cpp b/src/server/game/Entities/AreaTrigger/AreaTrigger.cpp index 8de274cb4bd..4393d15a401 100644 --- a/src/server/game/Entities/AreaTrigger/AreaTrigger.cpp +++ b/src/server/game/Entities/AreaTrigger/AreaTrigger.cpp @@ -16,18 +16,33 @@ */ #include "AreaTrigger.h" +#include "AreaTriggerDataStore.h" +#include "AreaTriggerPackets.h" +#include "AreaTriggerTemplate.h" +#include "CellImpl.h" +#include "Chat.h" #include "DB2Stores.h" +#include "GridNotifiersImpl.h" +#include "Language.h" #include "Log.h" +#include "Object.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "ScriptMgr.h" #include "SpellInfo.h" +#include "Transport.h" #include "Unit.h" #include "UpdateData.h" -AreaTrigger::AreaTrigger() : WorldObject(false), _duration(0), _spellXSpellVisualId(0) +AreaTrigger::AreaTrigger() : WorldObject(false), MapObject(), + _duration(0), _totalDuration(0), _timeSinceCreated(0), _previousCheckOrientation(std::numeric_limits<float>::infinity()), + _isRemoved(false), _reachedDestination(true), _lastSplineIndex(0), _movementTime(0), + _areaTriggerMiscTemplate(nullptr) { m_objectType |= TYPEMASK_AREATRIGGER; m_objectTypeId = TYPEID_AREATRIGGER; - m_updateFlag = UPDATEFLAG_STATIONARY_POSITION; + m_updateFlag = UPDATEFLAG_STATIONARY_POSITION | UPDATEFLAG_AREATRIGGER; m_valuesCount = AREATRIGGER_END; _dynamicValuesCount = AREATRIGGER_DYNAMIC_END; @@ -57,52 +72,619 @@ void AreaTrigger::RemoveFromWorld() } } -bool AreaTrigger::CreateAreaTrigger(ObjectGuid::LowType guidlow, uint32 triggerEntry, Unit* caster, SpellInfo const* spell, Position const& pos, uint32 spellXSpellVisualId) +bool AreaTrigger::CreateAreaTrigger(uint32 spellMiscId, Unit* caster, Unit* target, SpellInfo const* spell, Position const& pos, int32 duration, uint32 spellXSpellVisualId, ObjectGuid const& castId /*= ObjectGuid::Empty*/) { - _spellXSpellVisualId = spellXSpellVisualId; + _targetGuid = target ? target->GetGUID() : ObjectGuid::Empty; + SetMap(caster->GetMap()); Relocate(pos); if (!IsPositionValid()) { - TC_LOG_ERROR("misc", "AreaTrigger (spell %u) not created. Invalid coordinates (X: %f Y: %f)", spell->Id, GetPositionX(), GetPositionY()); + TC_LOG_ERROR("entities.areatrigger", "AreaTrigger (spellMiscId %u) not created. Invalid coordinates (X: %f Y: %f)", spellMiscId, GetPositionX(), GetPositionY()); return false; } - Object::_Create(ObjectGuid::Create<HighGuid::AreaTrigger>(GetMapId(), triggerEntry, guidlow)); + _areaTriggerMiscTemplate = sAreaTriggerDataStore->GetAreaTriggerMiscTemplate(spellMiscId); + if (!_areaTriggerMiscTemplate) + { + TC_LOG_ERROR("entities.areatrigger", "AreaTrigger (spellMiscId %u) not created. Invalid areatrigger miscid (%u)", spellMiscId, spellMiscId); + return false; + } + + Object::_Create(ObjectGuid::Create<HighGuid::AreaTrigger>(GetMapId(), GetTemplate()->Id, caster->GetMap()->GenerateLowGuid<HighGuid::AreaTrigger>())); SetPhaseMask(caster->GetPhaseMask(), false); - SetEntry(triggerEntry); - SetDuration(spell->GetDuration()); - SetObjectScale(1); + SetEntry(GetTemplate()->Id); + SetDuration(duration); + + SetObjectScale(1.0f); SetGuidValue(AREATRIGGER_CASTER, caster->GetGUID()); + SetGuidValue(AREATRIGGER_CREATING_EFFECT_GUID, castId); + SetUInt32Value(AREATRIGGER_SPELLID, spell->Id); SetUInt32Value(AREATRIGGER_SPELL_X_SPELL_VISUAL_ID, spellXSpellVisualId); - SetUInt32Value(AREATRIGGER_DURATION, spell->GetDuration()); + uint32 timeToTarget = GetMiscTemplate()->TimeToTarget != 0 ? GetMiscTemplate()->TimeToTarget : GetUInt32Value(AREATRIGGER_DURATION); + SetUInt32Value(AREATRIGGER_TIME_TO_TARGET_SCALE, GetMiscTemplate()->TimeToTargetScale != 0 ? GetMiscTemplate()->TimeToTargetScale : GetUInt32Value(AREATRIGGER_DURATION)); + SetFloatValue(AREATRIGGER_BOUNDS_RADIUS_2D, GetTemplate()->MaxSearchRadius); + SetUInt32Value(AREATRIGGER_DECAL_PROPERTIES_ID, GetMiscTemplate()->DecalPropertiesId); CopyPhaseFrom(caster); + if (target && GetTemplate()->HasFlag(AREATRIGGER_FLAG_HAS_ATTACHED)) + { + m_movementInfo.transport.guid = target->GetGUID(); + } + + UpdateShape(); + + if (GetMiscTemplate()->HasSplines()) + InitSplineOffsets(GetMiscTemplate()->SplinePoints, timeToTarget); + + sScriptMgr->OnAreaTriggerEntityInitialize(this); + + // movement on transport of areatriggers on unit is handled by themself + Transport* 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); + } + if (!GetMap()->AddToMap(this)) + { + // Returning false will cause the object to be deleted - remove from transport + if (transport) + transport->RemovePassenger(this); return false; + } + + caster->_RegisterAreaTrigger(this); + + sScriptMgr->OnAreaTriggerEntityCreate(this); return true; } void AreaTrigger::Update(uint32 p_time) { - if (GetDuration() > int32(p_time)) - _duration -= p_time; + WorldObject::Update(p_time); + _timeSinceCreated += p_time; + + if (GetTemplate()->HasFlag(AREATRIGGER_FLAG_HAS_ATTACHED)) + { + if (Unit* target = GetTarget()) + GetMap()->AreaTriggerRelocation(this, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), target->GetOrientation()); + } else - Remove(); // expired + UpdateSplinePosition(p_time); - WorldObject::Update(p_time); + sScriptMgr->OnAreaTriggerEntityUpdate(this, p_time); + + if (GetDuration() != -1) + { + if (GetDuration() > int32(p_time)) + _UpdateDuration(_duration - p_time); + else + { + Remove(); // expired + return; + } + } + + UpdateTargetList(); } void AreaTrigger::Remove() { if (IsInWorld()) { + _isRemoved = true; + + if (Unit* caster = GetCaster()) + caster->_UnregisterAreaTrigger(this); + + // Handle removal of all units, calling OnUnitExit & deleting auras if needed + HandleUnitEnterExit({}); + + sScriptMgr->OnAreaTriggerEntityRemove(this); + RemoveFromWorld(); AddObjectToRemoveList(); } } + +void AreaTrigger::SetDuration(int32 newDuration) +{ + _duration = newDuration; + _totalDuration = newDuration; + + // negative duration (permanent areatrigger) sent as 0 + SetUInt32Value(AREATRIGGER_DURATION, std::max(newDuration, 0)); +} + +void AreaTrigger::_UpdateDuration(int32 newDuration) +{ + _duration = newDuration; + + // should be sent in object create packets only + m_uint32Values[AREATRIGGER_DURATION] = _duration; +} + +float AreaTrigger::GetProgress() const +{ + return GetTimeSinceCreated() < GetTimeToTargetScale() ? float(GetTimeSinceCreated()) / float(GetTimeToTargetScale()) : 1.0f; +} + +void AreaTrigger::UpdateTargetList() +{ + std::list<Unit*> targetList; + + switch (GetTemplate()->Type) + { + case AREATRIGGER_TYPE_SPHERE: + SearchUnitInSphere(targetList); + break; + case AREATRIGGER_TYPE_BOX: + SearchUnitInBox(targetList); + break; + case AREATRIGGER_TYPE_POLYGON: + SearchUnitInPolygon(targetList); + break; + case AREATRIGGER_TYPE_CYLINDER: + SearchUnitInCylinder(targetList); + break; + default: + break; + } + + HandleUnitEnterExit(targetList); +} + +void AreaTrigger::SearchUnitInSphere(std::list<Unit*>& targetList) +{ + float radius = GetTemplate()->SphereDatas.Radius; + if (GetTemplate()->HasFlag(AREATRIGGER_FLAG_HAS_DYNAMIC_SHAPE)) + { + if (GetMiscTemplate()->MorphCurveId) + { + radius = G3D::lerp(GetTemplate()->SphereDatas.Radius, + GetTemplate()->SphereDatas.RadiusTarget, + sDB2Manager.GetCurveValueAt(GetMiscTemplate()->MorphCurveId, GetProgress())); + } + } + + Trinity::AnyUnitInObjectRangeCheck check(this, radius); + Trinity::UnitListSearcher<Trinity::AnyUnitInObjectRangeCheck> searcher(this, targetList, check); + VisitNearbyObject(GetTemplate()->MaxSearchRadius, searcher); +} + +void AreaTrigger::SearchUnitInBox(std::list<Unit*>& targetList) +{ + float extentsX = GetTemplate()->BoxDatas.Extents[0]; + float extentsY = GetTemplate()->BoxDatas.Extents[1]; + float extentsZ = GetTemplate()->BoxDatas.Extents[2]; + + Trinity::AnyUnitInObjectRangeCheck check(this, GetTemplate()->MaxSearchRadius, false); + Trinity::UnitListSearcher<Trinity::AnyUnitInObjectRangeCheck> searcher(this, targetList, check); + VisitNearbyObject(GetTemplate()->MaxSearchRadius, searcher); + + float halfExtentsX = extentsX / 2.0f; + float halfExtentsY = extentsY / 2.0f; + float halfExtentsZ = extentsZ / 2.0f; + + float minX = GetPositionX() - halfExtentsX; + float maxX = GetPositionX() + halfExtentsX; + + float minY = GetPositionY() - halfExtentsY; + float maxY = GetPositionY() + halfExtentsY; + + float minZ = GetPositionZ() - halfExtentsZ; + float maxZ = GetPositionZ() + halfExtentsZ; + + G3D::AABox const box({ minX, minY, minZ }, { maxX, maxY, maxZ }); + + targetList.remove_if([&box](Unit* unit) -> bool + { + return !box.contains({ unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ() }); + }); +} + +void AreaTrigger::SearchUnitInPolygon(std::list<Unit*>& targetList) +{ + Trinity::AnyUnitInObjectRangeCheck check(this, GetTemplate()->MaxSearchRadius, false); + Trinity::UnitListSearcher<Trinity::AnyUnitInObjectRangeCheck> searcher(this, targetList, check); + VisitNearbyObject(GetTemplate()->MaxSearchRadius, searcher); + + float height = GetTemplate()->PolygonDatas.Height; + float minZ = GetPositionZ() - height; + float maxZ = GetPositionZ() + height; + + targetList.remove_if([this, minZ, maxZ](Unit* unit) -> bool + { + return !CheckIsInPolygon2D(unit) + || unit->GetPositionZ() < minZ + || unit->GetPositionZ() > maxZ; + }); +} + +void AreaTrigger::SearchUnitInCylinder(std::list<Unit*>& targetList) +{ + Trinity::AnyUnitInObjectRangeCheck check(this, GetTemplate()->MaxSearchRadius, false); + Trinity::UnitListSearcher<Trinity::AnyUnitInObjectRangeCheck> searcher(this, targetList, check); + VisitNearbyObject(GetTemplate()->MaxSearchRadius, searcher); + + float height = GetTemplate()->CylinderDatas.Height; + float minZ = GetPositionZ() - height; + float maxZ = GetPositionZ() + height; + + targetList.remove_if([minZ, maxZ](Unit* unit) -> bool + { + return unit->GetPositionZ() < minZ + || unit->GetPositionZ() > maxZ; + }); +} + +void AreaTrigger::HandleUnitEnterExit(std::list<Unit*> const& newTargetList) +{ + GuidUnorderedSet exitUnits = _insideUnits; + _insideUnits.clear(); + + std::vector<Unit*> 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) + { + if (Player* player = unit->ToPlayer()) + if (player->isDebugAreaTriggers) + ChatHandler(player->GetSession()).PSendSysMessage(LANG_DEBUG_AREATRIGGER_ENTERED, GetTemplate()->Id); + + DoActions(unit); + sScriptMgr->OnAreaTriggerEntityUnitEnter(this, unit); + } + + for (ObjectGuid const& exitUnitGuid : exitUnits) + { + if (Unit* leavingUnit = ObjectAccessor::GetUnit(*this, exitUnitGuid)) + { + if (Player* player = leavingUnit->ToPlayer()) + if (player->isDebugAreaTriggers) + ChatHandler(player->GetSession()).PSendSysMessage(LANG_DEBUG_AREATRIGGER_LEFT, GetTemplate()->Id); + + UndoActions(leavingUnit); + sScriptMgr->OnAreaTriggerEntityUnitExit(this, leavingUnit); + } + } +} + +AreaTriggerTemplate const* AreaTrigger::GetTemplate() const +{ + return _areaTriggerMiscTemplate->Template; +} + +uint32 AreaTrigger::GetScriptId() const +{ + return GetTemplate()->ScriptId; +} + +Unit* AreaTrigger::GetCaster() const +{ + return ObjectAccessor::GetUnit(*this, GetCasterGuid()); +} + +Unit* AreaTrigger::GetTarget() const +{ + return ObjectAccessor::GetUnit(*this, _targetGuid); +} + +void AreaTrigger::UpdatePolygonOrientation() +{ + float newOrientation = GetOrientation(); + + // No need to recalculate, orientation didn't change + if (G3D::fuzzyEq(_previousCheckOrientation, newOrientation)) + return; + + _polygonVertices = GetTemplate()->PolygonVertices; + + float angleSin = std::sin(newOrientation); + float angleCos = std::cos(newOrientation); + + // This is needed to rotate the vertices, following orientation + for (G3D::Vector2& vertice : _polygonVertices) + { + float tempX = vertice.x; + float tempY = vertice.y; + + vertice.x = tempX * angleCos - tempY * angleSin; + vertice.y = tempX * angleSin + tempY * angleCos; + } + + _previousCheckOrientation = newOrientation; +} + +bool AreaTrigger::CheckIsInPolygon2D(Position const* pos) const +{ + float testX = pos->GetPositionX(); + float testY = pos->GetPositionY(); + + //this method uses the ray tracing algorithm to determine if the point is in the polygon + int nPoints = _polygonVertices.size(); + bool locatedInPolygon = false; + + for (int i = 0; i < nPoints; ++i) + { + int j = -999; + + //repeat loop for all sets of points + if (i == (nPoints - 1)) + { + //if i is the last vertex, let j be the first vertex + j = 0; + } + else + { + //for all-else, let j=(i+1)th vertex + j = i + 1; + } + + float vertX_i = GetPositionX() + _polygonVertices[i].x; + float vertY_i = GetPositionY() + _polygonVertices[i].y; + float vertX_j = GetPositionX() + _polygonVertices[j].x; + float vertY_j = GetPositionY() + _polygonVertices[j].y; + + // following statement checks if testPoint.Y is below Y-coord of i-th vertex + bool belowLowY = vertY_i > testY; + // following statement checks if testPoint.Y is below Y-coord of i+1-th vertex + bool belowHighY = vertY_j > testY; + + /* following statement is true if testPoint.Y satisfies either (only one is possible) + -->(i).Y < testPoint.Y < (i+1).Y OR + -->(i).Y > testPoint.Y > (i+1).Y + + (Note) + Both of the conditions indicate that a point is located within the edges of the Y-th coordinate + of the (i)-th and the (i+1)- th vertices of the polygon. If neither of the above + conditions is satisfied, then it is assured that a semi-infinite horizontal line draw + to the right from the testpoint will NOT cross the line that connects vertices i and i+1 + of the polygon + */ + bool withinYsEdges = belowLowY != belowHighY; + + if (withinYsEdges) + { + // this is the slope of the line that connects vertices i and i+1 of the polygon + float slopeOfLine = (vertX_j - vertX_i) / (vertY_j - vertY_i); + + // this looks up the x-coord of a point lying on the above line, given its y-coord + float pointOnLine = (slopeOfLine* (testY - vertY_i)) + vertX_i; + + //checks to see if x-coord of testPoint is smaller than the point on the line with the same y-coord + bool isLeftToLine = testX < pointOnLine; + + if (isLeftToLine) + { + //this statement changes true to false (and vice-versa) + locatedInPolygon = !locatedInPolygon; + }//end if (isLeftToLine) + }//end if (withinYsEdges + } + + return locatedInPolygon; +} + +void AreaTrigger::UpdateShape() +{ + if (GetTemplate()->IsPolygon()) + UpdatePolygonOrientation(); +} + +bool UnitFitToActionRequirement(Unit* unit, Unit* caster, AreaTriggerActionUserTypes targetType) +{ + switch (targetType) + { + case AREATRIGGER_ACTION_USER_FRIEND: + { + return caster->IsFriendlyTo(unit); + } + case AREATRIGGER_ACTION_USER_ENEMY: + { + return !caster->IsFriendlyTo(unit); + } + 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) +{ + if (Unit* caster = GetCaster()) + { + for (AreaTriggerAction const& action : GetTemplate()->Actions) + { + if (UnitFitToActionRequirement(unit, caster, action.TargetType)) + { + switch (action.ActionType) + { + case AREATRIGGER_ACTION_CAST: + caster->CastSpell(unit, action.Param, true); + break; + case AREATRIGGER_ACTION_ADDAURA: + caster->AddAura(action.Param, unit); + break; + default: + break; + } + } + } + } +} + +void AreaTrigger::UndoActions(Unit* unit) +{ + for (AreaTriggerAction const& action : GetTemplate()->Actions) + { + if (action.ActionType == AREATRIGGER_ACTION_CAST || action.ActionType == AREATRIGGER_ACTION_ADDAURA) + unit->RemoveAurasDueToSpell(action.Param, GetCasterGuid()); + } +} + +void AreaTrigger::InitSplineOffsets(std::vector<G3D::Vector3> splinePoints, uint32 timeToTarget) +{ + float angleSin = std::sin(GetOrientation()); + float angleCos = std::cos(GetOrientation()); + + // This is needed to rotate the spline, following caster orientation + for (G3D::Vector3& spline : splinePoints) + { + float tempX = spline.x; + float tempY = spline.y; + float tempZ = GetPositionZ(); + + spline.x = (tempX * angleCos - tempY * angleSin) + GetPositionX(); + spline.y = (tempX * angleSin + tempY * angleCos) + GetPositionY(); + UpdateAllowedPositionZ(spline.x, spline.y, tempZ); + spline.z += tempZ; + } + + InitSplines(splinePoints, timeToTarget); +} + +void AreaTrigger::InitSplines(std::vector<G3D::Vector3> const& splinePoints, uint32 timeToTarget) +{ + if (splinePoints.size() < 2) + return; + + _movementTime = 0; + + _spline.init_spline(&splinePoints[0], splinePoints.size(), ::Movement::SplineBase::ModeLinear); + _spline.initLengths(); + + // should be sent in object create packets only + m_uint32Values[AREATRIGGER_TIME_TO_TARGET] = timeToTarget; + + if (IsInWorld()) + { + if (_reachedDestination) + { + WorldPackets::AreaTrigger::AreaTriggerReShape reshape; + reshape.TriggerGUID = GetGUID(); + SendMessageToSet(reshape.Write(), true); + } + + WorldPackets::AreaTrigger::AreaTriggerReShape reshape; + reshape.TriggerGUID = GetGUID(); + reshape.AreaTriggerSpline = boost::in_place(); + reshape.AreaTriggerSpline->ElapsedTimeForMovement = GetElapsedTimeForMovement(); + reshape.AreaTriggerSpline->TimeToTarget = timeToTarget; + reshape.AreaTriggerSpline->Points = splinePoints; + SendMessageToSet(reshape.Write(), true); + } + + _reachedDestination = false; +} + +void AreaTrigger::UpdateSplinePosition(uint32 diff) +{ + if (_reachedDestination) + return; + + if (!HasSplines()) + return; + + _movementTime += diff; + + if (_movementTime >= 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 + + sScriptMgr->OnAreaTriggerEntitySplineIndexReached(this, _lastSplineIndex); + sScriptMgr->OnAreaTriggerEntityDestinationReached(this); + return; + } + + float currentTimePercent = float(_movementTime) / float(GetTimeToTarget()); + + if (currentTimePercent <= 0.f) + return; + + if (GetMiscTemplate()->MoveCurveId) + { + float progress = sDB2Manager.GetCurveValueAt(GetMiscTemplate()->MoveCurveId, currentTimePercent); + if (progress < 0.f || progress > 1.f) + { + TC_LOG_ERROR("entities.areatrigger", "AreaTrigger (Id: %u, SpellMiscId: %u) has wrong progress (%f) caused by curve calculation (MoveCurveId: %u)", + GetTemplate()->Id, GetMiscTemplate()->MiscId, progress, GetMiscTemplate()->MorphCurveId); + } + else + currentTimePercent = progress; + } + + int lastPositionIndex = 0; + float percentFromLastPoint = 0; + _spline.computeIndex(currentTimePercent, lastPositionIndex, percentFromLastPoint); + + G3D::Vector3 currentPosition; + _spline.evaluate_percent(lastPositionIndex, percentFromLastPoint, currentPosition); + + float orientation = GetOrientation(); + if (GetTemplate()->HasFlag(AREATRIGGER_FLAG_HAS_FACE_MOVEMENT_DIR)) + { + G3D::Vector3 const& nextPoint = _spline.getPoint(lastPositionIndex + 1); + orientation = GetAngle(nextPoint.x, nextPoint.y); + } + + GetMap()->AreaTriggerRelocation(this, currentPosition.x, currentPosition.y, currentPosition.z, orientation); +#ifdef TRINITY_DEBUG + DebugVisualizePosition(); +#endif + + if (_lastSplineIndex != lastPositionIndex) + { + _lastSplineIndex = lastPositionIndex; + sScriptMgr->OnAreaTriggerEntitySplineIndexReached(this, _lastSplineIndex); + } +} + +void AreaTrigger::DebugVisualizePosition() +{ + if (Unit* caster = GetCaster()) + if (Player* player = caster->ToPlayer()) + if (player->isDebugAreaTriggers) + player->SummonCreature(1, *this, TEMPSUMMON_TIMED_DESPAWN, GetTimeToTarget()); +} diff --git a/src/server/game/Entities/AreaTrigger/AreaTrigger.h b/src/server/game/Entities/AreaTrigger/AreaTrigger.h index e6396544cd7..57ea7b4e704 100644 --- a/src/server/game/Entities/AreaTrigger/AreaTrigger.h +++ b/src/server/game/Entities/AreaTrigger/AreaTrigger.h @@ -19,11 +19,16 @@ #define TRINITYCORE_AREATRIGGER_H #include "Object.h" +#include "Position.h" +#include "Spline.h" -class Unit; +class AreaTriggerTemplate; +class AreaTriggerMiscTemplate; class SpellInfo; +class Unit; +struct AreaTriggerPolygonVertice; -class TC_GAME_API AreaTrigger : public WorldObject, public GridObject<AreaTrigger> +class TC_GAME_API AreaTrigger : public WorldObject, public GridObject<AreaTrigger>, public MapObject { public: AreaTrigger(); @@ -32,16 +37,78 @@ class TC_GAME_API AreaTrigger : public WorldObject, public GridObject<AreaTrigge void AddToWorld() override; void RemoveFromWorld() override; - bool CreateAreaTrigger(ObjectGuid::LowType guidlow, uint32 triggerEntry, Unit* caster, SpellInfo const* spell, Position const& pos, uint32 spellXSpellVisualId); + bool CreateAreaTrigger(uint32 triggerEntry, Unit* caster, Unit* target, SpellInfo const* spell, Position const& pos, int32 duration, uint32 spellXSpellVisualId, ObjectGuid const& castId = ObjectGuid::Empty); void Update(uint32 p_time) override; void Remove(); + bool IsRemoved() const { return _isRemoved; } uint32 GetSpellId() const { return GetUInt32Value(AREATRIGGER_SPELLID); } + uint32 GetTimeSinceCreated() const { return _timeSinceCreated; } + uint32 GetTimeToTarget() const { return GetUInt32Value(AREATRIGGER_TIME_TO_TARGET); } + uint32 GetTimeToTargetScale() const { return GetUInt32Value(AREATRIGGER_TIME_TO_TARGET_SCALE); } int32 GetDuration() const { return _duration; } - void SetDuration(int32 newDuration) { _duration = newDuration; } + int32 GetTotalDuration() const { return _totalDuration; } + void SetDuration(int32 newDuration); void Delay(int32 delaytime) { SetDuration(GetDuration() - delaytime); } + GuidUnorderedSet const& GetInsideUnits() const { return _insideUnits; } + + AreaTriggerMiscTemplate const* GetMiscTemplate() const { return _areaTriggerMiscTemplate; } + AreaTriggerTemplate const* GetTemplate() const; + uint32 GetScriptId() const; + + ObjectGuid const& GetCasterGuid() const { return GetGuidValue(AREATRIGGER_CASTER); } + Unit* GetCaster() const; + Unit* GetTarget() const; + + G3D::Vector3 const& GetRollPitchYaw() const { return _rollPitchYaw; } + G3D::Vector3 const& GetTargetRollPitchYaw() const { return _targetRollPitchYaw; } + void InitSplineOffsets(std::vector<G3D::Vector3> splinePoints, uint32 timeToTarget); + void InitSplines(std::vector<G3D::Vector3> const& splinePoints, uint32 timeToTarget); + bool HasSplines() const { return !_spline.empty(); } + ::Movement::Spline<int32> const& GetSpline() const { return _spline; } + uint32 GetElapsedTimeForMovement() const { return GetTimeSinceCreated(); } /// @todo: research the right value, in sniffs both timers are nearly identical + + void UpdateShape(); + protected: + void _UpdateDuration(int32 newDuration); + float GetProgress() const; + + void UpdateTargetList(); + void SearchUnitInSphere(std::list<Unit*>& targetList); + void SearchUnitInBox(std::list<Unit*>& targetList); + void SearchUnitInPolygon(std::list<Unit*>& targetList); + void SearchUnitInCylinder(std::list<Unit*>& targetList); + bool CheckIsInPolygon2D(Position const* pos) const; + void HandleUnitEnterExit(std::list<Unit*> const& targetList); + + void DoActions(Unit* unit); + void UndoActions(Unit* unit); + + void UpdatePolygonOrientation(); + void UpdateSplinePosition(uint32 diff); + + void DebugVisualizePosition(); // Debug purpose only + + ObjectGuid _targetGuid; + int32 _duration; - uint32 _spellXSpellVisualId; + int32 _totalDuration; + uint32 _timeSinceCreated; + float _previousCheckOrientation; + bool _isRemoved; + + G3D::Vector3 _rollPitchYaw; + G3D::Vector3 _targetRollPitchYaw; + std::vector<G3D::Vector2> _polygonVertices; + ::Movement::Spline<int32> _spline; + + bool _reachedDestination; + int32 _lastSplineIndex; + uint32 _movementTime; + + AreaTriggerMiscTemplate const* _areaTriggerMiscTemplate; + GuidUnorderedSet _insideUnits; }; + #endif diff --git a/src/server/game/Entities/AreaTrigger/AreaTriggerTemplate.cpp b/src/server/game/Entities/AreaTrigger/AreaTriggerTemplate.cpp new file mode 100644 index 00000000000..81b1b8c7900 --- /dev/null +++ b/src/server/game/Entities/AreaTrigger/AreaTriggerTemplate.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2008-2017 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "AreaTriggerTemplate.h" + +// Init the MaxSearchRadius that will be used in TrinitySearcher, avoiding calculate it at each update +void AreaTriggerTemplate::InitMaxSearchRadius() +{ + switch (Type) + { + case AREATRIGGER_TYPE_SPHERE: + { + MaxSearchRadius = std::max(SphereDatas.Radius, SphereDatas.RadiusTarget); + break; + } + case AREATRIGGER_TYPE_BOX: + { + MaxSearchRadius = std::sqrt(BoxDatas.Extents[0] * BoxDatas.Extents[0] / 4 + BoxDatas.Extents[1] * BoxDatas.Extents[1] / 4); + break; + } + case AREATRIGGER_TYPE_POLYGON: + { + if (PolygonDatas.Height <= 0.0f) + PolygonDatas.Height = 1.0f; + + for (G3D::Vector2 const& vertice : PolygonVertices) + { + float pointDist = vertice.length(); + + if (pointDist > MaxSearchRadius) + MaxSearchRadius = pointDist; + } + + break; + } + case AREATRIGGER_TYPE_CYLINDER: + { + MaxSearchRadius = CylinderDatas.Radius; + break; + } + default: + break; + } +} diff --git a/src/server/game/Entities/AreaTrigger/AreaTriggerTemplate.h b/src/server/game/Entities/AreaTrigger/AreaTriggerTemplate.h new file mode 100644 index 00000000000..c4701567abe --- /dev/null +++ b/src/server/game/Entities/AreaTrigger/AreaTriggerTemplate.h @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2008-2017 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_AREATRIGGER_TEMPLATE_H +#define TRINITYCORE_AREATRIGGER_TEMPLATE_H + +#include <G3D/Vector3.h> + +#define MAX_AREATRIGGER_ENTITY_DATA 6 + +enum AreaTriggerFlags +{ + AREATRIGGER_FLAG_HAS_ABSOLUTE_ORIENTATION = 0x00001, // NYI + AREATRIGGER_FLAG_HAS_DYNAMIC_SHAPE = 0x00002, // Implemented for Spheres + AREATRIGGER_FLAG_HAS_ATTACHED = 0x00004, + AREATRIGGER_FLAG_HAS_FACE_MOVEMENT_DIR = 0x00008, + AREATRIGGER_FLAG_HAS_FOLLOWS_TERRAIN = 0x00010, // NYI + AREATRIGGER_FLAG_UNK1 = 0x00020, + AREATRIGGER_FLAG_HAS_TARGET_ROLL_PITCH_YAW = 0x00040, // NYI + AREATRIGGER_FLAG_UNK2 = 0x00080, + AREATRIGGER_FLAG_UNK3 = 0x00100, + AREATRIGGER_FLAG_UNK4 = 0x00200, + AREATRIGGER_FLAG_UNK5 = 0x00400 +}; + +enum AreaTriggerTypes +{ + AREATRIGGER_TYPE_SPHERE = 0, + AREATRIGGER_TYPE_BOX = 1, + AREATRIGGER_TYPE_UNK = 2, + AREATRIGGER_TYPE_POLYGON = 3, + AREATRIGGER_TYPE_CYLINDER = 4, + AREATRIGGER_TYPE_MAX = 5 +}; + +enum AreaTriggerActionTypes +{ + AREATRIGGER_ACTION_CAST = 0, + AREATRIGGER_ACTION_ADDAURA = 1, + AREATRIGGER_ACTION_MAX = 2 +}; + +enum AreaTriggerActionUserTypes +{ + AREATRIGGER_ACTION_USER_ANY = 0, + AREATRIGGER_ACTION_USER_FRIEND = 1, + AREATRIGGER_ACTION_USER_ENEMY = 2, + AREATRIGGER_ACTION_USER_RAID = 3, + AREATRIGGER_ACTION_USER_PARTY = 4, + AREATRIGGER_ACTION_USER_CASTER = 5, + AREATRIGGER_ACTION_USER_MAX = 6 +}; + +struct AreaTriggerAction +{ + uint32 Param; + AreaTriggerActionTypes ActionType; + AreaTriggerActionUserTypes TargetType; +}; + +class AreaTriggerTemplate +{ +public: + AreaTriggerTemplate() + { + Id = 0; + Flags = 0; + ScriptId = 0; + MaxSearchRadius = 0.0f; + + memset(DefaultDatas.Data, 0, sizeof(DefaultDatas.Data)); + } + + bool HasFlag(uint32 flag) const { return (Flags & flag) != 0; } + + bool IsSphere() const { return Type == AREATRIGGER_TYPE_SPHERE; } + bool IsBox() const { return Type == AREATRIGGER_TYPE_BOX; } + bool IsPolygon() const { return Type == AREATRIGGER_TYPE_POLYGON; } + bool IsCylinder() const { return Type == AREATRIGGER_TYPE_CYLINDER; } + + void InitMaxSearchRadius(); + + uint32 Id; + AreaTriggerTypes Type; + uint32 Flags; + uint32 ScriptId; + float MaxSearchRadius; + std::vector<G3D::Vector2> PolygonVertices; + std::vector<G3D::Vector2> PolygonVerticesTarget; + std::vector<AreaTriggerAction> Actions; + + union + { + struct + { + float Data[MAX_AREATRIGGER_ENTITY_DATA]; + } DefaultDatas; + + // AREATRIGGER_TYPE_SPHERE + struct + { + float Radius; + float RadiusTarget; + } SphereDatas; + + // AREATRIGGER_TYPE_BOX + struct + { + float Extents[3]; + float ExtentsTarget[3]; + } BoxDatas; + + // AREATRIGGER_TYPE_POLYGON + struct + { + float Height; + float HeightTarget; + } PolygonDatas; + + // AREATRIGGER_TYPE_CYLINDER + struct + { + float Radius; + float RadiusTarget; + float Height; + float HeightTarget; + float LocationZOffset; + float LocationZOffsetTarget; + } CylinderDatas; + }; +}; + +class AreaTriggerMiscTemplate +{ +public: + AreaTriggerMiscTemplate() + { + MiscId = 0; + AreaTriggerEntry = 0; + + MoveCurveId = 0; + ScaleCurveId = 0; + MorphCurveId = 0; + FacingCurveId = 0; + + DecalPropertiesId = 0; + + TimeToTarget = 0; + TimeToTargetScale = 0; + + Template = nullptr; + } + + bool HasSplines() const { return SplinePoints.size() >= 2; } + + uint32 MiscId; + uint32 AreaTriggerEntry; + + uint32 MoveCurveId; + uint32 ScaleCurveId; + uint32 MorphCurveId; + uint32 FacingCurveId; + + uint32 DecalPropertiesId; + + uint32 TimeToTarget; + uint32 TimeToTargetScale; + + AreaTriggerTemplate const* Template; + std::vector<G3D::Vector3> SplinePoints; +}; + +#endif |
