diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index 0f7fc51ac..5150f4cd7 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -249,6 +249,7 @@ bool CreatureAI::_EnterEvadeMode() me->SetLootRecipient(nullptr); me->ResetPlayerDamageReq(); me->SetLastDamagedTime(0); + me->SetCannotReachTarget(false); if (me->IsInEvadeMode()) return false; diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index d5f00cb95..20120c96a 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -626,12 +626,7 @@ void SmartAI::EnterEvadeMode() me->RemoveEvadeAuras(); me->AddUnitState(UNIT_STATE_EVADE); - me->DeleteThreatList(); - me->CombatStop(true); - me->LoadCreaturesAddon(true); - me->SetLootRecipient(nullptr); - me->ResetPlayerDamageReq(); - me->SetLastDamagedTime(0); + _EnterEvadeMode(); GetScript()->ProcessEventsFor(SMART_EVENT_EVADE);//must be after aura clear so we can cast spells from db diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 4ebdbe7de..c79a1890e 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -47,6 +47,8 @@ #include "LuaEngine.h" #endif +constexpr uint32 DEF_CANNOT_REACH = 5 * IN_MILLISECONDS; // this is when creatures are in los / no path to the target, 5s wait time then they return to spawn pos with evade + TrainerSpell const* TrainerSpellData::Find(uint32 spell_id) const { TrainerSpellMap::const_iterator itr = spellList.find(spell_id); @@ -164,7 +166,7 @@ Creature::Creature(bool isWorldObject): Unit(isWorldObject), MovableMapObject(), m_transportCheckTimer(1000), lootPickPocketRestoreTime(0), m_reactState(REACT_AGGRESSIVE), m_defaultMovementType(IDLE_MOTION_TYPE), m_DBTableGuid(0), m_equipmentId(0), m_originalEquipmentId(0), m_AlreadyCallAssistance(false), m_AlreadySearchedAssistance(false), m_regenHealth(true), m_AI_locked(false), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), m_originalEntry(0), m_moveInLineOfSightDisabled(false), m_moveInLineOfSightStrictlyDisabled(false), - m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_waypointID(0), m_path_id(0), m_formation(nullptr), _lastDamagedTime(0) + m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_waypointID(0), m_path_id(0), m_formation(nullptr), _lastDamagedTime(0), m_cannotReachTarget(false), m_cannotReachTimer(0) { m_regenTimer = CREATURE_REGEN_INTERVAL; m_valuesCount = UNIT_END; @@ -610,6 +612,16 @@ void Creature::Update(uint32 diff) m_regenTimer += CREATURE_REGEN_INTERVAL; } + + if (CanNotReachTarget() && !IsInEvadeMode() && !GetMap()->IsRaid()) + { + m_cannotReachTimer += diff; + if (m_cannotReachTimer >= DEF_CANNOT_REACH && IsAIEnabled) + { + AI()->EnterEvadeMode(); + } + } + break; } default: diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 1213c0c17..f250dea96 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -499,6 +499,7 @@ public: uint8 getLevelForTarget(WorldObject const* target) const override; // overwrite Unit::getLevelForTarget for boss level support bool IsInEvadeMode() const { return HasUnitState(UNIT_STATE_EVADE); } + bool IsEvadingAttacks() const { return IsInEvadeMode() || CanNotReachTarget(); } bool AIM_Initialize(CreatureAI* ai = nullptr); void Motion_Initialize(); @@ -664,6 +665,9 @@ public: return m_charmInfo->GetCharmSpell(pos)->GetAction(); } + void SetCannotReachTarget(bool cannotReach) { if (cannotReach == m_cannotReachTarget) return; m_cannotReachTarget = cannotReach; m_cannotReachTimer = 0; } + bool CanNotReachTarget() const { return m_cannotReachTarget; } + void SetPosition(float x, float y, float z, float o); void SetPosition(const Position& pos) { SetPosition(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation()); } @@ -784,6 +788,9 @@ private: time_t _lastDamagedTime; // Part of Evade mechanics + 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/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 28e7c3905..115470d4d 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -665,7 +665,7 @@ bool Unit::HasBreakableByDamageCrowdControlAura(Unit* excludeCasterChannel) cons void Unit::DealDamageMods(Unit const* victim, uint32& damage, uint32* absorb) { - if (!victim || !victim->IsAlive() || victim->IsInFlight() || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsInEvadeMode())) + if (!victim || !victim->IsAlive() || victim->IsInFlight() || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks())) { if (absorb) *absorb += damage; @@ -1237,7 +1237,7 @@ void Unit::DealSpellDamage(SpellNonMeleeDamage* damageInfo, bool durabilityLoss) if (!victim) return; - if (!victim->IsAlive() || victim->IsInFlight() || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsInEvadeMode())) + if (!victim->IsAlive() || victim->IsInFlight() || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks())) return; SpellInfo const* spellProto = sSpellMgr->GetSpellInfo(damageInfo->SpellID); @@ -1468,7 +1468,7 @@ void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) { Unit* victim = damageInfo->target; - if (!victim->IsAlive() || victim->IsInFlight() || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsInEvadeMode())) + if (!victim->IsAlive() || victim->IsInFlight() || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks())) return; // Hmmmm dont like this emotes client must by self do all animations @@ -2224,7 +2224,7 @@ MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(const Unit* victim, WeaponAttackTy MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(const Unit* victim, WeaponAttackType attType, int32 crit_chance, int32 miss_chance, int32 dodge_chance, int32 parry_chance, int32 block_chance) const { - if (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsInEvadeMode()) + if (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks()) return MELEE_HIT_EVADE; int32 attackerMaxSkillValueForLevel = GetMaxSkillValueForLevel(victim); @@ -2897,7 +2897,7 @@ SpellMissInfo Unit::SpellHitResult(Unit* victim, SpellInfo const* spell, bool Ca return SPELL_MISS_NONE; // Return evade for units in evade mode - if (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsInEvadeMode() && !spell->HasAura(SPELL_AURA_CONTROL_VEHICLE)) + if (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks() && !spell->HasAura(SPELL_AURA_CONTROL_VEHICLE)) return SPELL_MISS_EVADE; // Try victim reflect spell @@ -9586,7 +9586,7 @@ bool Unit::Attack(Unit* victim, bool meleeAttack) } else { - if (victim->ToCreature()->IsInEvadeMode()) + if (victim->ToCreature()->IsEvadingAttacks()) return false; } diff --git a/src/server/game/Movement/MovementGenerators/FleeingMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/FleeingMovementGenerator.cpp index 9b4f08fa3..d623dc03c 100644 --- a/src/server/game/Movement/MovementGenerators/FleeingMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/FleeingMovementGenerator.cpp @@ -42,7 +42,7 @@ void FleeingMovementGenerator::_setTargetLocation(T* owner) if (!isInLOS) { - i_nextCheckTime.Reset(500); + i_nextCheckTime.Reset(100); return; } diff --git a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp index 65bb23f6c..4bc9d5ba7 100644 --- a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp @@ -43,7 +43,7 @@ void PointMovementGenerator::DoInitialize(T* unit) i_y += 0.2f * sin(unit->GetOrientation()); } - init.MoveTo(i_x, i_y, i_z); + init.MoveTo(i_x, i_y, i_z, true); } } else @@ -55,7 +55,7 @@ void PointMovementGenerator::DoInitialize(T* unit) i_y += 0.2f * sin(unit->GetOrientation()); } - init.MoveTo(i_x, i_y, i_z); + init.MoveTo(i_x, i_y, i_z, true); } if (speed > 0.0f) init.SetVelocity(speed); @@ -101,10 +101,10 @@ bool PointMovementGenerator::DoUpdate(T* unit, uint32 /*diff*/) if (m_precomputedPath.size() > 2) init.MovebyPath(m_precomputedPath); else if (m_precomputedPath.size() == 2) - init.MoveTo(m_precomputedPath[1].x, m_precomputedPath[1].y, m_precomputedPath[1].z); + init.MoveTo(m_precomputedPath[1].x, m_precomputedPath[1].y, m_precomputedPath[1].z, true); } else - init.MoveTo(i_x, i_y, i_z); + init.MoveTo(i_x, i_y, i_z, true); if (speed > 0.0f) // Default value for point motion type is 0.0, if 0.0 spline will use GetSpeed on unit init.SetVelocity(speed); diff --git a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp index 2604c4a89..98d93ce0c 100644 --- a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp @@ -39,16 +39,21 @@ void TargetedMovementGeneratorMedium::_setTargetLocation(T* owner, bool in if (owner->GetMapId() == 631 && owner->GetTransport() && owner->GetTransport()->IsMotionTransport() && i_target->GetTransport() && i_target->GetTransport()->IsMotionTransport()) // for ICC, if both on a motion transport => don't use mmaps sameTransport = owner->GetTypeId() == TYPEID_UNIT && i_target->isInAccessiblePlaceFor(owner->ToCreature()); bool useMMaps = MMAP::MMapFactory::IsPathfindingEnabled(owner->FindMap()) && !sameTransport; - bool forceDest = (owner->FindMap() && owner->FindMap()->IsDungeon() && !isPlayerPet) || // force in instances to prevent exploiting - (owner->GetTypeId() == TYPEID_UNIT && ((owner->IsPet() && owner->HasUnitState(UNIT_STATE_FOLLOW)) || // allow pets following their master to cheat while generating paths - ((Creature*)owner)->isWorldBoss() || ((Creature*)owner)->IsDungeonBoss())) || // force for all bosses, even not in instances + bool forceDest = + // (owner->FindMap() && owner->FindMap()->IsDungeon() && !isPlayerPet) || // force in instances to prevent exploiting + (owner->GetTypeId() == TYPEID_UNIT && ((owner->IsPet() && owner->HasUnitState(UNIT_STATE_FOLLOW)))) || // allow pets following their master to cheat while generating paths + // ((Creature*)owner)->isWorldBoss() || ((Creature*)owner)->IsDungeonBoss())) || // force for all bosses, even not in instances (owner->GetMapId() == 572 && (owner->GetPositionX() < 1275.0f || i_target->GetPositionX() < 1275.0f)) || // pussywizard: Ruins of Lordaeron - special case (acid) sameTransport || // nothing to comment, can't find path on transports so allow it - (i_target->GetTypeId() == TYPEID_PLAYER && i_target->ToPlayer()->IsGameMaster()); // for .npc follow + (i_target->GetTypeId() == TYPEID_PLAYER && i_target->ToPlayer()->IsGameMaster()) // for .npc follow*/ + ; // closes "bool forceDest", that way it is more appropriate, so we can comment out crap whenever we need to bool forcePoint = ((!isPlayerPet || owner->GetMapId() == 618) && (forceDest || !useMMaps)) || sameTransport; if (owner->GetTypeId() == TYPEID_UNIT && !i_target->isInAccessiblePlaceFor(owner->ToCreature()) && !sameTransport && !forceDest && !forcePoint) + { + owner->ToCreature()->SetCannotReachTarget(true); return; + } lastOwnerXYZ.Relocate(owner->GetPositionX(), owner->GetPositionY(), owner->GetPositionZ()); lastTargetXYZ.Relocate(i_target->GetPositionX(), i_target->GetPositionY(), i_target->GetPositionZ()); @@ -186,6 +191,11 @@ void TargetedMovementGeneratorMedium::_setTargetLocation(T* owner, bool in float maxDist = MELEE_RANGE + owner->GetMeleeReach() + i_target->GetMeleeReach(); if (!forceDest && (i_path->GetPathType() & PATHFIND_NOPATH || (!i_offset && !isPlayerPet && i_target->GetExactDistSq(i_path->GetActualEndPosition().x, i_path->GetActualEndPosition().y, i_path->GetActualEndPosition().z) > maxDist * maxDist))) { + if (owner->GetTypeId() == TYPEID_UNIT) + { + owner->ToCreature()->SetCannotReachTarget(false); + } + lastPathingFailMSTime = World::GetGameTimeMS(); owner->m_targetsNotAcceptable[i_target->GetGUID()] = MMapTargetData(sWorld->GetGameTime() + DISALLOW_TIME_AFTER_FAIL, owner, i_target.getTarget()); return; @@ -203,8 +213,15 @@ void TargetedMovementGeneratorMedium::_setTargetLocation(T* owner, bool in return; } } - - // if failed to generate, just use normal MoveTo + else + { + // evade first + if (owner->GetTypeId() == TYPEID_UNIT) + { + owner->ToCreature()->SetCannotReachTarget(true); + } + // then use normal MoveTo - if we have to + } } owner->AddUnitState(UNIT_STATE_CHASE); diff --git a/src/tools/mmaps_generator/MapBuilder.cpp b/src/tools/mmaps_generator/MapBuilder.cpp index 34df17ad5..a493f4784 100644 --- a/src/tools/mmaps_generator/MapBuilder.cpp +++ b/src/tools/mmaps_generator/MapBuilder.cpp @@ -606,10 +606,11 @@ namespace MMAP config.walkableRadius = m_bigBaseUnit ? 1 : 2; config.borderSize = config.walkableRadius + 3; config.maxEdgeLen = VERTEX_PER_TILE + 1; // anything bigger than tileSize - config.walkableHeight = m_bigBaseUnit ? 3 : 6; // a value >= 3|6 allows npcs to walk over some fences // a value >= 4|8 allows npcs to walk over all fences - config.walkableClimb = m_bigBaseUnit ? 4 : 8; + // we prefer 3 and 6 for both Height/Climb as it's closer to be like retail + config.walkableHeight = m_bigBaseUnit ? 3 : 6; + config.walkableClimb = m_bigBaseUnit ? 3 : 6; config.minRegionArea = rcSqr(60); config.mergeRegionArea = rcSqr(50); config.maxSimplificationError = 1.8f; // eliminates most jagged edges (tiny polygons) diff --git a/src/tools/mmaps_generator/MapBuilder.h b/src/tools/mmaps_generator/MapBuilder.h index e8a35b0cb..3b32e8a84 100644 --- a/src/tools/mmaps_generator/MapBuilder.h +++ b/src/tools/mmaps_generator/MapBuilder.h @@ -64,7 +64,7 @@ namespace MMAP class MapBuilder { public: - MapBuilder(float maxWalkableAngle = 70.f, + MapBuilder(float maxWalkableAngle = 60.f, bool skipLiquid = false, bool skipContinents = false, bool skipJunkMaps = true, diff --git a/src/tools/mmaps_generator/PathGenerator.cpp b/src/tools/mmaps_generator/PathGenerator.cpp index 9ae492a1c..086897364 100644 --- a/src/tools/mmaps_generator/PathGenerator.cpp +++ b/src/tools/mmaps_generator/PathGenerator.cpp @@ -231,7 +231,7 @@ int main(int argc, char** argv) { unsigned int threads = std::thread::hardware_concurrency(); int mapnum = -1; - float maxAngle = 70.0f; + float maxAngle = 60.0f; int tileX = -1, tileY = -1; bool skipLiquid = false, skipContinents = false,