From c8f43d85849c40e08d0da38913489426a0c35388 Mon Sep 17 00:00:00 2001 From: Yehonal Date: Mon, 1 Feb 2021 01:34:27 +0100 Subject: [PATCH] feat(Core/Movement): Improved pathfinding, collisions and movements (#4220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Npc positioning Implemented slope check to avoid unwanted climbing for some kind of movements (backwards, repositioning etc.) Implemented backwards movement Re-implemented circle repositioning algorithm (smartest than retail, but with the same feeling) Fixed random position of summoned minions Improved pet following movement. Also, they attack NPC from behind now. Thanks to @Footman Swimming creatures Fixed max_z coordinate for swimming creatures. Now only part of their body is allowed to be out of the water level Fixed pathfinder for swimming creatures creating shortcuts for specific segments, now they swim underwater to reach the seashore instead of flying above the water level. Creatures with water InhabitType but no swimming flag now, when not in combat, will walk on water depth instead of swimming. Thanks @jackpoz for the original code UNIT_FLAG_SWIMMING in UpdateEnvironmentIfNeeded to show the swimming animation correctly when underwater Implemented HasEnoughWater check to avoid swimming creatures to go where the water level is too low but also to properly enable swimming animation only when a creature has enough water to swim. Walking creatures Extended the DetourNavMeshQuery adding area cost based on walkability (slope angle + source height) to find better paths at runtime instead of completely remove them from mmaps improve Z height in certain conditions (see #4205, #4203, #4247 ) Flying creatures Rewriting of the hover system Removed hacks and improved the UpdateEnvironmentIfNeeded. Now creatures can properly switch from flying to walk etc. Spells LOS on spell effect must be calculated on CollisionHeight and HitSpherePoint instead of position coords. Improved position for object/creature spawned via spells Improved checks for Fleeing movements (fear spells) Other improvements Implemented method to calculate the CollisionWidth from dbc (used by repositioning algorithm etc.) Improved raycast and collision checks Co-authored-by: Footman Co-authored-by: Helias Co-authored-by: Francesco Borzì Co-authored-by: Kitzunu <24550914+Kitzunu@users.noreply.github.com> --- .../Detour/Include/DetourNavMeshQuery.h | 2 +- src/common/Collision/DynamicTree.cpp | 4 +- src/common/Collision/Management/MMapFactory.h | 2 +- src/common/Collision/Management/MMapManager.h | 2 +- .../Collision/Management/VMapManager2.cpp | 7 +- src/common/Collision/Maps/MapTree.cpp | 2 +- src/common/Common.h | 4 - src/common/Define.h | 1 + src/common/Navigation/DetourExtended.cpp | 23 + src/common/Navigation/DetourExtended.h | 19 + src/common/Utilities/Geometry.h | 56 + src/common/Utilities/Physics.h | 42 + src/server/game/AI/CoreAI/PetAI.cpp | 21 +- src/server/game/AI/CreatureAI.cpp | 34 + src/server/game/AI/CreatureAI.h | 3 + src/server/game/Combat/ThreatManager.cpp | 5 +- .../game/Entities/Creature/Creature.cpp | 217 ++- src/server/game/Entities/Creature/Creature.h | 34 +- src/server/game/Entities/Object/Object.cpp | 497 +++---- src/server/game/Entities/Object/Object.h | 55 +- .../game/Entities/Object/ObjectDefines.h | 21 + src/server/game/Entities/Pet/Pet.h | 2 +- src/server/game/Entities/Player/Player.cpp | 4 +- src/server/game/Entities/Player/Player.h | 35 +- src/server/game/Entities/Unit/Unit.cpp | 353 ++++- src/server/game/Entities/Unit/Unit.h | 64 +- src/server/game/Handlers/CharacterHandler.cpp | 4 +- src/server/game/Handlers/MovementHandler.cpp | 3 +- src/server/game/Handlers/PetHandler.cpp | 2 +- src/server/game/Maps/Map.cpp | 246 +++- src/server/game/Maps/Map.h | 21 +- src/server/game/Miscellaneous/SharedDefines.h | 3 + src/server/game/Movement/MotionMaster.cpp | 98 +- src/server/game/Movement/MotionMaster.h | 32 +- .../ConfusedMovementGenerator.cpp | 2 +- .../EscortMovementGenerator.cpp | 4 +- .../FleeingMovementGenerator.cpp | 59 +- .../HomeMovementGenerator.cpp | 6 +- .../MovementGenerators/PathGenerator.cpp | 1280 +++++++++-------- .../MovementGenerators/PathGenerator.h | 227 +-- .../RandomMovementGenerator.cpp | 16 +- .../TargetedMovementGenerator.cpp | 628 ++++---- .../TargetedMovementGenerator.h | 93 +- .../WaypointMovementGenerator.cpp | 10 +- .../game/Movement/Spline/MoveSplineInit.cpp | 27 +- .../game/Spells/Auras/SpellAuraEffects.cpp | 4 +- src/server/game/Spells/Spell.cpp | 360 +---- src/server/game/Spells/SpellEffects.cpp | 26 +- src/server/game/Spells/SpellInfo.cpp | 5 + src/server/game/Spells/SpellInfo.h | 1 + src/server/game/World/IWorld.h | 2 + src/server/game/World/World.cpp | 3 + src/server/scripts/Commands/cs_mmaps.cpp | 13 +- .../ScarletEnclave/chapter1.cpp | 4 +- .../SunwellPlateau/boss_felmyst.cpp | 2 +- .../EasternKingdoms/ZulAman/zulaman.cpp | 2 +- src/server/scripts/Events/brewfest.cpp | 2 +- .../TheBlackMorass/the_black_morass.cpp | 2 +- .../AzjolNerub/AzjolNerub/boss_anubarak.cpp | 422 +++--- .../boss_blood_prince_council.cpp | 4 +- .../boss_icecrown_gunship_battle.cpp | 7 +- .../IcecrownCitadel/boss_sindragosa.cpp | 14 +- .../IcecrownCitadel/boss_the_lich_king.cpp | 6 +- .../Northrend/Naxxramas/boss_kelthuzad.cpp | 4 +- .../Northrend/Nexus/Nexus/boss_ormorok.cpp | 2 +- .../Ulduar/Ulduar/boss_flame_leviathan.cpp | 2 +- .../Northrend/Ulduar/Ulduar/boss_freya.cpp | 18 +- .../Northrend/Ulduar/Ulduar/boss_xt002.cpp | 8 +- .../UtgardeKeep/boss_ingvar_the_plunderer.cpp | 2 +- .../Northrend/VioletHold/boss_cyanigosa.cpp | 2 +- .../scripts/Northrend/zone_sholazar_basin.cpp | 2 +- .../Outland/zone_blades_edge_mountains.cpp | 2 +- .../Outland/zone_shadowmoon_valley.cpp | 2 +- src/server/scripts/Pet/pet_dk.cpp | 7 +- src/server/scripts/World/go_scripts.cpp | 2 +- src/server/shared/DataStores/DBCStructure.h | 2 +- src/server/shared/DataStores/DBCfmt.h | 2 +- src/server/worldserver/worldserver.conf.dist | 16 + src/tools/mesh_extractor/MeshExtractor.cpp | 2 +- 79 files changed, 3025 insertions(+), 2199 deletions(-) create mode 100644 src/common/Navigation/DetourExtended.cpp create mode 100644 src/common/Navigation/DetourExtended.h create mode 100644 src/common/Utilities/Geometry.h create mode 100644 src/common/Utilities/Physics.h 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);