diff --git a/deps/recastnavigation/Detour/Include/DetourNavMeshQuery.h b/deps/recastnavigation/Detour/Include/DetourNavMeshQuery.h index 0b40371be..2be877181 100644 --- a/deps/recastnavigation/Detour/Include/DetourNavMeshQuery.h +++ b/deps/recastnavigation/Detour/Include/DetourNavMeshQuery.h @@ -28,7 +28,7 @@ // setting is to use non-virtual functions, the actual implementations of the functions // are declared as inline for maximum speed. -//#define DT_VIRTUAL_QUERYFILTER 1 +#define DT_VIRTUAL_QUERYFILTER 1 // for acore /// Defines polygon filtering and traversal costs for navigation mesh query operations. /// @ingroup detour diff --git a/src/common/Collision/DynamicTree.cpp b/src/common/Collision/DynamicTree.cpp index 84dac1171..3253eee04 100644 --- a/src/common/Collision/DynamicTree.cpp +++ b/src/common/Collision/DynamicTree.cpp @@ -220,7 +220,7 @@ bool DynamicMapTree::isInLineOfSight(float x1, float y1, float z1, float x2, flo float DynamicMapTree::getHeight(float x, float y, float z, float maxSearchDist, uint32 phasemask) const { - G3D::Vector3 v(x, y, z + 2.0f); + G3D::Vector3 v(x, y, z); G3D::Ray r(v, G3D::Vector3(0, 0, -1)); DynamicTreeIntersectionCallback callback(phasemask); impl->intersectZAllignedRay(r, callback, maxSearchDist); @@ -228,5 +228,5 @@ float DynamicMapTree::getHeight(float x, float y, float z, float maxSearchDist, if (callback.didHit()) return v.z - maxSearchDist; else - return -G3D::inf(); + return -G3D::finf(); } diff --git a/src/common/Collision/Management/MMapFactory.h b/src/common/Collision/Management/MMapFactory.h index b4038a212..56958ee5f 100644 --- a/src/common/Collision/Management/MMapFactory.h +++ b/src/common/Collision/Management/MMapFactory.h @@ -10,7 +10,7 @@ #include "MMapManager.h" #include "DetourAlloc.h" #include "DetourNavMesh.h" -#include "DetourNavMeshQuery.h" +#include "Navigation/DetourExtended.h" #include "Map.h" #include diff --git a/src/common/Collision/Management/MMapManager.h b/src/common/Collision/Management/MMapManager.h index 514379283..70f260041 100644 --- a/src/common/Collision/Management/MMapManager.h +++ b/src/common/Collision/Management/MMapManager.h @@ -9,7 +9,7 @@ #include "DetourAlloc.h" #include "DetourNavMesh.h" -#include "DetourNavMeshQuery.h" +#include "DetourExtended.h" #include "World.h" #include diff --git a/src/common/Collision/Management/VMapManager2.cpp b/src/common/Collision/Management/VMapManager2.cpp index 0a33af592..cbdd2813d 100644 --- a/src/common/Collision/Management/VMapManager2.cpp +++ b/src/common/Collision/Management/VMapManager2.cpp @@ -29,6 +29,7 @@ #include "DBCStores.h" #include "Log.h" #include "VMapDefinitions.h" +#include "GridDefines.h" using G3D::Vector3; @@ -54,7 +55,7 @@ namespace VMAP Vector3 VMapManager2::convertPositionToInternalRep(float x, float y, float z) const { Vector3 pos; - const float mid = 0.5f * 64.0f * 533.33333333f; + const float mid = 0.5f * MAX_NUMBER_OF_GRIDS * SIZE_OF_GRIDS; pos.x = mid - x; pos.y = mid - y; pos.z = z; @@ -201,7 +202,7 @@ namespace VMAP { Vector3 pos = convertPositionToInternalRep(x, y, z); float height = instanceTree->second->getHeight(pos, maxSearchDist); - if (!(height < G3D::inf())) + if (!(height < G3D::finf())) return height = VMAP_INVALID_HEIGHT_VALUE; // No height return height; @@ -308,4 +309,4 @@ namespace VMAP return StaticMapTree::CanLoadMap(std::string(basePath), mapId, x, y); } -} // namespace VMAP \ No newline at end of file +} // namespace VMAP diff --git a/src/common/Collision/Maps/MapTree.cpp b/src/common/Collision/Maps/MapTree.cpp index 1a81a5524..e41dcee04 100644 --- a/src/common/Collision/Maps/MapTree.cpp +++ b/src/common/Collision/Maps/MapTree.cpp @@ -212,7 +212,7 @@ namespace VMAP float StaticMapTree::getHeight(const Vector3& pPos, float maxSearchDist) const { - float height = G3D::inf(); + float height = G3D::finf(); Vector3 dir = Vector3(0, 0, -1); G3D::Ray ray(pPos, dir); // direction with length of 1 float maxDist = maxSearchDist; diff --git a/src/common/Common.h b/src/common/Common.h index f89d8d751..fff6e689b 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -185,10 +185,6 @@ typedef std::vector StringVector; #undef min #endif -#ifndef M_PI -#define M_PI 3.14159265358979323846f -#endif - #define MAX_QUERY_LEN 32*1024 #define ACORE_GUARD(MUTEX, LOCK) \ diff --git a/src/common/Define.h b/src/common/Define.h index 4641361a6..23b16edaf 100644 --- a/src/common/Define.h +++ b/src/common/Define.h @@ -32,6 +32,7 @@ #if AC_PLATFORM == AC_PLATFORM_WINDOWS # define ACORE_PATH_MAX MAX_PATH +# define _USE_MATH_DEFINES # ifndef DECLSPEC_NORETURN # define DECLSPEC_NORETURN __declspec(noreturn) # endif //DECLSPEC_NORETURN diff --git a/src/common/Navigation/DetourExtended.cpp b/src/common/Navigation/DetourExtended.cpp new file mode 100644 index 000000000..b3bae8756 --- /dev/null +++ b/src/common/Navigation/DetourExtended.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-GPL2 + */ + +#include "DetourExtended.h" +#include "DetourCommon.h" +#include "Geometry.h" + + +float dtQueryFilterExt::getCost(const float* pa, const float* pb, + const dtPolyRef /*prevRef*/, const dtMeshTile* /*prevTile*/, const dtPoly* /*prevPoly*/, + const dtPolyRef /*curRef*/, const dtMeshTile* /*curTile*/, const dtPoly* curPoly, + const dtPolyRef /*nextRef*/, const dtMeshTile* /*nextTile*/, const dtPoly* /*nextPoly*/) const +{ + float startX=pa[2], startY=pa[0], startZ=pa[1]; + float destX=pb[2], destY=pb[0], destZ=pb[1]; + float slopeAngle = getSlopeAngle(startX, startY, startZ, destX, destY, destZ); + float slopeAngleDegree = (slopeAngle * 180.0f / M_PI); + float cost = slopeAngleDegree > 0 ? 1.0f + (1.0f * (slopeAngleDegree/100)) : 1.0f; + float dist = dtVdist(pa, pb); + auto totalCost = dist * cost * getAreaCost(curPoly->getArea()); + return totalCost; +} diff --git a/src/common/Navigation/DetourExtended.h b/src/common/Navigation/DetourExtended.h new file mode 100644 index 000000000..492a3d4c1 --- /dev/null +++ b/src/common/Navigation/DetourExtended.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-GPL2 + */ + +#ifndef _ACORE_DETOUR_EXTENDED_H +#define _ACORE_DETOUR_EXTENDED_H + +#include "DetourNavMeshQuery.h" + +class dtQueryFilterExt: public dtQueryFilter +{ +public: + float getCost(const float* pa, const float* pb, + const dtPolyRef prevRef, const dtMeshTile* prevTile, const dtPoly* prevPoly, + const dtPolyRef curRef, const dtMeshTile* curTile, const dtPoly* curPoly, + const dtPolyRef nextRef, const dtMeshTile* nextTile, const dtPoly* nextPoly) const override; +}; + +#endif // _ACORE_DETOUR_EXTENDED_H diff --git a/src/common/Utilities/Geometry.h b/src/common/Utilities/Geometry.h new file mode 100644 index 000000000..c3aa47661 --- /dev/null +++ b/src/common/Utilities/Geometry.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + */ + +/** + * + * Utility library to define some global function for geometric calculations + * + */ + +#ifndef _ACORE_GEOMETRY_H +#define _ACORE_GEOMETRY_H + +#include +#include +#include + +using namespace std; + +[[nodiscard]] inline float getAngle(float startX, float startY, float destX, float destY) +{ + auto dx = destX - startX; + auto dy = destY - startY; + + auto ang = atan2(dy, dx); + ang = (ang >= 0) ? ang : 2 * float(M_PI) + ang; + return ang; +} + +[[nodiscard]] inline float getSlopeAngle(float startX, float startY, float startZ, float destX, float destY, float destZ) +{ + float floorDist = sqrt(pow(startY - destY, 2.0f) + pow(startX - destX,2.0f)); + return atan(abs(destZ - startZ) / abs(floorDist)); +} + +[[nodiscard]] inline float getSlopeAngleAbs(float startX, float startY, float startZ, float destX, float destY, float destZ) +{ + return abs(getSlopeAngle(startX, startY, startZ, destX, destY, destZ)); +} + +[[nodiscard]] inline double getCircleAreaByRadius(double radius) +{ + return radius * radius * M_PI; +} + +[[nodiscard]] inline double getCirclePerimeterByRadius(double radius) +{ + return radius * M_PI; +} + +[[nodiscard]] inline double getCylinderVolume(double height, double radius) +{ + return height * getCircleAreaByRadius(radius); +} + +#endif // _ACORE_GEOMETRY_H diff --git a/src/common/Utilities/Physics.h b/src/common/Utilities/Physics.h new file mode 100644 index 000000000..abf30176b --- /dev/null +++ b/src/common/Utilities/Physics.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + */ + +/** + * + * Utility library to define some global function for simple physics calculations + * + */ + +#ifndef _ACORE_PHYSICS_H +#define _ACORE_PHYSICS_H + +#include "Geometry.h" +#include +#include +#include + +using namespace std; + +[[nodiscard]] inline float getWeight(float height, float width, float specificWeight) +{ + auto volume = getCylinderVolume(height, width / 2.0f); + auto weight = volume * specificWeight; + return weight; +} + +/** + * @brief Get the height immersed in water + * + * @param height + * @param width + * @param weight specific weight + * @return float + */ +[[nodiscard]] inline float getOutOfWater(float width, float weight, float density) +{ + auto baseArea = getCircleAreaByRadius(width / 2.0f); + return weight / (baseArea * density); +} + +#endif // _ACORE_PHYSICS_H diff --git a/src/server/game/AI/CoreAI/PetAI.cpp b/src/server/game/AI/CoreAI/PetAI.cpp index 7b999fe82..282bf1285 100644 --- a/src/server/game/AI/CoreAI/PetAI.cpp +++ b/src/server/game/AI/CoreAI/PetAI.cpp @@ -51,9 +51,6 @@ bool PetAI::_needToStop() if (!me->_CanDetectFeignDeathOf(me->GetVictim())) return true; - if (me->isTargetNotAcceptableByMMaps(me->GetVictim()->GetGUID(), sWorld->GetGameTime(), me->GetVictim())) - return true; - return !me->CanCreatureAttack(me->GetVictim()); } @@ -338,6 +335,12 @@ void PetAI::UpdateAI(uint32 diff) for (TargetSpellList::const_iterator itr = targetSpellStore.begin(); itr != targetSpellStore.end(); ++itr) delete itr->second; } + + + // Update speed as needed to prevent dropping too far behind and despawning + me->UpdateSpeed(MOVE_RUN, true); + me->UpdateSpeed(MOVE_WALK, true); + me->UpdateSpeed(MOVE_FLIGHT, true); } void PetAI::UpdateAllies() @@ -470,7 +473,7 @@ Unit* PetAI::SelectNextTarget(bool allowAutoSelect) const // Check pet attackers first so we don't drag a bunch of targets to the owner if (Unit* myAttacker = me->getAttackerForHelper()) - if (!myAttacker->HasBreakableByDamageCrowdControlAura() && me->_CanDetectFeignDeathOf(myAttacker) && me->CanCreatureAttack(myAttacker) && !me->isTargetNotAcceptableByMMaps(myAttacker->GetGUID(), sWorld->GetGameTime(), myAttacker)) + if (!myAttacker->HasBreakableByDamageCrowdControlAura() && me->_CanDetectFeignDeathOf(myAttacker) && me->CanCreatureAttack(myAttacker)) return myAttacker; // Check pet's attackers first to prevent dragging mobs back to owner @@ -491,13 +494,13 @@ Unit* PetAI::SelectNextTarget(bool allowAutoSelect) const // Check owner attackers if (Unit* ownerAttacker = owner->getAttackerForHelper()) - if (!ownerAttacker->HasBreakableByDamageCrowdControlAura() && me->_CanDetectFeignDeathOf(ownerAttacker) && me->CanCreatureAttack(ownerAttacker) && !me->isTargetNotAcceptableByMMaps(ownerAttacker->GetGUID(), sWorld->GetGameTime(), ownerAttacker)) + if (!ownerAttacker->HasBreakableByDamageCrowdControlAura() && me->_CanDetectFeignDeathOf(ownerAttacker) && me->CanCreatureAttack(ownerAttacker)) return ownerAttacker; // Check owner victim // 3.0.2 - Pets now start attacking their owners victim in defensive mode as soon as the hunter does if (Unit* ownerVictim = owner->GetVictim()) - if (me->_CanDetectFeignDeathOf(ownerVictim) && me->CanCreatureAttack(ownerVictim) && !me->isTargetNotAcceptableByMMaps(ownerVictim->GetGUID(), sWorld->GetGameTime(), ownerVictim)) + if (me->_CanDetectFeignDeathOf(ownerVictim) && me->CanCreatureAttack(ownerVictim)) return ownerVictim; // Neither pet or owner had a target and aggressive pets can pick any target @@ -601,7 +604,11 @@ void PetAI::DoAttack(Unit* target, bool chase) if (chase) { if (_canMeleeAttack()) - me->GetMotionMaster()->MoveChase(target, combatRange, float(M_PI)); + { + float angle = combatRange == 0.f && target->GetTypeId() != TYPEID_PLAYER && !target->IsPet() ? float(M_PI) : 0.f; + float tolerance = combatRange == 0.f ? float(M_PI_4) : float(M_PI * 2); + me->GetMotionMaster()->MoveChase(target, ChaseRange(0.f, combatRange), ChaseAngle(angle, tolerance)); + } } else // (Stay && ((Aggressive || Defensive) && In Melee Range))) { diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index 5150f4cd7..615c05af8 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -257,6 +257,40 @@ bool CreatureAI::_EnterEvadeMode() return true; } +void CreatureAI::MoveCircleChecks() +{ + Unit *victim = me->GetVictim(); + + if ( + !victim || + !me->IsFreeToMove() || me->HasUnitMovementFlag(MOVEMENTFLAG_ROOT) || + !me->IsWithinMeleeRange(victim) || me == victim->GetVictim() || + (victim->GetTypeId() != TYPEID_PLAYER && !victim->IsPet()) // only player & pets to save CPU + ) + { + return; + } + + me->GetMotionMaster()->MoveCircleTarget(me->GetVictim()); +} + +void CreatureAI::MoveBackwardsChecks() { + Unit *victim = me->GetVictim(); + + if ( + !victim || + !me->IsFreeToMove() || me->HasUnitMovementFlag(MOVEMENTFLAG_ROOT) || + (victim->GetTypeId() != TYPEID_PLAYER && !victim->IsPet()) + ) + { + return; + } + + float moveDist = me->GetMeleeRange(victim) / 2; + + me->GetMotionMaster()->MoveBackwards(victim, moveDist); +} + Creature* CreatureAI::DoSummon(uint32 entry, const Position& pos, uint32 despawnTime, TempSummonType summonType) { return me->SummonCreature(entry, pos, summonType, despawnTime); diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h index 0f1a8c665..3e229bd37 100644 --- a/src/server/game/AI/CreatureAI.h +++ b/src/server/game/AI/CreatureAI.h @@ -71,6 +71,9 @@ public: ~CreatureAI() override {} + void MoveCircleChecks(); + void MoveBackwardsChecks(); + /// == Reactions At ================================= // Called if IsVisible(Unit* who) is true at each who move, reaction at visibility zone enter diff --git a/src/server/game/Combat/ThreatManager.cpp b/src/server/game/Combat/ThreatManager.cpp index eb723e8c0..c26c3f2a3 100644 --- a/src/server/game/Combat/ThreatManager.cpp +++ b/src/server/game/Combat/ThreatManager.cpp @@ -296,13 +296,12 @@ HostileReference* ThreatContainer::selectNextVictim(Creature* attacker, HostileR HostileReference* currentRef = nullptr; bool found = false; bool noPriorityTargetFound = false; - uint32 currTime = sWorld->GetGameTime(); // pussywizard: currentVictim is needed to compare if threat was exceeded by 10%/30% for melee/range targets (only then switching current target) if (currentVictim) { Unit* cvUnit = currentVictim->getTarget(); - if (!attacker->_CanDetectFeignDeathOf(cvUnit) || !attacker->CanCreatureAttack(cvUnit) || attacker->isTargetNotAcceptableByMMaps(cvUnit->GetGUID(), currTime, cvUnit)) // pussywizard: if currentVictim is not valid => don't compare the threat with it, just take the highest threat valid target + if (!attacker->_CanDetectFeignDeathOf(cvUnit) || !attacker->CanCreatureAttack(cvUnit)) // pussywizard: if currentVictim is not valid => don't compare the threat with it, just take the highest threat valid target currentVictim = nullptr; else if (cvUnit->IsImmunedToDamageOrSchool(attacker->GetMeleeDamageSchoolMask()) || cvUnit->HasNegativeAuraWithInterruptFlag(AURA_INTERRUPT_FLAG_TAKE_DAMAGE)) // pussywizard: no 10%/30% if currentVictim is immune to damage or has auras breakable by damage currentVictim = nullptr; @@ -337,7 +336,7 @@ HostileReference* ThreatContainer::selectNextVictim(Creature* attacker, HostileR } // pussywizard: skip not valid targets - if (attacker->_CanDetectFeignDeathOf(target) && attacker->CanCreatureAttack(target) && !attacker->isTargetNotAcceptableByMMaps(target->GetGUID(), currTime, target)) + if (attacker->_CanDetectFeignDeathOf(target) && attacker->CanCreatureAttack(target)) { if (currentVictim) // pussywizard: if not NULL then target must have 10%/30% more threat { diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 77f5f40ca..53c5284aa 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -47,8 +47,6 @@ #include "LuaEngine.h" #endif -constexpr uint32 DEF_CANNOT_REACH = 5 * IN_MILLISECONDS; // this is when creatures are in los / no path to the target, 5s wait time then they return to spawn pos with evade - TrainerSpell const* TrainerSpellData::Find(uint32 spell_id) const { TrainerSpellMap::const_iterator itr = spellList.find(spell_id); @@ -164,9 +162,10 @@ bool ForcedDespawnDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) Creature::Creature(bool isWorldObject): Unit(isWorldObject), MovableMapObject(), m_groupLootTimer(0), lootingGroupLowGUID(0), m_PlayerDamageReq(0), m_lootRecipient(0), m_lootRecipientGroup(0), m_corpseRemoveTime(0), m_respawnTime(0), m_respawnDelay(300), m_corpseDelay(60), m_wanderDistance(0.0f), m_transportCheckTimer(1000), lootPickPocketRestoreTime(0), m_reactState(REACT_AGGRESSIVE), m_defaultMovementType(IDLE_MOTION_TYPE), - m_DBTableGuid(0), m_equipmentId(0), m_originalEquipmentId(0), m_AlreadyCallAssistance(false), + m_DBTableGuid(0), m_equipmentId(0), m_originalEquipmentId(0), m_originalAnimTier(UNIT_BYTE1_FLAG_GROUND), m_AlreadyCallAssistance(false), m_AlreadySearchedAssistance(false), m_regenHealth(true), m_AI_locked(false), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), m_originalEntry(0), m_moveInLineOfSightDisabled(false), m_moveInLineOfSightStrictlyDisabled(false), - m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_waypointID(0), m_path_id(0), m_formation(nullptr), _lastDamagedTime(0), m_cannotReachTarget(false), m_cannotReachTimer(0) + m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_waypointID(0), m_path_id(0), m_formation(nullptr), _lastDamagedTime(0), m_cannotReachTarget(false), m_cannotReachTimer(0), + _isMissingSwimmingFlagOutOfCombat(false) { m_regenTimer = CREATURE_REGEN_INTERVAL; m_valuesCount = UNIT_END; @@ -591,6 +590,35 @@ void Creature::Update(uint32 diff) RemoveCharmAuras(); } + if (Unit *victim = GetVictim()) + { + // If we are closer than 50% of the combat reach we are going to reposition the victim + if (diff >= m_moveBackwardsMovementTime) + { + float MaxRange = GetCollisionRadius() + GetVictim()->GetCollisionRadius(); + + if (IsInDist(victim, MaxRange)) + AI()->MoveBackwardsChecks(); + + m_moveBackwardsMovementTime = urand(MOVE_BACKWARDS_CHECK_INTERVAL, MOVE_BACKWARDS_CHECK_INTERVAL * 3); + } + else + { + m_moveBackwardsMovementTime -= diff; + } + + // Circling the target + if (diff >= m_moveCircleMovementTime) + { + AI()->MoveCircleChecks(); + m_moveCircleMovementTime = urand(MOVE_CIRCLE_CHECK_INTERVAL, MOVE_CIRCLE_CHECK_INTERVAL * 2); + } + else + { + m_moveCircleMovementTime -= diff; + } + } + if (!IsInEvadeMode() && IsAIEnabled) { // do not allow the AI to be changed during update @@ -607,8 +635,24 @@ void Creature::Update(uint32 diff) m_regenTimer -= diff; if (m_regenTimer <= 0) { - if (!IsInEvadeMode() && (!IsInCombat() || IsPolymorphed())) // regenerate health if not in combat or if polymorphed - RegenerateHealth(); + if (!IsInEvadeMode()) + { + // regenerate health if not in combat or if polymorphed) + if (!IsInCombat() || IsPolymorphed()) + RegenerateHealth(); + else if (CanNotReachTarget()) + { + // regenerate health if cannot reach the target and the setting is set to do so. + // this allows to disable the health regen of raid bosses if pathfinding has issues for whatever reason + if (sWorld->getBoolConfig(CONFIG_REGEN_HP_CANNOT_REACH_TARGET_IN_RAID) || !GetMap()->IsRaid()) + { + RegenerateHealth(); + sLog->outDebug(LOG_FILTER_UNITS, "RegenerateHealth() enabled because Creature cannot reach the target. Detail: %s", GetDebugInfo().c_str()); + } + else + sLog->outDebug(LOG_FILTER_UNITS, "RegenerateHealth() disabled even if the Creature cannot reach the target. Detail: %s", GetDebugInfo().c_str()); + } + } if (getPowerType() == POWER_ENERGY) Regenerate(POWER_ENERGY); @@ -621,7 +665,7 @@ void Creature::Update(uint32 diff) if (CanNotReachTarget() && !IsInEvadeMode() && !GetMap()->IsRaid()) { m_cannotReachTimer += diff; - if (m_cannotReachTimer >= DEF_CANNOT_REACH && IsAIEnabled) + if (m_cannotReachTimer >= (sWorld->getIntConfig(CONFIG_NPC_EVADE_IF_NOT_REACHABLE)*IN_MILLISECONDS) && IsAIEnabled) { AI()->EnterEvadeMode(); } @@ -660,6 +704,20 @@ void Creature::Update(uint32 diff) } } +bool Creature::IsFreeToMove() +{ + uint32 moveFlags = m_movementInfo.GetMovementFlags(); + // Do not reposition ourself when we are not allowed to move + if ((IsMovementPreventedByCasting() || isMoving() || !CanFreeMove()) && + (GetMotionMaster()->GetCurrentMovementGeneratorType() != CHASE_MOTION_TYPE || + moveFlags & MOVEMENTFLAG_SPLINE_ENABLED)) + { + return false; + } + + return true; +} + void Creature::Regenerate(Powers power) { uint32 curValue = GetPower(power); @@ -900,6 +958,9 @@ bool Creature::Create(uint32 guidlow, Map* map, uint32 phaseMask, uint32 Entry, SetByteValue(UNIT_FIELD_BYTES_0, 2, minfo->gender); } + //! Need to be called after LoadCreaturesAddon - MOVEMENTFLAG_HOVER is set there + m_positionZ += GetHoverHeight(); + LastUsedScriptID = GetCreatureTemplate()->ScriptID; if (IsSpiritHealer() || IsSpiritGuide() || (GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_GHOST_VISIBILITY)) @@ -1524,8 +1585,10 @@ bool Creature::CanStartAttack(Unit const* who) const // This set of checks is should be done only for creatures if ((HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC) && who->GetTypeId() != TYPEID_PLAYER) || // flag is valid only for non player characters - (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC) && who->GetTypeId() == TYPEID_PLAYER)) // immune to PC and target is a player, return false + (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC) && who->GetTypeId() == TYPEID_PLAYER)) // immune to PC and target is a player, return false + { return false; + } if (Unit* owner = who->GetOwner()) if (owner->GetTypeId() == TYPEID_PLAYER && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC)) // immune to PC and target has player owner @@ -1578,7 +1641,7 @@ void Creature::setDeathState(DeathState s, bool despawn) SetTarget(0); // remove target selection in any cases (can be set at aura remove in Unit::setDeathState) SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); - SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, 0); // if creature is mounted on a virtual mount, remove it at death + Dismount(); // if creature is mounted on a virtual mount, remove it at death setActive(false); @@ -1597,7 +1660,11 @@ void Creature::setDeathState(DeathState s, bool despawn) if (m_formation && m_formation->getLeader() == this) m_formation->FormationReset(true); - if (!despawn && (CanFly() || IsFlying()) && !HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING)) // pussywizard: added MOVEMENTFLAG_SWIMMING check because IsFlying() returns true when swimming creatures have MOVEMENTFLAG_DISABLE_GRAVITY + bool needsFalling = !despawn && (IsFlying() || IsHovering()) && !IsUnderWater(); + SetHover(false, false); + SetDisableGravity(false, false); + + if (needsFalling) GetMotionMaster()->MoveFall(0, true); Unit::setDeathState(CORPSE, despawn); @@ -1609,6 +1676,7 @@ void Creature::setDeathState(DeathState s, bool despawn) SetFullHealth(); SetLootRecipient(nullptr); ResetPlayerDamageReq(); + SetCannotReachTarget(false); CreatureTemplate const* cinfo = GetCreatureTemplate(); // Xinef: npc run by default //SetWalk(true); @@ -2203,11 +2271,12 @@ bool Creature::LoadCreaturesAddon(bool reload) // 2 StandFlags // 3 StandMiscFlags - SetByteValue(UNIT_FIELD_BYTES_1, 0, uint8(cainfo->bytes1 & 0xFF)); - //SetByteValue(UNIT_FIELD_BYTES_1, 1, uint8((cainfo->bytes1 >> 8) & 0xFF)); - SetByteValue(UNIT_FIELD_BYTES_1, 1, 0); - SetByteValue(UNIT_FIELD_BYTES_1, 2, uint8((cainfo->bytes1 >> 16) & 0xFF)); - SetByteValue(UNIT_FIELD_BYTES_1, 3, uint8((cainfo->bytes1 >> 24) & 0xFF)); + SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, uint8(cainfo->bytes1 & 0xFF)); + //SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_PET_TALENTS, uint8((cainfo->bytes1 >> 8) & 0xFF)); + SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_PET_TALENTS, 0); + SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_VIS_FLAG, uint8((cainfo->bytes1 >> 16) & 0xFF)); + m_originalAnimTier = uint8((cainfo->bytes1 >> 24) & 0xFF); + SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, m_originalAnimTier); //! Suspected correlation between UNIT_FIELD_BYTES_1, offset 3, value 0x2: //! If no inhabittype_fly (if no MovementFlag_DisableGravity or MovementFlag_CanFly flag found in sniffs) @@ -2660,10 +2729,6 @@ bool Creature::SetDisableGravity(bool disable, bool packetOnly/*=false*/) if (!movespline->Initialized()) return true; - // pussywizard: artificial disable_gravity to hovering npcs, don't send! - if (!disable && GetHoverHeight() >= 2.0f) - return true; - WorldPacket data(disable ? SMSG_SPLINE_MOVE_GRAVITY_DISABLE : SMSG_SPLINE_MOVE_GRAVITY_ENABLE, 9); data.append(GetPackGUID()); SendMessageToSet(&data, false); @@ -2684,6 +2749,43 @@ bool Creature::SetSwim(bool enable) return true; } +/** + * @brief This method check the current flag/status of a creature and its inhabit type + * + * Pets should swim by default to properly follow the player + * NOTE: You can set the UNIT_FLAG_CANNOT_SWIM temporary to deny a creature to swim + * + */ +bool Creature::CanSwim() const +{ + if (Unit::CanSwim()) + return true; + + if (IsPet() || IS_PLAYER_GUID(GetOwnerGUID())) + return true; + + return false; +} + +bool Creature::CanEnterWater() const +{ + if (CanSwim()) + return true; + + return GetCreatureTemplate()->InhabitType & INHABIT_WATER; +} + +void Creature::RefreshSwimmingFlag(bool recheck) +{ + if (!_isMissingSwimmingFlagOutOfCombat || recheck) + _isMissingSwimmingFlagOutOfCombat = !HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SWIMMING); + + // Check if the creature has UNIT_FLAG_SWIMMING and add it if it's missing + // Creatures must be able to chase a target in water if they can enter water + if (_isMissingSwimmingFlagOutOfCombat && CanEnterWater()) + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SWIMMING); +} + bool Creature::SetCanFly(bool enable, bool /*packetOnly*/ /* = false */) { if (!Unit::SetCanFly(enable)) @@ -2698,6 +2800,14 @@ bool Creature::SetCanFly(bool enable, bool /*packetOnly*/ /* = false */) return true; } +bool Creature::CanFly() const +{ + if (Unit::IsFlying()) + return true; + + return GetCreatureTemplate()->InhabitType & INHABIT_AIR; +} + bool Creature::SetWaterWalking(bool enable, bool packetOnly /* = false */) { if (!packetOnly && !Unit::SetWaterWalking(enable)) @@ -2733,9 +2843,9 @@ bool Creature::SetHover(bool enable, bool packetOnly /*= false*/) //! Unconfirmed for players: if (enable) - SetByteFlag(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_HOVER); + SetByteFlag(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, UNIT_BYTE1_FLAG_HOVER); else - RemoveByteFlag(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_HOVER); + RemoveByteFlag(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, UNIT_BYTE1_FLAG_HOVER); if (!movespline->Initialized()) return true; @@ -2793,22 +2903,38 @@ void Creature::SetObjectScale(float scale) { Unit::SetObjectScale(scale); + float combatReach = DEFAULT_WORLD_OBJECT_SIZE; + if (CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelInfo(GetDisplayId())) { SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, (IsPet() ? 1.0f : minfo->bounding_radius) * scale); - SetFloatValue(UNIT_FIELD_COMBATREACH, (IsPet() ? DEFAULT_COMBAT_REACH : minfo->combat_reach) * scale); + if (minfo->combat_reach > 0) + combatReach = minfo->combat_reach; } + + if (IsPet()) + combatReach = DEFAULT_COMBAT_REACH; + + SetFloatValue(UNIT_FIELD_COMBATREACH, combatReach * GetFloatValue(OBJECT_FIELD_SCALE_X) * scale); } void Creature::SetDisplayId(uint32 modelId) { Unit::SetDisplayId(modelId); + float combatReach = DEFAULT_WORLD_OBJECT_SIZE; + if (CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelInfo(modelId)) { - SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, (IsPet() ? 1.0f : minfo->bounding_radius) * GetFloatValue(OBJECT_FIELD_SCALE_X)); - SetFloatValue(UNIT_FIELD_COMBATREACH, (IsPet() ? DEFAULT_COMBAT_REACH : minfo->combat_reach) * GetFloatValue(OBJECT_FIELD_SCALE_X)); + SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, (IsPet() ? 1.0f : minfo->bounding_radius) * GetObjectScale()); + if (minfo->combat_reach > 0) + combatReach = minfo->combat_reach; } + + if (IsPet()) + combatReach = DEFAULT_COMBAT_REACH; + + SetFloatValue(UNIT_FIELD_COMBATREACH, combatReach * GetObjectScale()); } void Creature::SetTarget(uint64 guid) @@ -2835,6 +2961,16 @@ void Creature::FocusTarget(Spell const* focusSpell, WorldObject const* target) SetInFront(target); } +bool Creature::HasSpellFocus(Spell const* focusSpell) const +{ + if (isDead()) // dead creatures cannot focus + { + return false; + } + + return focusSpell ? (focusSpell == _spellFocusInfo.Spell) : (_spellFocusInfo.Spell || _spellFocusInfo.Delay); +} + void Creature::ReleaseFocus(Spell const* focusSpell) { // focused to something else @@ -2892,3 +3028,36 @@ float Creature::GetAttackDistance(Unit const* player) const return (retDistance * aggroRate); } + +bool Creature::IsMovementPreventedByCasting() const +{ + Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL]; + // first check if currently a movement allowed channel is active and we're not casting + if (!!spell && spell->getState() != SPELL_STATE_FINISHED && spell->IsChannelActive() && spell->GetSpellInfo()->IsMoveAllowedChannel()) + { + return false; + } + + if (HasSpellFocus()) + { + return true; + } + + if (HasUnitState(UNIT_STATE_CASTING)) + { + return true; + } + + return false; +} + +void Creature::SetCannotReachTarget(bool cannotReach) +{ + if (cannotReach == m_cannotReachTarget) + return; + m_cannotReachTarget = cannotReach; + m_cannotReachTimer = 0; + + if (cannotReach) + sLog->outDebug(LOG_FILTER_UNITS, "Creature::SetCannotReachTarget() called with true. Details: %s", GetDebugInfo().c_str()); +} diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 2baf9cd1e..eca8cc633 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -465,8 +465,10 @@ public: [[nodiscard]] bool IsTrigger() const { return GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_TRIGGER; } [[nodiscard]] bool IsGuard() const { return GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_GUARD; } [[nodiscard]] bool CanWalk() const { return GetCreatureTemplate()->InhabitType & INHABIT_GROUND; } - [[nodiscard]] bool CanSwim() const override { return (GetCreatureTemplate()->InhabitType & INHABIT_WATER) || IS_PLAYER_GUID(GetOwnerGUID()); } - [[nodiscard]] bool CanFly() const override { return GetCreatureTemplate()->InhabitType & INHABIT_AIR; } + [[nodiscard]] bool CanSwim() const override; + [[nodiscard]] bool CanEnterWater() const override; + [[nodiscard]] bool CanFly() const override; + [[nodiscard]] bool CanHover() const { return m_originalAnimTier & UNIT_BYTE1_FLAG_HOVER || IsHovering(); } void SetReactState(ReactStates st) { m_reactState = st; } [[nodiscard]] ReactStates GetReactState() const { return m_reactState; } @@ -521,6 +523,15 @@ public: bool SetWaterWalking(bool enable, bool packetOnly = false) override; bool SetFeatherFall(bool enable, bool packetOnly = false) override; bool SetHover(bool enable, bool packetOnly = false) override; + bool HasSpellFocus(Spell const* focusSpell = nullptr) const; + + struct + { + ::Spell const* Spell = nullptr; + uint32 Delay = 0; // ms until the creature's target should snap back (0 = no snapback scheduled) + uint64 Target; // the creature's "real" target while casting + float Orientation = 0.0f; // the creature's "real" orientation while casting + } _spellFocusInfo; [[nodiscard]] uint32 GetShieldBlockValue() const override { @@ -673,7 +684,7 @@ public: return m_charmInfo->GetCharmSpell(pos)->GetAction(); } - void SetCannotReachTarget(bool cannotReach) { if (cannotReach == m_cannotReachTarget) return; m_cannotReachTarget = cannotReach; m_cannotReachTimer = 0; } + void SetCannotReachTarget(bool cannotReach); [[nodiscard]] bool CanNotReachTarget() const { return m_cannotReachTarget; } void SetPosition(float x, float y, float z, float o); @@ -725,11 +736,24 @@ public: void SetTarget(uint64 guid) override; void FocusTarget(Spell const* focusSpell, WorldObject const* target); void ReleaseFocus(Spell const* focusSpell); + bool IsMovementPreventedByCasting() const; // Part of Evade mechanics [[nodiscard]] time_t GetLastDamagedTime() const { return _lastDamagedTime; } void SetLastDamagedTime(time_t val) { _lastDamagedTime = val; } + bool IsFreeToMove(); + static constexpr uint32 MOVE_CIRCLE_CHECK_INTERVAL = 3000; + static constexpr uint32 MOVE_BACKWARDS_CHECK_INTERVAL = 2000; + uint32 m_moveCircleMovementTime = MOVE_CIRCLE_CHECK_INTERVAL; + uint32 m_moveBackwardsMovementTime = MOVE_BACKWARDS_CHECK_INTERVAL; + + bool HasSwimmingFlagOutOfCombat() const + { + return !_isMissingSwimmingFlagOutOfCombat; + } + void RefreshSwimmingFlag(bool recheck = false); + protected: bool CreateFromProto(uint32 guidlow, uint32 Entry, uint32 vehId, const CreatureData* data = nullptr); bool InitEntry(uint32 entry, const CreatureData* data = nullptr); @@ -759,6 +783,8 @@ protected: uint8 m_equipmentId; int8 m_originalEquipmentId; // can be -1 + uint8 m_originalAnimTier; + bool m_AlreadyCallAssistance; bool m_AlreadySearchedAssistance; bool m_regenHealth; @@ -800,6 +826,8 @@ private: uint32 m_cannotReachTimer; Spell const* _focusSpell; ///> Locks the target during spell cast for proper facing + + bool _isMissingSwimmingFlagOutOfCombat; }; class AssistDelayEvent : public BasicEvent diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index eb33265a6..c4945f9f9 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -5,6 +5,7 @@ */ #include "Common.h" +#include "Physics.h" #include "SharedDefines.h" #include "WorldPacket.h" #include "Opcodes.h" @@ -357,7 +358,7 @@ void Object::BuildMovementUpdate(ByteBuffer* data, uint16 flags) const *data << object->GetPositionX(); *data << object->GetPositionY(); - *data << object->GetPositionZ() + (unit ? unit->GetHoverHeight() : 0.0f); + *data << object->GetPositionZ(); if (transport) { @@ -369,7 +370,7 @@ void Object::BuildMovementUpdate(ByteBuffer* data, uint16 flags) const { *data << object->GetPositionX(); *data << object->GetPositionY(); - *data << object->GetPositionZ() + (unit ? unit->GetHoverHeight() : 0.0f); + *data << object->GetPositionZ(); } *data << object->GetOrientation(); @@ -386,7 +387,7 @@ void Object::BuildMovementUpdate(ByteBuffer* data, uint16 flags) const { *data << object->GetStationaryX(); *data << object->GetStationaryY(); - *data << object->GetStationaryZ() + (unit ? unit->GetHoverHeight() : 0.0f); + *data << object->GetStationaryZ(); *data << object->GetStationaryO(); } } @@ -897,6 +898,15 @@ bool Object::PrintIndexError(uint32 index, bool set) const return false; } + +bool Position::operator==(Position const& a) +{ + return (G3D::fuzzyEq(a.m_positionX, m_positionX) && + G3D::fuzzyEq(a.m_positionY, m_positionY) && + G3D::fuzzyEq(a.m_positionZ, m_positionZ) && + G3D::fuzzyEq(a.m_orientation, m_orientation)); +} + void Position::RelocatePolarOffset(float angle, float dist, float z /*= 0.0f*/) { SetOrientation(GetOrientation() + angle); @@ -988,7 +998,7 @@ WorldObject::WorldObject(bool isWorldObject) : WorldLocation(), elunaEvents(nullptr), #endif LastUsedScriptID(0), m_name(""), m_isActive(false), m_isVisibilityDistanceOverride(false), m_isWorldObject(isWorldObject), m_zoneScript(nullptr), - m_transport(nullptr), m_currMap(nullptr), m_InstanceId(0), + m_staticFloorZ(INVALID_HEIGHT), m_transport(nullptr), m_currMap(nullptr), m_InstanceId(0), m_phaseMask(PHASEMASK_NORMAL), m_useCombinedPhases(true), m_notifyflags(0), m_executed_notifies(0) { m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE | GHOST_VISIBILITY_GHOST); @@ -1139,7 +1149,7 @@ bool WorldObject::_IsWithinDist(WorldObject const* obj, float dist2compare, bool Position WorldObject::GetHitSpherePointFor(Position const& dest) const { - G3D::Vector3 vThis(GetPositionX(), GetPositionY(), GetPositionZ()); + G3D::Vector3 vThis(GetPositionX(), GetPositionY(), GetPositionZ() + GetCollisionHeight()); G3D::Vector3 vObj(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ()); G3D::Vector3 contactPoint = vThis + (vObj - vThis).directionOrZero() * std::min(dest.GetExactDist(this), GetObjectSize()); @@ -1150,29 +1160,47 @@ bool WorldObject::IsWithinLOS(float ox, float oy, float oz, LineOfSightChecks ch { if (IsInWorld()) { + oz += GetCollisionHeight(); float x, y, z; if (GetTypeId() == TYPEID_PLAYER) + { GetPosition(x, y, z); + z += GetCollisionHeight(); + } else + { GetHitSpherePointFor({ ox, oy, oz }, x, y, z); + } - return GetMap()->isInLineOfSight(x, y, z + 2.0f, ox, oy, oz + 2.0f, GetPhaseMask(), checks); + return GetMap()->isInLineOfSight(x, y, z, ox, oy, oz, GetPhaseMask(), checks); } return true; } bool WorldObject::IsWithinLOSInMap(const WorldObject* obj, LineOfSightChecks checks) const { - if (!IsInMap(obj)) + if (!IsInMap(obj)) return false; - float x, y, z; + float ox, oy, oz; if (obj->GetTypeId() == TYPEID_PLAYER) - obj->GetPosition(x, y, z); + { + obj->GetPosition(ox, oy, oz); + oz += GetCollisionHeight(); + } else - obj->GetHitSpherePointFor(GetPosition(), x, y, z); + obj->GetHitSpherePointFor({ GetPositionX(), GetPositionY(), GetPositionZ() + GetCollisionHeight() }, ox, oy, oz); - return IsWithinLOS(x, y, z, checks); + float x, y, z; + if (GetTypeId() == TYPEID_PLAYER) + { + GetPosition(x, y, z); + z += GetCollisionHeight(); + } + else + GetHitSpherePointFor({ obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ() + obj->GetCollisionHeight() }, x, y, z); + + return GetMap()->isInLineOfSight(x, y, z, ox, oy, oz, GetPhaseMask(), checks); } void WorldObject::GetHitSpherePointFor(Position const& dest, float& x, float& y, float& z) const @@ -1302,12 +1330,7 @@ float Position::GetAngle(const Position* obj) const // Return angle in range 0..2*pi float Position::GetAngle(const float x, const float y) const { - float dx = x - GetPositionX(); - float dy = y - GetPositionY(); - - float ang = atan2(dy, dx); - ang = (ang >= 0) ? ang : 2 * M_PI + ang; - return ang; + return getAngle(GetPositionX(), GetPositionY(), x, y); } void Position::GetSinCos(const float x, const float y, float& vsin, float& vcos) const @@ -1446,78 +1469,95 @@ void WorldObject::GetRandomPoint(const Position& pos, float distance, float& ran UpdateGroundPositionZ(rand_x, rand_y, rand_z); // update to LOS height if available } -void WorldObject::UpdateGroundPositionZ(float x, float y, float& z) const +void WorldObject::UpdateGroundPositionZ(float x, float y, float &z) const { - float new_z = GetMap()->GetHeight(GetPhaseMask(), x, y, z /*+ 2.0f*/, true); // pussywizard: +2.0f is added in all inner functions + float new_z = GetMapHeight(x, y, z); if (new_z > INVALID_HEIGHT) - z = new_z + 0.05f; // just to be sure that we are not a few pixel under the surface + z = new_z + (isType(TYPEMASK_UNIT) ? static_cast(this)->GetHoverHeight() : 0.0f); } -void WorldObject::UpdateAllowedPositionZ(float x, float y, float& z) const +/** + * @brief Get the minimum height of a object that should be in water + * to start floating/swim + * + * @return float + */ +float WorldObject::GetMinHeightInWater() const { - // TODO: Allow transports to be part of dynamic vmap tree - //if (GetTransport()) - // return; + // have a fun with Archimedes' formula + auto height = GetCollisionHeight(); + auto width = GetCollisionWidth(); + auto weight = getWeight(height, width, 1040); // avg human specific weight + auto heightOutOfWater = getOutOfWater(width, weight, 10202) * 4.0f; // avg human density + auto heightInWater = height - heightOutOfWater; + return (height > heightInWater ? heightInWater : (height - (height / 3))); +} - switch (GetTypeId()) +void WorldObject::UpdateAllowedPositionZ(float x, float y, float& z, float* groundZ) const +{ + if (GetTransport()) { - case TYPEID_UNIT: + if (groundZ) + *groundZ = z; + + return; + } + + if (Unit const* unit = ToUnit()) + { + if (!unit->CanFly()) + { + Creature const* c = unit->ToCreature(); + bool canSwim = c ? c->CanSwim() : true; + float ground_z = z; + float max_z; + if (canSwim) + max_z = GetMapWaterOrGroundLevel(x, y, z, &ground_z); + else + max_z = ground_z = GetMapHeight(x, y, z); + + if (max_z > INVALID_HEIGHT) { - // non fly unit don't must be in air - // non swim unit must be at ground (mostly speedup, because it don't must be in water and water level check less fast - if (!ToCreature()->CanFly()) - { - bool canSwim = ToCreature()->CanSwim(); - float ground_z = z; - float max_z = canSwim - ? GetMap()->GetWaterOrGroundLevel(GetPhaseMask(), x, y, z, &ground_z, !ToUnit()->HasAuraType(SPELL_AURA_WATER_WALK)) - : ((ground_z = GetMap()->GetHeight(GetPhaseMask(), x, y, z, true))); - if (max_z > INVALID_HEIGHT) - { - if (z > max_z) - z = max_z; - else if (z < ground_z) - z = ground_z; - } + if (canSwim && unit->GetMap()->IsInWater(x, y, max_z - WATER_HEIGHT_TOLERANCE)) { + // do not allow creatures to walk on + // water level while swimming + max_z = max_z - GetMinHeightInWater(); } else { - float ground_z = GetMap()->GetHeight(GetPhaseMask(), x, y, z, true); - if (z < ground_z) - z = ground_z; + // hovering units cannot go below their hover height + float hoverOffset = unit->GetHoverHeight(); + max_z += hoverOffset; + ground_z += hoverOffset; } - break; - } - case TYPEID_PLAYER: - { - // for server controlled moves playr work same as creature (but it can always swim) - if (!ToPlayer()->CanFly()) - { - float ground_z = z; - float max_z = GetMap()->GetWaterOrGroundLevel(GetPhaseMask(), x, y, z, &ground_z, !ToUnit()->HasAuraType(SPELL_AURA_WATER_WALK)); - if (max_z > INVALID_HEIGHT) - { - if (z > max_z) - z = max_z; - else if (z < ground_z) - z = ground_z; - } - } - else - { - float ground_z = GetMap()->GetHeight(GetPhaseMask(), x, y, z, true); - if (z < ground_z) - z = ground_z; - } - break; - } - default: - { - float ground_z = GetMap()->GetHeight(GetPhaseMask(), x, y, z, true); - if (ground_z > INVALID_HEIGHT) + + if (z > max_z) + z = max_z; + else if (z < ground_z) z = ground_z; - break; } + + if (groundZ) + *groundZ = ground_z; + } + else + { + float ground_z = GetMapHeight(x, y, z) + unit->GetHoverHeight(); + if (z < ground_z) + z = ground_z; + + if (groundZ) + *groundZ = ground_z; + } + } + else + { + float ground_z = GetMapHeight(x, y, z); + if (ground_z > INVALID_HEIGHT) + z = ground_z; + + if (groundZ) + *groundZ = ground_z; } } @@ -2552,23 +2592,64 @@ namespace acore //=================================================================================================== -void WorldObject::GetNearPoint2D(float& x, float& y, float distance2d, float absAngle) const +void WorldObject::GetNearPoint2D(WorldObject const* searcher, float& x, float& y, float distance2d, float absAngle) const { - x = GetPositionX() + (GetObjectSize() + distance2d) * cos(absAngle); - y = GetPositionY() + (GetObjectSize() + distance2d) * sin(absAngle); + float effectiveReach = GetCombatReach(); + + if (searcher) + { + effectiveReach += searcher->GetCombatReach(); + + if (this != searcher) + { + float myHover = 0.0f, searcherHover = 0.0f; + if (Unit const* unit = ToUnit()) + myHover = unit->GetHoverHeight(); + if (Unit const* searchUnit = searcher->ToUnit()) + searcherHover = searchUnit->GetHoverHeight(); + + float hoverDelta = myHover - searcherHover; + if (hoverDelta != 0.0f) + effectiveReach = std::sqrt(std::max(effectiveReach * effectiveReach - hoverDelta * hoverDelta, 0.0f)); + } + } + + x = GetPositionX() + (effectiveReach + distance2d) * std::cos(absAngle); + y = GetPositionY() + (effectiveReach + distance2d) * std::sin(absAngle); acore::NormalizeMapCoord(x); acore::NormalizeMapCoord(y); } +void WorldObject::GetNearPoint2D(float& x, float& y, float distance2d, float absAngle) const +{ + GetNearPoint2D(nullptr, x, y, distance2d, absAngle); +} + void WorldObject::GetNearPoint(WorldObject const* searcher, float& x, float& y, float& z, float searcher_size, float distance2d, float absAngle, float controlZ) const { GetNearPoint2D(x, y, distance2d + searcher_size, absAngle); z = GetPositionZ(); - if (searcher) + + if (searcher) { + if (Unit const* unit = searcher->ToUnit(); Unit const* target = ToUnit()) + { + if (unit && target && unit->IsInWater() && target->IsInWater()) + { + // if the searcher is in water + // we have no ground so we can + // set the target height to the + // z-coord to keep the searcher + // at the correct height (face to face) + z += GetCollisionHeight() - unit->GetCollisionHeight(); + } + } searcher->UpdateAllowedPositionZ(x, y, z); + } else + { UpdateAllowedPositionZ(x, y, z); + } // if detection disabled, return first point if (!sWorld->getBoolConfig(CONFIG_DETECT_POS_COLLISION)) @@ -2679,8 +2760,8 @@ void WorldObject::MovePosition(Position& pos, float dist, float angle) return; } - ground = GetMap()->GetHeight(GetPhaseMask(), destx, desty, MAX_HEIGHT, true); - floor = GetMap()->GetHeight(GetPhaseMask(), destx, desty, pos.m_positionZ, true); + ground = GetMapHeight(destx, desty, MAX_HEIGHT); + floor = GetMapHeight(destx, desty, pos.m_positionZ); destz = fabs(ground - pos.m_positionZ) <= fabs(floor - pos.m_positionZ) ? ground : floor; float step = dist / 10.0f; @@ -2692,215 +2773,74 @@ void WorldObject::MovePosition(Position& pos, float dist, float angle) { destx -= step * cos(angle); desty -= step * sin(angle); - ground = GetMap()->GetHeight(GetPhaseMask(), destx, desty, MAX_HEIGHT, true); - floor = GetMap()->GetHeight(GetPhaseMask(), destx, desty, pos.m_positionZ, true); + ground = GetMapHeight(destx, desty, MAX_HEIGHT); + floor = GetMapHeight(destx, desty, pos.m_positionZ); destz = fabs(ground - pos.m_positionZ) <= fabs(floor - pos.m_positionZ) ? ground : floor; } // we have correct destz now else + { + pos.Relocate(destx, desty, destz); break; + } } - pos.Relocate(destx, desty, destz); - acore::NormalizeMapCoord(pos.m_positionX); acore::NormalizeMapCoord(pos.m_positionY); UpdateGroundPositionZ(pos.m_positionX, pos.m_positionY, pos.m_positionZ); pos.m_orientation = m_orientation; } +Position WorldObject::GetFirstCollisionPosition(float startX, float startY, float startZ, float destX, float destY) +{ + auto dx = destX - startX; + auto dy = destY - startY; + + auto ang = atan2(dy, dx); + ang = (ang >= 0) ? ang : 2 * M_PI + ang; + Position pos = Position(startX, startY, startZ, ang); + + auto distance = pos.GetExactDist2d(destX,destY); + + MovePositionToFirstCollision(pos, distance, ang); + return pos; +}; + +Position WorldObject::GetFirstCollisionPosition(float destX, float destY, float destZ) +{ + Position pos = GetPosition(); + auto distance = GetExactDistSq(destX,destY,destZ); + + auto dx = destX - pos.GetPositionX(); + auto dy = destY - pos.GetPositionY(); + + auto ang = atan2(dy, dx); + ang = (ang >= 0) ? ang : 2 * M_PI + ang; + + MovePositionToFirstCollision(pos, distance, ang); + return pos; +}; + +Position WorldObject::GetFirstCollisionPosition(float dist, float angle) +{ + Position pos = GetPosition(); + GetFirstCollisionPosition(pos, dist, angle); + return pos; +} + void WorldObject::MovePositionToFirstCollision(Position& pos, float dist, float angle) { - angle += m_orientation; + angle += GetOrientation(); float destx, desty, destz; destx = pos.m_positionX + dist * cos(angle); desty = pos.m_positionY + dist * sin(angle); destz = pos.m_positionZ; - if (isType(TYPEMASK_UNIT | TYPEMASK_PLAYER) && !ToUnit()->IsInWater()) - destz += 2.0f; - // Prevent invalid coordinates here, position is unchanged - if (!acore::IsValidMapCoord(destx, desty)) - { - sLog->outCrash("WorldObject::MovePositionToFirstCollision invalid coordinates X: %f and Y: %f were passed!", destx, desty); + if (!GetMap()->CheckCollisionAndGetValidCoords(this, pos.m_positionX, pos.m_positionY, pos.m_positionZ, destx, desty, destz, false)) return; - } - - // Xinef: ugly hack for dalaran arena - float selfAddition = 1.5f; - float allowedDiff = 6.0f; - float newDist = dist; - if (GetMapId() == 617) - { - allowedDiff = 3.5f; - selfAddition = 1.0f; - destz = pos.m_positionZ + 1.0f; - } - else - UpdateAllowedPositionZ(destx, desty, destz); - - bool col = VMAP::VMapFactory::createOrGetVMapManager()->getObjectHitPos(GetMapId(), pos.m_positionX, pos.m_positionY, pos.m_positionZ + selfAddition, destx, desty, destz + 0.5f, destx, desty, destz, -0.5f); - - // collision occured - if (col) - { - // move back a bit - if (pos.GetExactDist2d(destx, desty) > CONTACT_DISTANCE) - { - destx -= CONTACT_DISTANCE * cos(angle); - desty -= CONTACT_DISTANCE * sin(angle); - } - - newDist = sqrt((pos.m_positionX - destx) * (pos.m_positionX - destx) + (pos.m_positionY - desty) * (pos.m_positionY - desty)); - } - - // check dynamic collision - col = GetMap()->getObjectHitPos(GetPhaseMask(), pos.m_positionX, pos.m_positionY, pos.m_positionZ + selfAddition, destx, desty, destz + 0.5f, destx, desty, destz, -0.5f); - - // Collided with a gameobject - if (col) - { - // move back a bit - if (pos.GetExactDist2d(destx, desty) > CONTACT_DISTANCE) - { - destx -= CONTACT_DISTANCE * cos(angle); - desty -= CONTACT_DISTANCE * sin(angle); - } - newDist = sqrt((pos.m_positionX - destx) * (pos.m_positionX - destx) + (pos.m_positionY - desty) * (pos.m_positionY - desty)); - } - - float step = newDist / 10.0f; - - for (uint8 j = 0; j < 10; ++j) - { - // do not allow too big z changes - if (fabs(pos.m_positionZ - destz) > allowedDiff) - { - destx -= step * cos(angle); - desty -= step * sin(angle); - UpdateAllowedPositionZ(destx, desty, destz); - } - // we have correct destz now - else - break; - } - - acore::NormalizeMapCoord(destx); - acore::NormalizeMapCoord(desty); - UpdateAllowedPositionZ(destx, desty, destz); - - float ground = GetMap()->GetHeight(GetPhaseMask(), destx, desty, MAX_HEIGHT, true); - float floor = GetMap()->GetHeight(GetPhaseMask(), destx, desty, destz, true); - ground = fabs(ground - destz) <= fabs(floor - pos.m_positionZ) ? ground : floor; - if (destz < ground) - destz = ground; - - // Xinef: check if last z updates did not move z too far away - //newDist = pos.GetExactDist(destx, desty, destz); - //float ratio = newDist / dist; - //if (ratio > 1.3f) - //{ - // ratio = (1 / ratio) + (0.3f / ratio); - // destx = pos.GetPositionX() + (fabs(destx - pos.GetPositionX()) * cos(angle) * ratio); - // desty = pos.GetPositionY() + (fabs(desty - pos.GetPositionY()) * sin(angle) * ratio); - // destz = pos.GetPositionZ() + (fabs(destz - pos.GetPositionZ()) * ratio * (destz < pos.GetPositionZ() ? -1.0f : 1.0f)); - //} + pos.SetOrientation(GetOrientation()); pos.Relocate(destx, desty, destz); - pos.m_orientation = m_orientation; -} - -void WorldObject::MovePositionToFirstCollisionForTotem(Position& pos, float dist, float angle, bool forGameObject) -{ - angle += m_orientation; - float destx, desty, destz, ground, floor; - pos.m_positionZ += 2.0f; - destx = pos.m_positionX + dist * cos(angle); - desty = pos.m_positionY + dist * sin(angle); - destz = pos.GetPositionZ(); - - // Prevent invalid coordinates here, position is unchanged - if (!acore::IsValidMapCoord(destx, desty)) - { - sLog->outCrash("WorldObject::MovePositionToFirstCollision invalid coordinates X: %f and Y: %f were passed!", destx, desty); - return; - } - - bool col = VMAP::VMapFactory::createOrGetVMapManager()->getObjectHitPos(GetMapId(), pos.m_positionX, pos.m_positionY, pos.m_positionZ, destx, desty, destz, destx, desty, destz, -0.5f); - - // collision occured - if (col) - { - // move back a bit - if (pos.GetExactDist2d(destx, desty) > CONTACT_DISTANCE) - { - destx -= CONTACT_DISTANCE * cos(angle); - desty -= CONTACT_DISTANCE * sin(angle); - } - - dist = sqrt((pos.m_positionX - destx) * (pos.m_positionX - destx) + (pos.m_positionY - desty) * (pos.m_positionY - desty)); - } - - // check dynamic collision - col = GetMap()->getObjectHitPos(GetPhaseMask(), pos.m_positionX, pos.m_positionY, pos.m_positionZ + 0.5f, destx, desty, destz + 0.5f, destx, desty, destz, -0.5f); - - // Collided with a gameobject - if (col) - { - // move back a bit - if (pos.GetExactDist2d(destx, desty) > CONTACT_DISTANCE) - { - destx -= CONTACT_DISTANCE * cos(angle); - desty -= CONTACT_DISTANCE * sin(angle); - } - dist = sqrt((pos.m_positionX - destx) * (pos.m_positionX - destx) + (pos.m_positionY - desty) * (pos.m_positionY - desty)); - } - - float prevdx = destx, prevdy = desty, prevdz = destz; - bool anyvalid = false; - - ground = GetMap()->GetHeight(GetPhaseMask(), destx, desty, MAX_HEIGHT, true); - floor = GetMap()->GetHeight(GetPhaseMask(), destx, desty, pos.m_positionZ, true); - destz = fabs(ground - pos.m_positionZ) <= fabs(floor - pos.m_positionZ) ? ground : floor; - - // xinef: if we have gameobject, store last valid ground position - // xinef: I assume you wanted to spawn totem in air and allow it to fall down if no valid position was found - if (forGameObject) - prevdz = destz; - - float step = dist / 10.0f; - for (uint8 j = 0; j < 10; ++j) - { - // do not allow too big z changes - if (fabs(pos.m_positionZ - destz) > 4.0f) - { - destx -= step * cos(angle); - desty -= step * sin(angle); - ground = GetMap()->GetHeight(GetPhaseMask(), destx, desty, MAX_HEIGHT, true); - floor = GetMap()->GetHeight(GetPhaseMask(), destx, desty, pos.m_positionZ, true); - destz = fabs(ground - pos.m_positionZ) <= fabs(floor - pos.m_positionZ) ? ground : floor; - if (j == 9 && fabs(pos.m_positionZ - destz) <= 4.0f) - anyvalid = true; - } - // we have correct destz now - else - { - anyvalid = true; - break; - } - } - if (!anyvalid) - { - destx = prevdx; - desty = prevdy; - destz = prevdz; - } - - acore::NormalizeMapCoord(destx); - acore::NormalizeMapCoord(desty); - - pos.Relocate(destx, desty, destz); - pos.m_orientation = m_orientation; } void WorldObject::SetPhaseMask(uint32 newPhaseMask, bool update) @@ -3116,3 +3056,26 @@ uint64 WorldObject::GetTransGUID() const return GetTransport()->GetGUID(); return 0; } + +float WorldObject::GetMapHeight(float x, float y, float z, bool vmap/* = true*/, float distanceToSearch/* = DEFAULT_HEIGHT_SEARCH*/) const +{ + if (z != MAX_HEIGHT) + z += GetCollisionHeight(); + + return GetMap()->GetHeight(GetPhaseMask(), x, y, z, vmap, distanceToSearch); +} + +float WorldObject::GetMapWaterOrGroundLevel(float x, float y, float z, float* ground/* = nullptr*/) const +{ + return GetMap()->GetWaterOrGroundLevel(GetPhaseMask(), x, y, z, ground, + isType(TYPEMASK_UNIT) ? !static_cast(this)->HasAuraType(SPELL_AURA_WATER_WALK) : false, + GetCollisionHeight()); +} + +float WorldObject::GetFloorZ() const +{ + if (!IsInWorld()) + return m_staticFloorZ; + + return std::max(m_staticFloorZ, GetMap()->GetGameObjectFloor(GetPhaseMask(), GetPositionX(), GetPositionY(), GetPositionZ() + GetCollisionHeight())); +} diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index e6118a016..299cb71f0 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -24,26 +24,6 @@ class ElunaEventProcessor; #include #include -#define CONTACT_DISTANCE 0.5f -#define INTERACTION_DISTANCE 5.5f -#define ATTACK_DISTANCE 5.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 MAX_VISIBILITY_DISTANCE 250.0f // max distance for visible objects, experimental -#define VISIBILITY_INC_FOR_GOBJECTS 30.0f // pussywizard -#define VISIBILITY_COMPENSATION 15.0f // increase searchers -#define SPELL_SEARCHER_COMPENSATION 30.0f // increase searchers size in case we have large npc near cell border -#define VISIBILITY_DIST_WINTERGRASP 175.0f -#define SIGHT_RANGE_UNIT 50.0f -#define DEFAULT_VISIBILITY_DISTANCE 90.0f // default visible distance, 90 yards on continents -#define DEFAULT_VISIBILITY_INSTANCE 120.0f // default visible distance in instances, 120 yards -#define DEFAULT_VISIBILITY_BGARENAS 150.0f // default visible distance in BG/Arenas, 150 yards - -#define DEFAULT_WORLD_OBJECT_SIZE 0.388999998569489f // player size, also currently used (correctly?) for any non Unit world objects -#define DEFAULT_COMBAT_REACH 1.5f -#define MIN_MELEE_REACH 2.0f -#define NOMINAL_MELEE_RANGE 5.0f -#define MELEE_RANGE (NOMINAL_MELEE_RANGE - MIN_MELEE_REACH * 2) //center to center for players - enum TypeMask { TYPEMASK_OBJECT = 0x0001, @@ -138,6 +118,7 @@ public: [[nodiscard]] uint32 GetEntry() const { return GetUInt32Value(OBJECT_FIELD_ENTRY); } void SetEntry(uint32 entry) { SetUInt32Value(OBJECT_FIELD_ENTRY, entry); } + float GetObjectScale() const { return GetFloatValue(OBJECT_FIELD_SCALE_X); } virtual void SetObjectScale(float scale) { SetFloatValue(OBJECT_FIELD_SCALE_X, scale); } [[nodiscard]] TypeID GetTypeId() const { return m_objectTypeId; } @@ -540,11 +521,19 @@ struct Position float GetAngle(const Position* pos) const; [[nodiscard]] float GetAngle(float x, float y) const; + float GetAbsoluteAngle(float x, float y) const + { + float dx = x - m_positionX; + float dy = y - m_positionY; + return NormalizeOrientation(std::atan2(dy, dx)); + } float GetRelativeAngle(const Position* pos) const { return GetAngle(pos) - m_orientation; } [[nodiscard]] float GetRelativeAngle(float x, float y) const { return GetAngle(x, y) - m_orientation; } + float ToAbsoluteAngle(float relAngle) const { return NormalizeOrientation(relAngle + m_orientation); } + void GetSinCos(float x, float y, float& vsin, float& vcos) const; [[nodiscard]] bool IsInDist2d(float x, float y, float dist) const @@ -782,6 +771,7 @@ public: ElunaEventProcessor* elunaEvents; #endif + void GetNearPoint2D(WorldObject const* searcher, float& x, float& y, float distance, float absAngle) const; void GetNearPoint2D(float& x, float& y, float distance, float absAngle) const; void GetNearPoint(WorldObject const* searcher, float& x, float& y, float& z, float searcher_size, float distance2d, float absAngle, float controlZ = 0) const; void GetVoidClosePoint(float& x, float& y, float& z, float size, float distance2d = 0, float relAngle = 0, float controlZ = 0) const; @@ -793,17 +783,14 @@ public: MovePosition(pos, dist, angle); } void MovePositionToFirstCollision(Position& pos, float dist, float angle); + Position GetFirstCollisionPosition(float startX, float startY, float startZ, float destX, float destY); + Position GetFirstCollisionPosition(float destX, float destY, float destZ); + Position GetFirstCollisionPosition(float dist, float angle); void GetFirstCollisionPosition(Position& pos, float dist, float angle) { GetPosition(&pos); MovePositionToFirstCollision(pos, dist, angle); } - void MovePositionToFirstCollisionForTotem(Position& pos, float dist, float angle, bool forGameObject); - void GetFirstCollisionPositionForTotem(Position& pos, float dist, float angle, bool forGameObject) - { - GetPosition(&pos); - MovePositionToFirstCollisionForTotem(pos, dist, angle, forGameObject); - } void GetRandomNearPosition(Position& pos, float radius) { GetPosition(&pos); @@ -815,12 +802,12 @@ public: [[nodiscard]] float GetObjectSize() const { - return (m_valuesCount > UNIT_FIELD_COMBATREACH) ? m_floatValues[UNIT_FIELD_COMBATREACH] : DEFAULT_WORLD_OBJECT_SIZE; + return (m_valuesCount > UNIT_FIELD_COMBATREACH) ? m_floatValues[UNIT_FIELD_COMBATREACH] : DEFAULT_WORLD_OBJECT_SIZE * GetObjectScale(); } [[nodiscard]] virtual float GetCombatReach() const { return 0.0f; } // overridden (only) in Unit void UpdateGroundPositionZ(float x, float y, float& z) const; - void UpdateAllowedPositionZ(float x, float y, float& z) const; + void UpdateAllowedPositionZ(float x, float y, float& z, float* groundZ = nullptr) const; void GetRandomPoint(const Position& srcPos, float distance, float& rand_x, float& rand_y, float& rand_z) const; void GetRandomPoint(const Position& srcPos, float distance, Position& pos) const @@ -1057,6 +1044,16 @@ public: [[nodiscard]] virtual float GetStationaryZ() const { return GetPositionZ(); } [[nodiscard]] virtual float GetStationaryO() const { return GetOrientation(); } + [[nodiscard]] float GetMapWaterOrGroundLevel(float x, float y, float z, float* ground = nullptr) const; + [[nodiscard]] float GetMapHeight(float x, float y, float z, bool vmap = true, float distanceToSearch = 50.0f) const; // DEFAULT_HEIGHT_SEARCH in map.h + + [[nodiscard]] float GetFloorZ() const; + [[nodiscard]] float GetMinHeightInWater() const; + + [[nodiscard]] virtual float GetCollisionHeight() const { return 0.0f; } + [[nodiscard]] virtual float GetCollisionWidth() const { return GetObjectSize(); } + [[nodiscard]] virtual float GetCollisionRadius() const { return GetObjectSize() / 2; } + protected: std::string m_name; bool m_isActive; @@ -1064,6 +1061,8 @@ protected: const bool m_isWorldObject; ZoneScript* m_zoneScript; + float m_staticFloorZ; + // transports Transport* m_transport; diff --git a/src/server/game/Entities/Object/ObjectDefines.h b/src/server/game/Entities/Object/ObjectDefines.h index a0c4700e4..4ce3ccf9b 100644 --- a/src/server/game/Entities/Object/ObjectDefines.h +++ b/src/server/game/Entities/Object/ObjectDefines.h @@ -9,6 +9,27 @@ #include "Define.h" +#define CONTACT_DISTANCE 0.5f +#define INTERACTION_DISTANCE 5.5f +#define ATTACK_DISTANCE 5.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 MAX_VISIBILITY_DISTANCE 250.0f // max distance for visible objects, experimental +#define VISIBILITY_INC_FOR_GOBJECTS 30.0f // pussywizard +#define VISIBILITY_COMPENSATION 15.0f // increase searchers +#define SPELL_SEARCHER_COMPENSATION 30.0f // increase searchers size in case we have large npc near cell border +#define VISIBILITY_DIST_WINTERGRASP 175.0f +#define SIGHT_RANGE_UNIT 50.0f +#define DEFAULT_VISIBILITY_DISTANCE 90.0f // default visible distance, 90 yards on continents +#define DEFAULT_VISIBILITY_INSTANCE 120.0f // default visible distance in instances, 120 yards +#define DEFAULT_VISIBILITY_BGARENAS 150.0f // default visible distance in BG/Arenas, 150 yards + +#define DEFAULT_WORLD_OBJECT_SIZE 0.388999998569489f // player size, also currently used (correctly?) for any non Unit world objects +#define DEFAULT_COMBAT_REACH 1.5f +#define MIN_MELEE_REACH 2.0f +#define NOMINAL_MELEE_RANGE 5.0f +#define MELEE_RANGE (NOMINAL_MELEE_RANGE - MIN_MELEE_REACH * 2) //center to center for players +#define DEFAULT_COLLISION_HEIGHT 2.03128f // Most common value in dbc + enum HighGuid { HIGHGUID_ITEM = 0x4000, // blizz 4000 diff --git a/src/server/game/Entities/Pet/Pet.h b/src/server/game/Entities/Pet/Pet.h index 4cedc34e2..a46e05918 100644 --- a/src/server/game/Entities/Pet/Pet.h +++ b/src/server/game/Entities/Pet/Pet.h @@ -140,7 +140,7 @@ public: uint8 GetMaxTalentPointsForLevel(uint8 level); uint8 GetFreeTalentPoints() { return GetByteValue(UNIT_FIELD_BYTES_1, 1); } - void SetFreeTalentPoints(uint8 points) { SetByteValue(UNIT_FIELD_BYTES_1, 1, points); } + void SetFreeTalentPoints(uint8 points) { SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_PET_TALENTS, points); } uint32 m_usedTalentCount; diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index aa90d827c..cfcc59b32 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -5264,7 +5264,7 @@ void Player::BuildPlayerRepop() StopMirrorTimers(); //disable timers(bars) // set and clear other - SetByteValue(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_ALWAYS_STAND); + SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, UNIT_BYTE1_FLAG_ALWAYS_STAND); sScriptMgr->OnPlayerReleasedGhost(this); } @@ -5281,7 +5281,7 @@ void Player::ResurrectPlayer(float restore_percent, bool applySickness) // speed change, land walk // remove death flag + set aura - SetByteValue(UNIT_FIELD_BYTES_1, 3, 0x00); + SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, UNIT_BYTE1_FLAG_GROUND); RemoveAurasDueToSpell(20584); // speed bonuses RemoveAurasDueToSpell(8326); // SPELL_AURA_GHOST diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 7988bc38f..eb528b140 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -2559,40 +2559,7 @@ public: bool SetHover(bool enable, bool packetOnly = false) override; [[nodiscard]] bool CanFly() const override { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_CAN_FLY); } - - //! Return collision height sent to client - float GetCollisionHeight(bool mounted) - { - if (mounted) - { - CreatureDisplayInfoEntry const* mountDisplayInfo = sCreatureDisplayInfoStore.LookupEntry(GetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID)); - if (!mountDisplayInfo) - return GetCollisionHeight(false); - - CreatureModelDataEntry const* mountModelData = sCreatureModelDataStore.LookupEntry(mountDisplayInfo->ModelId); - if (!mountModelData) - return GetCollisionHeight(false); - - CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.LookupEntry(GetNativeDisplayId()); - ASSERT(displayInfo); - CreatureModelDataEntry const* modelData = sCreatureModelDataStore.LookupEntry(displayInfo->ModelId); - ASSERT(modelData); - - float scaleMod = GetFloatValue(OBJECT_FIELD_SCALE_X); // 99% sure about this - - return scaleMod * mountModelData->MountHeight + modelData->CollisionHeight * 0.5f; - } - else - { - //! Dismounting case - use basic default model data - CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.LookupEntry(GetNativeDisplayId()); - ASSERT(displayInfo); - CreatureModelDataEntry const* modelData = sCreatureModelDataStore.LookupEntry(displayInfo->ModelId); - ASSERT(modelData); - - return modelData->CollisionHeight; - } - } + [[nodiscard]] bool CanEnterWater() const override { return true; } // OURS // saving diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 8eeafcfb3..398b875a4 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -153,9 +153,28 @@ ProcEventInfo::ProcEventInfo(Unit* actor, Unit* actionTarget, Unit* procTarget, #pragma warning(disable:4355) #endif Unit::Unit(bool isWorldObject) : WorldObject(isWorldObject), - m_movedByPlayer(nullptr), m_lastSanctuaryTime(0), IsAIEnabled(false), NeedChangeAI(false), - m_ControlledByPlayer(false), m_CreatedByPlayer(false), movespline(new Movement::MoveSpline()), i_AI(nullptr), i_disabledAI(nullptr), m_realRace(0), m_race(0), m_AutoRepeatFirstCast(false), m_procDeep(0), m_removedAurasCount(0), - i_motionMaster(new MotionMaster(this)), m_regenTimer(0), m_ThreatManager(this), m_vehicle(nullptr), m_vehicleKit(nullptr), m_unitTypeMask(UNIT_MASK_NONE), m_HostileRefManager(this) + m_movedByPlayer(nullptr), + m_lastSanctuaryTime(0), + IsAIEnabled(false), + NeedChangeAI(false), + m_ControlledByPlayer(false), + m_CreatedByPlayer(false), + movespline(new Movement::MoveSpline()), + i_AI(nullptr), + i_disabledAI(nullptr), + m_realRace(0), + m_race(0), + m_AutoRepeatFirstCast(false), + m_procDeep(0), + m_removedAurasCount(0), + i_motionMaster(new MotionMaster(this)), + m_regenTimer(0), + m_ThreatManager(this), + m_vehicle(nullptr), + m_vehicleKit(nullptr), + m_unitTypeMask(UNIT_MASK_NONE), + m_HostileRefManager(this), + m_comboTarget(nullptr) { #ifdef _MSC_VER #pragma warning(default:4355) @@ -246,7 +265,6 @@ Unit::Unit(bool isWorldObject) : WorldObject(isWorldObject), m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE); - m_mmapNotAcceptableStartTime = 0; m_last_notify_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f); m_last_notify_mstime = 0; m_delayed_unit_relocation_timer = 0; @@ -445,7 +463,7 @@ void Unit::SendMonsterMove(float NewPosX, float NewPosY, float NewPosZ, uint32 T data.append(GetPackGUID()); data << uint8(0); // new in 3.1 - data << GetPositionX() << GetPositionY() << GetPositionZ() + GetHoverHeight(); + data << GetPositionX() << GetPositionY() << GetPositionZ(); data << World::GetGameTimeMS(); data << uint8(0); data << uint32(sf); @@ -571,12 +589,32 @@ bool Unit::IsWithinMeleeRange(const Unit* obj, float dist) const float dz = GetPositionZ() - obj->GetPositionZ(); float distsq = dx * dx + dy * dy + dz * dz; - float sizefactor = GetMeleeReach() + obj->GetMeleeReach(); - float maxdist = dist + sizefactor; + float maxdist = dist + GetMeleeRange(obj); return distsq < maxdist * maxdist; } +float Unit::GetMeleeRange(Unit const* target) const +{ + float range = GetCombatReach() + target->GetCombatReach() + 4.0f / 3.0f; + return std::max(range, NOMINAL_MELEE_RANGE); +} + +bool Unit::IsWithinRange(Unit const* obj, float dist) const +{ + if (!obj || !IsInMap(obj) || !InSamePhase(obj)) + { + return false; + } + + auto dx = GetPositionX() - obj->GetPositionX(); + auto dy = GetPositionY() - obj->GetPositionY(); + auto dz = GetPositionZ() - obj->GetPositionZ(); + auto distsq = dx * dx + dy * dy + dz * dz; + + return distsq <= dist * dist; +} + bool Unit::GetRandomContactPoint(const Unit* obj, float& x, float& y, float& z, bool force) const { float combat_reach = GetCombatReach(); @@ -595,14 +633,14 @@ bool Unit::GetRandomContactPoint(const Unit* obj, float& x, float& y, float& z, GetAngle(obj) + (attacker_number ? (static_cast(M_PI / 2) - static_cast(M_PI) * (float)rand_norm()) * float(attacker_number) / combat_reach * 0.3f : 0)); // pussywizard - if (fabs(this->GetPositionZ() - z) > 3.0f || !IsWithinLOS(x, y, z)) + if (fabs(this->GetPositionZ() - z) > this->GetCollisionHeight() || !IsWithinLOS(x, y, z)) { x = this->GetPositionX(); y = this->GetPositionY(); z = this->GetPositionZ(); obj->UpdateAllowedPositionZ(x, y, z); } - float maxDist = MELEE_RANGE + GetMeleeReach() + obj->GetMeleeReach(); + float maxDist = GetMeleeRange(obj); if (GetExactDistSq(x, y, z) >= maxDist * maxDist) { if (force) @@ -2141,7 +2179,7 @@ void Unit::CalcHealAbsorb(Unit const* victim, const SpellInfo* healSpell, uint32 healAmount = RemainingHeal; } -void Unit::AttackerStateUpdate (Unit* victim, WeaponAttackType attType, bool extra) +void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType, bool extra) { if (HasUnitState(UNIT_STATE_CANNOT_AUTOATTACK) || HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED)) return; @@ -2188,6 +2226,95 @@ void Unit::AttackerStateUpdate (Unit* victim, WeaponAttackType attType, bool ext } } +Position* Unit::GetMeleeAttackPoint(Unit* attacker) +{ + if (!attacker) + { + return nullptr; + } + + AttackerSet attackers = getAttackers(); + + if (attackers.size() <= 1) // if the attackers are not more than one + { + return nullptr; + } + + float meleeReach = GetExactDist2d(attacker); + if (meleeReach <= 0) + { + return nullptr; + } + + float minAngle = 0; + Unit *refUnit = nullptr; + uint32 validAttackers = 0; + + double attackerSize = attacker->GetCollisionRadius(); + + for (const auto& otherAttacker: attackers) + { + // if the otherAttacker is not valid, skip + if (!otherAttacker || + otherAttacker->GetGUID() == attacker->GetGUID() || + !otherAttacker->IsWithinMeleeRange(this) || + otherAttacker->isMoving() + ) + { + continue; + } + + float curretAngle = atan(attacker->GetExactDist2d(otherAttacker) / meleeReach); + + if (minAngle == 0 || curretAngle < minAngle) + { + minAngle = curretAngle; + refUnit = otherAttacker; + } + + validAttackers++; + } + + if (!validAttackers || !refUnit) + return nullptr; + + float contactDist = attackerSize + refUnit->GetCollisionRadius(); + float requiredAngle = atan(contactDist / meleeReach); + float attackersAngle = atan(attacker->GetExactDist2d(refUnit) / meleeReach); + + // in instance: the more attacker there are, the higher will be the tollerance + // outside: creatures should not intersecate + float angleTollerance = attacker->GetMap()->IsDungeon() ? requiredAngle - requiredAngle * tanh(validAttackers / 5.0f) : requiredAngle; + + if (attackersAngle > angleTollerance) + { + return nullptr; + } + + double angle = atan(contactDist / meleeReach); + + float angularRadius = frand(0.1f, 0.3f) + angle; + int8 direction = (urand(0, 1) ? -1 : 1); + float currentAngle = GetAngle(refUnit); + float absAngle = currentAngle + angularRadius * direction; + + float x, y, z; + float distance = meleeReach - GetObjectSize(); + GetNearPoint(attacker, x, y, z, distance, 0.0f, absAngle); + + if (!GetMap()->CanReachPositionAndGetValidCoords(this, x, y, z, true, true)) + { + GetNearPoint(attacker, x, y, z, distance, 0.0f, absAngle * -1); // try the other side + + if (!GetMap()->CanReachPositionAndGetValidCoords(this, x, y, z, true, true)) + { + return nullptr; + } + } + + return new Position(x,y,z); +} + void Unit::HandleProcExtraAttackFor(Unit* victim) { while (m_extraAttacks) @@ -3514,7 +3641,7 @@ bool Unit::isInAccessiblePlaceFor(Creature const* c) const } if (IsInWater()) - return IsUnderWater() ? c->CanSwim() : (c->CanSwim() || c->CanFly()); + return IsUnderWater() ? c->CanEnterWater() : (c->CanEnterWater() || c->CanFly()); else return c->CanWalk() || c->CanFly() || (c->CanSwim() && IsInWater(true)); } @@ -3534,33 +3661,38 @@ void Unit::UpdateEnvironmentIfNeeded(const uint8 option) return; } - if (option <= 1 && GetExactDistSq(&m_last_environment_position) < 2.5f * 2.5f) + // run environment checks everytime the unit moves + // more than it's average radius + // TODO: find better solution here + float radiusWidth = GetCollisionRadius(); + float radiusHeight = GetCollisionHeight() / 2; + float radiusAvg = (radiusWidth + radiusHeight) / 2; + if (option <= 1 && GetExactDistSq(&m_last_environment_position) < radiusAvg*radiusAvg) return; + m_last_environment_position.Relocate(GetPositionX(), GetPositionY(), GetPositionZ()); + m_staticFloorZ = GetMap()->GetHeight(GetPhaseMask(), GetPositionX(), GetPositionY(), GetPositionZ()); m_is_updating_environment = true; bool changed = false; - Creature* c = this->ToCreature(); Map* baseMap = const_cast(GetBaseMap()); + Creature* c = this->ToCreature(); if (!c || !baseMap) { m_is_updating_environment = false; return; } - - bool canChangeFlying = option == 3 || ((c->GetScriptId() == 0 || GetInstanceId() == 0) && GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) == NULL_MOTION_TYPE); + bool canChangeFlying = option == 3 || GetMotionMaster()->GetCurrentMovementGeneratorType() != WAYPOINT_MOTION_TYPE; bool canFallGround = option == 0 && canChangeFlying && GetInstanceId() == 0 && !IsInCombat() && !GetVehicle() && !GetTransport() && !HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && !c->IsTrigger() && !c->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE) && GetMotionMaster()->GetCurrentMovementGeneratorType() <= RANDOM_MOTION_TYPE && !HasUnitState(UNIT_STATE_EVADE) && !IsControlledByPlayer(); float x = GetPositionX(), y = GetPositionY(), z = GetPositionZ(); bool isInAir = true; float ground_z = z; LiquidData liquidData; liquidData.level = INVALID_HEIGHT; - ZLiquidStatus liquidStatus = baseMap->getLiquidStatus(x, y, z, MAP_ALL_LIQUIDS, &liquidData); - // IsInWater - bool enoughWater = (liquidData.level > INVALID_HEIGHT && liquidData.level > liquidData.depth_level && liquidData.level - liquidData.depth_level >= 1.5f); // also check if theres enough water - at least 2yd + bool enoughWater = baseMap->HasEnoughWater(this, liquidData); m_last_isinwater_status = (liquidStatus & (LIQUID_MAP_IN_WATER | LIQUID_MAP_UNDER_WATER)) && enoughWater; m_last_islittleabovewater_status = (liquidData.level > INVALID_HEIGHT && liquidData.level > liquidData.depth_level && liquidData.level <= z + 3.0f && liquidData.level > z - 1.0f); @@ -3615,12 +3747,12 @@ void Unit::UpdateEnvironmentIfNeeded(const uint8 option) // Refresh being in water if (m_last_isinwater_status) { - if (!c->CanFly() || z < liquidData.level - 2.0f) + if (!c->CanFly() || enoughWater) { if (!HasUnitState(UNIT_STATE_NO_ENVIRONMENT_UPD) && c->CanSwim() && (!HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) || !HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY))) { SetSwim(true); - SetDisableGravity(true); + // SetDisableGravity(true); changed = true; } isInAir = false; @@ -3648,11 +3780,12 @@ void Unit::UpdateEnvironmentIfNeeded(const uint8 option) { if (GetMap()->GetGrid(x, y)) { - float temp = GetMap()->GetHeight(GetPhaseMask(), x, y, z, true, 100.0f); + float temp = GetFloorZ(); if (temp > INVALID_HEIGHT) { ground_z = (c->CanSwim() && liquidData.level > INVALID_HEIGHT) ? liquidData.level : temp; - isInAir = flyingBarelyInWater || G3D::fuzzyGt(z, ground_z + 0.75f) || G3D::fuzzyLt(z, ground_z - 0.5f); + bool canHover = c->CanHover(); + isInAir = flyingBarelyInWater || (G3D::fuzzyGt(GetPositionZ(), ground_z + (canHover ? GetFloatValue(UNIT_FIELD_HOVERHEIGHT) : 0.0f) + GROUND_HEIGHT_TOLERANCE) || G3D::fuzzyLt(GetPositionZ(), ground_z - GROUND_HEIGHT_TOLERANCE)); // Can be underground too, prevent the falling } else isInAir = true; @@ -3685,24 +3818,26 @@ void Unit::UpdateEnvironmentIfNeeded(const uint8 option) } else if (c->CanFly() && isInAir) { - if (!c->IsFalling() && (!HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY) || !HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY) /*|| !HasUnitMovementFlag(MOVEMENTFLAG_HOVER)*/)) + if (!c->IsFalling() && (!HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY) || !HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY) || !HasUnitMovementFlag(MOVEMENTFLAG_HOVER))) { SetCanFly(true); SetDisableGravity(true); - //SetHover(true); + if (IsAlive() && (c->CanHover() || HasAuraType(SPELL_AURA_HOVER))) + SetHover(true); changed = true; } } else { - if (HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY) || HasUnitMovementFlag(MOVEMENTFLAG_FLYING) /*|| HasUnitMovementFlag(MOVEMENTFLAG_HOVER)*/) + if (HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY) || HasUnitMovementFlag(MOVEMENTFLAG_FLYING) || HasUnitMovementFlag(MOVEMENTFLAG_HOVER)) { SetCanFly(false); RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING); - //SetHover(false); + if (!HasAuraType(SPELL_AURA_HOVER)) + SetHover(false); changed = true; } - if (HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY) && !HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) /*&& !HasUnitMovementFlag(MOVEMENTFLAG_HOVER)*/) + if (HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY) && !HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) && !HasUnitMovementFlag(MOVEMENTFLAG_HOVER)) { SetDisableGravity(false); changed = true; @@ -9570,6 +9705,13 @@ bool Unit::Attack(Unit* victim, bool meleeAttack) if (GetTypeId() == TYPEID_PLAYER && IsMounted()) return false; + // creatures cannot attack while evading + Creature* creature = ToCreature(); + if (creature && creature->IsInEvadeMode()) + { + return false; + } + //if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED)) // pussywizard: wtf? why having this flag prevents from entering combat? it should just prevent melee attack // return false; @@ -9638,7 +9780,7 @@ bool Unit::Attack(Unit* victim, bool meleeAttack) //if (GetTypeId() == TYPEID_UNIT) // ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ()); - if (GetTypeId() == TYPEID_UNIT && !IsPet()) + if (creature && !IsPet()) { // should not let player enter combat by right clicking target - doesn't helps SetInCombatWith(victim); @@ -9646,8 +9788,8 @@ bool Unit::Attack(Unit* victim, bool meleeAttack) victim->SetInCombatWith(this); AddThreat(victim, 0.0f); - ToCreature()->SendAIReaction(AI_REACTION_HOSTILE); - ToCreature()->CallAssistance(); + creature->SendAIReaction(AI_REACTION_HOSTILE); + creature->CallAssistance(); } // delay offhand weapon attack to next attack time @@ -9659,7 +9801,7 @@ bool Unit::Attack(Unit* victim, bool meleeAttack) // Let the pet know we've started attacking someting. Handles melee attacks only // Spells such as auto-shot and others handled in WorldSession::HandleCastSpellOpcode - if (this->GetTypeId() == TYPEID_PLAYER && !m_Controlled.empty()) + if (GetTypeId() == TYPEID_PLAYER && !m_Controlled.empty()) for (Unit::ControlSet::iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr) if (Unit* pet = *itr) if (pet->IsAlive() && pet->GetTypeId() == TYPEID_UNIT) @@ -12546,7 +12688,7 @@ void Unit::Mount(uint32 mount, uint32 VehicleId, uint32 creatureEntry) WorldPacket data(SMSG_MOVE_SET_COLLISION_HGT, GetPackGUID().size() + 4 + 4); data.append(GetPackGUID()); data << uint32(sWorld->GetGameTime()); // Packet counter - data << player->GetCollisionHeight(true); + data << player->GetCollisionHeight(); player->GetSession()->SendPacket(&data); } @@ -12566,7 +12708,7 @@ void Unit::Dismount() WorldPacket data(SMSG_MOVE_SET_COLLISION_HGT, GetPackGUID().size() + 4 + 4); data.append(GetPackGUID()); data << uint32(sWorld->GetGameTime()); // Packet counter - data << thisPlayer->GetCollisionHeight(false); + data << thisPlayer->GetCollisionHeight(); thisPlayer->GetSession()->SendPacket(&data); } @@ -12728,7 +12870,6 @@ void Unit::SetInCombatState(bool PvP, Unit* enemy, uint32 duration) if (Creature* creature = ToCreature()) { - creature->m_targetsNotAcceptable.clear(); creature->UpdateEnvironmentIfNeeded(2); // Set home position at place of engaging combat for escorted creatures @@ -12746,6 +12887,8 @@ void Unit::SetInCombatState(bool PvP, Unit* enemy, uint32 duration) creature->GetFormation()->MemberAttackStart(creature, enemy); } + creature->RefreshSwimmingFlag(); + if (IsPet()) { UpdateSpeed(MOVE_RUN, true); @@ -12880,7 +13023,7 @@ bool Unit::_IsValidAttackTarget(Unit const* target, SpellInfo const* bySpell, Wo return false; } // check flags - if (target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_TAXI_FLIGHT | UNIT_FLAG_NOT_ATTACKABLE_1 | UNIT_FLAG_UNK_16) + if (target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_TAXI_FLIGHT | UNIT_FLAG_NOT_ATTACKABLE_1 | UNIT_FLAG_NON_ATTACKABLE_2) || (!HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE) && target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC)) || (!target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE) && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC)) || (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE) && target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC)) @@ -13748,33 +13891,15 @@ Unit* Creature::SelectVictim() if (target && _CanDetectFeignDeathOf(target) && CanCreatureAttack(target)) { - if (m_mmapNotAcceptableStartTime) m_mmapNotAcceptableStartTime = 0; // pussywizard: finding any valid target resets timer SetInFront(target); return target; } - // pussywizard: if victim is not acceptable only due to mmaps, it may be for example a knockback, wait for a few secs before evading - if (!target && !isWorldBoss() && !GetInstanceId() && IsAlive() && (!CanHaveThreatList() || !getThreatManager().isThreatListEmpty())) - if (Unit* v = GetVictim()) - if (isTargetNotAcceptableByMMaps(v->GetGUID(), sWorld->GetGameTime(), v)) - if (_CanDetectFeignDeathOf(v) && CanCreatureAttack(v)) - { - if (m_mmapNotAcceptableStartTime) - { - if (sWorld->GetGameTime() <= m_mmapNotAcceptableStartTime + 4) - return nullptr; - } - else - { - m_mmapNotAcceptableStartTime = sWorld->GetGameTime(); - return nullptr; - } - } - - // pussywizard: not sure why it's here - // pussywizard: if some npc (not player pet) is attacking us and we can't fight back - don't evade o_O + // last case when creature must not go to evade mode: + // it in combat but attacker not make any damage and not enter to aggro radius to have record in threat list + // Note: creature does not have targeted movement generator but has attacker in this case for (AttackerSet::const_iterator itr = m_attackers.begin(); itr != m_attackers.end(); ++itr) - if ((*itr) && !CanCreatureAttack(*itr) && (*itr)->GetTypeId() != TYPEID_PLAYER && !(*itr)->ToCreature()->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN)) + if ((*itr) && CanCreatureAttack(*itr) && (*itr)->GetTypeId() != TYPEID_PLAYER && !(*itr)->ToCreature()->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN)) return nullptr; if (GetVehicle()) @@ -15812,7 +15937,7 @@ bool Unit::IsStandState() const void Unit::SetStandState(uint8 state) { - SetByteValue(UNIT_FIELD_BYTES_1, 0, state); + SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, state); if (IsStandState()) RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_NOT_SEATED); @@ -17442,6 +17567,9 @@ bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* au CombatStop(); DeleteThreatList(); + if (Creature* creature = ToCreature()) + creature->RefreshSwimmingFlag(); + if (GetTypeId() == TYPEID_PLAYER) sScriptMgr->OnPlayerBeingCharmed(ToPlayer(), charmer, _oldFactionId, charmer->getFaction()); @@ -18634,7 +18762,7 @@ void Unit::_ExitVehicle(Position const* exitPosition) { float x = pos.GetPositionX() + 2.0f * cos(pos.GetOrientation() - M_PI / 2.0f); float y = pos.GetPositionY() + 2.0f * sin(pos.GetOrientation() - M_PI / 2.0f); - float z = GetMap()->GetHeight(GetPhaseMask(), x, y, pos.GetPositionZ()) + 0.1f; + float z = GetMapHeight(x, y, pos.GetPositionZ()); if (z > INVALID_HEIGHT) pos.Relocate(x, y, z); } @@ -18718,15 +18846,12 @@ void Unit::_ExitVehicle(Position const* exitPosition) void Unit::BuildMovementPacket(ByteBuffer* data) const { - if (GetTypeId() == TYPEID_UNIT && GetHoverHeight() >= 2.0f && !HasUnitMovementFlag(MOVEMENTFLAG_FALLING) && !movespline->isFalling()) // pussywizard: add disable gravity to hover (artifically, not to spoil speed and other things) - *data << uint32(GetUnitMovementFlags() | MOVEMENTFLAG_DISABLE_GRAVITY); // movement flags - else - *data << uint32(GetUnitMovementFlags()); // movement flags + *data << uint32(GetUnitMovementFlags()); // movement flags *data << uint16(GetExtraUnitMovementFlags()); // 2.3.0 *data << uint32(World::GetGameTimeMS()); // time / counter *data << GetPositionX(); *data << GetPositionY(); - *data << GetPositionZ() + GetHoverHeight(); + *data << GetPositionZ(); *data << GetOrientation(); // 0x00000200 @@ -18776,6 +18901,13 @@ bool Unit::IsFalling() const return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FALLING | MOVEMENTFLAG_FALLING_FAR) || movespline->isFalling(); } +/** + * @brief this method checks the current flag of a unit + * + * These flags can be set within the database or dynamically changed at runtime + * UNIT_FLAG_SWIMMING must be updated when a unit enters a swimmable area + * + */ bool Unit::CanSwim() const { // Mirror client behavior, if this method returns false then client will not use swimming animation and for players will apply gravity as if there was no water @@ -18787,7 +18919,7 @@ bool Unit::CanSwim() const return false; if (IsPet() && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT)) return true; - return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_RENAME | UNIT_FLAG_UNK_15); + return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_RENAME | UNIT_FLAG_SWIMMING); } void Unit::NearTeleportTo(float x, float y, float z, float orientation, bool casting /*= false*/, bool vehicleTeleport /*= false*/, bool withPet /*= false*/, bool removeTransport /*= false*/) @@ -19496,9 +19628,15 @@ bool Unit::SetSwim(bool enable) return false; if (enable) + { AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING); + SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SWIMMING); + } else + { RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING); + RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SWIMMING); + } return true; } @@ -19570,13 +19708,23 @@ bool Unit::SetHover(bool enable, bool /*packetOnly = false*/) if (enable == HasUnitMovementFlag(MOVEMENTFLAG_HOVER)) return false; + float hoverHeight = GetFloatValue(UNIT_FIELD_HOVERHEIGHT); + if (enable) { AddUnitMovementFlag(MOVEMENTFLAG_HOVER); + if (hoverHeight && GetPositionZ() - GetFloorZ() < hoverHeight) + UpdateHeight(GetPositionZ() + hoverHeight); } else { RemoveUnitMovementFlag(MOVEMENTFLAG_HOVER); + if (hoverHeight && (!isDying() || GetTypeId() != TYPEID_UNIT)) + { + float newZ = std::max(GetFloorZ(), GetPositionZ() - hoverHeight); + UpdateAllowedPositionZ(GetPositionX(), GetPositionY(), newZ); + UpdateHeight(newZ); + } SendMovementFlagUpdate(); // pussywizard: needed for falling after death (instead of falling onto air at hover height) } @@ -19835,3 +19983,78 @@ bool Unit::IsInCombatWith(Unit const* who) const // Nothing found, false. return false; } + +/** + * @brief this method gets the diameter of a Unit by DB if any value is defined, otherwise it gets the value by the DBC + * + * If the player is mounted the diameter also takes in consideration the mount size + * + * @return float The diameter of a unit + */ +float Unit::GetCollisionWidth() const +{ + if (GetTypeId() == TYPEID_PLAYER) + return GetObjectSize(); + + float scaleMod = GetObjectScale(); // 99% sure about this + float objectSize = GetObjectSize(); + float defaultSize = DEFAULT_WORLD_OBJECT_SIZE * scaleMod; + + //! Dismounting case - use basic default model data + CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.AssertEntry(GetNativeDisplayId()); + CreatureModelDataEntry const* modelData = sCreatureModelDataStore.AssertEntry(displayInfo->ModelId); + + if (IsMounted()) + { + if (CreatureDisplayInfoEntry const* mountDisplayInfo = sCreatureDisplayInfoStore.LookupEntry(GetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID))) + { + if (CreatureModelDataEntry const* mountModelData = sCreatureModelDataStore.LookupEntry(mountDisplayInfo->ModelId)) + { + if (G3D::fuzzyGt(mountModelData->CollisionWidth, modelData->CollisionWidth)) + modelData = mountModelData; + } + } + } + + float collisionWidth = scaleMod * modelData->CollisionWidth * modelData->Scale * displayInfo->scale * 2; + // if the objectSize is the default value or the creature is mounted and we have a DBC value, then we can retrieve DBC value instead + return G3D::fuzzyGt(collisionWidth, 0.0f) && (G3D::fuzzyEq(objectSize,defaultSize) || IsMounted()) ? collisionWidth : objectSize; +} + +/** + * @brief this method gets the radius of a Unit by DB if any value is defined, otherwise it gets the value by the DBC + * + * If the player is mounted the radius also takes in consideration the mount size + * + * @return float The radius of a unit + */ +float Unit::GetCollisionRadius() const +{ + return GetCollisionWidth() / 2; +} + +//! Return collision height sent to client +float Unit::GetCollisionHeight() const +{ + float scaleMod = GetObjectScale(); // 99% sure about this + float defaultHeight = DEFAULT_WORLD_OBJECT_SIZE * scaleMod; + + CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.AssertEntry(GetNativeDisplayId()); + CreatureModelDataEntry const* modelData = sCreatureModelDataStore.AssertEntry(displayInfo->ModelId); + float collisionHeight = 0.0f; + + if (IsMounted()) + { + if (CreatureDisplayInfoEntry const* mountDisplayInfo = sCreatureDisplayInfoStore.LookupEntry(GetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID))) + { + if (CreatureModelDataEntry const* mountModelData = sCreatureModelDataStore.LookupEntry(mountDisplayInfo->ModelId)) + { + collisionHeight = scaleMod * (mountModelData->MountHeight + modelData->CollisionHeight * modelData->Scale * displayInfo->scale * 0.5f); + } + } + } + else + collisionHeight = scaleMod * modelData->CollisionHeight * modelData->Scale * displayInfo->scale; + + return collisionHeight == 0.0f ? defaultHeight : collisionHeight; +} diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 78fd94a46..5d1eac46e 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -135,6 +135,14 @@ enum SpellFacingFlags #define BASE_MAXDAMAGE 2.0f #define BASE_ATTACK_TIME 2000 +enum UnitBytes1Offsets : uint8 +{ + UNIT_BYTES_1_OFFSET_STAND_STATE = 0, + UNIT_BYTES_1_OFFSET_PET_TALENTS = 1, + UNIT_BYTES_1_OFFSET_VIS_FLAG = 2, + UNIT_BYTES_1_OFFSET_ANIM_TIER = 3 +}; + // byte value (UNIT_FIELD_BYTES_1, 0) enum UnitStandStateType { @@ -164,9 +172,11 @@ enum UnitStandFlags // byte flags value (UNIT_FIELD_BYTES_1, 3) enum UnitBytes1_Flags { + UNIT_BYTE1_FLAG_GROUND = 0x00, UNIT_BYTE1_FLAG_ALWAYS_STAND = 0x01, UNIT_BYTE1_FLAG_HOVER = 0x02, - UNIT_BYTE1_FLAG_UNK_3 = 0x04, + UNIT_BYTE1_FLAG_FLY = 0x03, + UNIT_BYTE1_FLAG_SUBMERGED = 0x04, UNIT_BYTE1_FLAG_ALL = 0xFF }; @@ -586,8 +596,8 @@ enum UnitFlags UNIT_FLAG_PVP = 0x00001000, // changed in 3.0.3 UNIT_FLAG_SILENCED = 0x00002000, // silenced, 2.1.1 UNIT_FLAG_CANNOT_SWIM = 0x00004000, // 2.0.8 - UNIT_FLAG_UNK_15 = 0x00008000, - UNIT_FLAG_UNK_16 = 0x00010000, + UNIT_FLAG_SWIMMING = 0x00008000, // shows swim animation in water + UNIT_FLAG_NON_ATTACKABLE_2 = 0x00010000, // removes attackable icon, if on yourself, cannot assist self but can cast TARGET_SELF spells - added by SPELL_AURA_MOD_UNATTACKABLE UNIT_FLAG_PACIFIED = 0x00020000, // 3.0.3 ok UNIT_FLAG_STUNNED = 0x00040000, // 3.0.3 ok UNIT_FLAG_IN_COMBAT = 0x00080000, @@ -602,7 +612,7 @@ enum UnitFlags UNIT_FLAG_UNK_28 = 0x10000000, UNIT_FLAG_UNK_29 = 0x20000000, // used in Feing Death spell UNIT_FLAG_SHEATHE = 0x40000000, - UNIT_FLAG_UNK_31 = 0x80000000 + UNIT_FLAG_IMMUNE = 0x80000000, // Immune to damage }; // Value masks for UNIT_FIELD_FLAGS_2 @@ -1254,6 +1264,26 @@ private: GlobalCooldownMgr _GlobalCooldownMgr; }; +struct AttackPosition { + AttackPosition(Position pos) : _pos(pos), _taken(false) {} + bool operator==(const int val) + { + return !val; + }; + int operator=(const int val) + { + if (!val) + { + // _pos = NULL; + _taken = false; + return 0; // NULL + } + return 0; // NULL + }; + Position _pos; + bool _taken; +}; + // for clearing special attacks #define REACTIVE_TIMER_START 5000 @@ -1404,8 +1434,10 @@ public: virtual void SetCanDualWield(bool value) { m_canDualWield = value; } [[nodiscard]] float GetCombatReach() const override { return m_floatValues[UNIT_FIELD_COMBATREACH]; } [[nodiscard]] float GetMeleeReach() const { float reach = m_floatValues[UNIT_FIELD_COMBATREACH]; return reach > MIN_MELEE_REACH ? reach : MIN_MELEE_REACH; } + [[nodiscard]] bool IsWithinRange(Unit const* obj, float dist) const; bool IsWithinCombatRange(const Unit* obj, float dist2compare) const; - bool IsWithinMeleeRange(const Unit* obj, float dist = MELEE_RANGE) const; + bool IsWithinMeleeRange(const Unit* obj, float dist = 0.f) const; + float GetMeleeRange(Unit const* target) const; bool GetRandomContactPoint(const Unit* target, float& x, float& y, float& z, bool force = false) const; uint32 m_extraAttacks; bool m_canDualWield; @@ -1433,6 +1465,7 @@ public: bool AttackStop(); void RemoveAllAttackers(); [[nodiscard]] AttackerSet const& getAttackers() const { return m_attackers; } + [[nodiscard]] Position* GetMeleeAttackPoint(Unit* attacker); [[nodiscard]] bool isAttackingPlayer() const; [[nodiscard]] Unit* GetVictim() const { return m_attacking; } @@ -1570,8 +1603,8 @@ public: [[nodiscard]] bool IsStandState() const; void SetStandState(uint8 state); - void SetStandFlags(uint8 flags) { SetByteFlag(UNIT_FIELD_BYTES_1, 2, flags); } - void RemoveStandFlags(uint8 flags) { RemoveByteFlag(UNIT_FIELD_BYTES_1, 2, flags); } + void SetStandFlags(uint8 flags) { SetByteFlag(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_VIS_FLAG, flags); } + void RemoveStandFlags(uint8 flags) { RemoveByteFlag(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_VIS_FLAG, flags); } [[nodiscard]] bool IsMounted() const { return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_MOUNT); } [[nodiscard]] uint32 GetMountID() const { return GetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID); } @@ -1789,8 +1822,6 @@ public: //void SendMonsterMove(float NewPosX, float NewPosY, float NewPosZ, uint8 type, uint32 MovementFlags, uint32 Time, Player* player = nullptr); void SendMovementFlagUpdate(bool self = false); - [[nodiscard]] bool IsLevitating() const { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); } - [[nodiscard]] bool IsWalking() const { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_WALKING); } virtual bool SetWalk(bool enable); virtual bool SetDisableGravity(bool disable, bool packetOnly = false); virtual bool SetSwim(bool enable); @@ -2173,7 +2204,7 @@ public: uint32 GetDisplayId() { return GetUInt32Value(UNIT_FIELD_DISPLAYID); } virtual void SetDisplayId(uint32 modelId); - uint32 GetNativeDisplayId() { return GetUInt32Value(UNIT_FIELD_NATIVEDISPLAYID); } + [[nodiscard]] uint32 GetNativeDisplayId() const { return GetUInt32Value(UNIT_FIELD_NATIVEDISPLAYID); } void RestoreDisplayId(); void SetNativeDisplayId(uint32 modelId) { SetUInt32Value(UNIT_FIELD_NATIVEDISPLAYID, modelId); } void setTransForm(uint32 spellid) { m_transform = spellid;} @@ -2365,6 +2396,8 @@ public: void BuildMovementPacket(ByteBuffer* data) const; [[nodiscard]] virtual bool CanSwim() const; + [[nodiscard]] bool IsLevitating() const { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); } + [[nodiscard]] bool IsWalking() const { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_WALKING); } [[nodiscard]] bool isMoving() const { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_MASK_MOVING); } [[nodiscard]] bool isTurning() const { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_MASK_TURNING); } [[nodiscard]] bool IsHovering() const { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_HOVER); } @@ -2373,6 +2406,7 @@ public: [[nodiscard]] bool IsFlying() const { return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FLYING | MOVEMENTFLAG_DISABLE_GRAVITY); } [[nodiscard]] bool IsFalling() const; [[nodiscard]] float GetHoverHeight() const { return IsHovering() ? GetFloatValue(UNIT_FIELD_HOVERHEIGHT) : 0.0f; } + [[nodiscard]] virtual bool CanEnterWater() const = 0; void RewardRage(uint32 damage, uint32 weaponSpeedHitFactor, bool attacker); @@ -2387,11 +2421,6 @@ public: TempSummon* ToTempSummon() { if (IsSummon()) return reinterpret_cast(this); else return nullptr; } [[nodiscard]] const TempSummon* ToTempSummon() const { if (IsSummon()) return reinterpret_cast(this); else return nullptr; } - // pussywizard: - // MMaps - std::map m_targetsNotAcceptable; - bool isTargetNotAcceptableByMMaps(uint64 guid, uint32 currTime, const Position* t = nullptr) const { std::map::const_iterator itr = m_targetsNotAcceptable.find(guid); if (itr != m_targetsNotAcceptable.end() && (itr->second._endTime >= currTime || (t && !itr->second.PosChanged(*this, *t)))) return true; return false; } - uint32 m_mmapNotAcceptableStartTime; // Safe mover std::set SafeUnitPointerSet; void AddPointedBy(SafeUnitPointer* sup) { SafeUnitPointerSet.insert(sup); } @@ -2450,6 +2479,10 @@ public: // Movement info Movement::MoveSpline* movespline; + [[nodiscard]] float GetCollisionHeight() const override; + [[nodiscard]] float GetCollisionWidth() const override; + [[nodiscard]] float GetCollisionRadius() const override; + protected: explicit Unit (bool isWorldObject); @@ -2565,6 +2598,7 @@ private: HostileRefManager m_HostileRefManager; FollowerRefManager m_FollowingRefManager; + Unit* m_comboTarget; ComboPointHolderSet m_ComboPointHolders; diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 8e02cda7d..1cd7e0498 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -896,7 +896,7 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder* holder) data << pCurrChar->GetMapId(); data << pCurrChar->GetPositionX(); data << pCurrChar->GetPositionY(); - data << pCurrChar->GetPositionZ() + pCurrChar->GetHoverHeight(); + data << pCurrChar->GetPositionZ(); data << pCurrChar->GetOrientation(); SendPacket(&data); @@ -1214,7 +1214,7 @@ void WorldSession::HandlePlayerLoginToCharInWorld(Player* pCurrChar) data << pCurrChar->GetMapId(); data << pCurrChar->GetPositionX(); data << pCurrChar->GetPositionY(); - data << pCurrChar->GetPositionZ() + pCurrChar->GetHoverHeight(); + data << pCurrChar->GetPositionZ(); data << pCurrChar->GetOrientation(); SendPacket(&data); diff --git a/src/server/game/Handlers/MovementHandler.cpp b/src/server/game/Handlers/MovementHandler.cpp index efd5110fd..8633bdce1 100644 --- a/src/server/game/Handlers/MovementHandler.cpp +++ b/src/server/game/Handlers/MovementHandler.cpp @@ -85,7 +85,8 @@ void WorldSession::HandleMoveWorldportAckOpcode() return; } - GetPlayer()->Relocate(loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ(), loc.GetOrientation()); + float z = loc.GetPositionZ() + GetPlayer()->GetHoverHeight(); + GetPlayer()->Relocate(loc.GetPositionX(), loc.GetPositionY(), z, loc.GetOrientation()); GetPlayer()->ResetMap(); GetPlayer()->SetMap(newMap); diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp index 42c703f00..1bec21b7c 100644 --- a/src/server/game/Handlers/PetHandler.cpp +++ b/src/server/game/Handlers/PetHandler.cpp @@ -553,7 +553,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, uint64 guid1, uint16 spellid // pussywizard: if (Creature* creaturePet = pet->ToCreature()) - if (!creaturePet->_CanDetectFeignDeathOf(TargetUnit) || !creaturePet->CanCreatureAttack(TargetUnit) || creaturePet->isTargetNotAcceptableByMMaps(TargetUnit->GetGUID(), sWorld->GetGameTime(), TargetUnit)) + if (!creaturePet->_CanDetectFeignDeathOf(TargetUnit) || !creaturePet->CanCreatureAttack(TargetUnit)) return; // Not let attack through obstructions diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index e809695a5..4adb249c5 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -5,6 +5,7 @@ */ #include "Map.h" +#include "Geometry.h" #include "Battleground.h" #include "CellImpl.h" #include "DynamicTree.h" @@ -1908,19 +1909,27 @@ GridMap* Map::GetGrid(float x, float y) return GridMaps[gx][gy]; } -float Map::GetWaterOrGroundLevel(uint32 phasemask, float x, float y, float z, float* ground /*= NULL*/, bool /*swim = false*/, float maxSearchDist /*= 50.0f*/) const +float Map::GetWaterOrGroundLevel(uint32 phasemask, float x, float y, float z, float* ground /*= NULL*/, bool /*swim = false*/, float collisionHeight) const { if (const_cast(this)->GetGrid(x, y)) { // we need ground level (including grid height version) for proper return water level in point - float ground_z = GetHeight(phasemask, x, y, z, true, maxSearchDist); + float ground_z = GetHeight(phasemask, x, y, z + collisionHeight, true, 50.0f); if (ground) *ground = ground_z; LiquidData liquid_status; - ZLiquidStatus res = getLiquidStatus(x, y, ground_z, MAP_ALL_LIQUIDS, &liquid_status); - return res ? liquid_status.level : ground_z; + ZLiquidStatus res = getLiquidStatus(x, y, ground_z, MAP_ALL_LIQUIDS, &liquid_status, collisionHeight); + switch (res) + { + case LIQUID_MAP_ABOVE_WATER: + return std::max(liquid_status.level, ground_z); + case LIQUID_MAP_NO_WATER: + return ground_z; + default: + return liquid_status.level; + } } return VMAP_INVALID_HEIGHT_VALUE; @@ -1957,20 +1966,15 @@ float Map::GetHeight(float x, float y, float z, bool checkVMap /*= true*/, float { // find raw .map surface under Z coordinates float mapHeight = VMAP_INVALID_HEIGHT_VALUE; - if (GridMap* gmap = const_cast(this)->GetGrid(x, y)) - { - float gridHeight = gmap->getHeight(x, y); - // look from a bit higher pos to find the floor, ignore under surface case - if (z + 2.0f > gridHeight) - mapHeight = gridHeight; - } + float gridHeight = GetGridHeight(x, y); + if (G3D::fuzzyGe(z, gridHeight - GROUND_HEIGHT_TOLERANCE)) + mapHeight = gridHeight; float vmapHeight = VMAP_INVALID_HEIGHT_VALUE; if (checkVMap) { VMAP::IVMapManager* vmgr = VMAP::VMapFactory::createOrGetVMapManager(); - //if (vmgr->isHeightCalcEnabled()) // pussywizard: optimization - vmapHeight = vmgr->getHeight(GetId(), x, y, z + 2.0f, maxSearchDist); // look from a bit higher pos to find the floor + vmapHeight = vmgr->getHeight(GetId(), x, y, z, maxSearchDist); // look from a bit higher pos to find the floor } // mapHeight set for any above raw ground Z or <= INVALID_HEIGHT @@ -1995,6 +1999,14 @@ float Map::GetHeight(float x, float y, float z, bool checkVMap /*= true*/, float return mapHeight; // explicitly use map data } +float Map::GetGridHeight(float x, float y) const +{ + if (GridMap* gmap = const_cast(this)->GetGrid(x, y)) + return gmap->getHeight(x, y); + + return VMAP_INVALID_HEIGHT_VALUE; +} + float Map::GetMinHeight(float x, float y) const { if (GridMap const* grid = const_cast(this)->GetGrid(x, y)) @@ -2059,7 +2071,7 @@ bool Map::GetAreaInfo(float x, float y, float z, uint32& flags, int32& adtId, in { float _mapheight = gmap->getHeight(x, y); // z + 2.0f condition taken from GetHeight(), not sure if it's such a great choice... - if (z + 2.0f > _mapheight && _mapheight > vmap_z) + if (z + 2.0f > _mapheight && _mapheight > vmap_z) return false; } return true; @@ -2137,7 +2149,7 @@ uint8 Map::GetTerrainType(float x, float y) const return 0; } -ZLiquidStatus Map::getLiquidStatus(float x, float y, float z, uint8 ReqLiquidType, LiquidData* data) const +ZLiquidStatus Map::getLiquidStatus(float x, float y, float z, uint8 ReqLiquidType, LiquidData* data, float collisionHeight) const { ZLiquidStatus result = LIQUID_MAP_NO_WATER; VMAP::IVMapManager* vmgr = VMAP::VMapFactory::createOrGetVMapManager(); @@ -2193,7 +2205,7 @@ ZLiquidStatus Map::getLiquidStatus(float x, float y, float z, uint8 ReqLiquidTyp float delta = liquid_level - z; // Get position delta - if (delta > 2.0f) // Under water + if (delta > collisionHeight) // Under water return LIQUID_MAP_UNDER_WATER; if (delta > 0.0f) // In water return LIQUID_MAP_IN_WATER; @@ -2257,7 +2269,7 @@ bool Map::getObjectHitPos(uint32 phasemask, float x1, float y1, float z1, float return result; } -float Map::GetHeight(uint32 phasemask, float x, float y, float z, bool vmap/*=true*/, float maxSearchDist/*=DEFAULT_HEIGHT_SEARCH*/) const +float Map::GetHeight(uint32 phasemask, float x, float y, float z, bool vmap/*=true*/, float maxSearchDist /*= DEFAULT_HEIGHT_SEARCH*/) const { float h1, h2; h1 = GetHeight(x, y, z, vmap, maxSearchDist); @@ -2277,6 +2289,21 @@ bool Map::IsUnderWater(float x, float y, float z) const return getLiquidStatus(x, y, z, MAP_LIQUID_TYPE_WATER | MAP_LIQUID_TYPE_OCEAN) & LIQUID_MAP_UNDER_WATER; } +bool Map::HasEnoughWater(WorldObject const* searcher, float x, float y, float z) const +{ + LiquidData liquidData; + liquidData.level = INVALID_HEIGHT; + ZLiquidStatus liquidStatus = getLiquidStatus(x, y, z, MAP_ALL_LIQUIDS, &liquidData); + return (liquidStatus & (LIQUID_MAP_IN_WATER | LIQUID_MAP_UNDER_WATER)) && HasEnoughWater(searcher, liquidData); +} + +bool Map::HasEnoughWater(WorldObject const* searcher, LiquidData liquidData) const +{ + float minHeightInWater = searcher->GetMinHeightInWater(); + return liquidData.level > INVALID_HEIGHT && liquidData.level > liquidData.depth_level && liquidData.level - liquidData.depth_level >= minHeightInWater; +} + + char const* Map::GetMapName() const { return i_mapEntry ? i_mapEntry->name[sWorld->GetDefaultDbcLocale()] : "UNNAMEDMAP\x0"; @@ -3423,3 +3450,188 @@ void Map::SetZoneOverrideLight(uint32 zoneId, uint32 lightId, uint32 fadeInTime) player->SendDirectMessage(&data); } } + +/** + * @brief Check if a given source can reach a specific point following a path + * and normalize the coords. Use this method for long paths, otherwise use the + * overloaded method with the start coords when you need to do a quick check on small segments + * + */ +bool Map::CanReachPositionAndGetValidCoords(const WorldObject* source, PathGenerator *path, float &destX, float &destY, float &destZ, bool failOnCollision, bool failOnSlopes) const +{ + G3D::Vector3 prevPath = path->GetStartPosition(); + for (auto & vector : path->GetPath()) + { + float x = vector.x; + float y = vector.y; + float z = vector.z; + + if (!CanReachPositionAndGetValidCoords(source, prevPath.x, prevPath.y, prevPath.z, x, y, z, failOnCollision, failOnSlopes)) + { + destX = x; + destY = y; + destZ = z; + return false; + } + + prevPath = vector; + } + + destX = prevPath.x; + destY = prevPath.y; + destZ = prevPath.z; + + return true; +} + +bool Map::CanReachPositionAndGetValidCoords(const WorldObject* source, float &destX, float &destY, float &destZ, bool failOnCollision, bool failOnSlopes) const +{ + return CanReachPositionAndGetValidCoords(source, source->GetPositionX(), source->GetPositionY(), source->GetPositionZ(), destX, destY, destZ, failOnCollision, failOnSlopes); +} + +/** + * @brief validate the new destination and set reachable coords + * Check if a given unit can reach a specific point on a segment + * and set the correct dest coords + * NOTE: use this method with small segments. + * + * @param failOnCollision if true, the methods will return false when a collision occurs + * @param failOnSlopes if true, the methods will return false when a non walkable slope is found + * + * @return true if the destination is valid, false otherwise + * + **/ +bool Map::CanReachPositionAndGetValidCoords(const WorldObject* source, float startX, float startY, float startZ, float &destX, float &destY, float &destZ, bool failOnCollision, bool failOnSlopes) const +{ + float tempX=destX, tempY=destY, tempZ=destZ; + if (!CheckCollisionAndGetValidCoords(source, startX, startY, startZ, destX, destY, destZ, failOnCollision)) + { + return false; + } + + destX = tempX, destY = tempY, destZ = tempZ; + + const Unit* unit = source->ToUnit(); + // if it's not an unit (Object) then we do not have to continue + // with walkable checks + if (!unit) + { + return true; + } + + /* + * Walkable checks + */ + bool isWaterNext = HasEnoughWater(unit, destX, destY, destZ); + const Creature* creature = unit->ToCreature(); + bool cannotEnterWater = isWaterNext && (creature && !creature->CanEnterWater()); + bool cannotWalkOrFly = !isWaterNext && !source->ToPlayer() && !unit->CanFly() && (creature && !creature->CanWalk()); + if (cannotEnterWater || cannotWalkOrFly || + (failOnSlopes && !PathGenerator::IsWalkableClimb(startX, startY, startZ, destX, destY, destZ, source->GetCollisionHeight()))) + { + return false; + } + + return true; +} + +/** + * @brief validate the new destination and set coords + * Check if a given unit can face collisions in a specific segment + * + * @return true if the destination is valid, false otherwise + * + **/ +bool Map::CheckCollisionAndGetValidCoords(const WorldObject* source, float startX, float startY, float startZ, float &destX, float &destY, float &destZ, bool failOnCollision) const +{ + // Prevent invalid coordinates here, position is unchanged + if (!acore::IsValidMapCoord(startX, startY, startZ) || !acore::IsValidMapCoord(destX, destY, destZ)) + { + sLog->outCrash("Map::CheckCollisionAndGetValidCoords invalid coordinates startX: %f, startY: %f, startZ: %f, destX: %f, destY: %f, destZ: %f", startX, startY, startZ, destX, destY, destZ); + return false; + } + + bool isWaterNext = IsInWater(destX, destY, destZ); + + PathGenerator* path = new PathGenerator(source); + + // Use a detour raycast to get our first collision point + path->SetUseRaycast(true); + bool result = path->CalculatePath(startX, startY, startZ, destX, destY, destZ, false); + + const Unit* unit = source->ToUnit(); + bool notOnGround = path->GetPathType() & PATHFIND_NOT_USING_PATH + || isWaterNext || (unit && unit->IsFlying()); + + // Check for valid path types before we proceed + if (!result || (!notOnGround && path->GetPathType() & ~(PATHFIND_NORMAL | PATHFIND_SHORTCUT | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY_END))) + { + return false; + } + + G3D::Vector3 endPos = path->GetPath().back(); + destX = endPos.x; + destY = endPos.y; + destZ = endPos.z; + + // collision check + bool collided = false; + + float angle = getAngle(destX, destY, startX, startY); + + // check static LOS + float halfHeight = source->GetCollisionHeight() * 0.5f; + + // Unit is not on the ground, check for potential collision via vmaps + if (notOnGround) + { + bool col = VMAP::VMapFactory::createOrGetVMapManager()->getObjectHitPos(source->GetMapId(), + startX, startY, startZ + halfHeight, + destX, destY, destZ + halfHeight, + destX, destY, destZ, -0.5f); + + destZ -= halfHeight; + + // Collided with static LOS object, move back to collision point + if (col) + { + destX -= CONTACT_DISTANCE * std::cos(angle); + destY -= CONTACT_DISTANCE * std::sin(angle); + collided = true; + } + } + + // check dynamic collision + bool col = source->GetMap()->getObjectHitPos(source->GetPhaseMask(), + startX, startY, startZ + halfHeight, + destX, destY, destZ + halfHeight, + destX, destY, destZ, -0.5f); + + destZ -= halfHeight; + + // Collided with a gameobject, move back to collision point + if (col) + { + destX -= CONTACT_DISTANCE * std::cos(angle); + destY -= CONTACT_DISTANCE * std::sin(angle); + collided = true; + } + + float groundZ = VMAP_INVALID_HEIGHT_VALUE; + source->UpdateAllowedPositionZ(destX, destY, destZ, &groundZ); + + // position has no ground under it (or is too far away) + if (groundZ <= INVALID_HEIGHT && unit && unit->CanFly()) + { + // fall back to gridHeight if any + float gridHeight = GetGridHeight(destX, destY); + if (gridHeight > INVALID_HEIGHT) + { + destZ = gridHeight + unit->GetHoverHeight(); + } else { + return false; + } + } + + return failOnCollision ? !collided : true; +} diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h index 2b506dbf2..b07c29ac5 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -25,6 +25,8 @@ #include "MapRefManager.h" #include "DynamicTree.h" #include "GameObjectModel.h" +#include "PathGenerator.h" +#include "ObjectDefines.h" #include "Log.h" #include "DataMap.h" #include @@ -50,6 +52,7 @@ class BattlegroundMap; class Transport; class StaticTransport; class MotionTransport; +class PathGenerator; namespace acore { struct ObjectUpdater; @@ -254,7 +257,7 @@ struct ZoneDynamicInfo #define MAX_HEIGHT 100000.0f // can be use for find ground height at surface #define INVALID_HEIGHT -100000.0f // for check, must be equal to VMAP_INVALID_HEIGHT, real value for unknown height is VMAP_INVALID_HEIGHT_VALUE #define MAX_FALL_DISTANCE 250000.0f // "unlimited fall" to find VMap ground if it is available, just larger than MAX_HEIGHT - INVALID_HEIGHT -#define DEFAULT_HEIGHT_SEARCH 70.0f // default search distance to find height at nearby locations +#define DEFAULT_HEIGHT_SEARCH 50.0f // default search distance to find height at nearby locations #define MIN_UNLOAD_DELAY 1 // immediate unload typedef std::map CreatureGroupHolderType; @@ -352,10 +355,11 @@ public: // some calls like isInWater should not use vmaps due to processor power // can return INVALID_HEIGHT if under z+2 z coord not found height [[nodiscard]] float GetHeight(float x, float y, float z, bool checkVMap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const; + [[nodiscard]] float GetGridHeight(float x, float y) const; [[nodiscard]] float GetMinHeight(float x, float y) const; Transport* GetTransportForPos(uint32 phase, float x, float y, float z, WorldObject* worldobject = nullptr); - ZLiquidStatus getLiquidStatus(float x, float y, float z, uint8 ReqLiquidType, LiquidData* data = nullptr) const; + ZLiquidStatus getLiquidStatus(float x, float y, float z, uint8 ReqLiquidType, LiquidData* data = nullptr, float collisionHeight = DEFAULT_COLLISION_HEIGHT) const; uint32 GetAreaId(float x, float y, float z, bool* isOutdoors) const; bool GetAreaInfo(float x, float y, float z, uint32& mogpflags, int32& adtId, int32& rootId, int32& groupId) const; @@ -369,6 +373,8 @@ public: [[nodiscard]] float GetWaterLevel(float x, float y) const; bool IsInWater(float x, float y, float z, LiquidData* data = nullptr) const; [[nodiscard]] bool IsUnderWater(float x, float y, float z) const; + [[nodiscard]] bool HasEnoughWater(WorldObject const* searcher, float x, float y, float z) const; + [[nodiscard]] bool HasEnoughWater(WorldObject const* searcher, LiquidData liquidData) const; void MoveAllCreaturesInMoveList(); void MoveAllGameObjectsInMoveList(); @@ -469,16 +475,23 @@ public: BattlegroundMap* ToBattlegroundMap() { if (IsBattlegroundOrArena()) return reinterpret_cast(this); else return nullptr; } [[nodiscard]] const BattlegroundMap* ToBattlegroundMap() const { if (IsBattlegroundOrArena()) return reinterpret_cast(this); return nullptr; } - float GetWaterOrGroundLevel(uint32 phasemask, float x, float y, float z, float* ground = nullptr, bool swim = false, float maxSearchDist = 50.0f) const; + float GetWaterOrGroundLevel(uint32 phasemask, float x, float y, float z, float* ground = nullptr, bool swim = false, float collisionHeight = DEFAULT_COLLISION_HEIGHT) const; [[nodiscard]] float GetHeight(uint32 phasemask, float x, float y, float z, bool vmap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const; [[nodiscard]] bool isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, uint32 phasemask, LineOfSightChecks checks) const; + bool CanReachPositionAndGetValidCoords(const WorldObject* source, PathGenerator *path, float &destX, float &destY, float &destZ, bool failOnCollision = true, bool failOnSlopes = true) const; + bool CanReachPositionAndGetValidCoords(const WorldObject* source, float &destX, float &destY, float &destZ, bool failOnCollision = true, bool failOnSlopes = true) const; + bool CanReachPositionAndGetValidCoords(const WorldObject* source, float startX, float startY, float startZ, float &destX, float &destY, float &destZ, bool failOnCollision = true, bool failOnSlopes = true) const; + bool CheckCollisionAndGetValidCoords(const WorldObject* source, float startX, float startY, float startZ, float &destX, float &destY, float &destZ, bool failOnCollision = true) const; void Balance() { _dynamicTree.balance(); } void RemoveGameObjectModel(const GameObjectModel& model) { _dynamicTree.remove(model); } void InsertGameObjectModel(const GameObjectModel& model) { _dynamicTree.insert(model); } [[nodiscard]] bool ContainsGameObjectModel(const GameObjectModel& model) const { return _dynamicTree.contains(model);} [[nodiscard]] DynamicMapTree const& GetDynamicMapTree() const { return _dynamicTree; } bool getObjectHitPos(uint32 phasemask, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist); - + [[nodiscard]] float GetGameObjectFloor(uint32 phasemask, float x, float y, float z, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const + { + return _dynamicTree.getHeight(x, y, z, maxSearchDist, phasemask); + } /* RESPAWN TIMES */ diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index f18b474a4..72296d094 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -11,6 +11,9 @@ #include "DetourNavMesh.h" #include +float const GROUND_HEIGHT_TOLERANCE = 0.05f; // Extra tolerance to z position to check if it is in air or on ground. +float const WATER_HEIGHT_TOLERANCE = 0.5f; // Extra tolerance to z position to check if it is in water + enum SpellEffIndex { EFFECT_0 = 0, diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp index 70aec996d..3a26851a8 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -21,6 +21,34 @@ #include "MoveSplineInit.h" #include + + // ---- ChaseRange ---- // + +ChaseRange::ChaseRange(float range) : MinRange(range > CONTACT_DISTANCE ? 0 : range - CONTACT_DISTANCE), MinTolerance(range), MaxRange(range + CONTACT_DISTANCE), MaxTolerance(range) { } +ChaseRange::ChaseRange(float _minRange, float _maxRange) : MinRange(_minRange), MinTolerance(std::min(_minRange + CONTACT_DISTANCE, (_minRange + _maxRange) / 2)), MaxRange(_maxRange), MaxTolerance(std::max(_maxRange - CONTACT_DISTANCE, MinTolerance)) { } +ChaseRange::ChaseRange(float _minRange, float _minTolerance, float _maxTolerance, float _maxRange) : MinRange(_minRange), MinTolerance(_minTolerance), MaxRange(_maxRange), MaxTolerance(_maxTolerance) { } + +// ---- ChaseAngle ---- // + +ChaseAngle::ChaseAngle(float angle, float _tolerance/* = M_PI_4*/) : RelativeAngle(Position::NormalizeOrientation(angle)), Tolerance(_tolerance) { } + +float ChaseAngle::UpperBound() const +{ + return Position::NormalizeOrientation(RelativeAngle + Tolerance); +} + +float ChaseAngle::LowerBound() const +{ + return Position::NormalizeOrientation(RelativeAngle - Tolerance); +} + +bool ChaseAngle::IsAngleOkay(float relativeAngle) const +{ + float const diff = std::abs(relativeAngle - RelativeAngle); + + return (std::min(diff, float(2 * M_PI) - diff) <= Tolerance); +} + inline bool isStatic(MovementGenerator* mv) { return (mv == &si_idleMovement); @@ -290,7 +318,7 @@ void MotionMaster::MoveConfused() } } -void MotionMaster::MoveChase(Unit* target, float dist, float angle) +void MotionMaster::MoveChase(Unit* target, std::optional dist, std::optional angle) { // Xinef: do not allow to move with UNIT_FLAG_DISABLE_MOVE // ignore movement request if target not exist @@ -320,12 +348,60 @@ void MotionMaster::MoveChase(Unit* target, float dist, float angle) } } +void MotionMaster::MoveBackwards(Unit* target, float dist) +{ + if (!target) + { + return; + } + + Position const& pos = target->GetPosition(); + float angle = target->GetAngle(_owner); + G3D::Vector3 point; + point.x = pos.m_positionX + dist * cosf(angle); + point.y = pos.m_positionY + dist * sinf(angle); + point.z = pos.m_positionZ; + + if (!_owner->GetMap()->CanReachPositionAndGetValidCoords(_owner, point.x, point.y, point.z, true, true)) + { + return; + } + + Movement::MoveSplineInit init(_owner); + init.MoveTo(point.x, point.y, point.z, false); + init.SetFacing(target); + init.SetOrientationInversed(); + init.Launch(); +} + +void MotionMaster::MoveCircleTarget(Unit* target) +{ + if (!target) + { + return; + } + + Position* point = target->GetMeleeAttackPoint(_owner); + if (point == nullptr) + { + return; + } + + Movement::MoveSplineInit init(_owner); + init.MoveTo(point->m_positionX, point->m_positionY, point->m_positionZ, false); + init.SetWalk(true); + init.SetFacing(target); + init.Launch(); +} + void MotionMaster::MoveFollow(Unit* target, float dist, float angle, MovementSlot slot) { // Xinef: do not allow to move with UNIT_FLAG_DISABLE_MOVE // ignore movement request if target not exist if (!target || target == _owner || _owner->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISABLE_MOVE)) + { return; + } //_owner->AddUnitState(UNIT_STATE_FOLLOW); if (_owner->GetTypeId() == TYPEID_PLAYER) @@ -451,22 +527,16 @@ void MotionMaster::MoveKnockbackFrom(float srcX, float srcY, float speedXY, floa if (speedXY <= 0.1f) return; + Position dest = _owner->GetPosition(); float moveTimeHalf = speedZ / Movement::gravity; float dist = 2 * moveTimeHalf * speedXY; float max_height = -Movement::computeFallElevation(moveTimeHalf, false, -speedZ); - Position pos; - _owner->GetNearPoint(_owner, pos.m_positionX, pos.m_positionY, pos.m_positionZ, _owner->GetObjectSize(), dist, _owner->GetAngle(srcX, srcY) + M_PI); - - // xinef: check LoS! - if (!_owner->IsWithinLOS(pos.m_positionX, pos.m_positionY, pos.m_positionZ)) - { - _owner->GetPosition(&pos); - _owner->MovePositionToFirstCollision(pos, dist, _owner->GetAngle(srcX, srcY) + M_PI); - } + // Use a mmap raycast to get a valid destination. + _owner->MovePositionToFirstCollision(dest, dist, _owner->GetRelativeAngle(srcX, srcY) + float(M_PI)); Movement::MoveSplineInit init(_owner); - init.MoveTo(pos.m_positionX, pos.m_positionY, pos.m_positionZ); + init.MoveTo(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ()); init.SetParabolic(max_height, 0); init.SetOrientationFixed(true); init.SetVelocity(speedXY); @@ -517,12 +587,12 @@ void MotionMaster::MoveFall(uint32 id /*=0*/, bool addFlagForNPC) return; // use larger distance for vmap height search than in most other cases - float tz = _owner->GetMap()->GetHeight(_owner->GetPhaseMask(), _owner->GetPositionX(), _owner->GetPositionY(), _owner->GetPositionZ(), true, MAX_FALL_DISTANCE); + float tz = _owner->GetMapHeight(_owner->GetPositionX(), _owner->GetPositionY(), _owner->GetPositionZ(), true, MAX_FALL_DISTANCE); if (tz <= INVALID_HEIGHT) { #if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS) sLog->outStaticDebug("MotionMaster::MoveFall: unable retrive a proper height at map %u (x: %f, y: %f, z: %f).", - _owner->GetMap()->GetId(), _owner->GetPositionX(), _owner->GetPositionX(), _owner->GetPositionZ()); + _owner->GetMap()->GetId(), _owner->GetPositionX(), _owner->GetPositionX(), _owner->GetPositionZ() + _owner->GetPositionZ()); #endif return; } @@ -547,7 +617,7 @@ void MotionMaster::MoveFall(uint32 id /*=0*/, bool addFlagForNPC) } Movement::MoveSplineInit init(_owner); - init.MoveTo(_owner->GetPositionX(), _owner->GetPositionY(), tz); + init.MoveTo(_owner->GetPositionX(), _owner->GetPositionY(), tz + _owner->GetHoverHeight()); init.SetFall(); init.Launch(); Mutate(new EffectMovementGenerator(id), MOTION_SLOT_CONTROLLED); diff --git a/src/server/game/Movement/MotionMaster.h b/src/server/game/Movement/MotionMaster.h index c68ccf589..f006c26ee 100644 --- a/src/server/game/Movement/MotionMaster.h +++ b/src/server/game/Movement/MotionMaster.h @@ -12,6 +12,7 @@ #include "SharedDefines.h" #include "Object.h" #include "Spline/MoveSpline.h" +#include class MovementGenerator; class Unit; @@ -66,6 +67,31 @@ enum RotateDirection ROTATE_DIRECTION_RIGHT }; +struct ChaseRange +{ + ChaseRange(float range); + ChaseRange(float _minRange, float _maxRange); + ChaseRange(float _minRange, float _minTolerance, float _maxTolerance, float _maxRange); + + // this contains info that informs how we should path! + float MinRange; // we have to move if we are within this range... (min. attack range) + float MinTolerance; // ...and if we are, we will move this far away + float MaxRange; // we have to move if we are outside this range... (max. attack range) + float MaxTolerance; // ...and if we are, we will move into this range +}; + +struct ChaseAngle +{ + ChaseAngle(float angle, float _tolerance = M_PI_4); + + float RelativeAngle; // we want to be at this angle relative to the target (0 = front, M_PI = back) + float Tolerance; // but we'll tolerate anything within +- this much + + float UpperBound() const; + float LowerBound() const; + bool IsAngleOkay(float relativeAngle) const; +}; + // assume it is 25 yard per 0.6 second #define SPEED_CHARGE 42.0f @@ -163,7 +189,11 @@ public: void MoveTargetedHome(); void MoveRandom(float wanderDistance = 0.0f); void MoveFollow(Unit* target, float dist, float angle, MovementSlot slot = MOTION_SLOT_ACTIVE); - void MoveChase(Unit* target, float dist = 0.0f, float angle = 0.0f); + void MoveChase(Unit* target, std::optional dist = {}, std::optional angle = {}); + void MoveChase(Unit* target, float dist, float angle) { MoveChase(target, ChaseRange(dist), ChaseAngle(angle)); } + void MoveChase(Unit* target, float dist) { MoveChase(target, ChaseRange(dist)); } + void MoveCircleTarget(Unit* target); + void MoveBackwards(Unit* target, float dist); void MoveConfused(); void MoveFleeing(Unit* enemy, uint32 time = 0); void MovePoint(uint32 id, const Position& pos, bool generatePath = true, bool forceDestination = true) diff --git a/src/server/game/Movement/MovementGenerators/ConfusedMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/ConfusedMovementGenerator.cpp index 79f0e93c3..5fea29249 100644 --- a/src/server/game/Movement/MovementGenerators/ConfusedMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/ConfusedMovementGenerator.cpp @@ -90,7 +90,7 @@ void ConfusedMovementGenerator::DoInitialize(T* unit) template<> void ConfusedMovementGenerator::_InitSpecific(Creature* creature, bool& is_water_ok, bool& is_land_ok) { - is_water_ok = creature->CanSwim(); + is_water_ok = creature->CanEnterWater(); is_land_ok = creature->CanWalk(); } diff --git a/src/server/game/Movement/MovementGenerators/EscortMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/EscortMovementGenerator.cpp index 9fc2b9d40..3da7fe555 100644 --- a/src/server/game/Movement/MovementGenerators/EscortMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/EscortMovementGenerator.cpp @@ -22,7 +22,7 @@ void EscortMovementGenerator::DoInitialize(T* unit) Movement::MoveSplineInit init(unit); if (m_precomputedPath.size() == 2) // xinef: simple case, just call move to - init.MoveTo(m_precomputedPath[1].x, m_precomputedPath[1].y, m_precomputedPath[1].z); + init.MoveTo(m_precomputedPath[1].x, m_precomputedPath[1].y, m_precomputedPath[1].z, true); else if (m_precomputedPath.size()) init.MovebyPath(m_precomputedPath); @@ -66,7 +66,7 @@ bool EscortMovementGenerator::DoUpdate(T* unit, uint32 /*diff*/) if (m_precomputedPath.size() > 2) init.MovebyPath(m_precomputedPath); else if (m_precomputedPath.size() == 2) - init.MoveTo(m_precomputedPath[1].x, m_precomputedPath[1].y, m_precomputedPath[1].z); + init.MoveTo(m_precomputedPath[1].x, m_precomputedPath[1].y, m_precomputedPath[1].z, true); } init.Launch(); diff --git a/src/server/game/Movement/MovementGenerators/FleeingMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/FleeingMovementGenerator.cpp index d623dc03c..91128005f 100644 --- a/src/server/game/Movement/MovementGenerators/FleeingMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/FleeingMovementGenerator.cpp @@ -30,18 +30,7 @@ void FleeingMovementGenerator::_setTargetLocation(T* owner) return; float x, y, z; - if (!_getPoint(owner, x, y, z)) - return; - - // Add LOS check for target point - bool isInLOS = VMAP::VMapFactory::createOrGetVMapManager()->isInLineOfSight(owner->GetMapId(), - owner->GetPositionX(), - owner->GetPositionY(), - owner->GetPositionZ() + 2.0f, - x, y, z + 2.0f); - - if (!isInLOS) - { + if (!_getPoint(owner, x, y, z)) { i_nextCheckTime.Reset(100); return; } @@ -60,12 +49,13 @@ bool FleeingMovementGenerator::_getPoint(T* owner, float& x, float& y, float& if (!owner) return false; + const Map* _map = owner->GetBaseMap(); + x = owner->GetPositionX(); y = owner->GetPositionY(); z = owner->GetPositionZ(); float temp_x, temp_y, angle; - const Map* _map = owner->GetBaseMap(); // primitive path-finding for (uint8 i = 0; i < 18; ++i) { @@ -151,40 +141,25 @@ bool FleeingMovementGenerator::_getPoint(T* owner, float& x, float& y, float& temp_x = x + distance * cos(angle); temp_y = y + distance * sin(angle); - acore::NormalizeMapCoord(temp_x); - acore::NormalizeMapCoord(temp_y); - if (owner->IsWithinLOS(temp_x, temp_y, z)) - { - bool is_water_now = _map->IsInWater(x, y, z); + float temp_z = z; - if (is_water_now && _map->IsInWater(temp_x, temp_y, z)) + if (!_map->CanReachPositionAndGetValidCoords(owner, temp_x, temp_y, temp_z, true, true)) + { + break; + } + + if (!(temp_z - z) || distance / fabs(temp_z - z) > 1.0f) + { + float temp_z_left = _map->GetHeight(owner->GetPhaseMask(), temp_x + 1.0f * cos(angle + static_cast(M_PI / 2)), temp_y + 1.0f * sin(angle + static_cast(M_PI / 2)), z, true); + float temp_z_right = _map->GetHeight(owner->GetPhaseMask(), temp_x + 1.0f * cos(angle - static_cast(M_PI / 2)), temp_y + 1.0f * sin(angle - static_cast(M_PI / 2)), z, true); + if (fabs(temp_z_left - temp_z) < 1.2f && fabs(temp_z_right - temp_z) < 1.2f) { + // use new values x = temp_x; y = temp_y; + z = temp_z; return true; } - float new_z = _map->GetHeight(owner->GetPhaseMask(), temp_x, temp_y, z, true); - - if (new_z <= INVALID_HEIGHT || fabs(z - new_z) > 3.0f) - continue; - - bool is_water_next = _map->IsInWater(temp_x, temp_y, new_z); - - if ((is_water_now && !is_water_next && !is_land_ok) || (!is_water_now && is_water_next && !is_water_ok)) - continue; - - if (!(new_z - z) || distance / fabs(new_z - z) > 1.0f) - { - float new_z_left = _map->GetHeight(owner->GetPhaseMask(), temp_x + 1.0f * cos(angle + static_cast(M_PI / 2)), temp_y + 1.0f * sin(angle + static_cast(M_PI / 2)), z, true); - float new_z_right = _map->GetHeight(owner->GetPhaseMask(), temp_x + 1.0f * cos(angle - static_cast(M_PI / 2)), temp_y + 1.0f * sin(angle - static_cast(M_PI / 2)), z, true); - if (fabs(new_z_left - new_z) < 1.2f && fabs(new_z_right - new_z) < 1.2f) - { - x = temp_x; - y = temp_y; - z = new_z; - return true; - } - } } } i_to_distance_from_caster = 0.0f; @@ -322,7 +297,7 @@ void FleeingMovementGenerator::_Init(Creature* owner) return; //owner->SetTargetGuid(ObjectGuid()); - is_water_ok = owner->CanSwim(); + is_water_ok = owner->CanEnterWater(); is_land_ok = owner->CanWalk(); } diff --git a/src/server/game/Movement/MovementGenerators/HomeMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/HomeMovementGenerator.cpp index e239e55cd..7adfb5a7f 100644 --- a/src/server/game/Movement/MovementGenerators/HomeMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/HomeMovementGenerator.cpp @@ -26,7 +26,10 @@ void HomeMovementGenerator::DoFinalize(Creature* owner) owner->LoadCreaturesAddon(true); owner->AI()->JustReachedHome(); } - owner->m_targetsNotAcceptable.clear(); + + if (!owner->HasSwimmingFlagOutOfCombat()) + owner->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SWIMMING); + owner->UpdateEnvironmentIfNeeded(2); } @@ -51,6 +54,7 @@ void HomeMovementGenerator::_setTargetLocation(Creature* owner) init.SetFacing(o); } + owner->UpdateAllowedPositionZ(x, y, z); init.MoveTo(x, y, z, MMAP::MMapFactory::IsPathfindingEnabled(owner->FindMap()), true); init.SetWalk(false); init.Launch(); diff --git a/src/server/game/Movement/MovementGenerators/PathGenerator.cpp b/src/server/game/Movement/MovementGenerators/PathGenerator.cpp index 918a66448..24dbe138d 100644 --- a/src/server/game/Movement/MovementGenerators/PathGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/PathGenerator.cpp @@ -1,47 +1,35 @@ /* * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-GPL2 - * 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, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Copyright (C) 2008-2016 TrinityCore + * Copyright (C) 2005-2009 MaNGOS */ #include "PathGenerator.h" #include "Map.h" #include "Creature.h" +#include "MMapFactory.h" +#include "MMapManager.h" #include "Log.h" -#include "CellImpl.h" -#include "Cell.h" - +#include "DisableMgr.h" #include "DetourCommon.h" -#include "DetourNavMeshQuery.h" +#include "DetourExtended.h" +#include "Geometry.h" ////////////////// PathGenerator ////////////////// -PathGenerator::PathGenerator(const Unit* owner) : - _polyLength(0), _type(PATHFIND_BLANK), _useStraightPath(false), - _forceDestination(false), _pointPathLimit(MAX_POINT_PATH_LENGTH), - _endPosition(G3D::Vector3::zero()), _sourceUnit(owner), _navMesh(nullptr), +PathGenerator::PathGenerator(WorldObject const* owner) : + _polyLength(0), _type(PATHFIND_BLANK), _useStraightPath(false), _forceDestination(false), + _slopeCheck(false), _pointPathLimit(MAX_POINT_PATH_LENGTH), _useRaycast(false), + _endPosition(G3D::Vector3::zero()), _source(owner), _navMesh(nullptr), _navMeshQuery(nullptr) { memset(_pathPolyRefs, 0, sizeof(_pathPolyRefs)); - uint32 mapId = _sourceUnit->GetMapId(); - //if (MMAP::MMapFactory::IsPathfindingEnabled(_sourceUnit->FindMap())) // pussywizard: checked before creating new PathGenerator + uint32 mapId = _source->GetMapId(); + //if (MMAP::MMapFactory::IsPathfindingEnabled(_sourceUnit->FindMap())) { MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); - - ACORE_READ_GUARD(ACE_RW_Thread_Mutex, mmap->GetManagerLock()); _navMesh = mmap->GetNavMesh(mapId); - _navMeshQuery = mmap->GetNavMeshQuery(mapId, _sourceUnit->GetInstanceId()); + _navMeshQuery = mmap->GetNavMeshQuery(mapId, _source->GetInstanceId()); } CreateFilter(); @@ -54,55 +42,38 @@ PathGenerator::~PathGenerator() bool PathGenerator::CalculatePath(float destX, float destY, float destZ, bool forceDest) { float x, y, z; - if (!_sourceUnit->movespline->Finalized() && _sourceUnit->movespline->Initialized()) - { - Movement::Location realpos = _sourceUnit->movespline->ComputePosition(); - x = realpos.x; - y = realpos.y; - z = realpos.z; - } - else - _sourceUnit->GetPosition(x, y, z); + _source->GetPosition(x, y, z); + return CalculatePath(x, y, z, destX, destY, destZ, forceDest); +} + +bool PathGenerator::CalculatePath(float x, float y, float z, float destX, float destY, float destZ, bool forceDest) +{ if (!acore::IsValidMapCoord(destX, destY, destZ) || !acore::IsValidMapCoord(x, y, z)) return false; G3D::Vector3 dest(destX, destY, destZ); SetEndPosition(dest); + G3D::Vector3 start(x, y, z); SetStartPosition(start); + _forceDestination = forceDest; - // pussywizard: EnsureGridCreated may need map mutex, and it loads mmaps (may need mmap mutex) - // pussywizard: a deadlock can occur if here the map mutex is requested after acquiring mmap lock below - // pussywizard: so call EnsureGridCreated for all possible grids before acquiring mmap lock :/ this is so shit... because the core is shit :/ - { - Cell cellS(start.x, start.y); - _sourceUnit->GetMap()->EnsureGridCreated(GridCoord(cellS.GridX(), cellS.GridY())); - Cell cellD(dest.x, dest.y); - _sourceUnit->GetMap()->EnsureGridCreated(GridCoord(cellD.GridX(), cellD.GridY())); - } - - UpdateFilter(); // no mmap operations inside, no mutex needed - - // pussywizard: mutex with new that can be release at any moment, DON'T FORGET TO RELEASE ON EVERY RETURN !!! - const Map* base = _sourceUnit->GetBaseMap(); - ACE_RW_Thread_Mutex& mmapLock = (base ? base->GetMMapLock() : MMAP::MMapFactory::createOrGetMMapManager()->GetMMapGeneralLock()); - mmapLock.acquire_read(); - // make sure navMesh works - we can run on map w/o mmap // check if the start and end point have a .mmtile loaded (can we pass via not loaded tile on the way?) - if (!_navMesh || !_navMeshQuery || _sourceUnit->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING) || - _sourceUnit->GetObjectSize() >= SIZE_OF_GRIDS / 2.0f || _sourceUnit->GetExactDistSq(destX, destY, destZ) >= (SIZE_OF_GRIDS * SIZE_OF_GRIDS / 4.0f) || - !HaveTile(start) || !HaveTile(dest)) + Unit const* _sourceUnit = _source->ToUnit(); + if (!_navMesh || !_navMeshQuery || (_sourceUnit && _sourceUnit->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING)) || + !HaveTile(start) || !HaveTile(dest)) { BuildShortcut(); _type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); - mmapLock.release(); return true; } - BuildPolyPath(start, dest, mmapLock); + UpdateFilter(); + + BuildPolyPath(start, dest); return true; } @@ -111,32 +82,42 @@ dtPolyRef PathGenerator::GetPathPolyByPosition(dtPolyRef const* polyPath, uint32 if (!polyPath || !polyPathSize) return INVALID_POLYREF; - float polyHeight, height; + dtPolyRef nearestPoly = INVALID_POLYREF; + float minDist = FLT_MAX; + for (uint32 i = 0; i < polyPathSize; ++i) { - if (DT_SUCCESS != _navMeshQuery->getPolyHeight(polyPath[i], point, &polyHeight)) + float closestPoint[VERTEX_SIZE]; + if (dtStatusFailed(_navMeshQuery->closestPointOnPoly(polyPath[i], point, closestPoint, nullptr))) continue; - height = point[1] - polyHeight; - if (height > 0.0f && height < ALLOWED_DIST_FROM_POLY + ADDED_Z_FOR_POLY_LOOKUP) + + float d = dtVdistSqr(point, closestPoint); + if (d < minDist) { - if (distance) - *distance = height; - return polyPath[i]; + minDist = d; + nearestPoly = polyPath[i]; + } + + if (minDist < 1.0f) // shortcut out - close enough for us + { + break; } } - return INVALID_POLYREF; + if (distance) + { + *distance = dtMathSqrtf(minDist); + } + + return (minDist < 3.0f) ? nearestPoly : INVALID_POLYREF; } -dtPolyRef PathGenerator::GetPolyByLocation(float* point, float* distance) const +dtPolyRef PathGenerator::GetPolyByLocation(float const* point, float* distance) const { // first we check the current path // if the current path doesn't contain the current poly, // we need to use the expensive navMesh.findNearestPoly - - point[1] += ADDED_Z_FOR_POLY_LOOKUP; dtPolyRef polyRef = GetPathPolyByPosition(_pathPolyRefs, _polyLength, point, distance); - point[1] -= ADDED_Z_FOR_POLY_LOOKUP; if (polyRef != INVALID_POLYREF) return polyRef; @@ -145,8 +126,7 @@ dtPolyRef PathGenerator::GetPolyByLocation(float* point, float* distance) const // first try with low search box float extents[VERTEX_SIZE] = {3.0f, 5.0f, 3.0f}; // bounds of poly search area float closestPoint[VERTEX_SIZE] = {0.0f, 0.0f, 0.0f}; - dtStatus result = _navMeshQuery->findNearestPoly(point, extents, &_filter, &polyRef, closestPoint); - if (DT_SUCCESS == result && polyRef != INVALID_POLYREF) + if (dtStatusSucceed(_navMeshQuery->findNearestPoly(point, extents, &_filter, &polyRef, closestPoint)) && polyRef != INVALID_POLYREF) { *distance = dtVdist(closestPoint, point); return polyRef; @@ -163,534 +143,468 @@ dtPolyRef PathGenerator::GetPolyByLocation(float* point, float* distance) const return polyRef; } + *distance = FLT_MAX; return INVALID_POLYREF; } -G3D::Vector3 ClosestPointOnLine(const G3D::Vector3& a, const G3D::Vector3& b, const G3D::Vector3& Point) +void PathGenerator::BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 const& endPos) { - G3D::Vector3 c = Point - a; // Vector from a to Point - G3D::Vector3 v = (b - a).unit(); // Unit Vector from a to b - float d = (b - a).length(); // Length of the line segment - float t = v.dot(c); // Intersection point Distance from a + // *** getting start/end poly logic *** - // Check to see if the point is on the line - // if not then return the endpoint - if(t < 0) return a; - if(t > d) return b; + float distToStartPoly, distToEndPoly; + float startPoint[VERTEX_SIZE] = {startPos.y, startPos.z, startPos.x}; + float endPoint[VERTEX_SIZE] = {endPos.y, endPos.z, endPos.x}; - // get the distance to move from point a - v *= t; + dtPolyRef startPoly = GetPolyByLocation(startPoint, &distToStartPoly); + dtPolyRef endPoly = GetPolyByLocation(endPoint, &distToEndPoly); - // move from point a to the nearest point on the segment - return a + v; -} + _type = PathType(PATHFIND_NORMAL); -template -class MutexReleaser -{ -public: - MutexReleaser(MUTEX_TYPE& mutex) : _mutex(mutex) {} - ~MutexReleaser() { _mutex.release(); } -private: - MUTEX_TYPE& _mutex; -}; - -void PathGenerator::BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 const& endPos, ACE_RW_Thread_Mutex& mmapLock) -{ - bool endInWaterFar = false; - bool cutToFirstHigher = false; + Creature const* creature = _source->ToCreature(); + // we have a hole in our mesh + // make shortcut path and mark it as NOPATH ( with flying and swimming exception ) + // its up to caller how he will use this info + if (startPoly == INVALID_POLYREF || endPoly == INVALID_POLYREF) { - MutexReleaser mutexReleaser(mmapLock); + BuildShortcut(); - // *** getting start/end poly logic *** - - float distToStartPoly, distToEndPoly; - float startPoint[VERTEX_SIZE] = {startPos.y, startPos.z, startPos.x}; - float endPoint[VERTEX_SIZE] = {endPos.y, endPos.z, endPos.x}; - - dtPolyRef startPoly = GetPolyByLocation(startPoint, &distToStartPoly); - dtPolyRef endPoly = GetPolyByLocation(endPoint, &distToEndPoly); - - bool sourceIsFlying = (_sourceUnit->GetUnitMovementFlags() & (MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_FLYING)) || (_sourceUnit->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY) && !_sourceUnit->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING)) || (_sourceUnit->GetTypeId() == TYPEID_UNIT && ((Creature*)_sourceUnit)->CanFly()); - bool sourceCanSwim = _sourceUnit->GetTypeId() == TYPEID_UNIT ? _sourceUnit->ToCreature()->CanSwim() : true; - bool sourceCanWalk = _sourceUnit->GetTypeId() == TYPEID_UNIT ? _sourceUnit->ToCreature()->CanWalk() : true; - - // we have a hole in our mesh - // make shortcut path and mark it as NOPATH ( with flying and swimming exception ) - // its up to caller how he will use this info - if (startPoly == INVALID_POLYREF || endPoly == INVALID_POLYREF) + bool canSwim = creature ? creature->CanSwim() : true; + bool path = creature ? creature->CanFly() : true; + bool waterPath = IsWaterPath(_pathPoints); + if (path || (waterPath && canSwim)) + { + _type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); + return; + } + + // raycast doesn't need endPoly to be valid + if (!_useRaycast) { - BuildShortcut(); - if (sourceIsFlying) - { - _type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); - return; - } - if (sourceCanSwim) - { - if ((startPoly == INVALID_POLYREF && LIQUID_MAP_NO_WATER == _sourceUnit->GetBaseMap()->getLiquidStatus(startPos.x, startPos.y, startPos.z, MAP_ALL_LIQUIDS, nullptr)) || - (endPoly == INVALID_POLYREF && LIQUID_MAP_NO_WATER == _sourceUnit->GetBaseMap()->getLiquidStatus(endPos.x, endPos.y, endPos.z, MAP_ALL_LIQUIDS, nullptr))) - { - _type = PATHFIND_NOPATH; - return; - } - _type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); - return; - } _type = PATHFIND_NOPATH; return; } + } - // we may need a better number here - bool farFromStartPoly = (distToStartPoly > ALLOWED_DIST_FROM_POLY); - bool farFromEndPoly = (distToEndPoly > ALLOWED_DIST_FROM_POLY); - if (farFromStartPoly) + // we may need a better number here + bool startFarFromPoly = distToStartPoly > 7.0f; + bool endFarFromPoly = distToEndPoly > 7.0f; + // create a shortcut if the path begins or end too far + // away from the desired path points. + // swimming creatures should not use a shortcut + // because exiting the water must be done following a proper path + // we just need to remove/normalize paths between 2 adjacent points + if ((!creature || !creature->CanSwim() || !creature->IsInWater() || _useRaycast) + && (startFarFromPoly || endFarFromPoly)) + { + bool buildShotrcut = false; + + Unit const* _sourceUnit = _source->ToUnit(); + if (_useRaycast || + (_sourceUnit && (_sourceUnit->CanFly() || (_sourceUnit->IsFalling() && endPos.z < startPos.z))) + ) { - if (sourceIsFlying) - { - BuildShortcut(); - _type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); - return; - } - if (sourceCanSwim) - { - if (LIQUID_MAP_NO_WATER == _sourceUnit->GetBaseMap()->getLiquidStatus(startPos.x, startPos.y, startPos.z, MAP_ALL_LIQUIDS, nullptr)) - { - if (distToStartPoly > MAX_FIXABLE_Z_ERROR) - { - BuildShortcut(); - _type = PATHFIND_NOPATH; - return; - } - - if (farFromEndPoly) - { - if (LIQUID_MAP_NO_WATER == _sourceUnit->GetBaseMap()->getLiquidStatus(endPos.x, endPos.y, endPos.z, MAP_ALL_LIQUIDS, nullptr)) - { - BuildShortcut(); - _type = PATHFIND_NOPATH; - return; - } - } - } - else if (LIQUID_MAP_NO_WATER == _sourceUnit->GetBaseMap()->getLiquidStatus(endPos.x, endPos.y, endPos.z, MAP_ALL_LIQUIDS, nullptr)) - { - if (farFromEndPoly) - { - BuildShortcut(); - _type = PATHFIND_NOPATH; - return; - } - - cutToFirstHigher = true; - } - else // starting and ending points are in water - { - BuildShortcut(); - _type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); - return; - } - } - else - { - if (distToStartPoly > MAX_FIXABLE_Z_ERROR || farFromEndPoly) - { - BuildShortcut(); - _type = PATHFIND_NOPATH; - return; - } - } - } - else if (farFromEndPoly) - { - if (sourceIsFlying) - { - BuildShortcut(); - _type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); - return; - } - if (LIQUID_MAP_NO_WATER == _sourceUnit->GetBaseMap()->getLiquidStatus(endPos.x, endPos.y, endPos.z, MAP_ALL_LIQUIDS, nullptr)) - { - if (!sourceCanWalk) - { - BuildShortcut(); - _type = PATHFIND_NOPATH; - return; - } - } - else - { - if (!sourceCanSwim) - { - BuildShortcut(); - _type = PATHFIND_NOPATH; - return; - } - - // if both points are in water - if (LIQUID_MAP_NO_WATER != _sourceUnit->GetBaseMap()->getLiquidStatus(startPos.x, startPos.y, startPos.z, MAP_ALL_LIQUIDS, nullptr)) - { - BuildShortcut(); - _type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); - return; - } - - endInWaterFar = true; - } - - if (startPoly != endPoly || !endInWaterFar) - { - float closestPoint[VERTEX_SIZE]; - if (dtStatusSucceed(_navMeshQuery->closestPointOnPoly(endPoly, endPoint, closestPoint, nullptr))) - { - dtVcopy(endPoint, closestPoint); - SetActualEndPosition(G3D::Vector3(endPoint[2], endPoint[0], endPoint[1])); - } - _type = PATHFIND_INCOMPLETE; - } + buildShotrcut = true; } - // *** poly path generating logic *** - - if (startPoly == endPoly) + if (buildShotrcut) { BuildShortcut(); - _type = !farFromEndPoly || endInWaterFar ? PATHFIND_NORMAL : PATHFIND_INCOMPLETE; - _pathPolyRefs[0] = startPoly; - _polyLength = 1; + _type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); + + AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly); + return; } - - // look for startPoly/endPoly in current path - /// @todo we can merge it with getPathPolyByPosition() loop - bool startPolyFound = false; - bool endPolyFound = false; - uint32 pathStartIndex = 0; - uint32 pathEndIndex = 0; - - if (_polyLength) - { - for (; pathStartIndex < _polyLength; ++pathStartIndex) - { - // here to catch few bugs - ASSERT(_pathPolyRefs[pathStartIndex] != INVALID_POLYREF); - - if (_pathPolyRefs[pathStartIndex] == startPoly) - { - startPolyFound = true; - break; - } - } - - for (pathEndIndex = _polyLength - 1; pathEndIndex > pathStartIndex; --pathEndIndex) - if (_pathPolyRefs[pathEndIndex] == endPoly) - { - endPolyFound = true; - break; - } - } - - if (startPolyFound && endPolyFound) - { - _polyLength = pathEndIndex - pathStartIndex + 1; - memmove(_pathPolyRefs, _pathPolyRefs + pathStartIndex, _polyLength * sizeof(dtPolyRef)); - } - else if (startPolyFound && !endPolyFound && _polyLength - pathStartIndex >= 3 /*if (>=3) then 70% will return at least one more than just startPoly*/) - { - // we are moving on the old path but target moved out - // so we have atleast part of poly-path ready - - _polyLength -= pathStartIndex; - - // try to adjust the suffix of the path instead of recalculating entire length - // at given interval the target cannot get too far from its last location - // thus we have less poly to cover - // sub-path of optimal path is optimal - - // take ~65% of the original length - /// @todo play with the values here - uint32 prefixPolyLength = uint32(_polyLength * 0.7f + 0.5f); // this should be always >= 1 - memmove(_pathPolyRefs, _pathPolyRefs + pathStartIndex, prefixPolyLength * sizeof(dtPolyRef)); - - dtPolyRef suffixStartPoly = _pathPolyRefs[prefixPolyLength - 1]; - - bool error = false; // can't use a part of old path, generate whole new - - // we need any point on our suffix start poly to generate poly-path, so we need last poly in prefix data - float suffixEndPoint[VERTEX_SIZE]; - if (dtStatusFailed(_navMeshQuery->closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint, nullptr))) - { - // we can hit offmesh connection as last poly - closestPointOnPoly() don't like that - // try to recover by using prev polyref - --prefixPolyLength; - if (prefixPolyLength) - { - suffixStartPoly = _pathPolyRefs[prefixPolyLength - 1]; - if (dtStatusFailed(_navMeshQuery->closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint, NULL))) - error = true; - } - else - error = true; - } - - if (!error) - { - // generate suffix - uint32 suffixPolyLength = 0; - dtStatus dtResult = _navMeshQuery->findPath( - suffixStartPoly, // start polygon - endPoly, // end polygon - suffixEndPoint, // start position - endPoint, // end position - &_filter, // polygon search filter - _pathPolyRefs + prefixPolyLength - 1, // [out] path - (int*)&suffixPolyLength, - MAX_PATH_LENGTH - prefixPolyLength); // max number of polygons in output path - - if (!_polyLength || dtStatusFailed(dtResult)) - { - // this is probably an error state, but we'll leave it - // and hopefully recover on the next Update - // we still need to copy our preffix - } - - // new path = prefix + suffix - overlap - _polyLength = prefixPolyLength + suffixPolyLength - 1; - } - else - { - // free and invalidate old path data - Clear(); - - dtStatus dtResult = _navMeshQuery->findPath( - startPoly, // start polygon - endPoly, // end polygon - startPoint, // start position - endPoint, // end position - &_filter, // polygon search filter - _pathPolyRefs, // [out] path - (int*)&_polyLength, - MAX_PATH_LENGTH); // max number of polygons in output path - - if (!_polyLength || dtStatusFailed(dtResult)) - { - // only happens if we passed bad data to findPath(), or navmesh is messed up - BuildShortcut(); - _type = PATHFIND_NOPATH; - return; - } - } - } else { - // either we have no path at all -> first run - // or something went really wrong -> we aren't moving along the path to the target - // pussywizard: or knocked back away from our path, nothing special - // just generate new path - - // free and invalidate old path data - Clear(); - - dtStatus dtResult = _navMeshQuery->findPath( - startPoly, // start polygon - endPoly, // end polygon - startPoint, // start position - endPoint, // end position - &_filter, // polygon search filter - _pathPolyRefs, // [out] path - (int*)&_polyLength, - MAX_PATH_LENGTH); // max number of polygons in output path - - if (!_polyLength || dtStatusFailed(dtResult)) + float closestPoint[VERTEX_SIZE]; + // we may want to use closestPointOnPolyBoundary instead + if (dtStatusSucceed(_navMeshQuery->closestPointOnPoly(endPoly, endPoint, closestPoint, nullptr))) { - // only happens if we passed bad data to findPath(), or navmesh is messed up + dtVcopy(endPoint, closestPoint); + SetActualEndPosition(G3D::Vector3(endPoint[2], endPoint[0], endPoint[1])); + } + + _type = PathType(PATHFIND_INCOMPLETE); + + AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly); + } + } + + // *** poly path generating logic *** + + // start and end are on same polygon + // handle this case as if they were 2 different polygons, building a line path split in some few points + if (startPoly == endPoly && !_useRaycast) + { + _pathPolyRefs[0] = startPoly; + _polyLength = 1; + + if (startFarFromPoly || endFarFromPoly) + { + _type = PathType(PATHFIND_INCOMPLETE); + + AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly); + } + else + _type = PATHFIND_NORMAL; + + BuildPointPath(startPoint, endPoint); + return; + } + + // look for startPoly/endPoly in current path + /// @todo we can merge it with getPathPolyByPosition() loop + bool startPolyFound = false; + bool endPolyFound = false; + uint32 pathStartIndex = 0; + uint32 pathEndIndex = 0; + + if (_polyLength) + { + for (; pathStartIndex < _polyLength; ++pathStartIndex) + { + // here to catch few bugs + if (_pathPolyRefs[pathStartIndex] == INVALID_POLYREF) + { + break; + } + + if (_pathPolyRefs[pathStartIndex] == startPoly) + { + startPolyFound = true; + break; + } + } + + for (pathEndIndex = _polyLength-1; pathEndIndex > pathStartIndex; --pathEndIndex) + { + if (_pathPolyRefs[pathEndIndex] == endPoly) + { + endPolyFound = true; + break; + } + } + } + + if (startPolyFound && endPolyFound) + { + // we moved along the path and the target did not move out of our old poly-path + // our path is a simple subpath case, we have all the data we need + // just "cut" it out + + _polyLength = pathEndIndex - pathStartIndex + 1; + memmove(_pathPolyRefs, _pathPolyRefs + pathStartIndex, _polyLength * sizeof(dtPolyRef)); + } + else if (startPolyFound && !endPolyFound) + { + // we are moving on the old path but target moved out + // so we have atleast part of poly-path ready + + _polyLength -= pathStartIndex; + + // try to adjust the suffix of the path instead of recalculating entire length + // at given interval the target cannot get too far from its last location + // thus we have less poly to cover + // sub-path of optimal path is optimal + + // take ~80% of the original length + /// @todo play with the values here + uint32 prefixPolyLength = uint32(_polyLength * 0.8f + 0.5f); + memmove(_pathPolyRefs, _pathPolyRefs+pathStartIndex, prefixPolyLength * sizeof(dtPolyRef)); + + dtPolyRef suffixStartPoly = _pathPolyRefs[prefixPolyLength-1]; + + // we need any point on our suffix start poly to generate poly-path, so we need last poly in prefix data + float suffixEndPoint[VERTEX_SIZE]; + if (dtStatusFailed(_navMeshQuery->closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint, nullptr))) + { + // we can hit offmesh connection as last poly - closestPointOnPoly() don't like that + // try to recover by using prev polyref + --prefixPolyLength; + suffixStartPoly = _pathPolyRefs[prefixPolyLength-1]; + if (dtStatusFailed(_navMeshQuery->closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint, nullptr))) + { + // suffixStartPoly is still invalid, error state BuildShortcut(); _type = PATHFIND_NOPATH; return; } } - // by now we know what type of path we can get - if (_pathPolyRefs[_polyLength - 1] == endPoly && !(_type & PATHFIND_INCOMPLETE)) - _type = PATHFIND_NORMAL; + // generate suffix + uint32 suffixPolyLength = 0; + + dtStatus dtResult; + if (_useRaycast) + { + BuildShortcut(); + _type = PATHFIND_NOPATH; + return; + } else - _type = PATHFIND_INCOMPLETE; + { + dtResult = _navMeshQuery->findPath( + suffixStartPoly, // start polygon + endPoly, // end polygon + suffixEndPoint, // start position + endPoint, // end position + &_filter, // polygon search filter + _pathPolyRefs + prefixPolyLength - 1, // [out] path + (int*)&suffixPolyLength, + MAX_PATH_LENGTH - prefixPolyLength); // max number of polygons in output path + } - // generate the point-path out of our up-to-date poly-path - BuildPointPath(startPoint, endPoint); + if (!suffixPolyLength || dtStatusFailed(dtResult)) + { + // this is probably an error state, but we'll leave it + // and hopefully recover on the next Update + // we still need to copy our preffix + sLog->outError("PathGenerator::BuildPolyPath: Path Build failed\n%lu", _source->GetGUID()); + } - // pussywizard: no mmap usage below, release mutex - } // end of scope (mutex released in object destructor) - - if (_type == PATHFIND_NORMAL && cutToFirstHigher) // starting in water, far from bottom, target is on the ground (above starting Z) -> update beginning points that are lower than starting Z - { - uint32 i = 0; - uint32 size = _pathPoints.size(); - for (; i < size; ++i) - if (_pathPoints[i].z >= _sourceUnit->GetPositionZ() + 0.1f) - break; - if (i && i != size && LIQUID_MAP_NO_WATER != _sourceUnit->GetBaseMap()->getLiquidStatus(_pathPoints[i - 1].x, _pathPoints[i - 1].y, _pathPoints[i - 1].z, MAP_ALL_LIQUIDS, nullptr)) - for (uint32 j = 0; j < i; ++j) - _pathPoints[j].z = _sourceUnit->GetPositionZ(); + // new path = prefix + suffix - overlap + _polyLength = prefixPolyLength + suffixPolyLength - 1; } - - if (!_forceDestination) - if (uint32 lastIdx = _pathPoints.size()) - { - lastIdx = lastIdx - 1; - if (endInWaterFar) - { - SetActualEndPosition(GetEndPosition()); - _pathPoints[lastIdx] = GetEndPosition(); - } - else - _sourceUnit->UpdateAllowedPositionZ(_pathPoints[lastIdx].x, _pathPoints[lastIdx].y, _pathPoints[lastIdx].z); - } - - - // pussywizard: fix for running back and forth while target moves - // pussywizard: second point (first is actual position) is forward to current server position, but when received by the client it's already behind, so the npc runs back to that point - // pussywizard: the higher speed, the more probable the situation is, so try to move second point as far forward the path as possible - // pussywizard: changed path cannot differ much from the original (by max dist), because there might be walls and holes - if (_sourceUnit->GetCreatureType() != CREATURE_TYPE_NON_COMBAT_PET) - return; - uint32 size = _pathPoints.size(); - bool ok = true; - for (uint32 i = 2; i <= size; ++i) + else { - // pussywizard: line between start point and i'th point - // pussywizard: distance to that line of all points between 0 and i must be less than X and less than sourceUnit size + // either we have no path at all -> first run + // or something went really wrong -> we aren't moving along the path to the target + // just generate new path - if (i < size) - { - ok = true; - if ((_pathPoints[i] - _pathPoints[0]).squaredLength() > 15.0f * 15.0f) - ok = false; - else - for (uint32 j = 1; j < i; ++j) - { - float sqDist = (_pathPoints[j] - ClosestPointOnLine(_pathPoints[0], _pathPoints[i], _pathPoints[j])).squaredLength(); - float oSize = _sourceUnit->GetObjectSize(); - if (sqDist > 1.0f * 1.0f || sqDist > oSize * oSize) - { - ok = false; - break; - } - } - } + // free and invalidate old path data + Clear(); - if (!ok) + dtStatus dtResult; + if (_useRaycast) { - if (i < size) + float hit = 0; + float hitNormal[3]; + memset(hitNormal, 0, sizeof(hitNormal)); + + dtResult = _navMeshQuery->raycast( + startPoly, + startPoint, + endPoint, + &_filter, + &hit, + hitNormal, + _pathPolyRefs, + (int*)&_polyLength, + MAX_PATH_LENGTH); + + if (!_polyLength || dtStatusFailed(dtResult)) { - // pussywizard: check additional 3 quarter points after last fitting poly point - - G3D::Vector3 dir = _pathPoints[i] - _pathPoints[i - 1]; - G3D::Vector3 increment = (dir.length() / 4.0f) * dir.unit(); - for (uint8 k = 3; k > 0; --k) - { - G3D::Vector3 newPoint = _pathPoints[i - 1] + ((float)k) * increment; - - bool ok2 = true; - if ((newPoint - _pathPoints[0]).squaredLength() > 15.0f * 15.0f) - ok2 = false; - else - for (uint32 j = 1; j < i; ++j) - { - float sqDist = (_pathPoints[j] - ClosestPointOnLine(_pathPoints[0], newPoint, _pathPoints[j])).squaredLength(); - float oSize = _sourceUnit->GetObjectSize(); - if (sqDist > 1.0f * 1.0f || sqDist > oSize * oSize) - { - ok2 = false; - break; - } - } - - if (ok2) - { - _pathPoints[i - 1] = newPoint; - break; - } - } - - // pussywizard: memmove crashes o_O - // memmove(&_pathPoints + sizeof(G3D::Vector3), &_pathPoints + (i-1)*sizeof(G3D::Vector3), (size-i+1)*sizeof(G3D::Vector3)); - for (uint8 k = 1; k <= size - i + 1; ++k) - _pathPoints[k] = _pathPoints[k + i - 2]; - _pathPoints.resize(size - i + 2); + BuildShortcut(); + _type = PATHFIND_NOPATH; + AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly); + return; } - else if (size > 2) + + // raycast() sets hit to FLT_MAX if there is a ray between start and end + if (hit != FLT_MAX) { - _pathPoints[1] = _pathPoints[size - 1]; + float hitPos[3]; + + // Walk back a bit from the hit point to make sure it's in the mesh (sometimes the point is actually outside of the polygons due to float precision issues) + hit *= 0.99f; + dtVlerp(hitPos, startPoint, endPoint, hit); + + // if it fails again, clamp to poly boundary + if (dtStatusFailed(_navMeshQuery->getPolyHeight(_pathPolyRefs[_polyLength - 1], hitPos, &hitPos[1]))) + _navMeshQuery->closestPointOnPolyBoundary(_pathPolyRefs[_polyLength - 1], hitPos, hitPos); + _pathPoints.resize(2); - } + _pathPoints[0] = GetStartPosition(); + _pathPoints[1] = G3D::Vector3(hitPos[2], hitPos[0], hitPos[1]); - break; + NormalizePath(); + _type = PATHFIND_INCOMPLETE; + AddFarFromPolyFlags(startFarFromPoly, false); + return; + } + else + { + // clamp to poly boundary if we fail to get the height + if (dtStatusFailed(_navMeshQuery->getPolyHeight(_pathPolyRefs[_polyLength - 1], endPoint, &endPoint[1]))) + _navMeshQuery->closestPointOnPolyBoundary(_pathPolyRefs[_polyLength - 1], endPoint, endPoint); + + _pathPoints.resize(2); + _pathPoints[0] = GetStartPosition(); + _pathPoints[1] = G3D::Vector3(endPoint[2], endPoint[0], endPoint[1]); + + NormalizePath(); + if (startFarFromPoly || endFarFromPoly) + { + _type = PathType(PATHFIND_INCOMPLETE); + + AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly); + } + else + _type = PATHFIND_NORMAL; + return; + } + } + else + { + dtResult = _navMeshQuery->findPath( + startPoly, // start polygon + endPoly, // end polygon + startPoint, // start position + endPoint, // end position + &_filter, // polygon search filter + _pathPolyRefs, // [out] path + (int*)&_polyLength, + MAX_PATH_LENGTH); // max number of polygons in output path + } + + if (!_polyLength || dtStatusFailed(dtResult)) + { + // only happens if we passed bad data to findPath(), or navmesh is messed up + sLog->outError("PathGenerator::BuildPolyPath: %lu Path Build failed: 0 length path", _source->GetGUID()); + BuildShortcut(); + _type = PATHFIND_NOPATH; + return; } } + + if (!_polyLength) + { + sLog->outError("PathGenerator::BuildPolyPath: %lu Path Build failed: 0 length path", _source->GetGUID()); + BuildShortcut(); + _type = PATHFIND_NOPATH; + return; + } + + // by now we know what type of path we can get + if (_pathPolyRefs[_polyLength - 1] == endPoly && !(_type & PATHFIND_INCOMPLETE)) + { + _type = PATHFIND_NORMAL; + } + else + { + _type = PATHFIND_INCOMPLETE; + } + + AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly); + + // generate the point-path out of our up-to-date poly-path + BuildPointPath(startPoint, endPoint); } -void PathGenerator::BuildPointPath(const float* startPoint, const float* endPoint) +void PathGenerator::BuildPointPath(const float *startPoint, const float *endPoint) { - float pathPoints[MAX_POINT_PATH_LENGTH * VERTEX_SIZE]; + float pathPoints[MAX_POINT_PATH_LENGTH*VERTEX_SIZE]; uint32 pointCount = 0; dtStatus dtResult = DT_FAILURE; - if (_useStraightPath) + if (_useRaycast) + { + // _straightLine uses raycast and it currently doesn't support building a point path, only a 2-point path with start and hitpoint/end is returned + sLog->outError("PathGenerator::BuildPointPath() called with _useRaycast for unit %lu", _source->GetGUID()); + BuildShortcut(); + _type = PATHFIND_NOPATH; + return; + } + else if (_useStraightPath) { dtResult = _navMeshQuery->findStraightPath( - startPoint, // start position - endPoint, // end position - _pathPolyRefs, // current path - _polyLength, // lenth of current path - pathPoints, // [out] path corner points - NULL, // [out] flags - NULL, // [out] shortened path - (int*)&pointCount, - _pointPathLimit); // maximum number of points/polygons to use + startPoint, // start position + endPoint, // end position + _pathPolyRefs, // current path + _polyLength, // lenth of current path + pathPoints, // [out] path corner points + nullptr, // [out] flags + nullptr, // [out] shortened path + (int*)&pointCount, + _pointPathLimit); // maximum number of points/polygons to use } else { dtResult = FindSmoothPath( - startPoint, // start position - endPoint, // end position - _pathPolyRefs, // current path - _polyLength, // length of current path - pathPoints, // [out] path corner points - (int*)&pointCount, - _pointPathLimit); // maximum number of points + startPoint, // start position + endPoint, // end position + _pathPolyRefs, // current path + _polyLength, // length of current path + pathPoints, // [out] path corner points + (int*)&pointCount, + _pointPathLimit); // maximum number of points } - if (pointCount < 2 || dtStatusFailed(dtResult)) + // Special case with start and end positions very close to each other + if (_polyLength == 1 && pointCount == 1) + { + // First point is start position, append end position + dtVcopy(&pathPoints[1 * VERTEX_SIZE], endPoint); + pointCount++; + } + else if ( pointCount < 2 || dtStatusFailed(dtResult)) { // only happens if pass bad data to findStraightPath or navmesh is broken // single point paths can be generated here /// @todo check the exact cases BuildShortcut(); - _type = PATHFIND_NOPATH; + _type = PathType(_type | PATHFIND_NOPATH); return; } - else if (pointCount == _pointPathLimit) + else if (pointCount >= _pointPathLimit) { BuildShortcut(); - _type = PATHFIND_SHORT; + _type = PathType(_type | PATHFIND_SHORT); return; } _pathPoints.resize(pointCount); - for (uint32 i = 0; i < pointCount; ++i) - _pathPoints[i] = G3D::Vector3(pathPoints[i * VERTEX_SIZE + 2], pathPoints[i * VERTEX_SIZE], pathPoints[i * VERTEX_SIZE + 1]); + uint32 newPointCount = 0; + for (uint32 i = 0; i < pointCount; ++i) { + G3D::Vector3 vector = G3D::Vector3(pathPoints[i*VERTEX_SIZE+2], pathPoints[i*VERTEX_SIZE], pathPoints[i*VERTEX_SIZE+1]); + ZLiquidStatus status = _source->GetMap()->getLiquidStatus(vector.x, vector.y, vector.z, MAP_ALL_LIQUIDS, nullptr); + // One of the points is not in the water + if (status == LIQUID_MAP_UNDER_WATER) + { + // if the first point is under water + // then set a proper z for it + if (i == 0) + { + vector.z = std::fmaxf(vector.z, _source->GetPositionZ()); + _pathPoints[newPointCount] = vector; + } + // if the last point is under water + // then set the desired end position instead + else if (i == pointCount - 1 ) + { + _pathPoints[newPointCount] = GetActualEndPosition(); + } + // if one of the mid-points of the path is underwater + // then we can create a shortcut between the previous one + // and the next one by not including it inside the list + else + continue; + } + else + { + _pathPoints[newPointCount] = vector; + } + + newPointCount++; + } + + _pathPoints.resize(newPointCount); + + NormalizePath(); // first point is always our current location - we need the next one - SetActualEndPosition(_pathPoints[pointCount - 1]); + SetActualEndPosition(_pathPoints[newPointCount-1]); - if (_forceDestination && (!(_type & PATHFIND_NORMAL) || !InRange(GetEndPosition(), GetActualEndPosition(), 0.75f, 0.75f))) + // force the given destination, if needed + if (_forceDestination && + (!(_type & PATHFIND_NORMAL) || !InRange(GetEndPosition(), GetActualEndPosition(), 1.0f, 1.0f))) { // we may want to keep partial subpath - if (Dist3DSqr(GetActualEndPosition(), GetEndPosition()) < 0.33f * Dist3DSqr(GetStartPosition(), GetEndPosition())) + if (Dist3DSqr(GetActualEndPosition(), GetEndPosition()) < 0.3f * Dist3DSqr(GetStartPosition(), GetEndPosition())) { SetActualEndPosition(GetEndPosition()); - _pathPoints[_pathPoints.size() - 1] = GetEndPosition(); + _pathPoints[_pathPoints.size()-1] = GetEndPosition(); } else { @@ -702,12 +616,27 @@ void PathGenerator::BuildPointPath(const float* startPoint, const float* endPoin } } +void PathGenerator::NormalizePath() +{ + for (uint32 i = 0; i < _pathPoints.size(); ++i) + { + _source->UpdateAllowedPositionZ(_pathPoints[i].x, _pathPoints[i].y, _pathPoints[i].z); + } +} + void PathGenerator::BuildShortcut() { Clear(); + + // make two point path, our curr pos is the start, and dest is the end _pathPoints.resize(2); + + // set start and a default next position _pathPoints[0] = GetStartPosition(); _pathPoints[1] = GetActualEndPosition(); + + NormalizePath(); + _type = PATHFIND_SHORTCUT; } @@ -716,20 +645,20 @@ void PathGenerator::CreateFilter() uint16 includeFlags = 0; uint16 excludeFlags = 0; - if (_sourceUnit->GetTypeId() == TYPEID_UNIT) + if (_source->GetTypeId() == TYPEID_UNIT) { - Creature* creature = (Creature*)_sourceUnit; + Creature* creature = (Creature*)_source; if (creature->CanWalk()) includeFlags |= NAV_GROUND; // walk // creatures don't take environmental damage - if (creature->CanSwim()) - includeFlags |= (NAV_WATER | NAV_MAGMA | NAV_SLIME); // swim + if (creature->CanEnterWater()) + includeFlags |= (NAV_WATER | NAV_MAGMA); } else // assume Player { // perfect support not possible, just stay 'safe' - includeFlags |= (NAV_GROUND | NAV_WATER | NAV_MAGMA | NAV_SLIME); + includeFlags |= (NAV_GROUND | NAV_WATER | NAV_MAGMA); } _filter.setIncludeFlags(includeFlags); @@ -742,21 +671,28 @@ void PathGenerator::UpdateFilter() { // allow creatures to cheat and use different movement types if they are moved // forcefully into terrain they can't normally move in - if (_sourceUnit->IsInWater() || _sourceUnit->IsUnderWater()) + if (Unit const* _sourceUnit = _source->ToUnit()) { - uint16 includedFlags = _filter.getIncludeFlags(); - includedFlags |= GetNavTerrain(_sourceUnit->GetPositionX(), - _sourceUnit->GetPositionY(), - _sourceUnit->GetPositionZ()); + if (_sourceUnit->IsInWater() || _sourceUnit->IsUnderWater()) + { + uint16 includedFlags = _filter.getIncludeFlags(); + includedFlags |= GetNavTerrain(_source->GetPositionX(), + _source->GetPositionY(), + _source->GetPositionZ()); - _filter.setIncludeFlags(includedFlags); + _filter.setIncludeFlags(includedFlags); + } + + /*if (Creature const* _sourceCreature = _source->ToCreature()) + if (_sourceCreature->IsInCombat() || _sourceCreature->IsInEvadeMode()) + _filter.setIncludeFlags(_filter.getIncludeFlags() | NAV_GROUND_STEEP);*/ } } -NavTerrain PathGenerator::GetNavTerrain(float x, float y, float z) +NavTerrain PathGenerator::GetNavTerrain(float x, float y, float z) const { LiquidData data; - ZLiquidStatus liquidStatus = _sourceUnit->GetBaseMap()->getLiquidStatus(x, y, z, MAP_ALL_LIQUIDS, &data); + ZLiquidStatus liquidStatus = _source->GetMap()->getLiquidStatus(x, y, z, MAP_ALL_LIQUIDS, &data); if (liquidStatus == LIQUID_MAP_NO_WATER) return NAV_GROUND; @@ -766,9 +702,8 @@ NavTerrain PathGenerator::GetNavTerrain(float x, float y, float z) case MAP_LIQUID_TYPE_OCEAN: return NAV_WATER; case MAP_LIQUID_TYPE_MAGMA: - return NAV_MAGMA; case MAP_LIQUID_TYPE_SLIME: - return NAV_SLIME; + return NAV_MAGMA; default: return NAV_GROUND; } @@ -796,10 +731,10 @@ uint32 PathGenerator::FixupCorridor(dtPolyRef* path, uint32 npath, uint32 maxPat int32 furthestVisited = -1; // Find furthest common polygon. - for (int32 i = npath - 1; i >= 0; --i) + for (int32 i = npath-1; i >= 0; --i) { bool found = false; - for (int32 j = nvisited - 1; j >= 0; --j) + for (int32 j = nvisited-1; j >= 0; --j) { if (path[i] == visited[j]) { @@ -823,7 +758,7 @@ uint32 PathGenerator::FixupCorridor(dtPolyRef* path, uint32 npath, uint32 maxPat uint32 orig = uint32(furthestPath + 1) < npath ? furthestPath + 1 : npath; uint32 size = npath > orig ? npath - orig : 0; if (req + size > maxPath) - size = maxPath - req; + size = maxPath-req; if (size) memmove(path + req, path + orig, size * sizeof(dtPolyRef)); @@ -832,21 +767,21 @@ uint32 PathGenerator::FixupCorridor(dtPolyRef* path, uint32 npath, uint32 maxPat for (uint32 i = 0; i < req; ++i) path[i] = visited[(nvisited - 1) - i]; - return req + size; + return req+size; } bool PathGenerator::GetSteerTarget(float const* startPos, float const* endPos, - float minTargetDist, dtPolyRef const* path, uint32 pathSize, - float* steerPos, unsigned char& steerPosFlag, dtPolyRef& steerPosRef) + float minTargetDist, dtPolyRef const* path, uint32 pathSize, + float* steerPos, unsigned char& steerPosFlag, dtPolyRef& steerPosRef) { // Find steer target. static const uint32 MAX_STEER_POINTS = 3; - float steerPath[MAX_STEER_POINTS * VERTEX_SIZE]; + float steerPath[MAX_STEER_POINTS*VERTEX_SIZE]; unsigned char steerPathFlags[MAX_STEER_POINTS]; dtPolyRef steerPathPolys[MAX_STEER_POINTS]; uint32 nsteerPath = 0; dtStatus dtResult = _navMeshQuery->findStraightPath(startPos, endPos, path, pathSize, - steerPath, steerPathFlags, steerPathPolys, (int*)&nsteerPath, MAX_STEER_POINTS); + steerPath, steerPathFlags, steerPathPolys, (int*)&nsteerPath, MAX_STEER_POINTS); if (!nsteerPath || dtStatusFailed(dtResult)) return false; @@ -856,15 +791,16 @@ bool PathGenerator::GetSteerTarget(float const* startPos, float const* endPos, { // Stop at Off-Mesh link or when point is further than slop away. if ((steerPathFlags[ns] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) || - !InRangeYZX(&steerPath[ns * VERTEX_SIZE], startPos, minTargetDist, 1000.0f)) + !InRangeYZX(&steerPath[ns*VERTEX_SIZE], startPos, minTargetDist, 1000.0f)) break; + ns++; } // Failed to find good point to steer to. if (ns >= nsteerPath) return false; - dtVcopy(steerPos, &steerPath[ns * VERTEX_SIZE]); + dtVcopy(steerPos, &steerPath[ns*VERTEX_SIZE]); steerPos[1] = startPos[1]; // keep Z value steerPosFlag = steerPathFlags[ns]; steerPosRef = steerPathPolys[ns]; @@ -873,8 +809,8 @@ bool PathGenerator::GetSteerTarget(float const* startPos, float const* endPos, } dtStatus PathGenerator::FindSmoothPath(float const* startPos, float const* endPos, - dtPolyRef const* polyPath, uint32 polyPathSize, - float* smoothPath, int* smoothPathSize, uint32 maxSmoothPathSize) + dtPolyRef const* polyPath, uint32 polyPathSize, + float* smoothPath, int* smoothPathSize, uint32 maxSmoothPathSize) { *smoothPathSize = 0; uint32 nsmoothPath = 0; @@ -884,13 +820,28 @@ dtStatus PathGenerator::FindSmoothPath(float const* startPos, float const* endPo uint32 npolys = polyPathSize; float iterPos[VERTEX_SIZE], targetPos[VERTEX_SIZE]; - if (DT_SUCCESS != _navMeshQuery->closestPointOnPolyBoundary(polys[0], startPos, iterPos)) - return DT_FAILURE; - if (DT_SUCCESS != _navMeshQuery->closestPointOnPolyBoundary(polys[npolys - 1], endPos, targetPos)) - return DT_FAILURE; + if (polyPathSize > 1) + { + // Pick the closest points on poly border + if (dtStatusFailed(_navMeshQuery->closestPointOnPolyBoundary(polys[0], startPos, iterPos))) + { + return DT_FAILURE; + } - dtVcopy(&smoothPath[nsmoothPath * VERTEX_SIZE], iterPos); + if (dtStatusFailed(_navMeshQuery->closestPointOnPolyBoundary(polys[npolys - 1], endPos, targetPos))) + { + return DT_FAILURE; + } + } + else + { + // Case where the path is on the same poly + dtVcopy(iterPos, startPos); + dtVcopy(targetPos, endPos); + } + + dtVcopy(&smoothPath[nsmoothPath*VERTEX_SIZE], iterPos); nsmoothPath++; // Move towards target a small advancement at a time until target reached or @@ -905,8 +856,8 @@ dtStatus PathGenerator::FindSmoothPath(float const* startPos, float const* endPo if (!GetSteerTarget(iterPos, targetPos, SMOOTH_PATH_SLOP, polys, npolys, steerPos, steerPosFlag, steerPosRef)) break; - bool endOfPath = (steerPosFlag & DT_STRAIGHTPATH_END); - bool offMeshConnection = (steerPosFlag & DT_STRAIGHTPATH_OFFMESH_CONNECTION); + bool endOfPath = (steerPosFlag & DT_STRAIGHTPATH_END) != 0; + bool offMeshConnection = (steerPosFlag & DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0; // Find movement delta. float delta[VERTEX_SIZE]; @@ -927,13 +878,24 @@ dtStatus PathGenerator::FindSmoothPath(float const* startPos, float const* endPo dtPolyRef visited[MAX_VISIT_POLY]; uint32 nvisited = 0; - _navMeshQuery->moveAlongSurface(polys[0], iterPos, moveTgt, &_filter, result, visited, (int*)&nvisited, MAX_VISIT_POLY); + if (dtStatusFailed(_navMeshQuery->moveAlongSurface(polys[0], iterPos, moveTgt, &_filter, result, visited, (int*)&nvisited, MAX_VISIT_POLY))) + { + return DT_FAILURE; + } npolys = FixupCorridor(polys, npolys, MAX_PATH_LENGTH, visited, nvisited); - _navMeshQuery->getPolyHeight(polys[0], result, &result[1]); + if (dtStatusFailed(_navMeshQuery->getPolyHeight(polys[0], result, &result[1]))) + sLog->outDebug(LOG_FILTER_MAPS, "PathGenerator::FindSmoothPath: Cannot find height at position X: %f Y: %f Z: %f for %lu", result[2], result[0], result[1], _source->GetGUID()); result[1] += 0.5f; dtVcopy(iterPos, result); + bool canCheckSlope = _slopeCheck && (GetPathType() & ~(PATHFIND_NOT_USING_PATH)); + + if (canCheckSlope && !IsSwimmableSegment(iterPos, steerPos) && !IsWalkableClimb(iterPos, steerPos)) + { + return DT_FAILURE; + } + // Handle end of path and off-mesh links when close enough. if (endOfPath && InRangeYZX(iterPos, steerPos, SMOOTH_PATH_SLOP, 1.0f)) { @@ -941,7 +903,7 @@ dtStatus PathGenerator::FindSmoothPath(float const* startPos, float const* endPo dtVcopy(iterPos, targetPos); if (nsmoothPath < maxSmoothPathSize) { - dtVcopy(&smoothPath[nsmoothPath * VERTEX_SIZE], iterPos); + dtVcopy(&smoothPath[nsmoothPath*VERTEX_SIZE], iterPos); nsmoothPath++; } break; @@ -960,22 +922,23 @@ dtStatus PathGenerator::FindSmoothPath(float const* startPos, float const* endPo } for (uint32 i = npos; i < npolys; ++i) - polys[i - npos] = polys[i]; + polys[i-npos] = polys[i]; npolys -= npos; // Handle the connection. - float startPos[VERTEX_SIZE], endPos[VERTEX_SIZE]; - if (DT_SUCCESS == _navMesh->getOffMeshConnectionPolyEndPoints(prevRef, polyRef, startPos, endPos)) + float connectionStartPos[VERTEX_SIZE], connectionEndPos[VERTEX_SIZE]; + if (dtStatusSucceed(_navMesh->getOffMeshConnectionPolyEndPoints(prevRef, polyRef, connectionStartPos, connectionEndPos))) { if (nsmoothPath < maxSmoothPathSize) { - dtVcopy(&smoothPath[nsmoothPath * VERTEX_SIZE], startPos); + dtVcopy(&smoothPath[nsmoothPath*VERTEX_SIZE], connectionStartPos); nsmoothPath++; } // Move position at the other side of the off-mesh link. - dtVcopy(iterPos, endPos); - _navMeshQuery->getPolyHeight(polys[0], iterPos, &iterPos[1]); + dtVcopy(iterPos, connectionEndPos); + if (dtStatusFailed(_navMeshQuery->getPolyHeight(polys[0], iterPos, &iterPos[1]))) + return DT_FAILURE; iterPos[1] += 0.5f; } } @@ -983,7 +946,7 @@ dtStatus PathGenerator::FindSmoothPath(float const* startPos, float const* endPo // Store results. if (nsmoothPath < maxSmoothPathSize) { - dtVcopy(&smoothPath[nsmoothPath * VERTEX_SIZE], iterPos); + dtVcopy(&smoothPath[nsmoothPath*VERTEX_SIZE], iterPos); nsmoothPath++; } } @@ -994,7 +957,59 @@ dtStatus PathGenerator::FindSmoothPath(float const* startPos, float const* endPo return nsmoothPath < MAX_POINT_PATH_LENGTH ? DT_SUCCESS : DT_FAILURE; } -bool PathGenerator::InRangeYZX(const float* v1, const float* v2, float r, float h) const +bool PathGenerator::IsWalkableClimb(float const* v1, float const* v2) const +{ + return IsWalkableClimb(v1[2], v1[0], v1[1], v2[2], v2[0], v2[1]); +} + +bool PathGenerator::IsWalkableClimb(float x, float y, float z, float destX, float destY, float destZ) const +{ + return IsWalkableClimb(x, y, z, destX, destY, destZ, _source->GetCollisionHeight()); +} + +/** + * @brief Check if a slope can be climbed based on source height + * This method is meant for short distances or linear paths + * + * @param x start x coord + * @param y start y coord + * @param z start z coord + * @param destX destination x coord + * @param destY destination y coord + * @param destZ destination z coord + * @param sourceHeight height of the source + * @return bool check if you can climb the path + */ +bool PathGenerator::IsWalkableClimb(float x, float y, float z, float destX, float destY, float destZ, float sourceHeight) +{ + float diffHeight = abs(destZ - z); + float reqHeight = GetRequiredHeightToClimb(x, y, z, destX, destY, destZ, sourceHeight); + // check walkable slopes, based on unit height + return diffHeight <= reqHeight; +} + +/** + * @brief Return the height of a slope that can be climbed based on source height + * This method is meant for short distances or linear paths + * + * @param x start x coord + * @param y start y coord + * @param z start z coord + * @param destX destination x coord + * @param destY destination y coord + * @param destZ destination z coord + * @param sourceHeight height of the source + * @return float the maximum height that a source can climb based on slope angle + */ +float PathGenerator::GetRequiredHeightToClimb(float x, float y, float z, float destX, float destY, float destZ, float sourceHeight) +{ + float slopeAngle = getSlopeAngleAbs(x, y, z, destX, destY, destZ); + float slopeAngleDegree = (slopeAngle * 180.0f / M_PI); + float climbableHeight = sourceHeight - (sourceHeight * (slopeAngleDegree / 100)); + return climbableHeight; +} + +bool PathGenerator::InRangeYZX(float const* v1, float const* v2, float r, float h) const { const float dx = v2[0] - v1[0]; const float dy = v2[1] - v1[1]; // elevation @@ -1012,3 +1027,140 @@ float PathGenerator::Dist3DSqr(G3D::Vector3 const& p1, G3D::Vector3 const& p2) c { return (p1 - p2).squaredLength(); } + +void PathGenerator::ShortenPathUntilDist(G3D::Vector3 const& target, float dist) +{ + if (GetPathType() == PATHFIND_BLANK || _pathPoints.size() < 2) + { + sLog->outError("PathGenerator::ReducePathLengthByDist called before path was successfully built"); + return; + } + + float const distSq = dist * dist; + + // the first point of the path must be outside the specified range + // (this should have really been checked by the caller...) + if ((_pathPoints[0] - target).squaredLength() < distSq) + return; + + // check if we even need to do anything + if ((*_pathPoints.rbegin() - target).squaredLength() >= distSq) + return; + + size_t i = _pathPoints.size()-1; + float x, y, z, collisionHeight = _source->GetCollisionHeight(); + // find the first i s.t.: + // - _pathPoints[i] is still too close + // - _pathPoints[i-1] is too far away + // => the end point is somewhere on the line between the two + while (1) + { + // we know that pathPoints[i] is too close already (from the previous iteration) + if ((_pathPoints[i-1] - target).squaredLength() >= distSq) + break; // bingo! + + bool canCheckSlope = _slopeCheck && (GetPathType() & ~(PATHFIND_NOT_USING_PATH)); + + // check if the shortened path is still in LoS with the target and it is walkable + _source->GetHitSpherePointFor({ _pathPoints[i - 1].x, _pathPoints[i - 1].y, _pathPoints[i - 1].z + collisionHeight }, x, y, z); + if (!_source->GetMap()->isInLineOfSight(x, y, z, _pathPoints[i - 1].x, _pathPoints[i - 1].y, _pathPoints[i - 1].z + collisionHeight, _source->GetPhaseMask(), LINEOFSIGHT_ALL_CHECKS) + || (canCheckSlope + && !IsSwimmableSegment(_source->GetPositionX(), _source->GetPositionY(), _source->GetPositionZ(), _pathPoints[i - 1].x, _pathPoints[i - 1].y, _pathPoints[i - 1].z) + && !IsWalkableClimb(_source->GetPositionX(), _source->GetPositionY(), _source->GetPositionZ(), _pathPoints[i - 1].x, _pathPoints[i - 1].y, _pathPoints[i - 1].z) + ) + ) + { + // whenver we find a point that is not valid anymore, simply use last valid path + _pathPoints.resize(i + 1); + return; + } + + if (!--i) + { + // no point found that fulfills the condition + _pathPoints[0] = _pathPoints[1]; + _pathPoints.resize(2); + return; + } + } + + // ok, _pathPoints[i] is too close, _pathPoints[i-1] is not, so our target point is somewhere between the two... + // ... settle for a guesstimate since i'm not confident in doing trig on every chase motion tick... + // (@todo review this) + _pathPoints[i] += (_pathPoints[i - 1] - _pathPoints[i]).direction() * (dist - (_pathPoints[i] - target).length()); + _pathPoints.resize(i+1); +} + +bool PathGenerator::IsInvalidDestinationZ(Unit const* target) const +{ + return (target->GetPositionZ() - GetActualEndPosition().z) > 5.0f; +} + +void PathGenerator::AddFarFromPolyFlags(bool startFarFromPoly, bool endFarFromPoly) +{ + if (startFarFromPoly) + { + _type = PathType(_type | PATHFIND_FARFROMPOLY_START); + } + if (endFarFromPoly) + { + _type = PathType(_type | PATHFIND_FARFROMPOLY_END); + } +} + +/** + * @brief predict if a certain segment is underwater and the unit can swim + * Must only be used for very short segments since this check doesn't work on + * long paths that alternate terrain and water. + * + * @param v1 + * @param v2 + * @return true + * @return false + */ +bool PathGenerator::IsSwimmableSegment(float const* v1, float const* v2, bool checkSwim) const +{ + return IsSwimmableSegment(v1[2], v1[0], v1[1], v2[2], v2[0], v2[1], checkSwim); +} + + +/** + * @brief predict if a certain segment is underwater and the unit can swim + * Must only be used for very short segments since this check doesn't work on + * long paths that alternate terrain and water. + * + * @param x + * @param y + * @param z + * @param destX + * @param destY + * @param destZ + * @param checkSwim also check if the unit can swim + * @return true if there's water at the end AND at the start of the segment + * @return false if there's no water at the end OR at the start of the segment + */ +bool PathGenerator::IsSwimmableSegment(float x, float y, float z, float destX, float destY, float destZ, bool checkSwim) const +{ + Creature const* _sourceCreature = _source->ToCreature(); + return _source->GetMap()->IsInWater(x, y, z) && + _source->GetMap()->IsInWater(destX, destY, destZ) && + (!checkSwim || !_sourceCreature || _sourceCreature->CanSwim()); +} + +bool PathGenerator::IsWaterPath(Movement::PointsArray _pathPoints) const +{ + bool waterPath = true; + // Check both start and end points, if they're both in water, then we can *safely* let the creature move + for (uint32 i = 0; i < _pathPoints.size(); ++i) + { + NavTerrain terrain = GetNavTerrain(_pathPoints[i].x, _pathPoints[i].y, _pathPoints[i].z); + // One of the points is not in the water + if (terrain != NAV_MAGMA && terrain != NAV_WATER) + { + waterPath = false; + break; + } + } + + return waterPath; +} diff --git a/src/server/game/Movement/MovementGenerators/PathGenerator.h b/src/server/game/Movement/MovementGenerators/PathGenerator.h index be7f46edf..1adea54ac 100644 --- a/src/server/game/Movement/MovementGenerators/PathGenerator.h +++ b/src/server/game/Movement/MovementGenerators/PathGenerator.h @@ -1,18 +1,7 @@ /* * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-GPL2 - * 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, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Copyright (C) 2008-2016 TrinityCore + * Copyright (C) 2005-2009 MaNGOS */ #ifndef _PATH_GENERATOR_H @@ -22,138 +11,164 @@ #include "DetourNavMesh.h" #include "DetourNavMeshQuery.h" #include "MoveSplineInitArgs.h" +#include #include "MMapFactory.h" #include "MMapManager.h" class Unit; +class WorldObject; -// 74*4.0f=296y number_of_points*interval = max_path_len +// 74*4.0f=296y number_of_points*interval = max_path_len // this is way more than actual evade range // I think we can safely cut those down even more -#define MAX_PATH_LENGTH 74 -#define MAX_POINT_PATH_LENGTH 74 +#define MAX_PATH_LENGTH 74 +#define MAX_POINT_PATH_LENGTH 74 -#define SMOOTH_PATH_STEP_SIZE 4.0f -#define SMOOTH_PATH_SLOP 0.3f -#define ALLOWED_DIST_FROM_POLY 2.5f -#define ADDED_Z_FOR_POLY_LOOKUP 0.3f +#define SMOOTH_PATH_STEP_SIZE 4.0f +#define SMOOTH_PATH_SLOP 0.3f #define DISALLOW_TIME_AFTER_FAIL 3 // secs -#define MAX_FIXABLE_Z_ERROR 7.0f - #define VERTEX_SIZE 3 #define INVALID_POLYREF 0 enum PathType { - PATHFIND_BLANK = 0x00, // path not built yet - PATHFIND_NORMAL = 0x01, // normal path - PATHFIND_SHORTCUT = 0x02, // travel through obstacles, terrain, air, etc (old behavior) - PATHFIND_INCOMPLETE = 0x04, // we have partial path to follow - getting closer to target - PATHFIND_NOPATH = 0x08, // no valid path at all or error in generating one - PATHFIND_NOT_USING_PATH = 0x10, // used when we are either flying/swiming or on map w/o mmaps - PATHFIND_SHORT = 0x20, // path is longer or equal to its limited path length + PATHFIND_BLANK = 0x00, // path not built yet + PATHFIND_NORMAL = 0x01, // normal path + PATHFIND_SHORTCUT = 0x02, // travel through obstacles, terrain, air, etc (old behavior) + PATHFIND_INCOMPLETE = 0x04, // we have partial path to follow - getting closer to target + PATHFIND_NOPATH = 0x08, // no valid path at all or error in generating one + PATHFIND_NOT_USING_PATH = 0x10, // used when we are either flying/swiming or on map w/o mmaps + PATHFIND_SHORT = 0x20, // path is longer or equal to its limited path length + PATHFIND_FARFROMPOLY_START = 0x40, // start position is far from the mmap poligon + PATHFIND_FARFROMPOLY_END = 0x80, // end positions is far from the mmap poligon + PATHFIND_FARFROMPOLY = PATHFIND_FARFROMPOLY_START | PATHFIND_FARFROMPOLY_END, // start or end positions are far from the mmap poligon }; class PathGenerator { -public: - explicit PathGenerator(Unit const* owner); - ~PathGenerator(); + public: + explicit PathGenerator(WorldObject const* owner); + ~PathGenerator(); - // Calculate the path from owner to given destination - // return: true if new path was calculated, false otherwise (no change needed) - bool CalculatePath(float destX, float destY, float destZ, bool forceDest = false); + // Calculate the path from owner to given destination + // return: true if new path was calculated, false otherwise (no change needed) + bool CalculatePath(float destX, float destY, float destZ, bool forceDest = false); + bool CalculatePath(float x, float y, float z, float destX, float destY, float destZ, bool forceDest); + [[nodiscard]] bool IsInvalidDestinationZ(Unit const* target) const; + [[nodiscard]] bool IsWalkableClimb(float const* v1, float const* v2) const; + [[nodiscard]] bool IsWalkableClimb(float x, float y, float z, float destX, float destY, float destZ) const; + [[nodiscard]] static bool IsWalkableClimb(float x, float y, float z, float destX, float destY, float destZ, float sourceHeight); + [[nodiscard]] bool IsWaterPath(Movement::PointsArray _pathPoints) const; + [[nodiscard]] bool IsSwimmableSegment(float const* v1, float const* v2, bool checkSwim = true) const; + [[nodiscard]] bool IsSwimmableSegment(float x, float y, float z, float destX, float destY, float destZ, bool checkSwim = true) const; + [[nodiscard]] static float GetRequiredHeightToClimb(float x, float y, float z, float destX, float destY, float destZ, float sourceHeight); - // option setters - use optional - void SetUseStraightPath(bool useStraightPath) { _useStraightPath = useStraightPath; } - void SetPathLengthLimit(float distance) { _pointPathLimit = std::min(uint32(distance / SMOOTH_PATH_STEP_SIZE), MAX_POINT_PATH_LENGTH); } + // option setters - use optional - // result getters - G3D::Vector3 const& GetStartPosition() const { return _startPosition; } - G3D::Vector3 const& GetEndPosition() const { return _endPosition; } - G3D::Vector3 const& GetActualEndPosition() const { return _actualEndPosition; } + // when set, it skips paths with too high slopes (doesn't work with StraightPath enabled) + void SetSlopeCheck(bool checkSlope) { _slopeCheck = checkSlope; } + void SetUseStraightPath(bool useStraightPath) { _useStraightPath = useStraightPath; } + void SetPathLengthLimit(float distance) { _pointPathLimit = std::min(uint32(distance/SMOOTH_PATH_STEP_SIZE), MAX_POINT_PATH_LENGTH); } + void SetUseRaycast(bool useRaycast) { _useRaycast = useRaycast; } - Movement::PointsArray const& GetPath() const { return _pathPoints; } + // result getters + G3D::Vector3 const& GetStartPosition() const { return _startPosition; } + G3D::Vector3 const& GetEndPosition() const { return _endPosition; } + G3D::Vector3 const& GetActualEndPosition() const { return _actualEndPosition; } - PathType GetPathType() const { return _type; } - float getPathLength() const - { - float len = 0.0f; - float dx, dy, dz; - uint32 size = _pathPoints.size(); - if (size) + Movement::PointsArray const& GetPath() const { return _pathPoints; } + + PathType GetPathType() const { return _type; } + + // shortens the path until the destination is the specified distance from the target point + void ShortenPathUntilDist(G3D::Vector3 const& point, float dist); + + float getPathLength() const { - dx = _pathPoints[0].x - _startPosition.x; - dy = _pathPoints[0].y - _startPosition.y; - dz = _pathPoints[0].z - _startPosition.z; - len += sqrt( dx * dx + dy * dy + dz * dz ); - } - else + float len = 0.0f; + float dx, dy, dz; + uint32 size = _pathPoints.size(); + if (size) + { + dx = _pathPoints[0].x - _startPosition.x; + dy = _pathPoints[0].y - _startPosition.y; + dz = _pathPoints[0].z - _startPosition.z; + len += sqrt( dx * dx + dy * dy + dz * dz ); + } + else + { + return len; + } + + for (uint32 i = 1; i < size; ++i) + { + dx = _pathPoints[i].x - _pathPoints[i - 1].x; + dy = _pathPoints[i].y - _pathPoints[i - 1].y; + dz = _pathPoints[i].z - _pathPoints[i - 1].z; + len += sqrt( dx * dx + dy * dy + dz * dz ); + } return len; - for (uint32 i = 1; i < size; ++i) - { - dx = _pathPoints[i].x - _pathPoints[i - 1].x; - dy = _pathPoints[i].y - _pathPoints[i - 1].y; - dz = _pathPoints[i].z - _pathPoints[i - 1].z; - len += sqrt( dx * dx + dy * dy + dz * dz ); } - return len; - } -private: - dtPolyRef _pathPolyRefs[MAX_PATH_LENGTH]; // array of detour polygon references - uint32 _polyLength; // number of polygons in the path + private: + dtPolyRef _pathPolyRefs[MAX_PATH_LENGTH]; // array of detour polygon references + uint32 _polyLength; // number of polygons in the path - Movement::PointsArray _pathPoints; // our actual (x,y,z) path to the target - PathType _type; // tells what kind of path this is + Movement::PointsArray _pathPoints; // our actual (x,y,z) path to the target + PathType _type; // tells what kind of path this is - bool _useStraightPath; // type of path will be generated - bool _forceDestination; // when set, we will always arrive at given point - uint32 _pointPathLimit; // limit point path size; min(this, MAX_POINT_PATH_LENGTH) + bool _useStraightPath; // type of path will be generated (do not use it for movement paths) + bool _forceDestination; // when set, we will always arrive at given point + bool _slopeCheck; // when set, it skips paths with too high slopes (doesn't work with _useStraightPath) + uint32 _pointPathLimit; // limit point path size; min(this, MAX_POINT_PATH_LENGTH) + bool _useRaycast; // use raycast if true for a straight line path - G3D::Vector3 _startPosition; // {x, y, z} of current location - G3D::Vector3 _endPosition; // {x, y, z} of the destination - G3D::Vector3 _actualEndPosition; // {x, y, z} of the closest possible point to given destination + G3D::Vector3 _startPosition; // {x, y, z} of current location + G3D::Vector3 _endPosition; // {x, y, z} of the destination + G3D::Vector3 _actualEndPosition; // {x, y, z} of the closest possible point to given destination - Unit const* const _sourceUnit; // the unit that is moving - dtNavMesh const* _navMesh; // the nav mesh - dtNavMeshQuery const* _navMeshQuery; // the nav mesh query used to find the path + WorldObject const* const _source; // the object that is moving + dtNavMesh const* _navMesh; // the nav mesh + dtNavMeshQuery const* _navMeshQuery; // the nav mesh query used to find the path - dtQueryFilter _filter; // use single filter for all movements, update it when needed + dtQueryFilterExt _filter; // use single filter for all movements, update it when needed - void SetStartPosition(G3D::Vector3 const& point) { _startPosition = point; } - void SetEndPosition(G3D::Vector3 const& point) { _actualEndPosition = point; _endPosition = point; } - void SetActualEndPosition(G3D::Vector3 const& point) { _actualEndPosition = point; } + void SetStartPosition(G3D::Vector3 const& point) { _startPosition = point; } + void SetEndPosition(G3D::Vector3 const& point) { _actualEndPosition = point; _endPosition = point; } + void SetActualEndPosition(G3D::Vector3 const& point) { _actualEndPosition = point; } + void NormalizePath(); - void Clear() - { - _polyLength = 0; - _pathPoints.clear(); - } + void Clear() + { + _polyLength = 0; + _pathPoints.clear(); + } - bool InRange(G3D::Vector3 const& p1, G3D::Vector3 const& p2, float r, float h) const; - float Dist3DSqr(G3D::Vector3 const& p1, G3D::Vector3 const& p2) const; - bool InRangeYZX(float const* v1, float const* v2, float r, float h) const; + bool InRange(G3D::Vector3 const& p1, G3D::Vector3 const& p2, float r, float h) const; + float Dist3DSqr(G3D::Vector3 const& p1, G3D::Vector3 const& p2) const; + bool InRangeYZX(float const* v1, float const* v2, float r, float h) const; - dtPolyRef GetPathPolyByPosition(dtPolyRef const* polyPath, uint32 polyPathSize, float const* Point, float* Distance = nullptr) const; - dtPolyRef GetPolyByLocation(float* Point, float* Distance) const; - bool HaveTile(G3D::Vector3 const& p) const; + dtPolyRef GetPathPolyByPosition(dtPolyRef const* polyPath, uint32 polyPathSize, float const* Point, float* Distance = nullptr) const; + dtPolyRef GetPolyByLocation(float const* Point, float* Distance) const; + bool HaveTile(G3D::Vector3 const& p) const; - void BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 const& endPos, ACE_RW_Thread_Mutex& lock); - void BuildPointPath(float const* startPoint, float const* endPoint); - void BuildShortcut(); + void BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 const& endPos); + void BuildPointPath(float const* startPoint, float const* endPoint); + void BuildShortcut(); - NavTerrain GetNavTerrain(float x, float y, float z); - void CreateFilter(); - void UpdateFilter(); + NavTerrain GetNavTerrain(float x, float y, float z) const; + void CreateFilter(); + void UpdateFilter(); - // smooth path aux functions - uint32 FixupCorridor(dtPolyRef* path, uint32 npath, uint32 maxPath, dtPolyRef const* visited, uint32 nvisited); - bool GetSteerTarget(float const* startPos, float const* endPos, float minTargetDist, dtPolyRef const* path, uint32 pathSize, float* steerPos, - unsigned char& steerPosFlag, dtPolyRef& steerPosRef); - dtStatus FindSmoothPath(float const* startPos, float const* endPos, - dtPolyRef const* polyPath, uint32 polyPathSize, - float* smoothPath, int* smoothPathSize, uint32 smoothPathMaxSize); + // smooth path aux functions + uint32 FixupCorridor(dtPolyRef* path, uint32 npath, uint32 maxPath, dtPolyRef const* visited, uint32 nvisited); + bool GetSteerTarget(float const* startPos, float const* endPos, float minTargetDist, dtPolyRef const* path, uint32 pathSize, float* steerPos, + unsigned char& steerPosFlag, dtPolyRef& steerPosRef); + dtStatus FindSmoothPath(float const* startPos, float const* endPos, + dtPolyRef const* polyPath, uint32 polyPathSize, + float* smoothPath, int* smoothPathSize, uint32 smoothPathMaxSize); + + void AddFarFromPolyFlags(bool startFarFromPoly, bool endFarFromPoly); }; #endif diff --git a/src/server/game/Movement/MovementGenerators/RandomMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/RandomMovementGenerator.cpp index ea33beb56..7dafdfcb9 100644 --- a/src/server/game/Movement/MovementGenerators/RandomMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/RandomMovementGenerator.cpp @@ -62,7 +62,7 @@ void RandomMovementGenerator::_setRandomLocation(Creature* creature) } float ground = INVALID_HEIGHT; - float levelZ = map->GetWaterOrGroundLevel(creature->GetPhaseMask(), x, y, z + 4.0f, &ground); + float levelZ = creature->GetMapWaterOrGroundLevel(x, y, z, &ground); float newZ = INVALID_HEIGHT; // flying creature @@ -71,15 +71,11 @@ void RandomMovementGenerator::_setRandomLocation(Creature* creature) // point underwater else if (ground < levelZ) { - if (!creature->CanSwim()) + if (!creature->CanEnterWater()) { - if (ground < levelZ - 1.5f) - { - _validPointsVector[_currentPoint].erase(randomIter); - _preComputedPaths.erase(pathIdx); - return; - } - levelZ = ground; + _validPointsVector[_currentPoint].erase(randomIter); + _preComputedPaths.erase(pathIdx); + return; } else { @@ -99,6 +95,8 @@ void RandomMovementGenerator::_setRandomLocation(Creature* creature) } } + creature->UpdateAllowedPositionZ(x, y, newZ); + if (newZ > INVALID_HEIGHT) { // flying / swiming creature - dest not in los diff --git a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp index 8f6b7766f..44b5bbb41 100644 --- a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp @@ -4,373 +4,212 @@ * Copyright (C) 2005-2009 MaNGOS */ -#include "ByteBuffer.h" #include "TargetedMovementGenerator.h" -#include "Errors.h" #include "Creature.h" #include "CreatureAI.h" -#include "World.h" #include "MoveSplineInit.h" -#include "MoveSpline.h" #include "Player.h" #include "Spell.h" -#include "BattlegroundRV.h" -#include "VehicleDefines.h" #include "Transport.h" -#include "MapManager.h" +#include "Pet.h" -#include - -template -void TargetedMovementGeneratorMedium::_setTargetLocation(T* owner, bool initial) +static bool IsMutualChase(Unit* owner, Unit* target) { - if (!i_target.isValid() || !i_target->IsInWorld() || !owner->IsInMap(i_target.getTarget())) - return; + if (target->GetMotionMaster()->GetCurrentMovementGeneratorType() != CHASE_MOTION_TYPE) + return false; - if (owner->HasUnitState(UNIT_STATE_NOT_MOVE)) - return; - - if (owner->HasUnitState(UNIT_STATE_CASTING) && !owner->CanMoveDuringChannel()) - return; - - float x, y, z; - bool isPlayerPet = owner->IsPet() && IS_PLAYER_GUID(owner->GetOwnerGUID()); - bool sameTransport = owner->GetTransport() && owner->GetTransport() == i_target->GetTransport(); - if (owner->GetMapId() == 631 && owner->GetTransport() && owner->GetTransport()->IsMotionTransport() && i_target->GetTransport() && i_target->GetTransport()->IsMotionTransport()) // for ICC, if both on a motion transport => don't use mmaps - sameTransport = owner->GetTypeId() == TYPEID_UNIT && i_target->isInAccessiblePlaceFor(owner->ToCreature()); - bool useMMaps = MMAP::MMapFactory::IsPathfindingEnabled(owner->FindMap()) && !sameTransport; - bool forceDest = - // (owner->FindMap() && owner->FindMap()->IsDungeon() && !isPlayerPet) || // force in instances to prevent exploiting - (owner->GetTypeId() == TYPEID_UNIT && ((owner->IsPet() && owner->HasUnitState(UNIT_STATE_FOLLOW)))) || // allow pets following their master to cheat while generating paths - // ((Creature*)owner)->isWorldBoss() || ((Creature*)owner)->IsDungeonBoss())) || // force for all bosses, even not in instances - (owner->GetMapId() == 572 && (owner->GetPositionX() < 1275.0f || i_target->GetPositionX() < 1275.0f)) || // pussywizard: Ruins of Lordaeron - special case (acid) - sameTransport || // nothing to comment, can't find path on transports so allow it - (i_target->GetTypeId() == TYPEID_PLAYER && i_target->ToPlayer()->IsGameMaster()) // for .npc follow*/ - ; // closes "bool forceDest", that way it is more appropriate, so we can comment out crap whenever we need to - bool forcePoint = ((!isPlayerPet || owner->GetMapId() == 618) && (forceDest || !useMMaps)) || sameTransport; - - if (owner->GetTypeId() == TYPEID_UNIT && !i_target->isInAccessiblePlaceFor(owner->ToCreature()) && !sameTransport && !forceDest && !forcePoint) - { - owner->ToCreature()->SetCannotReachTarget(true); - return; - } - - lastOwnerXYZ.Relocate(owner->GetPositionX(), owner->GetPositionY(), owner->GetPositionZ()); - lastTargetXYZ.Relocate(i_target->GetPositionX(), i_target->GetPositionY(), i_target->GetPositionZ()); - - if (!i_offset) - { - if (i_target->IsWithinDistInMap(owner, CONTACT_DISTANCE)) - return; - - float allowedRange = MELEE_RANGE; - if ((!initial || (owner->movespline->Finalized() && this->GetMovementGeneratorType() == CHASE_MOTION_TYPE)) && i_target->IsWithinMeleeRange(owner, allowedRange) && i_target->IsWithinLOS(owner->GetPositionX(), owner->GetPositionY(), owner->GetPositionZ())) - return; - - bool inRange = i_target->GetRandomContactPoint(owner, x, y, z, forcePoint); - if (useMMaps && !inRange && (!isPlayerPet || i_target->GetPositionZ() - z > 50.0f)) - { - //useMMaps = false; - owner->m_targetsNotAcceptable[i_target->GetGUID()] = MMapTargetData(sWorld->GetGameTime() + DISALLOW_TIME_AFTER_FAIL, owner, i_target.getTarget()); - return; - } - - // to nearest contact position - i_target->GetContactPoint(owner, x, y, z); - } - else - { - float dist; - float size; - - // Pets need special handling. - // We need to subtract GetObjectSize() because it gets added back further down the chain - // and that makes pets too far away. Subtracting it allows pets to properly - // be (GetCombatReach() + i_offset) away. - // Only applies when i_target is pet's owner otherwise pets and mobs end up - // doing a "dance" while fighting - if (owner->IsPet() && i_target->GetTypeId() == TYPEID_PLAYER) - { - dist = i_target->GetCombatReach(); - size = i_target->GetCombatReach() - i_target->GetObjectSize(); - } - else - { - dist = i_offset; - size = owner->GetObjectSize(); - } - - if ((!initial || (owner->movespline->Finalized() && this->GetMovementGeneratorType() == CHASE_MOTION_TYPE)) && i_target->IsWithinDistInMap(owner, dist) && i_target->IsWithinLOS(owner->GetPositionX(), owner->GetPositionY(), owner->GetPositionZ())) - return; - - float angle = i_angle; - - if (i_target->GetTypeId() == TYPEID_PLAYER) - { - Creature* creature = owner->ToCreature(); - - if (creature && creature->GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET) - { - // fix distance and angle for vanity pets - dist = 0.3f; - angle = PET_FOLLOW_ANGLE + M_PI * 0.2f; - size = i_target->GetCombatReach() - i_target->GetObjectSize(); - } - } - - // Xinef: Fix follow angle for hostile units - if (angle == 0.0f && owner->GetVictim() && owner->GetVictim()->GetGUID() == i_target->GetGUID()) - angle = MapManager::NormalizeOrientation(i_target->GetAngle(owner) - i_target->GetOrientation()); - // to at i_offset distance from target and i_angle from target facing - bool inRange = i_target->GetClosePoint(x, y, z, size, dist, angle, owner, forcePoint); - if (!inRange && (forceDest || !useMMaps) && owner->HasUnitState(UNIT_STATE_FOLLOW) && fabs(i_target->GetPositionZ() - z) > 25.0f) - { - x = i_target->GetPositionX(); - y = i_target->GetPositionY(); - z = i_target->GetPositionZ(); - } - } - - D::_addUnitStateMove(owner); - i_targetReached = false; - i_recalculateTravel = false; - - Movement::MoveSplineInit init(owner); - - if (useMMaps) // pussywizard - { - if (!i_path) - i_path = new PathGenerator(owner); - - if (!forceDest) - { - if (owner->GetMapId() == 618) // pussywizard: 618 Ring of Valor - { - if (Map* map = owner->FindMap()) - if (Battleground* bg = ((BattlegroundMap*)map)->GetBG()) - { - Position dest = {x, y, z, 0.0f}; - if (GameObject* pillar = ((BattlegroundRV*)bg)->GetPillarAtPosition(&dest)) - { - if ((pillar->GetGoState() == GO_STATE_READY && pillar->ToTransport()->GetPathProgress() == 0) || owner->GetPositionZ() > 31.0f || owner->GetTransGUID() == pillar->GetGUID()) - { - if (pillar->GetGoState() == GO_STATE_READY && pillar->ToTransport()->GetPathProgress() == 0) - z = std::max(z, 28.28f); - else - z = i_target->GetPositionZ(); - - init.MoveTo(x, y, z); - if (i_angle == 0.f) - init.SetFacing(i_target.getTarget()); - init.SetWalk(((D*)this)->EnableWalking()); - init.Launch(); - return; - } - if (pillar->GetGoState() == GO_STATE_ACTIVE || (pillar->GetGoState() == GO_STATE_READY && pillar->ToTransport()->GetPathProgress() > 0)) - { - Position pos; - owner->GetFirstCollisionPositionForTotem(pos, owner->GetExactDist2d(i_target.getTarget()), owner->GetAngle(i_target.getTarget()) - owner->GetOrientation(), false); - x = pos.GetPositionX(); - y = pos.GetPositionY(); - z = 28.28f; - } - } - } - } - } - - if (!forceDest && getMSTimeDiff(lastPathingFailMSTime, World::GetGameTimeMS()) < 1000) - { - lastOwnerXYZ.Relocate(-5000.0f, -5000.0f, -5000.0f); - return; - } - - bool result = i_path->CalculatePath(x, y, z, forceDest); - if (result) - { - float maxDist = MELEE_RANGE + owner->GetMeleeReach() + i_target->GetMeleeReach(); - if (!forceDest && (i_path->GetPathType() & PATHFIND_NOPATH || (!i_offset && !isPlayerPet && i_target->GetExactDistSq(i_path->GetActualEndPosition().x, i_path->GetActualEndPosition().y, i_path->GetActualEndPosition().z) > maxDist * maxDist))) - { - if (owner->GetTypeId() == TYPEID_UNIT) - { - owner->ToCreature()->SetCannotReachTarget(false); - } - - lastPathingFailMSTime = World::GetGameTimeMS(); - owner->m_targetsNotAcceptable[i_target->GetGUID()] = MMapTargetData(sWorld->GetGameTime() + DISALLOW_TIME_AFTER_FAIL, owner, i_target.getTarget()); - return; - } - else - { - owner->m_targetsNotAcceptable.erase(i_target->GetGUID()); - owner->AddUnitState(UNIT_STATE_CHASE); - - init.MovebyPath(i_path->GetPath()); - if (i_angle == 0.f) - init.SetFacing(i_target.getTarget()); - init.SetWalk(((D*)this)->EnableWalking()); - init.Launch(); - return; - } - } - else - { - // evade first - if (owner->GetTypeId() == TYPEID_UNIT) - { - owner->ToCreature()->SetCannotReachTarget(true); - } - // then use normal MoveTo - if we have to - } - } - - owner->AddUnitState(UNIT_STATE_CHASE); - - init.MoveTo(x, y, z); - // Using the same condition for facing target as the one that is used for SetInFront on movement end - // - applies to ChaseMovementGenerator mostly - if (i_angle == 0.f) - init.SetFacing(i_target.getTarget()); - - init.SetWalk(((D*)this)->EnableWalking()); - init.Launch(); + return target->GetVictim() == owner->GetVictim(); } -template -bool TargetedMovementGeneratorMedium::DoUpdate(T* owner, uint32 time_diff) +template +bool ChaseMovementGenerator::PositionOkay(T* owner, Unit* target, std::optional maxDistance, std::optional angle) { - if (!i_target.isValid() || !i_target->IsInWorld()) + float const distSq = owner->GetExactDistSq(target); + if (maxDistance && distSq > G3D::square(*maxDistance)) + return false; + if (angle && !angle->IsAngleOkay(target->GetRelativeAngle(owner))) + return false; + if (!owner->IsWithinLOSInMap(target)) + return false; + return true; +} + +template +bool ChaseMovementGenerator::DoUpdate(T* owner, uint32 time_diff) +{ + if (!i_target.isValid() || !i_target->IsInWorld() || !owner->IsInMap(i_target.getTarget())) return false; if (!owner || !owner->IsAlive()) return false; - if (owner->HasUnitState(UNIT_STATE_NOT_MOVE)) + // the owner might be unable to move (rooted or casting), or we have lost the target, pause movement + if (owner->HasUnitState(UNIT_STATE_NOT_MOVE) || HasLostTarget(owner) || (owner->GetTypeId() == TYPEID_UNIT && owner->ToCreature()->IsMovementPreventedByCasting())) { - D::_clearUnitStateMove(owner); - return true; - } - - // prevent movement while casting spells with cast time or channel time - if (owner->HasUnitState(UNIT_STATE_CASTING) && !owner->CanMoveDuringChannel()) - { - bool stop = true; - if (Spell* spell = owner->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) - if (!(spell->GetSpellInfo()->ChannelInterruptFlags & (AURA_INTERRUPT_FLAG_MOVE | AURA_INTERRUPT_FLAG_TURNING)) && !(spell->GetSpellInfo()->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT)) - stop = false; - - if (stop) + i_path = nullptr; + owner->StopMoving(); + _lastTargetPosition.reset(); + if (Creature* cOwner = owner->ToCreature()) { - // Xinef: delay distance recheck in case of succeeding casts - i_recheckDistance.Reset(300); - if (!owner->IsStopped()) - owner->StopMoving(); - - return true; + cOwner->SetCannotReachTarget(false); } - } - // prevent crash after creature killed pet - if (static_cast(this)->_lostTarget(owner)) - { - D::_clearUnitStateMove(owner); return true; } - i_recheckDistanceForced.Update(time_diff); - if (i_recheckDistanceForced.Passed()) - { - i_recheckDistanceForced.Reset(2500); - lastOwnerXYZ.Relocate(-5000.0f, -5000.0f, -5000.0f); - } + Creature* cOwner = owner->ToCreature(); + + bool forceDest = + //(cOwner && (cOwner->isWorldBoss() || cOwner->IsDungeonBoss())) || // force for all bosses, even not in instances + (i_target->GetTypeId() == TYPEID_PLAYER && i_target->ToPlayer()->IsGameMaster()) || // for .npc follow + (owner->CanFly()) + ; // closes "bool forceDest", that way it is more appropriate, so we can comment out crap whenever we need to + + + Unit* target = i_target.getTarget(); + + bool const mutualChase = IsMutualChase(owner, target); + float const hitboxSum = owner->GetCombatReach() + target->GetCombatReach(); + float const minTarget = (_range ? _range->MinTolerance : 0.0f) + hitboxSum; + float const maxRange = _range ? _range->MaxRange + hitboxSum : owner->GetMeleeRange(target); // melee range already includes hitboxes + float const maxTarget = _range ? _range->MaxTolerance + hitboxSum : CONTACT_DISTANCE + hitboxSum; + std::optional angle = mutualChase ? std::optional() : _angle; i_recheckDistance.Update(time_diff); if (i_recheckDistance.Passed()) { - i_recheckDistance.Reset(50); - //More distance let have better performance, less distance let have more sensitive reaction at target move. - float allowed_dist_sq = i_target->GetObjectSize() + owner->GetObjectSize() + MELEE_RANGE - 0.5f; + i_recheckDistance.Reset(100); - // xinef: if offset is negative (follow distance is smaller than just object sizes), reduce minimum allowed distance which is based purely on object sizes - if (i_offset < 0.0f) + if (i_recalculateTravel && PositionOkay(owner, target, _movingTowards ? maxTarget : std::optional(), angle)) { - allowed_dist_sq += i_offset; - allowed_dist_sq = std::max(0.0f, allowed_dist_sq); + i_recalculateTravel = false; + i_path = nullptr; + if (Creature* cOwner = owner->ToCreature()) + { + cOwner->SetCannotReachTarget(false); + } + + owner->StopMoving(); + owner->SetInFront(target); + MovementInform(owner); + return true; } - - allowed_dist_sq *= allowed_dist_sq; - - G3D::Vector3 dest = owner->movespline->FinalDestination(); - if (owner->movespline->onTransport) - if (TransportBase* transport = owner->GetDirectTransport()) - transport->CalculatePassengerPosition(dest.x, dest.y, dest.z); - - float dist = (dest - G3D::Vector3(i_target->GetPositionX(), i_target->GetPositionY(), i_target->GetPositionZ())).squaredLength(); - float targetMoveDistSq = i_target->GetExactDistSq(&lastTargetXYZ); - if (dist >= allowed_dist_sq || (!i_offset && targetMoveDistSq >= 1.5f * 1.5f)) - if (targetMoveDistSq >= 0.1f * 0.1f || owner->GetExactDistSq(&lastOwnerXYZ) >= 0.1f * 0.1f) - _setTargetLocation(owner, false); } - if (owner->movespline->Finalized()) + bool hasMoveState = owner->HasUnitState(UNIT_STATE_CHASE_MOVE) || owner->HasUnitState(UNIT_STATE_FOLLOW_MOVE); + if (hasMoveState && owner->movespline->Finalized()) { - static_cast(this)->MovementInform(owner); - if (i_angle == 0.f && !owner->HasInArc(0.01f, i_target.getTarget())) - owner->SetInFront(i_target.getTarget()); + i_recalculateTravel = false; + i_path = nullptr; + owner->ClearUnitState(UNIT_STATE_CHASE_MOVE); + owner->SetInFront(target); + MovementInform(owner); - if (!i_targetReached) + if (owner->IsWithinMeleeRange(this->i_target.getTarget())) + owner->Attack(this->i_target.getTarget(), true); + } + + if (_lastTargetPosition && i_target->GetPosition() == _lastTargetPosition.value() && mutualChase == _mutualChase) + return true; + + _lastTargetPosition = i_target->GetPosition(); + + if (PositionOkay(owner, target, maxRange, angle) && !hasMoveState) + return true; + + bool moveToward = !owner->IsInDist(target, maxRange); + _mutualChase = mutualChase; + + if (owner->HasUnitState(UNIT_STATE_CHASE_MOVE)) + { + // can we get to the target? + if (cOwner && !target->isInAccessiblePlaceFor(cOwner)) { - i_targetReached = true; - static_cast(this)->_reachTarget(owner); + cOwner->SetCannotReachTarget(true); + cOwner->StopMoving(); + return true; } } + + if (!i_path || moveToward != _movingTowards) + i_path = new PathGenerator(owner); + + float x, y, z; + bool shortenPath; + // if we want to move toward the target and there's no fixed angle... + if (moveToward && !angle) + { + // ...we'll pathfind to the center, then shorten the path + target->GetPosition(x, y, z); + shortenPath = true; + } else { - if (i_recalculateTravel) - _setTargetLocation(owner, false); + if (target->GetTypeId() == TYPEID_PLAYER) + shortenPath = false; + // otherwise, we fall back to nearpoint finding + target->GetNearPoint(owner, x, y, z, (moveToward ? maxTarget : minTarget) - hitboxSum, 0, angle ? target->ToAbsoluteAngle(angle->RelativeAngle) : target->GetAngle(owner)); + shortenPath = false; } - Unit* pOwner = owner->GetCharmerOrOwner(); + if (owner->IsHovering()) + owner->UpdateAllowedPositionZ(x, y, z); - if (pOwner && pOwner->GetTypeId() == TYPEID_PLAYER) + bool success = i_path->CalculatePath(x, y, z, forceDest); + if (!success || i_path->GetPathType() & PATHFIND_NOPATH) { - // Update pet speed for players in order to avoid stuttering - if (pOwner->IsFlying()) - owner->UpdateSpeed(MOVE_FLIGHT, true); - else - owner->UpdateSpeed(MOVE_RUN, true); + if (cOwner) + cOwner->SetCannotReachTarget(true); + owner->StopMoving(); + return true; } + if (shortenPath) + i_path->ShortenPathUntilDist(G3D::Vector3(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ()), maxTarget); + + if (cOwner) + cOwner->SetCannotReachTarget(false); + + bool walk = false; + if (cOwner && !cOwner->IsPet()) + { + walk = owner->IsWalking(); + } + + owner->AddUnitState(UNIT_STATE_CHASE_MOVE); + i_recalculateTravel = true; + + Movement::MoveSplineInit init(owner); + init.MovebyPath(i_path->GetPath()); + init.SetFacing(target); + init.SetWalk(walk); + init.Launch(); + return true; } //-----------------------------------------------// -template -void ChaseMovementGenerator::_reachTarget(T* owner) -{ - if (owner->IsWithinMeleeRange(this->i_target.getTarget())) - owner->Attack(this->i_target.getTarget(), true); -} - template<> void ChaseMovementGenerator::DoInitialize(Player* owner) { - owner->AddUnitState(UNIT_STATE_CHASE | UNIT_STATE_CHASE_MOVE); - _setTargetLocation(owner, true); + _lastTargetPosition.reset(); + owner->AddUnitState(UNIT_STATE_CHASE); } template<> void ChaseMovementGenerator::DoInitialize(Creature* owner) { + _lastTargetPosition.reset(); owner->SetWalk(false); - owner->AddUnitState(UNIT_STATE_CHASE | UNIT_STATE_CHASE_MOVE); - _setTargetLocation(owner, true); + owner->AddUnitState(UNIT_STATE_CHASE); } template void ChaseMovementGenerator::DoFinalize(T* owner) { owner->ClearUnitState(UNIT_STATE_CHASE | UNIT_STATE_CHASE_MOVE); + if (Creature* cOwner = owner->ToCreature()) + cOwner->SetCannotReachTarget(false); } template @@ -380,29 +219,143 @@ void ChaseMovementGenerator::DoReset(T* owner) } template -void ChaseMovementGenerator::MovementInform(T* /*unit*/) +void ChaseMovementGenerator::MovementInform(T* owner) { -} + if (owner->GetTypeId() != TYPEID_UNIT) + return; -template<> -void ChaseMovementGenerator::MovementInform(Creature* unit) -{ // Pass back the GUIDLow of the target. If it is pet's owner then PetAI will handle - if (unit->AI()) - unit->AI()->MovementInform(CHASE_MOTION_TYPE, i_target.getTarget()->GetGUIDLow()); + if (CreatureAI* AI = owner->ToCreature()->AI()) + AI->MovementInform(CHASE_MOTION_TYPE, i_target.getTarget()->GetGUIDLow()); } //-----------------------------------------------// -template<> -bool FollowMovementGenerator::EnableWalking() const + +template +bool FollowMovementGenerator::PositionOkay(T* owner, Unit* target, float range, std::optional angle) { - return i_target.isValid() && (i_target->IsWalking() || i_target->movespline->isWalking()); + if (owner->GetExactDistSq(target) > G3D::square(owner->GetCombatReach() + target->GetCombatReach() + range)) + return false; + + return !owner->IsPet() || !angle || angle->IsAngleOkay(target->GetRelativeAngle(owner)); } -template<> -bool FollowMovementGenerator::EnableWalking() const +template +bool FollowMovementGenerator::DoUpdate(T* owner, uint32 time_diff) { - return false; + if (!i_target.isValid() || !i_target->IsInWorld() || !owner->IsInMap(i_target.getTarget())) + return false; + + if (!owner || !owner->IsAlive()) + return false; + + Creature* cOwner = owner->ToCreature(); + Unit* target = i_target.getTarget(); + + // the owner might be unable to move (rooted or casting), or we have lost the target, pause movement + if (owner->HasUnitState(UNIT_STATE_NOT_MOVE) || (cOwner && owner->ToCreature()->IsMovementPreventedByCasting())) + { + i_path = nullptr; + owner->StopMoving(); + _lastTargetPosition.reset(); + return true; + } + + bool followingMaster = false; + Pet* oPet = owner->ToPet(); + if (oPet) + { + if (target->GetGUID() == oPet->GetOwnerGUID()) + followingMaster = true; + } + + bool forceDest = + (followingMaster) || // allow pets following their master to cheat while generating paths + (i_target->GetTypeId() == TYPEID_PLAYER && i_target->ToPlayer()->IsGameMaster()) // for .npc follow + ; // closes "bool forceDest", that way it is more appropriate, so we can comment out crap whenever we need to + + + i_recheckDistance.Update(time_diff); + if (i_recheckDistance.Passed()) + { + i_recheckDistance.Reset(100); + + if (i_recalculateTravel && PositionOkay(owner, target, _range, _angle)) + { + i_recalculateTravel = false; + i_path = nullptr; + owner->StopMoving(); + _lastTargetPosition.reset(); + MovementInform(owner); + return true; + } + } + + if (owner->HasUnitState(UNIT_STATE_FOLLOW_MOVE) && owner->movespline->Finalized()) + { + i_recalculateTravel = false; + i_path = nullptr; + owner->ClearUnitState(UNIT_STATE_FOLLOW_MOVE); + MovementInform(owner); + } + + Position targetPosition = i_target->GetPosition(); + + if (_lastTargetPosition && _lastTargetPosition->GetExactDistSq(&targetPosition) == 0.0f) + return true; + + _lastTargetPosition = targetPosition; + + if (PositionOkay(owner, target, _range + PET_FOLLOW_DIST) && !owner->HasUnitState(UNIT_STATE_FOLLOW_MOVE)) + return true; + + if (!i_path) + i_path = new PathGenerator(owner); + + float x, y, z; + // select angle + float tAngle; + float const curAngle = target->GetRelativeAngle(owner); + if (!oPet) + { + // for non pets, keep the relative angle + // decided during the summon + tAngle = _angle.RelativeAngle; + } + else if (_angle.IsAngleOkay(curAngle)) + { + tAngle = curAngle; + } + else + { + float const diffUpper = Position::NormalizeOrientation(curAngle - _angle.UpperBound()); + float const diffLower = Position::NormalizeOrientation(_angle.LowerBound() - curAngle); + if (diffUpper < diffLower) + tAngle = _angle.UpperBound(); + else + tAngle = _angle.LowerBound(); + } + + target->GetNearPoint(owner, x, y, z, _range, 0.f, target->ToAbsoluteAngle(tAngle)); + + bool success = i_path->CalculatePath(x, y, z, forceDest); + if (!success || i_path->GetPathType() & PATHFIND_NOPATH) + { + owner->StopMoving(); + return true; + } + + owner->AddUnitState(UNIT_STATE_FOLLOW_MOVE); + + i_recalculateTravel = true; + + Movement::MoveSplineInit init(owner); + init.MovebyPath(i_path->GetPath()); + init.SetFacing(target->GetOrientation()); + init.SetWalk(target->IsWalking()); + init.Launch(); + + return true; } template<> @@ -424,20 +377,12 @@ void FollowMovementGenerator::_updateSpeed(Creature* owner) owner->UpdateSpeed(MOVE_SWIM, true); } -template<> -void FollowMovementGenerator::DoInitialize(Player* owner) +template +void FollowMovementGenerator::DoInitialize(T* owner) { - owner->AddUnitState(UNIT_STATE_FOLLOW | UNIT_STATE_FOLLOW_MOVE); + _lastTargetPosition.reset(); + owner->AddUnitState(UNIT_STATE_FOLLOW); _updateSpeed(owner); - _setTargetLocation(owner, true); -} - -template<> -void FollowMovementGenerator::DoInitialize(Creature* owner) -{ - owner->AddUnitState(UNIT_STATE_FOLLOW | UNIT_STATE_FOLLOW_MOVE); - _updateSpeed(owner); - _setTargetLocation(owner, true); } template @@ -454,38 +399,31 @@ void FollowMovementGenerator::DoReset(T* owner) } template -void FollowMovementGenerator::MovementInform(T* /*unit*/) +void FollowMovementGenerator::MovementInform(T* owner) { -} + if (owner->GetTypeId() != TYPEID_UNIT) + return; -template<> -void FollowMovementGenerator::MovementInform(Creature* unit) -{ // Pass back the GUIDLow of the target. If it is pet's owner then PetAI will handle - if (unit->AI()) - unit->AI()->MovementInform(FOLLOW_MOTION_TYPE, i_target.getTarget()->GetGUIDLow()); + if (CreatureAI* AI = owner->ToCreature()->AI()) + AI->MovementInform(FOLLOW_MOTION_TYPE, i_target.getTarget()->GetGUIDLow()); } //-----------------------------------------------// -template void TargetedMovementGeneratorMedium >::_setTargetLocation(Player*, bool initial); -template void TargetedMovementGeneratorMedium >::_setTargetLocation(Player*, bool initial); -template void TargetedMovementGeneratorMedium >::_setTargetLocation(Creature*, bool initial); -template void TargetedMovementGeneratorMedium >::_setTargetLocation(Creature*, bool initial); -template bool TargetedMovementGeneratorMedium >::DoUpdate(Player*, uint32); -template bool TargetedMovementGeneratorMedium >::DoUpdate(Player*, uint32); -template bool TargetedMovementGeneratorMedium >::DoUpdate(Creature*, uint32); -template bool TargetedMovementGeneratorMedium >::DoUpdate(Creature*, uint32); -template void ChaseMovementGenerator::_reachTarget(Player*); -template void ChaseMovementGenerator::_reachTarget(Creature*); template void ChaseMovementGenerator::DoFinalize(Player*); template void ChaseMovementGenerator::DoFinalize(Creature*); template void ChaseMovementGenerator::DoReset(Player*); template void ChaseMovementGenerator::DoReset(Creature*); -template void ChaseMovementGenerator::MovementInform(Player*); +template bool ChaseMovementGenerator::DoUpdate(Player*, uint32); +template bool ChaseMovementGenerator::DoUpdate(Creature*, uint32); +template void ChaseMovementGenerator::MovementInform(Unit*); +template void FollowMovementGenerator::DoInitialize(Player*); +template void FollowMovementGenerator::DoInitialize(Creature*); template void FollowMovementGenerator::DoFinalize(Player*); template void FollowMovementGenerator::DoFinalize(Creature*); template void FollowMovementGenerator::DoReset(Player*); template void FollowMovementGenerator::DoReset(Creature*); -template void FollowMovementGenerator::MovementInform(Player*); +template bool FollowMovementGenerator::DoUpdate(Player*, uint32); +template bool FollowMovementGenerator::DoUpdate(Creature*, uint32); diff --git a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h index f7b0f8e0b..15e61ecb7 100644 --- a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h +++ b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h @@ -12,6 +12,7 @@ #include "Timer.h" #include "Unit.h" #include "PathGenerator.h" +#include class TargetedMovementGeneratorBase { @@ -20,90 +21,80 @@ public: void stopFollowing() { } protected: FollowerReference i_target; - Position lastOwnerXYZ; - Position lastTargetXYZ; }; -template -class TargetedMovementGeneratorMedium : public MovementGeneratorMedium< T, D >, public TargetedMovementGeneratorBase -{ -protected: - TargetedMovementGeneratorMedium(Unit* target, float offset, float angle) : - TargetedMovementGeneratorBase(target), i_path(nullptr), lastPathingFailMSTime(0), - i_recheckDistance(0), i_recheckDistanceForced(2500), i_offset(offset), i_angle(angle), - i_recalculateTravel(false), i_targetReached(false) - { - } - ~TargetedMovementGeneratorMedium() { delete i_path; } - -public: - bool DoUpdate(T*, uint32); - Unit* GetTarget() const { return i_target.getTarget(); } - - void unitSpeedChanged() { i_recalculateTravel = true; } - bool IsReachable() const { return (i_path) ? (i_path->GetPathType() & PATHFIND_NORMAL) : true; } - -protected: - void _setTargetLocation(T* owner, bool initial); - - PathGenerator* i_path; - uint32 lastPathingFailMSTime; - TimeTrackerSmall i_recheckDistance; - TimeTrackerSmall i_recheckDistanceForced; - float i_offset; - float i_angle; - bool i_recalculateTravel : 1; - bool i_targetReached : 1; -}; template -class ChaseMovementGenerator : public TargetedMovementGeneratorMedium > +class ChaseMovementGenerator : public MovementGeneratorMedium>, public TargetedMovementGeneratorBase { public: - ChaseMovementGenerator(Unit* target) - : TargetedMovementGeneratorMedium >(target) {} - ChaseMovementGenerator(Unit* target, float offset, float angle) - : TargetedMovementGeneratorMedium >(target, offset, angle) {} + ChaseMovementGenerator(Unit* target, std::optional range = {}, std::optional angle = {}) + : TargetedMovementGeneratorBase(target), i_path(nullptr), i_recheckDistance(0), i_recalculateTravel(true), _range(range), _angle(angle) {} ~ChaseMovementGenerator() {} MovementGeneratorType GetMovementGeneratorType() { return CHASE_MOTION_TYPE; } + bool DoUpdate(T*, uint32); void DoInitialize(T*); void DoFinalize(T*); void DoReset(T*); void MovementInform(T*); - static void _clearUnitStateMove(T* u) { u->ClearUnitState(UNIT_STATE_CHASE_MOVE); } - static void _addUnitStateMove(T* u) { u->AddUnitState(UNIT_STATE_CHASE_MOVE); } + bool PositionOkay(T* owner, Unit* target, std::optional maxDistance, std::optional angle); + + void unitSpeedChanged() { _lastTargetPosition.reset(); } + Unit* GetTarget() const { return i_target.getTarget(); } + bool EnableWalking() const { return false;} - bool _lostTarget(T* u) const { return u->GetVictim() != this->GetTarget(); } - void _reachTarget(T*); + bool HasLostTarget(Unit* unit) const { return unit->GetVictim() != this->GetTarget(); } + +private: + PathGenerator* i_path; + TimeTrackerSmall i_recheckDistance; + bool i_recalculateTravel; + + std::optional _lastTargetPosition; + std::optional const _range; + std::optional const _angle; + bool _movingTowards = true; + bool _mutualChase = true; }; template -class FollowMovementGenerator : public TargetedMovementGeneratorMedium > +class FollowMovementGenerator : public MovementGeneratorMedium>, public TargetedMovementGeneratorBase { public: - FollowMovementGenerator(Unit* target) - : TargetedMovementGeneratorMedium >(target) {} - FollowMovementGenerator(Unit* target, float offset, float angle) - : TargetedMovementGeneratorMedium >(target, offset, angle) {} + FollowMovementGenerator(Unit* target, float range, ChaseAngle angle) + : TargetedMovementGeneratorBase(target), i_path(nullptr), i_recheckDistance(0), i_recalculateTravel(true), _range(range), _angle(angle) {} ~FollowMovementGenerator() {} MovementGeneratorType GetMovementGeneratorType() { return FOLLOW_MOTION_TYPE; } + bool DoUpdate(T*, uint32); void DoInitialize(T*); void DoFinalize(T*); void DoReset(T*); void MovementInform(T*); + Unit* GetTarget() const { return i_target.getTarget(); } + + void unitSpeedChanged() { _lastTargetPosition.reset(); } + + bool PositionOkay(T* owner, Unit* target, float range, std::optional angle = {}); + static void _clearUnitStateMove(T* u) { u->ClearUnitState(UNIT_STATE_FOLLOW_MOVE); } static void _addUnitStateMove(T* u) { u->AddUnitState(UNIT_STATE_FOLLOW_MOVE); } - bool EnableWalking() const; - bool _lostTarget(T*) const { return false; } - void _reachTarget(T*) {} -private: + void _updateSpeed(T* owner); + +private: + PathGenerator* i_path; + TimeTrackerSmall i_recheckDistance; + bool i_recalculateTravel; + + std::optional _lastTargetPosition; + float _range; + ChaseAngle _angle; }; #endif diff --git a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp index 500ec9ead..db9b6317d 100644 --- a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp @@ -158,9 +158,11 @@ bool WaypointMovementGenerator::StartMove(Creature* creature) trans->CalculatePassengerPosition(formationDest.x, formationDest.y, formationDest.z, &formationDest.orientation); } + float z = node->z; + creature->UpdateAllowedPositionZ(node->x, node->y, z); //! Do not use formationDest here, MoveTo requires transport offsets due to DisableTransportPathTransformations() call //! but formationDest contains global coordinates - init.MoveTo(node->x, node->y, node->z); + init.MoveTo(node->x, node->y, z, true, true); //! Accepts angles such as 0.00001 and -0.00001, 0 must be ignored, default value in waypoint table if (node->orientation && node->delay) @@ -236,11 +238,11 @@ bool WaypointMovementGenerator::DoUpdate(Creature* creature, uint32 di Stop(sWorld->getIntConfig(CONFIG_WAYPOINT_MOVEMENT_STOP_TIME_FOR_PLAYER) * IN_MILLISECONDS); else { + bool finished = creature->movespline->Finalized(); // xinef: code to detect pre-empetively if we should start movement to next waypoint // xinef: do not start pre-empetive movement if current node has delay or we are ending waypoint movement - bool finished = creature->movespline->Finalized(); - if (!finished && !i_path->at(i_currentNode)->delay && ((i_currentNode != i_path->size() - 1) || repeating)) - finished = (creature->movespline->_Spline().length(creature->movespline->_currentSplineIdx() + 1) - creature->movespline->timePassed()) < 200; + //if (!finished && !i_path->at(i_currentNode)->delay && ((i_currentNode != i_path->size() - 1) || repeating)) + // finished = (creature->movespline->_Spline().length(creature->movespline->_currentSplineIdx() + 1) - creature->movespline->timePassed()) < 200; if (finished) { diff --git a/src/server/game/Movement/Spline/MoveSplineInit.cpp b/src/server/game/Movement/Spline/MoveSplineInit.cpp index f6fbe7111..686e0b0e6 100644 --- a/src/server/game/Movement/Spline/MoveSplineInit.cpp +++ b/src/server/game/Movement/Spline/MoveSplineInit.cpp @@ -79,7 +79,16 @@ namespace Movement move_spline.onTransport = transport; uint32 moveFlags = unit->m_movementInfo.GetMovementFlags(); - moveFlags |= (MOVEMENTFLAG_SPLINE_ENABLED | MOVEMENTFLAG_FORWARD); + moveFlags |= MOVEMENTFLAG_SPLINE_ENABLED; + + if (!args.flags.orientationInversed) + { + moveFlags = (moveFlags & ~(MOVEMENTFLAG_BACKWARD)) | MOVEMENTFLAG_FORWARD; + } + else + { + moveFlags = (moveFlags & ~(MOVEMENTFLAG_FORWARD)) | MOVEMENTFLAG_BACKWARD; + } if (moveFlags & MOVEMENTFLAG_ROOT) moveFlags &= ~MOVEMENTFLAG_MASK_MOVING; @@ -95,8 +104,14 @@ namespace Movement moveFlagsForSpeed &= ~MOVEMENTFLAG_WALKING; args.velocity = unit->GetSpeed(SelectSpeedType(moveFlagsForSpeed)); + if (Creature* creature = unit->ToCreature()) + if (creature->HasSearchedAssistance()) + args.velocity *= 0.66f; } + // limit the speed in the same way the client does + args.velocity = std::min(args.velocity, args.flags.catmullrom || args.flags.flying ? 50.0f : std::max(28.0f, unit->GetSpeed(MOVE_RUN) * 4.0f)); + if (!args.Validate(unit)) return 0; @@ -115,10 +130,7 @@ namespace Movement Movement::SplineBase::ControlArray* visualPoints = const_cast(move_spline._Spline().allocateVisualPoints()); visualPoints->resize(move_spline._Spline().getPointCount()); // Xinef: Apply hover in creature movement packet - if (unit->IsHovering()) - std::transform(move_spline._Spline().getPoints(false).begin(), move_spline._Spline().getPoints(false).end(), visualPoints->begin(), HoverMovementTransform(unit->GetHoverHeight())); - else - std::copy(move_spline._Spline().getPoints(false).begin(), move_spline._Spline().getPoints(false).end(), visualPoints->begin()); + std::copy(move_spline._Spline().getPoints(false).begin(), move_spline._Spline().getPoints(false).end(), visualPoints->begin()); PacketBuilder::WriteMonsterMove(move_spline, data); unit->SendMessageToSet(&data, true); @@ -153,7 +165,7 @@ namespace Movement } args.flags = MoveSplineFlag::Done; - unit->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_SPLINE_ENABLED); + unit->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_BACKWARD | MOVEMENTFLAG_SPLINE_ENABLED); move_spline.onTransport = transport; move_spline.Initialize(args); @@ -166,11 +178,10 @@ namespace Movement data << int8(unit->GetTransSeat()); } - // Xinef: increase z position in packet - loc.z += unit->GetHoverHeight(); PacketBuilder::WriteStopMovement(loc, args.splineId, data); unit->SendMessageToSet(&data, true); } + MoveSplineInit::MoveSplineInit(Unit* m) : unit(m) { args.splineId = splineIdGen.NewId(); diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index 3078dfc37..96ac29872 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -2715,13 +2715,13 @@ void AuraEffect::HandleAuraUntrackable(AuraApplication const* aurApp, uint8 mode Unit* target = aurApp->GetTarget(); if (apply) - target->SetByteFlag(UNIT_FIELD_BYTES_1, 2, UNIT_STAND_FLAGS_UNTRACKABLE); + target->SetByteFlag(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_VIS_FLAG, UNIT_STAND_FLAGS_UNTRACKABLE); else { // do not remove unit flag if there are more than this auraEffect of that kind on unit on unit if (target->HasAuraType(GetAuraType())) return; - target->RemoveByteFlag(UNIT_FIELD_BYTES_1, 2, UNIT_STAND_FLAGS_UNTRACKABLE); + target->RemoveByteFlag(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_VIS_FLAG, UNIT_STAND_FLAGS_UNTRACKABLE); } } diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 4e9c4fb73..6bd0a0516 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -1333,7 +1333,7 @@ void Spell::SelectImplicitCasterDestTargets(SpellEffIndex effIndex, SpellImplici //m_caster->GetClosePoint(x, y, z, DEFAULT_WORLD_OBJECT_SIZE, dis, angle); this contains extra code that breaks fishing m_caster->GetNearPoint(m_caster, x, y, z, DEFAULT_WORLD_OBJECT_SIZE, dis, m_caster->GetOrientation() + angle); - float ground = m_caster->GetMap()->GetHeight(m_caster->GetPhaseMask(), x, y, z, true, 120.0f); + float ground = m_caster->GetMapHeight(x, y, z, true); float liquidLevel = VMAP_INVALID_HEIGHT_VALUE; LiquidData liquidData; if (m_caster->GetMap()->getLiquidStatus(x, y, z, MAP_ALL_LIQUIDS, &liquidData)) @@ -1360,256 +1360,58 @@ void Spell::SelectImplicitCasterDestTargets(SpellEffIndex effIndex, SpellImplici } case TARGET_DEST_CASTER_FRONT_LEAP: { - float distance = m_spellInfo->Effects[effIndex].CalcRadius(m_caster); - Map* map = m_caster->GetMap(); - uint32 mapid = m_caster->GetMapId(); - uint32 phasemask = m_caster->GetPhaseMask(); - float destx, desty, destz = 0, ground, startx, starty, startz, starto; - - Position pos; - Position lastpos; - m_caster->GetPosition(startx, starty, startz, starto); - pos.Relocate(startx, starty, startz, starto); - destx = pos.GetPositionX() + distance * cos(pos.GetOrientation()); - desty = pos.GetPositionY() + distance * sin(pos.GetOrientation()); - - ground = map->GetHeight(phasemask, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); - - if (!m_caster->HasUnitMovementFlag(MOVEMENTFLAG_FALLING) || (pos.GetPositionZ() - ground < distance)) + Unit* unitCaster = m_caster->ToUnit(); + if (!unitCaster) { - float tstX = 0, tstY = 0, tstZ = 0, prevX = 0, prevY = 0, prevZ = 0; - float tstZ1 = 0, tstZ2 = 0, tstZ3 = 0, destz1 = 0, destz2 = 0, destz3 = 0, srange = 0, srange1 = 0, srange2 = 0, srange3 = 0; - float maxtravelDistZ = 2.65f; - float overdistance = 0.0f; - float totalpath = 0.0f; - float beforewaterz = 0.0f; - bool inwater = false; - bool wcol = false; - const float step = 2.0f; - const uint8 numChecks = ceil(fabs(distance / step)); - const float DELTA_X = (destx - pos.GetPositionX()) / numChecks; - const float DELTA_Y = (desty - pos.GetPositionY()) / numChecks; - int j = 1; - for (; j < (numChecks + 1); j++) - { - prevX = pos.GetPositionX() + (float(j - 1) * DELTA_X); - prevY = pos.GetPositionY() + (float(j - 1) * DELTA_Y); - tstX = pos.GetPositionX() + (float(j) * DELTA_X); - tstY = pos.GetPositionY() + (float(j) * DELTA_Y); - - if (j < 2) - { - prevZ = pos.GetPositionZ(); - } - else - { - prevZ = tstZ; - } - - tstZ = map->GetHeight(phasemask, tstX, tstY, prevZ + maxtravelDistZ, true); - ground = tstZ; - - if (!map->IsInWater(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ())) - { - if (map->IsInWater(tstX, tstY, tstZ)) - { - if (!(beforewaterz != 0.0f)) - beforewaterz = prevZ; - tstZ = beforewaterz; - srange = sqrt((tstY - prevY) * (tstY - prevY) + (tstX - prevX) * (tstX - prevX)); - //TC_LOG_ERROR("server", "(start was from land) step in water , number of cycle = %i , distance of step = %f, total path = %f, Z = %f", j, srange, totalpath, tstZ); - } - } - else if (map->IsInWater(tstX, tstY, tstZ)) - { - prevZ = pos.GetPositionZ(); - tstZ = pos.GetPositionZ(); - srange = sqrt((tstY - prevY) * (tstY - prevY) + (tstX - prevX) * (tstX - prevX)); - - inwater = true; - if (inwater && (fabs(tstZ - ground) < 2.0f)) - { - wcol = true; - //TC_LOG_ERROR("server", "step in water with collide and use standart check (for continue way after possible collide), number of cycle = %i ", j); - } - - // if (j < 2) - // TC_LOG_ERROR("server", "(start in water) step in water, number of cycle = %i , distance of step = %f, total path = %f", j, srange, totalpath); - // else - // TC_LOG_ERROR("server", "step in water, number of cycle = %i , distance of step = %f, total path = %f", j, srange, totalpath); - } - - if ((!map->IsInWater(tstX, tstY, tstZ) && tstZ != beforewaterz) || wcol) // second safety check z for blink way if on the ground - { - if (inwater && !map->IsInWater(tstX, tstY, tstZ)) - inwater = false; - - // highest available point - tstZ1 = map->GetHeight(phasemask, tstX, tstY, prevZ + maxtravelDistZ, true, 25.0f); - // upper or floor - tstZ2 = map->GetHeight(phasemask, tstX, tstY, prevZ, true, 25.0f); - //lower than floor - tstZ3 = map->GetHeight(phasemask, tstX, tstY, prevZ - maxtravelDistZ / 2, true, 25.0f); - - //distance of rays, will select the shortest in 3D - srange1 = sqrt((tstY - prevY) * (tstY - prevY) + (tstX - prevX) * (tstX - prevX) + (tstZ1 - prevZ) * (tstZ1 - prevZ)); - //TC_LOG_ERROR("server", "step = %i, distance of ray1 = %f", j, srange1); - srange2 = sqrt((tstY - prevY) * (tstY - prevY) + (tstX - prevX) * (tstX - prevX) + (tstZ2 - prevZ) * (tstZ2 - prevZ)); - //TC_LOG_ERROR("server", "step = %i, distance of ray2 = %f", j, srange2); - srange3 = sqrt((tstY - prevY) * (tstY - prevY) + (tstX - prevX) * (tstX - prevX) + (tstZ3 - prevZ) * (tstZ3 - prevZ)); - //TC_LOG_ERROR("server", "step = %i, distance of ray3 = %f", j, srange3); - - if (srange1 < srange2) - { - tstZ = tstZ1; - srange = srange1; - } - else if (srange3 < srange2) - { - tstZ = tstZ3; - srange = srange3; - } - else - { - tstZ = tstZ2; - srange = srange2; - } - - //TC_LOG_ERROR("server", "step on ground, number of cycle = %i , distance of step = %f, total path = %f", j, srange, totalpath); - } - - destx = tstX; - desty = tstY; - destz = tstZ; - - totalpath += srange; - - if (totalpath > distance) - { - overdistance = totalpath - distance; - //TC_LOG_ERROR("server", "total path > than distance in 3D , need to move back a bit for save distance, total path = %f, overdistance = %f", totalpath, overdistance); - } - - bool col = VMAP::VMapFactory::createOrGetVMapManager()->getObjectHitPos(mapid, prevX, prevY, prevZ + 0.5f, tstX, tstY, tstZ + 0.5f, tstX, tstY, tstZ, -0.5f); - // check dynamic collision - bool dcol = m_caster->GetMap()->getObjectHitPos(phasemask, prevX, prevY, prevZ + 0.5f, tstX, tstY, tstZ + 0.5f, tstX, tstY, tstZ, -0.5f); - - // collision occured - if (col || dcol || (overdistance > 0.0f && !map->IsInWater(tstX, tstY, ground)) || (fabs(prevZ - tstZ) > maxtravelDistZ && (tstZ > prevZ))) - { - if ((overdistance > 0.0f) && (overdistance < step)) - { - destx = prevX + overdistance * cos(pos.GetOrientation()); - desty = prevY + overdistance * sin(pos.GetOrientation()); - //TC_LOG_ERROR("server", "(collision) collision occured 1"); - } - else - { - // move back a bit - destx = tstX - (0.6 * cos(pos.GetOrientation())); - desty = tstY - (0.6 * sin(pos.GetOrientation())); - //TC_LOG_ERROR("server", "(collision) collision occured 2"); - } - - // highest available point - destz1 = map->GetHeight(phasemask, destx, desty, prevZ + maxtravelDistZ, true, 25.0f); - // upper or floor - destz2 = map->GetHeight(phasemask, destx, desty, prevZ, true, 25.0f); - //lower than floor - destz3 = map->GetHeight(phasemask, destx, desty, prevZ - maxtravelDistZ / 2, true, 25.0f); - - //distance of rays, will select the shortest in 3D - srange1 = sqrt((desty - prevY) * (desty - prevY) + (destx - prevX) * (destx - prevX) + (destz1 - prevZ) * (destz1 - prevZ)); - srange2 = sqrt((desty - prevY) * (desty - prevY) + (destx - prevX) * (destx - prevX) + (destz2 - prevZ) * (destz2 - prevZ)); - srange3 = sqrt((desty - prevY) * (desty - prevY) + (destx - prevX) * (destx - prevX) + (destz3 - prevZ) * (destz3 - prevZ)); - - if (srange1 < srange2) - destz = destz1; - else if (srange3 < srange2) - destz = destz3; - else - destz = destz2; - - if (inwater && destz < prevZ && !wcol) - destz = prevZ; - //TC_LOG_ERROR("server", "(collision) destZ rewrited in prevZ"); - - break; - } - // we have correct destz now - } - //} - - lastpos.Relocate(destx, desty, destz + 0.5f, pos.GetOrientation()); - dest = SpellDestination(lastpos); - } - else - { - float z = pos.GetPositionZ(); - bool col = VMAP::VMapFactory::createOrGetVMapManager()->getObjectHitPos(mapid, pos.GetPositionX(), pos.GetPositionY(), z + 0.5f, destx, desty, z + 0.5f, destx, desty, z, -0.5f); - // check dynamic collision - bool dcol = m_caster->GetMap()->getObjectHitPos(phasemask, pos.GetPositionX(), pos.GetPositionY(), z + 0.5f, destx, desty, z + 0.5f, destx, desty, z, -0.5f); - - // collision occured - if (col || dcol) - { - // move back a bit - destx = destx - (0.6 * cos(pos.GetOrientation())); - desty = desty - (0.6 * sin(pos.GetOrientation())); - } - - lastpos.Relocate(destx, desty, z, pos.GetOrientation()); - dest = SpellDestination(lastpos); - //float range = sqrt((desty - pos.GetPositionY())*(desty - pos.GetPositionY()) + (destx - pos.GetPositionX())*(destx - pos.GetPositionX())); - //TC_LOG_ERROR("server", "Blink number 2, in falling but at a hight, distance of blink = %f", range); + break; } + float dist = m_spellInfo->Effects[effIndex].CalcRadius(unitCaster); + float angle = targetType.CalcDirectionAngle(); + + Position pos = dest._position; + + unitCaster->MovePositionToFirstCollision(pos, dist, angle); + dest.Relocate(pos); break; } default: { - float dist; + float dist = m_spellInfo->Effects[effIndex].CalcRadius(m_caster); float angle = targetType.CalcDirectionAngle(); - float objSize = m_caster->GetObjectSize(); - if (targetType.GetTarget() == TARGET_DEST_CASTER_SUMMON) + float objSize = m_caster->GetCombatReach(); + + switch (targetType.GetTarget()) + { + case TARGET_DEST_CASTER_SUMMON: dist = PET_FOLLOW_DIST; - else - dist = m_spellInfo->Effects[effIndex].CalcRadius(m_caster); + break; + case TARGET_DEST_CASTER_RANDOM: + if (dist > objSize) + dist = objSize + (dist - objSize) * float(rand_norm()); + break; + case TARGET_DEST_CASTER_FRONT_LEFT: + case TARGET_DEST_CASTER_BACK_LEFT: + case TARGET_DEST_CASTER_FRONT_RIGHT: + case TARGET_DEST_CASTER_BACK_RIGHT: + { + static float const DefaultTotemDistance = 3.0f; + if (!m_spellInfo->Effects[effIndex].HasRadius()) + dist = DefaultTotemDistance; + break; + } + default: + break; + } if (dist < objSize) { dist = objSize; - // xinef: give the summon some space (eg. totems) - if (m_caster->GetTypeId() == TYPEID_PLAYER && m_spellInfo->Effects[effIndex].IsEffect(SPELL_EFFECT_SUMMON)) - dist += objSize; - } - else if (targetType.GetTarget() == TARGET_DEST_CASTER_RANDOM) - dist = objSize + (dist - objSize) * (float)rand_norm(); - - Position pos; - bool totemCollision = false; - if (m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_SUMMON) - { - SummonPropertiesEntry const* properties = sSummonPropertiesStore.LookupEntry(m_spellInfo->Effects[effIndex].MiscValueB); - if (properties && (properties->Type == SUMMON_TYPE_TOTEM || properties->Type == SUMMON_TYPE_LIGHTWELL)) - { - totemCollision = true; - m_caster->GetFirstCollisionPositionForTotem(pos, dist, angle, false); - } - } - else if (m_spellInfo->Effects[effIndex].Effect >= SPELL_EFFECT_SUMMON_OBJECT_SLOT1 && m_spellInfo->Effects[effIndex].Effect <= SPELL_EFFECT_SUMMON_OBJECT_SLOT4) - { - totemCollision = true; - m_caster->GetFirstCollisionPositionForTotem(pos, dist, angle, true); } - if (!totemCollision) - { - if (m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_LEAP || m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_TELEPORT_UNITS || m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_JUMP_DEST || (m_caster->GetTypeId() == TYPEID_PLAYER && m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_SUMMON)) - m_caster->GetFirstCollisionPosition(pos, dist, angle); - else - m_caster->GetNearPosition(pos, dist, angle); - } + Position pos = dest._position; + m_caster->MovePositionToFirstCollision(pos, dist, angle); + dest.Relocate(pos); break; } @@ -1631,24 +1433,20 @@ void Spell::SelectImplicitTargetDestTargets(SpellEffIndex effIndex, SpellImplici case TARGET_DEST_TARGET_ANY: break; default: + { + float angle = targetType.CalcDirectionAngle(); + float dist = m_spellInfo->Effects[effIndex].CalcRadius(nullptr); + if (targetType.GetTarget() == TARGET_DEST_TARGET_RANDOM) { - float angle = targetType.CalcDirectionAngle(); - float objSize = target->GetObjectSize(); - float dist = m_spellInfo->Effects[effIndex].CalcRadius(m_caster); - if (dist < objSize) - dist = objSize; - else if (targetType.GetTarget() == TARGET_DEST_TARGET_RANDOM) - dist = objSize + (dist - objSize) * (float)rand_norm(); - - Position pos; - if (m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_LEAP || m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_TELEPORT_UNITS || m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_JUMP_DEST || (m_caster->GetTypeId() == TYPEID_PLAYER && m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_SUMMON)) - target->GetFirstCollisionPosition(pos, dist, angle); - else - target->GetNearPosition(pos, dist, angle); - - dest.Relocate(pos); - break;; + dist *= float(rand_norm()); } + + Position pos = dest._position; + target->MovePositionToFirstCollision(pos, dist, angle); + + dest.Relocate(pos); + break; + } } CallScriptDestinationTargetSelectHandlers(dest, effIndex, targetType); @@ -1676,18 +1474,18 @@ void Spell::SelectImplicitDestDestTargets(SpellEffIndex effIndex, SpellImplicitT SelectImplicitTrajTargets(effIndex, targetType); return; default: - { - float angle = targetType.CalcDirectionAngle(); - float dist = m_spellInfo->Effects[effIndex].CalcRadius(m_caster); - if (targetType.GetTarget() == TARGET_DEST_DEST_RANDOM) - dist *= float(rand_norm()); + { + float angle = targetType.CalcDirectionAngle(); + float dist = m_spellInfo->Effects[effIndex].CalcRadius(m_caster); + if (targetType.GetTarget() == TARGET_DEST_DEST_RANDOM) + dist *= float(rand_norm()); - Position pos = dest._position; - m_caster->MovePosition(pos, dist, angle); + Position pos = dest._position; + m_caster->MovePosition(pos, dist, angle); - dest.Relocate(pos); - break; - } + dest.Relocate(pos); + break; + } } CallScriptDestinationTargetSelectHandlers(dest, effIndex, targetType); @@ -5869,8 +5667,8 @@ SpellCastResult Spell::CheckCast(bool strict) if (m_caster->GetMapId() == 618) // pussywizard: 618 Ring of Valor pos.m_positionZ = std::max(pos.m_positionZ, 28.28f); - float maxdist = MELEE_RANGE + m_caster->GetMeleeReach() + target->GetMeleeReach(); - if (target->GetExactDistSq(&pos) > maxdist * maxdist) + float maxdist = m_caster->GetMeleeRange(target); + if (!target->IsInDist(&pos, maxdist)) return SPELL_FAILED_NOPATH; if (m_caster->GetMapId() == 618) // pussywizard: 618 Ring of Valor @@ -7455,24 +7253,9 @@ bool Spell::CheckEffectTarget(Unit const* target, uint32 eff) const if (m_spellInfo->HasAttribute(SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS) || (target->GetTypeId() == TYPEID_UNIT && target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE) && (m_spellInfo->Effects[eff].TargetA.GetCheckType() == TARGET_CHECK_ENTRY || m_spellInfo->Effects[eff].TargetB.GetCheckType() == TARGET_CHECK_ENTRY))) return true; - if (IsTriggered()) - { - if (!m_caster->IsInMap(target)) // pussywizard: crashfix, avoid IsWithinLOS on another map! >_> - return true; - - float x = m_caster->GetPositionX(), y = m_caster->GetPositionY(), z = m_caster->GetPositionZ(); - if (m_targets.HasDst()) - { - x = m_targets.GetDstPos()->GetPositionX(); - y = m_targets.GetDstPos()->GetPositionY(); - z = m_targets.GetDstPos()->GetPositionZ(); - } - - if ((!m_caster->IsTotem() || !m_spellInfo->IsPositive()) && !target->IsWithinLOS(x, y, z, LINEOFSIGHT_ALL_CHECKS)) - return false; - + // if spell is triggered, need to check for LOS disable on the aura triggering it and inherit that behaviour + if (IsTriggered() && m_triggeredByAuraSpell && (m_triggeredByAuraSpell->HasAttribute(SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS) || DisableMgr::IsDisabledFor(DISABLE_TYPE_SPELL, m_triggeredByAuraSpell->Id, nullptr, SPELL_DISABLE_LOS))) return true; - } // todo: shit below shouldn't be here, but it's temporary //Check targets for LOS visibility (except spells without range limitations) @@ -7544,17 +7327,18 @@ bool Spell::CheckEffectTarget(Unit const* target, uint32 eff) const caster = m_caster->GetMap()->GetGameObject(m_originalCasterGUID); if (!caster) caster = m_caster; - if(target != caster) + if (target != m_caster) { - float x = caster->GetPositionX(), y = caster->GetPositionY(), z = caster->GetPositionZ(); if (m_targets.HasDst()) { - x = m_targets.GetDstPos()->GetPositionX(); - y = m_targets.GetDstPos()->GetPositionY(); - z = m_targets.GetDstPos()->GetPositionZ(); - } + float x = m_targets.GetDstPos()->GetPositionX(); + float y = m_targets.GetDstPos()->GetPositionY(); + float z = m_targets.GetDstPos()->GetPositionZ(); - if (!target->IsInMap(caster) || !target->IsWithinLOS(x, y, z, LINEOFSIGHT_ALL_CHECKS)) + if (!target->IsWithinLOS(x, y, z, LINEOFSIGHT_ALL_CHECKS)) + return false; + } + else if (!target->IsWithinLOSInMap(caster, LINEOFSIGHT_ALL_CHECKS)) return false; } break; diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 628395dca..1e39b5d5e 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -1,4 +1,4 @@ -/* + /* * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-GPL2 * Copyright (C) 2008-2016 TrinityCore * Copyright (C) 2005-2009 MaNGOS @@ -2447,10 +2447,12 @@ void Spell::EffectSummonType(SpellEffIndex effIndex) TempSummonType summonType = (duration <= 0) ? TEMPSUMMON_DEAD_DESPAWN : TEMPSUMMON_TIMED_DESPAWN; + uint32 currMinionsCount = m_caster->m_Controlled.size(); + uint32 totalNumGuardians = numSummons + currMinionsCount; for (uint32 count = 0; count < numSummons; ++count) { Position pos; - if (count == 0) + if (totalNumGuardians == 1) pos = *destTarget; else // randomize position for multiple summons @@ -5070,7 +5072,7 @@ void Spell::EffectCharge(SpellEffIndex /*effIndex*/) m_caster->GetFirstCollisionPosition(pos, dist, angle); } - m_caster->GetMotionMaster()->MoveCharge(pos.m_positionX, pos.m_positionY, pos.m_positionZ + 0.5f); + m_caster->GetMotionMaster()->MoveCharge(pos.m_positionX, pos.m_positionY, pos.m_positionZ); } } @@ -6069,16 +6071,24 @@ void Spell::SummonGuardian(uint32 i, uint32 entry, SummonPropertiesEntry const* Map* map = caster->GetMap(); TempSummon* summon = nullptr; + uint32 currMinionsCount = m_caster->m_Controlled.size(); + uint32 totalNumGuardians = numGuardians + currMinionsCount; + for (uint32 count = 0; count < numGuardians; ++count) { Position pos; - // xinef: do not use precalculated position for effect summon pet in this function, it means it was cast by NPC and should have its position overridden - if (count == 0 && GetSpellInfo()->Effects[i].Effect != SPELL_EFFECT_SUMMON_PET) + // xinef: do not use precalculated position for effect summon pet in this function + // it means it was cast by NPC and should have its position overridden + if (totalNumGuardians == 1 && GetSpellInfo()->Effects[i].Effect != SPELL_EFFECT_SUMMON_PET) + { pos = *destTarget; + } else - // randomize position for multiple summons + { + // randomize position m_caster->GetRandomPoint(*destTarget, radius, pos); + } summon = map->SummonCreature(entry, pos, properties, duration, caster, m_spellInfo->Id); if (!summon) @@ -6088,8 +6098,8 @@ void Spell::SummonGuardian(uint32 i, uint32 entry, SummonPropertiesEntry const* summon->SetLevel(summonLevel); // xinef: if we have more than one guardian, change follow angle - if (summon->HasUnitTypeMask(UNIT_MASK_MINION) && numGuardians > 1) - ((Minion*)summon)->SetFollowAngle(PET_FOLLOW_ANGLE + (count * M_PI / (numGuardians - 1))); + if (summon->HasUnitTypeMask(UNIT_MASK_MINION) && totalNumGuardians > 1) + ((Minion*)summon)->SetFollowAngle(m_caster->GetAbsoluteAngle(pos.GetPositionX(), pos.GetPositionY())); //else if (summon->HasUnitTypeMask(UNIT_MASK_MINION) && m_targets.HasDst()) // ((Minion*)summon)->SetFollowAngle(m_caster->GetAngle(summon)); diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 808b8599b..253b867ca 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -1235,6 +1235,11 @@ bool SpellInfo::IsChanneled() const return (AttributesEx & (SPELL_ATTR1_CHANNELED_1 | SPELL_ATTR1_CHANNELED_2)); } +bool SpellInfo::IsMoveAllowedChannel() const +{ + return IsChanneled() && (HasAttribute(SPELL_ATTR5_CAN_CHANNEL_WHEN_MOVING) || (!(ChannelInterruptFlags & (AURA_INTERRUPT_FLAG_MOVE | AURA_INTERRUPT_FLAG_TURNING)))); +} + bool SpellInfo::NeedsComboPoints() const { return (AttributesEx & (SPELL_ATTR1_REQ_COMBO_POINTS1 | SPELL_ATTR1_REQ_COMBO_POINTS2)); diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h index 6aece6da0..0b2bdcfb5 100644 --- a/src/server/game/Spells/SpellInfo.h +++ b/src/server/game/Spells/SpellInfo.h @@ -437,6 +437,7 @@ public: bool IsPositive() const; bool IsPositiveEffect(uint8 effIndex) const; bool IsChanneled() const; + [[nodiscard]] bool IsMoveAllowedChannel() const; bool NeedsComboPoints() const; bool IsBreakingStealth() const; bool IsRangedWeaponSpell() const; diff --git a/src/server/game/World/IWorld.h b/src/server/game/World/IWorld.h index a29ee3c75..623cd6278 100644 --- a/src/server/game/World/IWorld.h +++ b/src/server/game/World/IWorld.h @@ -164,6 +164,7 @@ enum WorldBoolConfigs CONFIG_SET_ALL_CREATURES_WITH_WAYPOINT_MOVEMENT_ACTIVE, CONFIG_DEBUG_BATTLEGROUND, CONFIG_DEBUG_ARENA, + CONFIG_REGEN_HP_CANNOT_REACH_TARGET_IN_RAID, BOOL_CONFIG_VALUE_COUNT }; @@ -370,6 +371,7 @@ enum WorldIntConfigs CONFIG_GUILD_BANK_TAB_COST_4, CONFIG_GUILD_BANK_TAB_COST_5, CONFIG_GM_LEVEL_CHANNEL_MODERATION, + CONFIG_NPC_EVADE_IF_NOT_REACHABLE, INT_CONFIG_VALUE_COUNT }; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index ff47680e7..81d029fd4 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1404,6 +1404,9 @@ void World::LoadConfigSettings(bool reload) m_int_configs[CONFIG_WAYPOINT_MOVEMENT_STOP_TIME_FOR_PLAYER] = sConfigMgr->GetIntDefault("WaypointMovementStopTimeForPlayer", 120); + m_int_configs[CONFIG_NPC_EVADE_IF_NOT_REACHABLE] = sConfigMgr->GetIntDefault("NpcEvadeIfTargetIsUnreachable", 5); + m_bool_configs[CONFIG_REGEN_HP_CANNOT_REACH_TARGET_IN_RAID] = sConfigMgr->GetBoolDefault("NpcRegenHPIfTargetIsUnreachable", true); + //Debug m_bool_configs[CONFIG_DEBUG_BATTLEGROUND] = sConfigMgr->GetBoolDefault("Debug.Battleground", false); m_bool_configs[CONFIG_DEBUG_ARENA] = sConfigMgr->GetBoolDefault("Debug.Arena", false); diff --git a/src/server/scripts/Commands/cs_mmaps.cpp b/src/server/scripts/Commands/cs_mmaps.cpp index 2ba3d48c5..484421006 100644 --- a/src/server/scripts/Commands/cs_mmaps.cpp +++ b/src/server/scripts/Commands/cs_mmaps.cpp @@ -85,9 +85,11 @@ public: if (para && strcmp(para, "true") == 0) useStraightPath = true; - bool useStraightLine = false; - if (para && strcmp(para, "line") == 0) - useStraightLine = true; + bool useRaycast = false; + if (para && (strcmp(para, "line") == 0 || strcmp(para, "ray") == 0 || strcmp(para, "raycast") == 0)) + { + useRaycast = true; + } // unit locations float x, y, z; @@ -96,11 +98,12 @@ public: // path PathGenerator path(target); path.SetUseStraightPath(useStraightPath); + path.SetUseRaycast(useRaycast); bool result = path.CalculatePath(x, y, z, false); Movement::PointsArray const& pointPath = path.GetPath(); handler->PSendSysMessage("%s's path to %s:", target->GetName().c_str(), player->GetName().c_str()); - handler->PSendSysMessage("Building: %s", useStraightPath ? "StraightPath" : useStraightLine ? "Raycast" : "SmoothPath"); + handler->PSendSysMessage("Building: %s", useStraightPath ? "StraightPath" : useRaycast ? "Raycast" : "SmoothPath"); handler->PSendSysMessage("Result: %s - Length: " SZFMTD " - Type: %u", (result ? "true" : "false"), pointPath.size(), path.GetPathType()); G3D::Vector3 const& start = path.GetStartPosition(); @@ -154,7 +157,7 @@ public: handler->PSendSysMessage("Calc [%02i, %02i]", tilex, tiley); // navmesh poly -> navmesh tile location - dtQueryFilter filter = dtQueryFilter(); + dtQueryFilterExt filter = dtQueryFilterExt(); dtPolyRef polyRef = INVALID_POLYREF; if (dtStatusFailed(navmeshquery->findNearestPoly(location, extents, &filter, &polyRef, nullptr))) { diff --git a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp index cc0403ce4..4a93c794a 100644 --- a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp +++ b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp @@ -190,7 +190,7 @@ public: return true; creature->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); - creature->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_UNK_15); + creature->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SWIMMING); player->CastSpell(creature, SPELL_DUEL, false); player->CastSpell(player, SPELL_DUEL_FLAG, true); @@ -246,7 +246,7 @@ public: me->RestoreFaction(); CombatAI::Reset(); - me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_UNK_15); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SWIMMING); } void SpellHit(Unit* caster, const SpellInfo* pSpell) override diff --git a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp index 1120f502f..2b050978b 100644 --- a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp +++ b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp @@ -276,7 +276,7 @@ public: me->GetMotionMaster()->MoveChase(me->GetVictim()); break; case EVENT_LAND: - me->GetMotionMaster()->MovePoint(POINT_GROUND, me->GetPositionX(), me->GetPositionY(), me->GetMap()->GetHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()), false, true); + me->GetMotionMaster()->MovePoint(POINT_GROUND, me->GetPositionX(), me->GetPositionY(), me->GetMapHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()), false, true); break; case EVENT_SPELL_BERSERK: Talk(YELL_BERSERK); diff --git a/src/server/scripts/EasternKingdoms/ZulAman/zulaman.cpp b/src/server/scripts/EasternKingdoms/ZulAman/zulaman.cpp index 4ea412fc4..f3a54e22f 100644 --- a/src/server/scripts/EasternKingdoms/ZulAman/zulaman.cpp +++ b/src/server/scripts/EasternKingdoms/ZulAman/zulaman.cpp @@ -630,7 +630,7 @@ public: me->SetEntry(NPC_HARRISON_JONES_2); me->SetDisplayId(MODEL_HARRISON_JONES_2); me->SetTarget(0); - me->SetByteValue(UNIT_FIELD_BYTES_1, 0, UNIT_STAND_STATE_DEAD); + me->SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, UNIT_STAND_STATE_DEAD); me->SetFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_DEAD); instance->SetData(DATA_GONGEVENT, DONE); } diff --git a/src/server/scripts/Events/brewfest.cpp b/src/server/scripts/Events/brewfest.cpp index 499da074a..582362cba 100644 --- a/src/server/scripts/Events/brewfest.cpp +++ b/src/server/scripts/Events/brewfest.cpp @@ -1561,7 +1561,7 @@ public: { if (Unit* caster = GetCaster()) { - float z = caster->GetMap()->GetHeight(caster->GetPositionX() + 14 * cos(caster->GetOrientation()), caster->GetPositionY() + 14 * sin(caster->GetOrientation()), MAX_HEIGHT); + float z = caster->GetMapHeight(caster->GetPositionX() + 14 * cos(caster->GetOrientation()), caster->GetPositionY() + 14 * sin(caster->GetOrientation()), caster->GetPositionZ()); WorldLocation pPosition = WorldLocation(caster->GetMapId(), caster->GetPositionX() + 14 * cos(caster->GetOrientation()), caster->GetPositionY() + 14 * sin(caster->GetOrientation()), z, caster->GetOrientation()); SetExplTargetDest(pPosition); } diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/TheBlackMorass/the_black_morass.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/TheBlackMorass/the_black_morass.cpp index 91ff58ba6..10ac0f013 100644 --- a/src/server/scripts/Kalimdor/CavernsOfTime/TheBlackMorass/the_black_morass.cpp +++ b/src/server/scripts/Kalimdor/CavernsOfTime/TheBlackMorass/the_black_morass.cpp @@ -255,7 +255,7 @@ public: { if (Creature* cr = me->SummonCreature(NPC_SHADOW_COUNCIL_ENFORCER, -2091.731f, 7133.083f - 3.0f * i, 34.589f, 0.0f)) { - cr->GetMotionMaster()->MovePoint(0, (first && i == 3) ? x + 2.0f : x, cr->GetPositionY() + y, cr->GetMap()->GetHeight(x, cr->GetPositionY() + y, MAX_HEIGHT, true)); + cr->GetMotionMaster()->MovePoint(0, (first && i == 3) ? x + 2.0f : x, cr->GetPositionY() + y, cr->GetMapHeight(x, cr->GetPositionY() + y, cr->GetPositionZ(), true)); cr->m_Events.AddEvent(new NpcRunToHome(*cr), cr->m_Events.CalculateTime(homeTime + urand(0, 2000))); cr->DespawnOrUnsummon(duration + urand(0, 2000)); } diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp index 2092d7b59..1bec71d51 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp @@ -57,259 +57,259 @@ enum Misc class boss_anub_arak : public CreatureScript { -public: - boss_anub_arak() : CreatureScript("boss_anub_arak") { } + public: + boss_anub_arak() : CreatureScript("boss_anub_arak") { } - struct boss_anub_arakAI : public BossAI - { - boss_anub_arakAI(Creature* creature) : BossAI(creature, DATA_ANUBARAK_EVENT) + struct boss_anub_arakAI : public BossAI { - me->m_SightDistance = 120.0f; - intro = false; - } - - bool intro; - - void EnterEvadeMode() override - { - me->DisableRotate(false); - BossAI::EnterEvadeMode(); - } - - void MoveInLineOfSight(Unit* who) override - { - if (!intro && who->GetTypeId() == TYPEID_PLAYER) + boss_anub_arakAI(Creature* creature) : BossAI(creature, DATA_ANUBARAK_EVENT) { - intro = true; - Talk(SAY_INTRO); + me->m_SightDistance = 120.0f; + intro = false; } - BossAI::MoveInLineOfSight(who); - } - void JustDied(Unit* killer) override - { - Talk(SAY_DEATH); - BossAI::JustDied(killer); - } + bool intro; - void KilledUnit(Unit* /*victim*/) override - { - if (events.GetNextEventTime(EVENT_KILL_TALK) == 0) + void EnterEvadeMode() override { - Talk(SAY_SLAY); - events.ScheduleEvent(EVENT_KILL_TALK, 6000); + me->DisableRotate(false); + BossAI::EnterEvadeMode(); } - } - void JustSummoned(Creature* summon) override - { - summons.Summon(summon); - if (!summon->IsTrigger()) - summon->SetInCombatWithZone(); - } - - void Reset() override - { - BossAI::Reset(); - me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); - instance->DoStopTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_START_EVENT); - } - - void EnterCombat(Unit* ) override - { - Talk(SAY_AGGRO); - instance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_START_EVENT); - - events.ScheduleEvent(EVENT_CARRION_BEETELS, 6500); - events.ScheduleEvent(EVENT_LEECHING_SWARM, 20000); - events.ScheduleEvent(EVENT_POUND, 15000); - events.ScheduleEvent(EVENT_CHECK_HEALTH_75, 1000); - events.ScheduleEvent(EVENT_CHECK_HEALTH_50, 1000); - events.ScheduleEvent(EVENT_CHECK_HEALTH_25, 1000); - events.ScheduleEvent(EVENT_CLOSE_DOORS, 5000); - } - - void SummonHelpers(float x, float y, float z, uint32 spellId) - { - const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId); - me->SummonCreature(spellInfo->Effects[EFFECT_0].MiscValue, x, y, z); - } - - void UpdateAI(uint32 diff) override - { - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (uint32 eventId = events.ExecuteEvent()) + void MoveInLineOfSight(Unit* who) override { - case EVENT_CLOSE_DOORS: - _EnterCombat(); - break; - case EVENT_CARRION_BEETELS: - me->CastSpell(me, SPELL_CARRION_BEETLES, false); - events.ScheduleEvent(EVENT_CARRION_BEETELS, 25000); - break; - case EVENT_LEECHING_SWARM: - Talk(SAY_LOCUST); - me->CastSpell(me, SPELL_LEECHING_SWARM, false); - events.ScheduleEvent(EVENT_LEECHING_SWARM, 20000); - break; - case EVENT_POUND: - if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 10.0f)) - { - me->CastSpell(me, SPELL_SELF_ROOT, true); - me->DisableRotate(true); - me->SendMovementFlagUpdate(); - events.ScheduleEvent(EVENT_ENABLE_ROTATE, 3300); - me->CastSpell(target, SPELL_POUND, false); - } - events.ScheduleEvent(EVENT_POUND, 18000); - break; - case EVENT_ENABLE_ROTATE: - me->RemoveAurasDueToSpell(SPELL_SELF_ROOT); - me->DisableRotate(false); - break; - case EVENT_CHECK_HEALTH_25: - case EVENT_CHECK_HEALTH_50: - case EVENT_CHECK_HEALTH_75: - if (me->HealthBelowPct(eventId * 25)) - { - Talk(SAY_SUBMERGE); - me->CastSpell(me, SPELL_IMPALE_PERIODIC, true); - me->CastSpell(me, SPELL_SUBMERGE, false); - me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); + if (!intro && who->GetTypeId() == TYPEID_PLAYER) + { + intro = true; + Talk(SAY_INTRO); + } + BossAI::MoveInLineOfSight(who); + } - events.DelayEvents(46000, 0); - events.ScheduleEvent(EVENT_EMERGE, 45000); - events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 2000); - events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 4000); - events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 15000); - events.ScheduleEvent(EVENT_SUMMON_VENOMANCER, 20000); - events.ScheduleEvent(EVENT_SUMMON_DARTER, 30000); - events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 35000); + void JustDied(Unit* killer) override + { + Talk(SAY_DEATH); + BossAI::JustDied(killer); + } + + void KilledUnit(Unit* /*victim*/) override + { + if (events.GetNextEventTime(EVENT_KILL_TALK) == 0) + { + Talk(SAY_SLAY); + events.ScheduleEvent(EVENT_KILL_TALK, 6000); + } + } + + void JustSummoned(Creature* summon) override + { + summons.Summon(summon); + if (!summon->IsTrigger()) + summon->SetInCombatWithZone(); + } + + void Reset() override + { + BossAI::Reset(); + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE|UNIT_FLAG_NOT_SELECTABLE); + instance->DoStopTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_START_EVENT); + } + + void EnterCombat(Unit* ) override + { + Talk(SAY_AGGRO); + instance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_START_EVENT); + + events.ScheduleEvent(EVENT_CARRION_BEETELS, 6500); + events.ScheduleEvent(EVENT_LEECHING_SWARM, 20000); + events.ScheduleEvent(EVENT_POUND, 15000); + events.ScheduleEvent(EVENT_CHECK_HEALTH_75, 1000); + events.ScheduleEvent(EVENT_CHECK_HEALTH_50, 1000); + events.ScheduleEvent(EVENT_CHECK_HEALTH_25, 1000); + events.ScheduleEvent(EVENT_CLOSE_DOORS, 5000); + } + + void SummonHelpers(float x, float y, float z, uint32 spellId) + { + const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId); + me->SummonCreature(spellInfo->Effects[EFFECT_0].MiscValue, x, y, z); + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + events.Update(diff); + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + switch (uint32 eventId = events.ExecuteEvent()) + { + case EVENT_CLOSE_DOORS: + _EnterCombat(); break; - } - events.ScheduleEvent(eventId, 500); - break; - case EVENT_EMERGE: - me->CastSpell(me, SPELL_EMERGE, true); - me->RemoveAura(SPELL_SUBMERGE); - me->RemoveAura(SPELL_IMPALE_PERIODIC); - me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); - break; - case EVENT_SUMMON_ASSASSINS: - SummonHelpers(509.32f, 247.42f, 239.48f, SPELL_SUMMON_ASSASSIN); - SummonHelpers(589.51f, 240.19f, 236.0f, SPELL_SUMMON_ASSASSIN); - break; - case EVENT_SUMMON_DARTER: - SummonHelpers(509.32f, 247.42f, 239.48f, SPELL_SUMMON_DARTER); - SummonHelpers(589.51f, 240.19f, 236.0f, SPELL_SUMMON_DARTER); - break; - case EVENT_SUMMON_GUARDIAN: - SummonHelpers(550.34f, 316.00f, 234.30f, SPELL_SUMMON_GUARDIAN); - break; - case EVENT_SUMMON_VENOMANCER: - SummonHelpers(550.34f, 316.00f, 234.30f, SPELL_SUMMON_VENOMANCER); - break; + case EVENT_CARRION_BEETELS: + me->CastSpell(me, SPELL_CARRION_BEETLES, false); + events.ScheduleEvent(EVENT_CARRION_BEETELS, 25000); + break; + case EVENT_LEECHING_SWARM: + Talk(SAY_LOCUST); + me->CastSpell(me, SPELL_LEECHING_SWARM, false); + events.ScheduleEvent(EVENT_LEECHING_SWARM, 20000); + break; + case EVENT_POUND: + if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 10.0f)) + { + me->CastSpell(me, SPELL_SELF_ROOT, true); + me->DisableRotate(true); + me->SendMovementFlagUpdate(); + events.ScheduleEvent(EVENT_ENABLE_ROTATE, 3300); + me->CastSpell(target, SPELL_POUND, false); + } + events.ScheduleEvent(EVENT_POUND, 18000); + break; + case EVENT_ENABLE_ROTATE: + me->RemoveAurasDueToSpell(SPELL_SELF_ROOT); + me->DisableRotate(false); + break; + case EVENT_CHECK_HEALTH_25: + case EVENT_CHECK_HEALTH_50: + case EVENT_CHECK_HEALTH_75: + if (me->HealthBelowPct(eventId*25)) + { + Talk(SAY_SUBMERGE); + me->CastSpell(me, SPELL_IMPALE_PERIODIC, true); + me->CastSpell(me, SPELL_SUBMERGE, false); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE|UNIT_FLAG_NOT_SELECTABLE); + + events.DelayEvents(46000, 0); + events.ScheduleEvent(EVENT_EMERGE, 45000); + events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 2000); + events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 4000); + events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 15000); + events.ScheduleEvent(EVENT_SUMMON_VENOMANCER, 20000); + events.ScheduleEvent(EVENT_SUMMON_DARTER, 30000); + events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 35000); + break; + } + events.ScheduleEvent(eventId, 500); + break; + case EVENT_EMERGE: + me->CastSpell(me, SPELL_EMERGE, true); + me->RemoveAura(SPELL_SUBMERGE); + me->RemoveAura(SPELL_IMPALE_PERIODIC); + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE|UNIT_FLAG_NOT_SELECTABLE); + break; + case EVENT_SUMMON_ASSASSINS: + SummonHelpers(509.32f, 247.42f, 239.48f, SPELL_SUMMON_ASSASSIN); + SummonHelpers(589.51f, 240.19f, 236.0f, SPELL_SUMMON_ASSASSIN); + break; + case EVENT_SUMMON_DARTER: + SummonHelpers(509.32f, 247.42f, 239.48f, SPELL_SUMMON_DARTER); + SummonHelpers(589.51f, 240.19f, 236.0f, SPELL_SUMMON_DARTER); + break; + case EVENT_SUMMON_GUARDIAN: + SummonHelpers(550.34f, 316.00f, 234.30f, SPELL_SUMMON_GUARDIAN); + break; + case EVENT_SUMMON_VENOMANCER: + SummonHelpers(550.34f, 316.00f, 234.30f, SPELL_SUMMON_VENOMANCER); + break; + } + + if (!me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)) + DoMeleeAttackIfReady(); } + }; - if (!me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)) - DoMeleeAttackIfReady(); + CreatureAI* GetAI(Creature* creature) const override + { + return new boss_anub_arakAI(creature); } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return new boss_anub_arakAI(creature); - } }; class spell_azjol_nerub_carrion_beetels : public SpellScriptLoader { -public: - spell_azjol_nerub_carrion_beetels() : SpellScriptLoader("spell_azjol_nerub_carrion_beetels") { } + public: + spell_azjol_nerub_carrion_beetels() : SpellScriptLoader("spell_azjol_nerub_carrion_beetels") { } - class spell_azjol_nerub_carrion_beetelsAuraScript : public AuraScript - { - PrepareAuraScript(spell_azjol_nerub_carrion_beetelsAuraScript) - - void HandleEffectPeriodic(AuraEffect const* /*aurEff*/) + class spell_azjol_nerub_carrion_beetelsAuraScript : public AuraScript { - // Xinef: 2 each second - GetUnitOwner()->CastSpell(GetUnitOwner(), SPELL_SUMMON_CARRION_BEETLES, true); - GetUnitOwner()->CastSpell(GetUnitOwner(), SPELL_SUMMON_CARRION_BEETLES, true); - } + PrepareAuraScript(spell_azjol_nerub_carrion_beetelsAuraScript) - void Register() override + void HandleEffectPeriodic(AuraEffect const* /*aurEff*/) + { + // Xinef: 2 each second + GetUnitOwner()->CastSpell(GetUnitOwner(), SPELL_SUMMON_CARRION_BEETLES, true); + GetUnitOwner()->CastSpell(GetUnitOwner(), SPELL_SUMMON_CARRION_BEETLES, true); + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_azjol_nerub_carrion_beetelsAuraScript::HandleEffectPeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + } + }; + + AuraScript *GetAuraScript() const override { - OnEffectPeriodic += AuraEffectPeriodicFn(spell_azjol_nerub_carrion_beetelsAuraScript::HandleEffectPeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); + return new spell_azjol_nerub_carrion_beetelsAuraScript(); } - }; - - AuraScript* GetAuraScript() const override - { - return new spell_azjol_nerub_carrion_beetelsAuraScript(); - } }; class spell_azjol_nerub_pound : public SpellScriptLoader { -public: - spell_azjol_nerub_pound() : SpellScriptLoader("spell_azjol_nerub_pound") { } + public: + spell_azjol_nerub_pound() : SpellScriptLoader("spell_azjol_nerub_pound") { } - class spell_azjol_nerub_pound_SpellScript : public SpellScript - { - PrepareSpellScript(spell_azjol_nerub_pound_SpellScript); - - void HandleApplyAura(SpellEffIndex /*effIndex*/) + class spell_azjol_nerub_pound_SpellScript : public SpellScript { - if (Unit* unitTarget = GetHitUnit()) - GetCaster()->CastSpell(unitTarget, SPELL_POUND_DAMAGE, true); - } + PrepareSpellScript(spell_azjol_nerub_pound_SpellScript); - void Register() override + void HandleApplyAura(SpellEffIndex /*effIndex*/) + { + if (Unit* unitTarget = GetHitUnit()) + GetCaster()->CastSpell(unitTarget, SPELL_POUND_DAMAGE, true); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_azjol_nerub_pound_SpellScript::HandleApplyAura, EFFECT_0, SPELL_EFFECT_APPLY_AURA); + } + }; + + SpellScript* GetSpellScript() const override { - OnEffectHitTarget += SpellEffectFn(spell_azjol_nerub_pound_SpellScript::HandleApplyAura, EFFECT_0, SPELL_EFFECT_APPLY_AURA); + return new spell_azjol_nerub_pound_SpellScript(); } - }; - - SpellScript* GetSpellScript() const override - { - return new spell_azjol_nerub_pound_SpellScript(); - } }; class spell_azjol_nerub_impale_summon : public SpellScriptLoader { -public: - spell_azjol_nerub_impale_summon() : SpellScriptLoader("spell_azjol_nerub_impale_summon") { } + public: + spell_azjol_nerub_impale_summon() : SpellScriptLoader("spell_azjol_nerub_impale_summon") { } - class spell_azjol_nerub_impale_summon_SpellScript : public SpellScript - { - PrepareSpellScript(spell_azjol_nerub_impale_summon_SpellScript); - - void SetDest(SpellDestination& dest) + class spell_azjol_nerub_impale_summon_SpellScript : public SpellScript { - // Adjust effect summon position - float floorZ = GetCaster()->GetMap()->GetHeight(GetCaster()->GetPositionX(), GetCaster()->GetPositionY(), GetCaster()->GetPositionZ(), true); - if (floorZ > INVALID_HEIGHT) - dest._position.m_positionZ = floorZ; - } + PrepareSpellScript(spell_azjol_nerub_impale_summon_SpellScript); - void Register() override + void SetDest(SpellDestination& dest) + { + // Adjust effect summon position + float floorZ = GetCaster()->GetMapHeight(GetCaster()->GetPositionX(), GetCaster()->GetPositionY(), GetCaster()->GetPositionZ(), true); + if (floorZ > INVALID_HEIGHT) + dest._position.m_positionZ = floorZ; + } + + void Register() override + { + OnDestinationTargetSelect += SpellDestinationTargetSelectFn(spell_azjol_nerub_impale_summon_SpellScript::SetDest, EFFECT_0, TARGET_DEST_CASTER); + } + }; + + SpellScript* GetSpellScript() const override { - OnDestinationTargetSelect += SpellDestinationTargetSelectFn(spell_azjol_nerub_impale_summon_SpellScript::SetDest, EFFECT_0, TARGET_DEST_CASTER); + return new spell_azjol_nerub_impale_summon_SpellScript(); } - }; - - SpellScript* GetSpellScript() const override - { - return new spell_azjol_nerub_impale_summon_SpellScript(); - } }; void AddSC_boss_anub_arak() diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp index fbf72a15a..b7cedacfe 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp @@ -1376,7 +1376,7 @@ public: me->GetPosition(x, y, z); _x = x; _y = y; - _groundZ = me->GetMap()->GetHeight(me->GetPhaseMask(), x, y, z, true, 500.0f); + _groundZ = me->GetMapHeight(x, y, z, true, 500.0f); me->GetMotionMaster()->MoveCharge(_x, _y, _groundZ, me->GetSpeed(MOVE_WALK)); } @@ -1748,7 +1748,7 @@ public: float destY = summoner->GetPositionY() + sin(angle + a * M_PI) * i * 10.0f; if (summoner->GetMap()->isInLineOfSight(summoner->GetPositionX(), summoner->GetPositionY(), summoner->GetPositionZ() + 10.0f, destX, destY, summoner->GetPositionZ() + 10.0f, summoner->GetPhaseMask(), LINEOFSIGHT_ALL_CHECKS) && destX > 4585.0f && destY > 2716.0f && destY < 2822.0f) { - float destZ = summoner->GetMap()->GetHeight(summoner->GetPhaseMask(), destX, destY, summoner->GetPositionZ() + 10.0f); + float destZ = summoner->GetMapHeight(summoner->GetPhaseMask(), destX, destY, summoner->GetPositionZ()); if (fabs(destZ - summoner->GetPositionZ()) < 10.0f) // valid z found { dest._position.Relocate(destX, destY, destZ); diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp index 76c489540..1f4245952 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp @@ -2532,7 +2532,8 @@ public: bool operator()(WorldObject* unit) { - return unit->GetTypeId() != TYPEID_PLAYER || unit->GetPositionZ() > 478.0f || !unit->GetTransport() || unit->GetTransport()->GetEntry() != _entry || unit->GetMap()->GetHeight(unit->GetPhaseMask(), unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ()) < 465.0f; + return unit->GetTypeId() != TYPEID_PLAYER || unit->GetPositionZ() > 478.0f || !unit->GetTransport() || unit->GetTransport()->GetEntry() != _entry + || unit->GetMapHeight(unit->GetPhaseMask(), unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ()) < 465.0f; } private: @@ -2566,7 +2567,9 @@ public: void HandleScript(SpellEffIndex effIndex) { PreventHitDefaultEffect(effIndex); - GetCaster()->CastSpell(GetHitUnit()->GetPositionX(), GetHitUnit()->GetPositionY(), GetHitUnit()->GetMap()->GetHeight(GetCaster()->GetPhaseMask(), GetHitUnit()->GetPositionX(), GetHitUnit()->GetPositionY(), GetHitUnit()->GetPositionZ()), uint32(GetEffectValue()), TRIGGERED_NONE); + GetCaster()->CastSpell(GetHitUnit()->GetPositionX(), GetHitUnit()->GetPositionY(), + GetHitUnit()->GetMapHeight(GetCaster()->GetPhaseMask(), GetHitUnit()->GetPositionX(), GetHitUnit()->GetPositionY(), GetHitUnit()->GetPositionZ()), + uint32(GetEffectValue()), TRIGGERED_NONE); } void Register() override diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp index 5019baed7..e22b4d461 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp @@ -201,18 +201,16 @@ public: { if (!sindragosa->IsAlive()) return true; + Position pos; _owner->GetPosition(&pos); - _owner->m_positionZ -= 1.0f; // +2.0f in UpdateGroundPositionZ, prevent going over GO model of another ice block, because new would be spawned on top of the old one xd _owner->UpdateGroundPositionZ(pos.m_positionX, pos.m_positionY, pos.m_positionZ); - if (pos.GetPositionZ() < 203.0f) - pos.m_positionZ = 203.0f; + if (TempSummon* summon = sindragosa->SummonCreature(NPC_ICE_TOMB, pos)) { - summon->m_positionZ = summon->GetPositionZ() + 5.0f; summon->AI()->SetGUID(_owner->GetGUID(), DATA_TRAPPED_PLAYER); _owner->CastSpell(_owner, SPELL_ICE_TOMB_UNTARGETABLE, true); - if (GameObject* go = summon->SummonGameObject(GO_ICE_BLOCK, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ() - 3.5f, pos.GetOrientation(), 0.0f, 0.0f, 0.0f, 0.0f, 0)) + if (GameObject* go = summon->SummonGameObject(GO_ICE_BLOCK, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), 0.0f, 0.0f, 0.0f, 0.0f, 0)) { go->SetSpellId(SPELL_ICE_TOMB_DAMAGE); summon->AddGameObject(go); @@ -661,7 +659,7 @@ public: me->GetMotionMaster()->MoveLand(POINT_LAND_GROUND, SindragosaLandPos, 10.0f); break; case EVENT_THIRD_PHASE_CHECK: - if (!me->HasByteFlag(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_HOVER)) + if (!me->HasByteFlag(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, UNIT_BYTE1_FLAG_HOVER)) { Talk(SAY_PHASE_2); events.ScheduleEvent(EVENT_ICE_TOMB, urand(7000, 10000)); @@ -1654,7 +1652,7 @@ public: me->SetDisableGravity(true); me->SetHover(true); me->SendMovementFlagUpdate(); - float floorZ = me->GetMap()->GetHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ() + 2.0f); + float floorZ = me->GetMapHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()); float destZ; if (floorZ > 190.0f) destZ = floorZ + 25.0f; else destZ = me->GetPositionZ() + 25.0f; @@ -1676,7 +1674,7 @@ public: } else { - float floorZ = me->GetMap()->GetHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ() + 2.0f); + float floorZ = me->GetMapHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()); float destZ; if (floorZ > 190.0f) destZ = floorZ; else destZ = me->GetPositionZ() - 25.0f; diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp index 660aa2fc6..c9a3bbb18 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp @@ -798,13 +798,13 @@ public: if (_phase == PHASE_OUTRO) { - if (!me->HasByteFlag(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_HOVER)) + if (!me->HasByteFlag(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, UNIT_BYTE1_FLAG_HOVER)) damage = me->GetHealth() > 1 ? 1 : 0; else if (damage >= me->GetHealth()) // dying... { damage = me->GetHealth() - 1; me->SetDisableGravity(false); - me->RemoveByteFlag(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_ALWAYS_STAND | UNIT_BYTE1_FLAG_HOVER); + me->RemoveByteFlag(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, UNIT_BYTE1_FLAG_ALWAYS_STAND | UNIT_BYTE1_FLAG_HOVER); me->SendMonsterMove(me->GetPositionX() + 0.25f, me->GetPositionY(), 840.86f, 300, SPLINEFLAG_FALLING); me->m_positionZ = 840.86f; me->SetOrientation(0.0f); @@ -1546,7 +1546,7 @@ public: theLichKing->CastSpell((Unit*)NULL, SPELL_SOUL_BARRAGE, TRIGGERED_IGNORE_CAST_IN_PROGRESS); sCreatureTextMgr->SendSound(theLichKing, SOUND_PAIN, CHAT_MSG_MONSTER_YELL, 0, TEXT_RANGE_NORMAL, TEAM_NEUTRAL, false); theLichKing->SetDisableGravity(true); - theLichKing->SetByteFlag(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_ALWAYS_STAND | UNIT_BYTE1_FLAG_HOVER); + theLichKing->SetByteFlag(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, UNIT_BYTE1_FLAG_ALWAYS_STAND | UNIT_BYTE1_FLAG_HOVER); theLichKing->GetMotionMaster()->MovePoint(0, OutroFlying); _events.ScheduleEvent(EVENT_OUTRO_AFTER_SOUL_BARRAGE, 3000); diff --git a/src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.cpp b/src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.cpp index 2ad80211a..2ac2ef686 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_kelthuzad.cpp @@ -146,14 +146,14 @@ public: for (uint8 j = 0; j < 8; ++j) { float angle = M_PI * 2 / 8 * j; - me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, SummonPositions[i].GetPositionX() + 6 * cos(angle), SummonPositions[i].GetPositionY() + 6 * sin(angle), SummonPositions[i].GetPositionZ() + 0.5f, SummonPositions[i].GetOrientation(), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000); + me->SummonCreature(NPC_SOLDIER_OF_THE_FROZEN_WASTES, SummonPositions[i].GetPositionX() + 6 * cos(angle), SummonPositions[i].GetPositionY() + 6 * sin(angle), SummonPositions[i].GetPositionZ(), SummonPositions[i].GetOrientation(), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000); } for (uint8 i = 6; i < 12; ++i) for (uint8 j = 1; j < 4; ++j) { float dist = j == 2 ? 0.0f : 8.0f; // second in middle float angle = SummonPositions[i].GetOrientation() + M_PI * 2 / 4 * j; - me->SummonCreature(NPC_UNSTOPPABLE_ABOMINATION, SummonPositions[i].GetPositionX() + dist * cos(angle), SummonPositions[i].GetPositionY() + dist * sin(angle), SummonPositions[i].GetPositionZ() + 0.5f, SummonPositions[i].GetOrientation(), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000); + me->SummonCreature(NPC_UNSTOPPABLE_ABOMINATION, SummonPositions[i].GetPositionX() + dist * cos(angle), SummonPositions[i].GetPositionY() + dist * sin(angle), SummonPositions[i].GetPositionZ(), SummonPositions[i].GetOrientation(), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000); } for (uint8 i = 6; i < 12; ++i) for (uint8 j = 0; j < 1; ++j) diff --git a/src/server/scripts/Northrend/Nexus/Nexus/boss_ormorok.cpp b/src/server/scripts/Northrend/Nexus/Nexus/boss_ormorok.cpp index ddfc783ae..4c780ce74 100644 --- a/src/server/scripts/Northrend/Nexus/Nexus/boss_ormorok.cpp +++ b/src/server/scripts/Northrend/Nexus/Nexus/boss_ormorok.cpp @@ -153,7 +153,7 @@ public: float o = rand_norm() * 2.0f * M_PI; float x = me->GetPositionX() + 5.0f * _spikesCount * cos(o); float y = me->GetPositionY() + 5.0f * _spikesCount * sin(o); - float h = me->GetMap()->GetHeight(x, y, me->GetPositionZ() + 5.0f); + float h = me->GetMapHeight(x, y, me->GetPositionZ()); if (h != INVALID_HEIGHT) me->SummonCreature(NPC_CRYSTAL_SPIKE, x, y, h, 0, TEMPSUMMON_TIMED_DESPAWN, 7000); diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp index c57067d62..07388f3ad 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp @@ -1383,7 +1383,7 @@ public: { float x, y, z; me->GetPosition(x, y, z); - z = me->GetMap()->GetHeight(me->GetPhaseMask(), x, y, z); + z = me->GetMapHeight(x, y, z); me->GetMotionMaster()->MovePoint(me->GetEntry(), x, y, z); me->SetPosition(x, y, z, 0); } diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp index 7a6c39867..4c4f35d94 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp @@ -387,16 +387,16 @@ public: { me->MonsterYell("Children, assist me!", LANG_UNIVERSAL, 0); me->PlayDirectSound(SOUND_TRIO); - me->SummonCreature(NPC_ANCIENT_WATER_SPIRIT, me->GetPositionX() + urand(5, 15), me->GetPositionY() + urand(5, 15), me->GetMap()->GetHeight(me->GetPositionX(), me->GetPositionY(), MAX_HEIGHT)); - me->SummonCreature(NPC_STORM_LASHER, me->GetPositionX() + urand(5, 15), me->GetPositionY() + urand(5, 15), me->GetMap()->GetHeight(me->GetPositionX(), me->GetPositionY(), MAX_HEIGHT)); - me->SummonCreature(NPC_SNAPLASHER, me->GetPositionX() + urand(5, 15), me->GetPositionY() + urand(5, 15), me->GetMap()->GetHeight(me->GetPositionX(), me->GetPositionY(), MAX_HEIGHT)); + me->SummonCreature(NPC_ANCIENT_WATER_SPIRIT, me->GetPositionX() + urand(5, 15), me->GetPositionY() + urand(5, 15), me->GetMapHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ())); + me->SummonCreature(NPC_STORM_LASHER, me->GetPositionX() + urand(5, 15), me->GetPositionY() + urand(5, 15), me->GetMapHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ())); + me->SummonCreature(NPC_SNAPLASHER, me->GetPositionX() + urand(5, 15), me->GetPositionY() + urand(5, 15), me->GetMapHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ())); } // Ancient Conservator else if (_waveNumber == 2) { me->MonsterYell("Eonar, your servant requires aid!", LANG_UNIVERSAL, 0); me->PlayDirectSound(SOUND_CONSERVATOR); - me->SummonCreature(NPC_ANCIENT_CONSERVATOR, me->GetPositionX() + urand(5, 15), me->GetPositionY() + urand(5, 15), me->GetMap()->GetHeight(me->GetPositionX(), me->GetPositionY(), MAX_HEIGHT), 0, TEMPSUMMON_CORPSE_DESPAWN); + me->SummonCreature(NPC_ANCIENT_CONSERVATOR, me->GetPositionX() + urand(5, 15), me->GetPositionY() + urand(5, 15), me->GetMapHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()), 0, TEMPSUMMON_CORPSE_DESPAWN); } // Detonating Lashers else if (_waveNumber == 3) @@ -404,7 +404,7 @@ public: me->MonsterYell("The swarm of the elements shall overtake you!", LANG_UNIVERSAL, 0); me->PlayDirectSound(SOUND_DETONATING); for (uint8 i = 0; i < 10; ++i) - me->SummonCreature(NPC_DETONATING_LASHER, me->GetPositionX() + urand(5, 20), me->GetPositionY() + urand(5, 20), me->GetMap()->GetHeight(me->GetPositionX(), me->GetPositionY(), MAX_HEIGHT), 0, TEMPSUMMON_CORPSE_DESPAWN); + me->SummonCreature(NPC_DETONATING_LASHER, me->GetPositionX() + urand(5, 20), me->GetPositionY() + urand(5, 20), me->GetMapHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()), 0, TEMPSUMMON_CORPSE_DESPAWN); } } @@ -586,7 +586,7 @@ public: { x = me->GetPositionX() + urand(7, 25); y = me->GetPositionY() + urand(7, 25); - z = me->GetMap()->GetHeight(x, y, MAX_HEIGHT) + 0.5f; + z = me->GetMapHeight(x, y, me->GetPositionZ()); if (me->IsWithinLOS(x, y, z)) { me->CastSpell(x, y, z, SPELL_SUMMON_LIFEBINDER, true); @@ -641,11 +641,11 @@ public: events.RepeatEvent(45000 + urand(0, 10000)); break; case EVENT_FREYA_UNSTABLE_SUN_BEAM: - me->SummonCreature(NPC_FREYA_UNSTABLE_SUN_BEAM, me->GetPositionX() + urand(7, 25), me->GetPositionY() + urand(7, 25), me->GetMap()->GetHeight(me->GetPositionX(), me->GetPositionY(), MAX_HEIGHT), 0, TEMPSUMMON_TIMED_DESPAWN, 10000); + me->SummonCreature(NPC_FREYA_UNSTABLE_SUN_BEAM, me->GetPositionX() + urand(7, 25), me->GetPositionY() + urand(7, 25), me->GetMapHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()), 0, TEMPSUMMON_TIMED_DESPAWN, 10000); if (Is25ManRaid()) { - me->SummonCreature(NPC_FREYA_UNSTABLE_SUN_BEAM, me->GetPositionX() + urand(7, 25), me->GetPositionY() + urand(7, 25), me->GetMap()->GetHeight(me->GetPositionX(), me->GetPositionY(), MAX_HEIGHT), 0, TEMPSUMMON_TIMED_DESPAWN, 10000); - me->SummonCreature(NPC_FREYA_UNSTABLE_SUN_BEAM, me->GetPositionX() + urand(7, 25), me->GetPositionY() + urand(7, 25), me->GetMap()->GetHeight(me->GetPositionX(), me->GetPositionY(), MAX_HEIGHT), 0, TEMPSUMMON_TIMED_DESPAWN, 10000); + me->SummonCreature(NPC_FREYA_UNSTABLE_SUN_BEAM, me->GetPositionX() + urand(7, 25), me->GetPositionY() + urand(7, 25), me->GetMapHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()), 0, TEMPSUMMON_TIMED_DESPAWN, 10000); + me->SummonCreature(NPC_FREYA_UNSTABLE_SUN_BEAM, me->GetPositionX() + urand(7, 25), me->GetPositionY() + urand(7, 25), me->GetMapHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()), 0, TEMPSUMMON_TIMED_DESPAWN, 10000); } events.RepeatEvent(38000 + urand(0, 10000)); break; diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp index a71d77205..2b14689df 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp @@ -152,7 +152,7 @@ public: _nerfAchievement = true; _gravityAchievement = true; - me->SetByteValue(UNIT_FIELD_BYTES_1, 0, UNIT_STAND_STATE_STAND); // emerge + me->SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, UNIT_STAND_STATE_STAND); // emerge me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE); me->SetControlled(false, UNIT_STATE_STUNNED); @@ -262,7 +262,7 @@ public: me->SetLootMode(3); // hard mode + normal loot me->SetMaxHealth(me->GetMaxHealth()); me->SetHealth(me->GetMaxHealth()); - me->SetByteValue(UNIT_FIELD_BYTES_1, 0, UNIT_STAND_STATE_STAND); // emerge + me->SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, UNIT_STAND_STATE_STAND); // emerge me->CastSpell(me, SPELL_HEARTBREAK, true); @@ -314,7 +314,7 @@ public: { _healthCheck -= 25; me->SetControlled(true, UNIT_STATE_STUNNED); - me->SetByteValue(UNIT_FIELD_BYTES_1, 0, UNIT_STAND_STATE_SUBMERGED); // submerge with animation + me->SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, UNIT_STAND_STATE_SUBMERGED); // submerge with animation me->MonsterYell("So tired. I will rest for just a moment!", LANG_UNIVERSAL, 0); me->PlayDirectSound(XT_SOUND_HEART_OPEN); @@ -373,7 +373,7 @@ public: me->MonsterYell("I'm ready to play!", LANG_UNIVERSAL, 0); me->PlayDirectSound(XT_SOUND_HEART_CLOSED); - me->SetByteValue(UNIT_FIELD_BYTES_1, 0, UNIT_STAND_STATE_STAND); // emerge + me->SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, UNIT_STAND_STATE_STAND); // emerge // Hide heart if (Unit* heart = me->GetVehicleKit() ? me->GetVehicleKit()->GetPassenger(HEART_VEHICLE_SEAT) : nullptr) heart->GetAI()->DoAction(ACTION_HIDE_HEART); diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp index 263c5f9ae..5d8e0ea8a 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp @@ -358,7 +358,7 @@ public: break; case EVENT_AXE_RETURN: if (Creature* c = ObjectAccessor::GetCreature(*me, ThrowGUID)) - c->GetMotionMaster()->MoveCharge(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ() + 0.5f); + c->GetMotionMaster()->MoveCharge(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()); events.RescheduleEvent(EVENT_AXE_PICKUP, 1500); break; case EVENT_AXE_PICKUP: diff --git a/src/server/scripts/Northrend/VioletHold/boss_cyanigosa.cpp b/src/server/scripts/Northrend/VioletHold/boss_cyanigosa.cpp index 3c3d5dde3..b974ad6aa 100644 --- a/src/server/scripts/Northrend/VioletHold/boss_cyanigosa.cpp +++ b/src/server/scripts/Northrend/VioletHold/boss_cyanigosa.cpp @@ -148,7 +148,7 @@ public: Talk(SAY_DEATH); if (pInstance) pInstance->SetData(DATA_BOSS_DIED, 0); - float h = me->GetMap()->GetHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ() + 2.0f); + float h = me->GetMapHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()); if (h != INVALID_HEIGHT && me->GetPositionZ() - h > 3.0f) { me->UpdatePosition(me->GetPositionX(), me->GetPositionY(), h, me->GetOrientation(), true); // move to ground diff --git a/src/server/scripts/Northrend/zone_sholazar_basin.cpp b/src/server/scripts/Northrend/zone_sholazar_basin.cpp index 614266717..b04a99558 100644 --- a/src/server/scripts/Northrend/zone_sholazar_basin.cpp +++ b/src/server/scripts/Northrend/zone_sholazar_basin.cpp @@ -1289,7 +1289,7 @@ public: Unit::Kill(bird, bird); crunchy->GetMotionMaster()->MovePoint(0, bird->GetPositionX(), bird->GetPositionY(), - bird->GetMap()->GetWaterOrGroundLevel(bird->GetPhaseMask(), bird->GetPositionX(), bird->GetPositionY(), bird->GetPositionZ())); + bird->GetMapWaterOrGroundLevel(bird->GetPositionX(), bird->GetPositionY(), bird->GetPositionZ())); /// @todo Make crunchy perform emote eat when he reaches the bird break; diff --git a/src/server/scripts/Outland/zone_blades_edge_mountains.cpp b/src/server/scripts/Outland/zone_blades_edge_mountains.cpp index eef1fbea3..1218697de 100644 --- a/src/server/scripts/Outland/zone_blades_edge_mountains.cpp +++ b/src/server/scripts/Outland/zone_blades_edge_mountains.cpp @@ -1084,7 +1084,7 @@ public: else { // Spell 37392 does not exist in dbc, manually spawning - me->SummonCreature(NPC_OSCILLATING_FREQUENCY_SCANNER_TOP_BUNNY, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ() + 0.5f, me->GetOrientation(), TEMPSUMMON_TIMED_OR_DEAD_DESPAWN, 50000); + me->SummonCreature(NPC_OSCILLATING_FREQUENCY_SCANNER_TOP_BUNNY, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), me->GetOrientation(), TEMPSUMMON_TIMED_OR_DEAD_DESPAWN, 50000); me->SummonGameObject(GO_OSCILLATING_FREQUENCY_SCANNER, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), me->GetOrientation(), 0, 0, 0, 0, 50); me->DespawnOrUnsummon(50000); } diff --git a/src/server/scripts/Outland/zone_shadowmoon_valley.cpp b/src/server/scripts/Outland/zone_shadowmoon_valley.cpp index 7e4e4627b..922f1f96f 100644 --- a/src/server/scripts/Outland/zone_shadowmoon_valley.cpp +++ b/src/server/scripts/Outland/zone_shadowmoon_valley.cpp @@ -156,7 +156,7 @@ public: void Reset() override { - ground = me->GetMap()->GetHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()); + ground = me->GetMapHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()); SummonInfernal(); events.ScheduleEvent(EVENT_CAST_SUMMON_INFERNAL, urand(1000, 3000)); } diff --git a/src/server/scripts/Pet/pet_dk.cpp b/src/server/scripts/Pet/pet_dk.cpp index f84c4a6b4..7903c31d6 100644 --- a/src/server/scripts/Pet/pet_dk.cpp +++ b/src/server/scripts/Pet/pet_dk.cpp @@ -67,11 +67,12 @@ public: if (aur->GetEffect(0)) aur->GetEffect(0)->SetAmount(-aurEff->GetSpellInfo()->Effects[EFFECT_2].CalcValue()); - float tz = me->GetMap()->GetHeight(me->GetPhaseMask(), me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), true, MAX_FALL_DISTANCE); - me->GetMotionMaster()->MoveCharge(me->GetPositionX(), me->GetPositionY(), tz, 7.0f, 1); - me->AddUnitState(UNIT_STATE_NO_ENVIRONMENT_UPD); me->SetCanFly(true); me->SetDisableGravity(true); + + float tz = me->GetMapHeight(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), true, MAX_FALL_DISTANCE); + me->GetMotionMaster()->MoveCharge(me->GetPositionX(), me->GetPositionY(), tz, 7.0f, 1); + me->AddUnitState(UNIT_STATE_NO_ENVIRONMENT_UPD); _selectionTimer = 2000; _initialCastTimer = 0; } diff --git a/src/server/scripts/World/go_scripts.cpp b/src/server/scripts/World/go_scripts.cpp index 4cb03b770..fe01ea813 100644 --- a/src/server/scripts/World/go_scripts.cpp +++ b/src/server/scripts/World/go_scripts.cpp @@ -284,7 +284,7 @@ public: requireSummon = 0; int8 count = urand(1, 3); for (int8 i = 0; i < count; ++i) - go->SummonCreature(NPC_WINTERFIN_TADPOLE, go->GetPositionX() + cos(2 * M_PI * i / 3.0f) * 0.60f, go->GetPositionY() + sin(2 * M_PI * i / 3.0f) * 0.60f, go->GetPositionZ() + 0.5f, go->GetOrientation(), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000); + go->SummonCreature(NPC_WINTERFIN_TADPOLE, go->GetPositionX() + cos(2 * M_PI * i / 3.0f) * 0.60f, go->GetPositionY() + sin(2 * M_PI * i / 3.0f) * 0.60f, go->GetPositionZ(), go->GetOrientation(), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000); } void OnStateChanged(uint32 state, Unit* /*unit*/) override diff --git a/src/server/shared/DataStores/DBCStructure.h b/src/server/shared/DataStores/DBCStructure.h index fbcf4b0f7..bb25c6b96 100644 --- a/src/server/shared/DataStores/DBCStructure.h +++ b/src/server/shared/DataStores/DBCStructure.h @@ -741,7 +741,7 @@ struct CreatureModelDataEntry //float Unk8 //uint32 Unk9 //uint32 Unk10 - //float CollisionWidth; + float CollisionWidth; float CollisionHeight; float MountHeight; // Used in calculation of unit collision data when mounted //float Unks[11] diff --git a/src/server/shared/DataStores/DBCfmt.h b/src/server/shared/DataStores/DBCfmt.h index 1fe23900d..b811cf037 100644 --- a/src/server/shared/DataStores/DBCfmt.h +++ b/src/server/shared/DataStores/DBCfmt.h @@ -26,7 +26,7 @@ char constexpr CinematicCameraEntryfmt[] = "nsiffff"; char constexpr CinematicSequencesEntryfmt[] = "nxixxxxxxx"; char constexpr CreatureDisplayInfofmt[] = "nixxfxxxxxxxxxxx"; char constexpr CreatureFamilyfmt[] = "nfifiiiiixssssssssssssssssxx"; -char constexpr CreatureModelDatafmt[] = "nxxxfxxxxxxxxxxffxxxxxxxxxxx"; +char constexpr CreatureModelDatafmt[] = "nxxxfxxxxxxxxxfffxxxxxxxxxxx"; char constexpr CreatureSpellDatafmt[] = "niiiixxxx"; char constexpr CreatureTypefmt[] = "nxxxxxxxxxxxxxxxxxx"; char constexpr CurrencyTypesfmt[] = "xnxi"; diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 71443bb40..f887653f3 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -1950,6 +1950,22 @@ ListenRange.Yell = 300 WaypointMovementStopTimeForPlayer = 120 +# NpcEvadeIfTargetIsUnreachable +# Description: Specifies the time (in seconds) that a creature whom target +# is unreachable to end up in evade mode. +# Default: 5 + +NpcEvadeIfTargetIsUnreachable = 5 + +# NpcRegenHPIfTargetIsUnreachable +# Description: Regenerates HP for Creatures in Raids if they cannot reach the target. +# Keep disabled if you are experiencing mmaps/pathing issues. +# +# Default: 1 - (Enabled) +# 0 - (Disabled) + +NpcRegenHPIfTargetIsUnreachable = 1 + # ################################################################################################### diff --git a/src/tools/mesh_extractor/MeshExtractor.cpp b/src/tools/mesh_extractor/MeshExtractor.cpp index 2cafcae5c..39d06969a 100644 --- a/src/tools/mesh_extractor/MeshExtractor.cpp +++ b/src/tools/mesh_extractor/MeshExtractor.cpp @@ -399,7 +399,7 @@ int main(int argc, char* argv[]) m_epos[2] = -end[0]; // - dtQueryFilter m_filter; + dtQueryFilterExt m_filter; m_filter.setIncludeFlags(Constants::POLY_AREA_ROAD | Constants::POLY_AREA_TERRAIN); m_filter.setExcludeFlags(Constants::POLY_AREA_WATER);