diff --git a/src/common/Collision/Management/VMapManager2.cpp b/src/common/Collision/Management/VMapManager2.cpp index 0a33af592..ea5f5c3ae 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; @@ -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/Utilities/Geometry.h b/src/common/Utilities/Geometry.h new file mode 100644 index 000000000..e8b5f8463 --- /dev/null +++ b/src/common/Utilities/Geometry.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + */ + +#ifndef _GEOMETRY_H +#define _GEOMETRY_H + +#include +#include +#include + +using namespace std; + +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 * M_PI + ang; + return ang; +} + +inline float getSlopeAngle(float startX, float startY, float startZ, float destX, float destY, float destZ) +{ + auto a = (startX * destX + startY * destY + startZ * destZ); + auto b = sqrt(pow(startX,2.0f) + pow(startY,2.0f) + pow(startZ, 2.0f)); + auto c = sqrt(pow(destX,2.0f) + pow(destY,2.0f) + pow(destZ, 2.0f)); + + auto ang = acos(a / (b * c)); + + if (isnan(ang)) + { + return 0.0f; + } + + return ang; +} + +inline float getSlopeAngleAbs(float startX, float startY, float startZ, float destX, float destY, float destZ) +{ + return abs(getSlopeAngle(startX, startY, startZ, destX, destY, destZ)); +} + +#endif diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index 5150f4cd7..b3fec7598 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->IsWithinMeleeRange(victim) || + (victim->GetTypeId() != TYPEID_PLAYER && !victim->IsPet()) + ) + { + return; + } + + me->GetMotionMaster()->MoveCircleTarget(me->GetVictim()); +} + +void CreatureAI::MoveBackwardsChecks() { + Unit *victim = me->GetVictim(); + + if ( + !victim || + !me->IsFreeToMove() || + (victim->GetTypeId() != TYPEID_PLAYER && !victim->IsPet()) + ) + { + return; + } + + float moveDist = CalculatePct(me->GetCombatReach() + victim->GetCombatReach(), 100); + + 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/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index c79a1890e..9443045f0 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -586,6 +586,36 @@ void Creature::Update(uint32 diff) RemoveCharmAuras(); } + // Circling the target + if (diff >= m_moveCircleMovementTime) + { + AI()->MoveCircleChecks(); + m_moveCircleMovementTime = urand(MOVE_CIRCLE_CHECK_INTERVAL * 2, MOVE_CIRCLE_CHECK_INTERVAL * 3); + } + else + { + m_moveCircleMovementTime -= diff; + } + + // If we are closer than 50% of the combat reach we are going to reposition the victim + if (Unit *victim = GetVictim(); + victim && GetDistance(victim->GetPosition()) < CalculatePct(GetCombatReach() + victim->GetCombatReach(), 50)) { + if (diff >= m_moveBackwardsMovementTime) + { + AI()->MoveBackwardsChecks(); + m_moveBackwardsMovementTime = urand(MOVE_BACKWARDS_CHECK_INTERVAL/2, MOVE_BACKWARDS_CHECK_INTERVAL * 2); + } + else + { + m_moveBackwardsMovementTime -= diff; + } + } + else + { + m_moveBackwardsMovementTime = MOVE_BACKWARDS_CHECK_INTERVAL; + } + + if (!IsInEvadeMode() && IsAIEnabled) { // do not allow the AI to be changed during update @@ -620,7 +650,7 @@ void Creature::Update(uint32 diff) { AI()->EnterEvadeMode(); } - } + } break; } @@ -655,6 +685,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); @@ -1522,8 +1566,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 @@ -2833,6 +2879,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 @@ -2890,3 +2946,25 @@ 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; +} diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 4bf221545..ae5486673 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -385,7 +385,7 @@ typedef std::list VendorItemCounts; struct TrainerSpell { - TrainerSpell() + TrainerSpell() { for (unsigned int & i : learnedSpell) i = 0; @@ -510,6 +510,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 { @@ -714,11 +723,18 @@ 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 = 1500; + static constexpr uint32 MOVE_BACKWARDS_CHECK_INTERVAL = 2000; + uint32 m_moveCircleMovementTime = MOVE_CIRCLE_CHECK_INTERVAL; + uint32 m_moveBackwardsMovementTime = MOVE_BACKWARDS_CHECK_INTERVAL; + protected: bool CreateFromProto(uint32 guidlow, uint32 Entry, uint32 vehId, const CreatureData* data = nullptr); bool InitEntry(uint32 entry, const CreatureData* data = nullptr); @@ -787,7 +803,7 @@ private: bool m_cannotReachTarget; uint32 m_cannotReachTimer; - + Spell const* _focusSpell; ///> Locks the target during spell cast for proper facing }; diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 3681225b2..a694983ef 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 "Geometry.h" #include "SharedDefines.h" #include "WorldPacket.h" #include "Opcodes.h" @@ -1150,13 +1151,19 @@ 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; } @@ -1302,12 +1309,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 @@ -2680,23 +2682,83 @@ void WorldObject::MovePosition(Position& pos, float dist, float angle) pos.m_orientation = m_orientation; } -void WorldObject::MovePositionToFirstCollision(Position& pos, float dist, float angle) +Position WorldObject::GetFirstCollisionPosition(float startX, float startY, float startZ, float destX, float destY) { - angle += m_orientation; + 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; +} + +/** + * + * \return true -> collision, false -> no collision + */ +bool WorldObject::MovePositionToFirstCollision(Position& pos, float dist, float angle) +{ + angle += pos.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); - return; + return false; } + // Use a detour raycast to get our first collision point + PathGenerator path(this); + path.SetUseRaycast(true); + bool result = path.CalculatePath(destx, desty, destz, false); + + // Check for valid path types before we proceed + if (!(path.GetPathType() & PATHFIND_NOT_USING_PATH)) + { + if (path.GetPathType() & ~(PATHFIND_NORMAL | PATHFIND_SHORTCUT | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY_END)) { + return false; + } + } + + // collision check + bool col = (!result || (path.GetPathType() & PATHFIND_SHORTCUT) || (path.GetPathType() & PATHFIND_FARFROMPOLY)); + + G3D::Vector3 endPos = path.GetPath().back(); + destx = endPos.x; + desty = endPos.y; + destz = endPos.z; + // Xinef: ugly hack for dalaran arena float selfAddition = 1.5f; float allowedDiff = 6.0f; @@ -2710,10 +2772,8 @@ void WorldObject::MovePositionToFirstCollision(Position& pos, float dist, float 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) + if (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)) { // move back a bit if (pos.GetExactDist2d(destx, desty) > CONTACT_DISTANCE) @@ -2723,13 +2783,11 @@ void WorldObject::MovePositionToFirstCollision(Position& pos, float dist, float } newDist = sqrt((pos.m_positionX - destx) * (pos.m_positionX - destx) + (pos.m_positionY - desty) * (pos.m_positionY - desty)); + col = true; } - // 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) + // check dynamic collision, Collided with a gameobject + if (GetMap()->getObjectHitPos(GetPhaseMask(), pos.m_positionX, pos.m_positionY, pos.m_positionZ + selfAddition, destx, desty, destz + 0.5f, destx, desty, destz, -0.5f)) { // move back a bit if (pos.GetExactDist2d(destx, desty) > CONTACT_DISTANCE) @@ -2738,6 +2796,7 @@ void WorldObject::MovePositionToFirstCollision(Position& pos, float dist, float desty -= CONTACT_DISTANCE * sin(angle); } newDist = sqrt((pos.m_positionX - destx) * (pos.m_positionX - destx) + (pos.m_positionY - desty) * (pos.m_positionY - desty)); + col = true; } float step = newDist / 10.0f; @@ -2779,6 +2838,28 @@ void WorldObject::MovePositionToFirstCollision(Position& pos, float dist, float pos.Relocate(destx, desty, destz); pos.m_orientation = m_orientation; + + // position has no ground under it (or is too far away) +/* if (ground <= INVALID_HEIGHT) + { + if (Unit const* unit = ToUnit()) + { + // unit can fly, ignore. + if (unit->CanFly()) + { + return false; + } + + // fall back to gridHeight if any + float gridHeight = GetMap()->GetHeight(pos.m_positionX, pos.m_positionY, pos.m_positionZ); + if (gridHeight > INVALID_HEIGHT) + { + pos.m_positionZ = gridHeight + unit->GetHoverHeight(); + } + } + } */ + + return col; } void WorldObject::MovePositionToFirstCollisionForTotem(Position& pos, float dist, float angle, bool forGameObject) @@ -3088,3 +3169,8 @@ 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 +{ + return GetMap()->GetHeight(GetPhaseMask(), x, y, z, vmap, distanceToSearch); +} diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 71f370e6f..cf7ba2268 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -643,7 +643,7 @@ struct MovementInfo // spline float splineElevation{0.0f}; - MovementInfo() + MovementInfo() { pos.Relocate(0.0f, 0.0f, 0.0f, 0.0f); transport.Reset(); @@ -796,7 +796,10 @@ public: GetPosition(&pos); MovePosition(pos, dist, angle); } - void MovePositionToFirstCollision(Position& pos, float dist, float angle); + bool 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); @@ -1060,6 +1063,9 @@ public: [[nodiscard]] virtual float GetStationaryY() const { return GetPositionY(); } [[nodiscard]] virtual float GetStationaryZ() const { return GetPositionZ(); } [[nodiscard]] virtual float GetStationaryO() const { return GetOrientation(); } + float GetMapHeight(float x, float y, float z, bool vmap = true, float distanceToSearch = 50.0f) const; // DEFAULT_HEIGHT_SEARCH in map.h + + virtual float GetCollisionHeight() const { return 0.0f; } protected: std::string m_name; diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 51390f320..c3f665607 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -642,7 +642,7 @@ enum EquipmentSetUpdateState struct EquipmentSet { - EquipmentSet() + EquipmentSet() { for (unsigned int & Item : Items) Item = 0; @@ -998,7 +998,7 @@ struct BGData // holder for Entry Point data (pussywizard: stored in db) struct EntryPointData { - EntryPointData() + EntryPointData() { ClearTaxiPath(); } @@ -2561,40 +2561,6 @@ public: [[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; - } - } - // OURS // saving void AdditionalSavingAddMask(uint8 mask) { m_additionalSaveTimer = 2000; m_additionalSaveMask |= mask; } diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index aa0857646..7e596fcec 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) @@ -577,6 +596,21 @@ bool Unit::IsWithinMeleeRange(const Unit* obj, float dist) const return distsq < maxdist * maxdist; } +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(); @@ -2189,6 +2223,122 @@ void Unit::AttackerStateUpdate (Unit* victim, WeaponAttackType attType, bool ext } } +Position* Unit::GetMeleeAttackPoint(Unit* attacker) +{ + if (!attacker) // only player & pets to save CPU + { + return nullptr; + } + + AttackerSet attackers = getAttackers(); + + if (attackers.size() <= 1) // if the attackers are not more than one + { + return nullptr; + } + + float meleeReach = attacker->GetMeleeReach(); + + if (meleeReach <= 0) + { + return nullptr; + } + + float currentAngle, minDistance = 0; + Unit *refUnit = nullptr; + uint32 validAttackers=0; + + 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 tempDist = attacker->GetExactDist2d(otherAttacker) - (attacker->GetObjectSize()/2) - (otherAttacker->GetObjectSize()/2); + + if (tempDist == 0 || minDistance == 0 || tempDist < minDistance) + { + minDistance = tempDist; + currentAngle = GetAngle(otherAttacker); + refUnit = otherAttacker; + } + + validAttackers++; + } + + auto attackerSize = attacker->GetObjectSize(); + + // in instance: the more attacker there are, the higher will be the tollerance + // outside: creatures should not intersecate + float distanceTollerance = attacker->GetMap()->IsDungeon() ? -attackerSize * tanh(validAttackers / 5.0f) : 0.0f; + + if (!refUnit || minDistance > distanceTollerance) + { + return nullptr; + } + + double ray = attackerSize > refUnit->GetObjectSize() ? attackerSize / 2.0f : refUnit->GetObjectSize() / 2.0f; + + // Equation of tangent point to get the ideal angle to + // move away from collisions with another unit during combat + // NOTE: it works only when there's enough space between the + // attacker and the victim. We use a simpler one otherwise. + if (GetExactDist2d(refUnit) > ray) + { + double refUnitX = refUnit->GetPositionX(); + double refUnitY = refUnit->GetPositionY(); + double victimX = GetPositionX(); + double victimY = GetPositionY(); + + // calculate tangent star + double a = 4.0f * ( pow(ray,2.0f) - pow(refUnitX,2.0f) + (2.0f * refUnitX * victimX) - pow(victimX,2.0f) ); + double b = 8.0f * ( (refUnitX * refUnitY) + (victimX * victimY) - (victimX * refUnitY) - (refUnitX * victimY) ); + double c = 4.0f * (- pow(victimY,2.0f) - pow(refUnitY,2.0f) + (2.0f*victimY*refUnitY) + pow(ray,2.0f)); + + if (a == 0) // should not happen + { + return nullptr; + } + + double sq = sqrt(pow(b,2.0f)-4.0f*a*c); + + double m1 = (-b + sq) / (2.0f*a); + double m2 = (-b - sq) / (2.0f*a); + + // tangents + double xT1 = ((-1.0f) * (m1*(victimY - m1*victimX - refUnitY) - refUnitX) ) / (1.0f + pow(m1,2.0f)); + double xT2 = ((-1.0f) * (m2*(victimY - m2*victimX - refUnitY) - refUnitX) ) / (1.0f + pow(m2,2.0f)); + + double yT1 = m1*(xT1 - victimX) + victimY; + double yT2 = m2*(xT2 - victimX) + victimY; + + double distance = sqrt(pow(yT2-yT1,2.0f) + pow(xT2-xT1,2.0f)); + double exactDist = GetExactDist2d(xT1, yT1); + + double ortDist = sqrt(pow(exactDist,2.0f) - pow(distance/2.0f,2.0f)); + + double angle = frand(0.1f,0.3f) + currentAngle + 2.0f * atan(distance / (2.0f * ortDist)) * (urand(0, 1) ? -1 : 1); + + float x, y, z; + GetNearPoint(attacker, x, y, z, attackerSize, 0.0f, angle); + + return new Position(x,y,z); + } + + float angle = frand(0.1f,0.3f) + currentAngle + atan(attackerSize / (meleeReach)) * (urand(0, 1) ? -1 : 1); + + float x, y, z; + GetNearPoint(attacker, x, y, z, attackerSize, 0.0f, angle); + + return new Position(x,y,z); +} + void Unit::HandleProcExtraAttackFor(Unit* victim) { while (m_extraAttacks) @@ -3229,7 +3379,7 @@ void Unit::_UpdateAutoRepeatSpell() { return; } - + static uint32 const HUNTER_AUTOSHOOT = 75; // Check "realtime" interrupts @@ -12554,7 +12704,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); } @@ -12574,7 +12724,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); } @@ -19862,3 +20012,32 @@ bool Unit::IsInCombatWith(Unit const* who) const // Nothing found, false. return false; } + +//! Return collision height sent to client +float Unit::GetCollisionHeight() const +{ + uint32 nativeDisplayId = GetNativeDisplayId(); + if (IsMounted()) + { + if (CreatureDisplayInfoEntry const* mountDisplayInfo = sCreatureDisplayInfoStore.LookupEntry(GetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID)); + CreatureModelDataEntry const* mountModelData = sCreatureModelDataStore.LookupEntry(mountDisplayInfo->ModelId)) + { + CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.LookupEntry(nativeDisplayId); + 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; + } + } + + //! Dismounting case - use basic default model data + CreatureDisplayInfoEntry const* displayInfo = sCreatureDisplayInfoStore.LookupEntry(nativeDisplayId); + ASSERT(displayInfo); + CreatureModelDataEntry const* modelData = sCreatureModelDataStore.LookupEntry(displayInfo->ModelId); + ASSERT(modelData); + + return modelData->CollisionHeight; +} diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index f5b5cc60f..c6af89835 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1255,6 +1255,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 @@ -1405,6 +1425,7 @@ 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 GetRandomContactPoint(const Unit* target, float& x, float& y, float& z, bool force = false) const; @@ -1434,6 +1455,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; } @@ -2174,7 +2196,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;} @@ -2451,6 +2473,8 @@ public: // Movement info Movement::MoveSpline* movespline; + [[nodiscard]] float GetCollisionHeight() const override; + protected: explicit Unit (bool isWorldObject); @@ -2566,6 +2590,7 @@ private: HostileRefManager m_HostileRefManager; FollowerRefManager m_FollowingRefManager; + Unit* m_comboTarget; ComboPointHolderSet m_ComboPointHolders; diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 62a8689c4..129f2e85d 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" @@ -710,7 +711,7 @@ void Map::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/) { if (t_diff) _dynamicTree.update(t_diff); - + /// update worldsessions for existing players for (m_mapRefIter = m_mapRefManager.begin(); m_mapRefIter != m_mapRefManager.end(); ++m_mapRefIter) { @@ -3391,3 +3392,121 @@ void Map::SetZoneOverrideLight(uint32 zoneId, uint32 lightId, uint32 fadeInTime) player->SendDirectMessage(&data); } } + +/** + * + * \param maxDeviationAngle the maximum deviation that a creature can take to reach the destination + * + */ +bool Map::CanReachPositionAndGetCoords(Unit* who, PathGenerator path, bool checkCollision /*= true */, float maxHeight/* = 3.0f */, float maxSlopeAngle/* = M_PI/2 */, float maxDeviationAngle /*= M_PI*2 */) const +{ + double deviation = 0.0f; + G3D::Vector3 prevPath = path.GetStartPosition(); + for (auto & vector : path.GetPath()) + { + float x = vector.x; + float y = vector.y; + float z = vector.z; + + deviation += who->GetRelativeAngle(x, y); + + // to reach the position the Unit must deviate + // the straight path with a higher margin than the one desired + // in this case we return false + if (deviation > maxDeviationAngle) + { + return false; + } + + float ang = getAngle(prevPath.x, prevPath.y, x, y); + + if (CanReachPositionAndGetCoords(who, prevPath.x, prevPath.y, prevPath.z, ang, x, y, z, checkCollision, maxHeight, maxSlopeAngle)) + { + return false; + } + + prevPath = vector; + } + + return true; +} + +bool Map::CanReachPositionAndGetCoords(Unit* who, float &destX, float &destY, float &destZ, bool checkCollision /*= true */, float maxHeight/* = 3.0f */, float maxSlopeAngle/* = M_PI/2 */) const +{ + return CanReachPositionAndGetCoords(who, who->GetPositionX(), who->GetPositionY(), who->GetPositionZ(), who->GetOrientation(), destX, destY, destZ, checkCollision, maxHeight, maxSlopeAngle); +} + +/** + * \brief validate the new destination + * + * Check if a given unit can reach a specific point and set the correct Z coord based on difference in height + * + * \param maxHeight the desired max Height for the calculated Z coord. If the new Z exceed the maxHeight, this method returns false + + * \return true if the destination is valid, false otherwise + * + **/ +bool Map::CanReachPositionAndGetCoords(Unit* who, float startX, float startY, float startZ, float startAngle, float &destX, float &destY, float &destZ, bool checkCollision /*= true */, float maxHeight/* = 3.0f */, float maxSlopeAngle/* = M_PI/2 */) const +{ + acore::NormalizeMapCoord(destX); + acore::NormalizeMapCoord(destY); + + const Map* _map = who->GetBaseMap(); + + // check map geometry for possible collision + if (checkCollision) + { + Position pos = Position(startX, startY, startZ, startAngle); + + auto distance = pos.GetExactDist2d(destX,destY); + + auto collided = who->MovePositionToFirstCollision(pos, distance, pos.GetRelativeAngle(destX,destY)); + + destX = pos.GetPositionX(); + destY = pos.GetPositionY(); + destZ = pos.GetPositionZ(); + + if (collided) + { + return false; + } + } + else + { + // otherwise calculate a new z + destZ = _map->GetHeight(who->GetPhaseMask(), destX, destY, destZ, true); + } + + + if (destZ <= INVALID_HEIGHT || (destZ - startZ) > maxHeight) + { + return false; + } + + float slopeAngle = getSlopeAngleAbs(startX, startY, startZ, destX, destY, destZ); + + if (slopeAngle > maxSlopeAngle) + { + return false; + } + + // if water environment + bool is_water_now = _map->IsInWater(startX, startY, startZ); + + if (!isInLineOfSight(startX, startY, startZ, destX, destY, destZ, who->GetPhaseMask(), LINEOFSIGHT_ALL_CHECKS)) + { + return false; + } + + bool is_water_next = _map->IsInWater(destX, destY, destZ); + + // if not compatible inhabit type for the next position, return false + if ((is_water_now && !is_water_next && who->GetTypeId() == TYPEID_UNIT && !((Creature*)who)->CanWalk()) || + (!is_water_now && is_water_next && !who->CanSwim())) + { + return false; + } + + // finally + return true; +} diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h index 8d722fa38..baa6fb934 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -25,6 +25,7 @@ #include "MapRefManager.h" #include "DynamicTree.h" #include "GameObjectModel.h" +#include "PathGenerator.h" #include "Log.h" #include "DataMap.h" #include @@ -50,6 +51,7 @@ class BattlegroundMap; class Transport; class StaticTransport; class MotionTransport; +class PathGenerator; namespace acore { struct ObjectUpdater; @@ -473,6 +475,9 @@ public: float GetWaterOrGroundLevel(uint32 phasemask, float x, float y, float z, float* ground = nullptr, bool swim = false, float maxSearchDist = 50.0f) 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 CanReachPositionAndGetCoords(Unit* who, PathGenerator path, bool checkCollision = true, float maxHeight = 3.0f, float maxSlopeAngle = M_PI/2, float maxDeviationAngle = M_PI*2) const; + bool CanReachPositionAndGetCoords(Unit* who, float &destX, float &destY, float &destZ, bool checkCollision = true, float maxHeight = 3.0f, float maxSlopeAngle = M_PI/2) const; + bool CanReachPositionAndGetCoords(Unit* who, float startX, float startY, float startZ, float startAngle, float &destX, float &destY, float &destZ, bool checkCollision = true, float maxHeight = 3.0f, float maxSlopeAngle = M_PI/2) const; void Balance() { _dynamicTree.balance(); } void RemoveGameObjectModel(const GameObjectModel& model) { _dynamicTree.remove(model); } void InsertGameObjectModel(const GameObjectModel& model) { _dynamicTree.insert(model); } diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp index 70aec996d..5a9b3a60b 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -320,12 +320,81 @@ 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->IsFlying()) + // point.z = pos.m_positionZ; + //else + // point.z = _owner->GetMapHeight(point.x, point.y, point.z); + + if (_owner->GetMap()->CanReachPositionAndGetCoords(_owner, point.x, point.y, point.z, true, 6.0f, M_PI/4)) + { + Movement::MoveSplineInit init(_owner); + init.MoveTo(point.x, point.y, point.z, true); + init.SetFacing(target); + init.SetOrientationInversed(); + init.Launch(); + } +} + +void MotionMaster::MoveCircleTarget(Unit* target) +{ + if (!target) + { + return; + } + + Position* point = target->GetMeleeAttackPoint(_owner); + if (point == NULL) + { + return; + } + + if (_owner->IsFlying()) { + // Dont do anything yet might add later + } + else + { + point->m_positionZ = _owner->GetMapHeight(point->m_positionX, point->m_positionY, point->m_positionZ); + } + + const Map* _map = _owner->GetBaseMap(); + + float x = point->m_positionX; + float y = point->m_positionY; + float z = point->m_positionZ; + + if (_map->CanReachPositionAndGetCoords(_owner, x, y, z, true, 6.0f, M_PI/3)) + { + Movement::MoveSplineInit init(_owner); + init.SetSmooth(); + init.MoveTo(x, y, z, true); + 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) diff --git a/src/server/game/Movement/MotionMaster.h b/src/server/game/Movement/MotionMaster.h index efb98170d..34f06ffd3 100644 --- a/src/server/game/Movement/MotionMaster.h +++ b/src/server/game/Movement/MotionMaster.h @@ -165,6 +165,8 @@ public: 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 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/FleeingMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/FleeingMovementGenerator.cpp index d623dc03c..c7b94a4bb 100644 --- a/src/server/game/Movement/MovementGenerators/FleeingMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/FleeingMovementGenerator.cpp @@ -60,12 +60,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,37 +152,20 @@ 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)) + float temp_z = z; + + if (_map->CanReachPositionAndGetCoords(owner, temp_x, temp_y, temp_z, true, 3.0f, M_PI/4)) { - bool is_water_now = _map->IsInWater(x, y, z); - - if (is_water_now && _map->IsInWater(temp_x, temp_y, z)) + if (!(temp_z - z) || distance / fabs(temp_z - z) > 1.0f) { - x = temp_x; - y = temp_y; - 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) + 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 = new_z; + z = temp_z; return true; } } diff --git a/src/server/game/Movement/MovementGenerators/PathGenerator.cpp b/src/server/game/Movement/MovementGenerators/PathGenerator.cpp index 918a66448..358972840 100644 --- a/src/server/game/Movement/MovementGenerators/PathGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/PathGenerator.cpp @@ -1,47 +1,34 @@ /* * 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" ////////////////// PathGenerator ////////////////// -PathGenerator::PathGenerator(const Unit* owner) : +PathGenerator::PathGenerator(WorldObject const* owner) : _polyLength(0), _type(PATHFIND_BLANK), _useStraightPath(false), - _forceDestination(false), _pointPathLimit(MAX_POINT_PATH_LENGTH), - _endPosition(G3D::Vector3::zero()), _sourceUnit(owner), _navMesh(nullptr), + _forceDestination(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(); + uint32 mapId = _source->GetMapId(); //if (MMAP::MMapFactory::IsPathfindingEnabled(_sourceUnit->FindMap())) // pussywizard: checked before creating new PathGenerator { 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 +41,33 @@ 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); 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 +76,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 +120,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 +137,452 @@ 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; -} - -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; + _type = PathType(PATHFIND_NORMAL); + // 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(); + bool path = _source->GetTypeId() == TYPEID_UNIT && _source->ToCreature()->CanFly(); - // *** 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 waterPath = _source->GetTypeId() == TYPEID_UNIT && _source->ToCreature()->CanSwim(); + if (waterPath) { - BuildShortcut(); - if (sourceIsFlying) + // 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) { - _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))) + ZLiquidStatus status = _source->GetMap()->getLiquidStatus(_pathPoints[i].x, _pathPoints[i].y, _pathPoints[i].z, MAP_ALL_LIQUIDS, nullptr); + // One of the points is not in the water, cancel movement. + if (status == LIQUID_MAP_NO_WATER) { - _type = PATHFIND_NOPATH; - return; + waterPath = false; + break; } - _type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); - return; } + } + + if (path || waterPath) + { + _type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); + return; + } + + // raycast doesn't need endPoly to be valid + if (!_useRaycast) + { _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; + if (startFarFromPoly || endFarFromPoly) + { + bool buildShotrcut = false; + + G3D::Vector3 const& p = (distToStartPoly > 7.0f) ? startPos : endPos; + if (_source->GetMap()->IsUnderWater(p.x, p.y, p.z)) { - if (sourceIsFlying) + if (Unit const* _sourceUnit = _source->ToUnit()) { - 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 (_sourceUnit->CanSwim()) { - 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; - } - } - - // *** poly path generating logic *** - - if (startPoly == endPoly) - { - BuildShortcut(); - _type = !farFromEndPoly || endInWaterFar ? PATHFIND_NORMAL : PATHFIND_INCOMPLETE; - _pathPolyRefs[0] = startPoly; - _polyLength = 1; - 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; + buildShotrcut = true; } } } 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)) + if (Unit const* _sourceUnit = _source->ToUnit()) { - // only happens if we passed bad data to findPath(), or navmesh is messed up + if (_sourceUnit->CanFly()) + { + buildShotrcut = true; + } + // Allow to build a shortcut if the unit is falling and it's trying to move downwards towards a target (i.e. charging) + else if (_sourceUnit->IsFalling() && endPos.z < startPos.z) + { + buildShotrcut = true; + } + } + } + + if (buildShotrcut) + { + BuildShortcut(); + _type = PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH); + + AddFarFromPolyFlags(startFarFromPoly, endFarFromPoly); + + return; + } + else + { + float closestPoint[VERTEX_SIZE]; + // we may want to use closestPointOnPolyBoundary instead + if (dtStatusSucceed(_navMeshQuery->closestPointOnPoly(endPoly, endPoint, closestPoint, nullptr))) + { + 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; } } + + // 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]); + _pathPoints[i] = G3D::Vector3(pathPoints[i*VERTEX_SIZE+2], pathPoints[i*VERTEX_SIZE], pathPoints[i*VERTEX_SIZE+1]); + + NormalizePath(); // first point is always our current location - we need the next one - SetActualEndPosition(_pathPoints[pointCount - 1]); + SetActualEndPosition(_pathPoints[pointCount-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 +594,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 +623,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 + includeFlags |= (NAV_WATER | NAV_MAGMA); // swim } 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 +649,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) { 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 +680,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 +709,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 +736,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 +745,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,7 +769,7 @@ 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++; } @@ -864,7 +777,7 @@ bool PathGenerator::GetSteerTarget(float const* startPos, float const* endPos, 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 +786,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 +797,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 +833,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,10 +855,14 @@ 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); @@ -941,7 +873,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 +892,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 +916,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 +927,7 @@ 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::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 +945,76 @@ 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! + + // check if the shortened path is still in LoS with the target + _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)) + { + // whenver we find a point that is not in LoS 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); + } +} diff --git a/src/server/game/Movement/MovementGenerators/PathGenerator.h b/src/server/game/Movement/MovementGenerators/PathGenerator.h index 5e86b82e2..bb576f3df 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,139 +11,153 @@ #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 IsInvalidDestinationZ(Unit const* target) const; - // 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 + 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; } - // result getters - G3D::Vector3 const& GetStartPosition() const { return _startPosition; } - G3D::Vector3 const& GetEndPosition() const { return _endPosition; } - G3D::Vector3 const& GetActualEndPosition() const { return _actualEndPosition; } + // result getters + G3D::Vector3 const& GetStartPosition() const { return _startPosition; } + G3D::Vector3 const& GetEndPosition() const { return _endPosition; } + G3D::Vector3 const& GetActualEndPosition() const { return _actualEndPosition; } - Movement::PointsArray const& GetPath() const { return _pathPoints; } + Movement::PointsArray const& GetPath() const { return _pathPoints; } - PathType GetPathType() const { return _type; } - float getPathLength() const - { - float len = 0.0f; - float dx, dy, dz; - uint32 size = _pathPoints.size(); - if (size) + 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: + private: - dtPolyRef _pathPolyRefs[MAX_PATH_LENGTH]; // array of detour polygon references - uint32 _polyLength; // number of polygons in the path + 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 + bool _forceDestination; // when set, we will always arrive at given point + 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 + dtQueryFilter _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); + 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/Spline/MoveSplineInit.cpp b/src/server/game/Movement/Spline/MoveSplineInit.cpp index f6fbe7111..d44f52314 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; 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..152a02370 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; + bool IsMoveAllowedChannel() const; bool NeedsComboPoints() const; bool IsBreakingStealth() const; bool IsRangedWeaponSpell() const; diff --git a/src/server/scripts/Commands/cs_mmaps.cpp b/src/server/scripts/Commands/cs_mmaps.cpp index 2ba3d48c5..442ba523f 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();