diff --git a/Source/ACE.Server/WorldObjects/Creature_Vitals.cs b/Source/ACE.Server/WorldObjects/Creature_Vitals.cs index 4f33a9d6e3..9277bd356a 100644 --- a/Source/ACE.Server/WorldObjects/Creature_Vitals.cs +++ b/Source/ACE.Server/WorldObjects/Creature_Vitals.cs @@ -206,9 +206,17 @@ public float GetAttributeMod(CreatureVital vital) /// public float GetStanceMod(CreatureVital vital) { - // only applies to players - if ((this as Player) == null) return 1.0f; + // Apply monster fast vital regen if idle (typically after returning home) + if (!(this is Player)) + { + // Only boost Health/Stamina, not Mana (consistent with player logic) + if (vital.Vital != PropertyAttribute2nd.MaxMana && MonsterState == State.Idle) + return 250.0f; // Large multiplier for fast regen + + return 1.0f; // Default for monsters not idle + } + // Player-specific logic below // does not apply for mana? if (vital.Vital == PropertyAttribute2nd.MaxMana) return 1.0f; diff --git a/Source/ACE.Server/WorldObjects/Monster_Awareness.cs b/Source/ACE.Server/WorldObjects/Monster_Awareness.cs index e1bbb77d68..30a399feac 100644 --- a/Source/ACE.Server/WorldObjects/Monster_Awareness.cs +++ b/Source/ACE.Server/WorldObjects/Monster_Awareness.cs @@ -59,6 +59,9 @@ public virtual void Sleep() PhysicsObj.CachedVelocity = Vector3.Zero; ClearRetaliateTargets(); + + // Reset damage history when going to sleep/idle + DamageHistory.Reset(); } public Tolerance Tolerance @@ -454,10 +457,22 @@ public void AlertFriendly() foreach (var obj in visibleObjs) { var nearbyCreature = obj.WeenieObj.WorldObject as Creature; - if (nearbyCreature == null || nearbyCreature.IsAwake || !nearbyCreature.Attackable && nearbyCreature.TargetingTactic == TargetingTactic.None) + + // Skip creatures that can't be alerted: + // - null creatures + // - creatures with tolerance exclusions + if (nearbyCreature == null || (nearbyCreature.Tolerance & AlertExclude) != 0) continue; - - if ((nearbyCreature.Tolerance & AlertExclude) != 0) + + // Original condition modified to allow monsters in Return state to hear distress calls + // Old condition: if (nearbyCreature == null || nearbyCreature.IsAwake || !nearbyCreature.Attackable && nearbyCreature.TargetingTactic == TargetingTactic.None) + // continue; + + // Modified checks: + // - skip already awake monsters UNLESS they're returning home + // - skip non-attackable monsters with no targeting tactic + if ((nearbyCreature.IsAwake && nearbyCreature.MonsterState != State.Return) || + (!nearbyCreature.Attackable && nearbyCreature.TargetingTactic == TargetingTactic.None)) continue; if (CreatureType != null && CreatureType == nearbyCreature.CreatureType || @@ -489,6 +504,15 @@ public void AlertFriendly() alerted = true; + // If monster was returning home, cancel that and respond to the distress call + if (nearbyCreature.MonsterState == State.Return) + { + if (nearbyCreature.DebugMove) + Console.WriteLine($"{nearbyCreature.Name} ({nearbyCreature.Guid}) - Interrupting return home to respond to distress call"); + + nearbyCreature.CancelMoveTo(); + } + nearbyCreature.AttackTarget = AttackTarget; nearbyCreature.WakeUp(false); } diff --git a/Source/ACE.Server/WorldObjects/Monster_Combat.cs b/Source/ACE.Server/WorldObjects/Monster_Combat.cs index a9ebe8c61d..ee9d7b66c9 100644 --- a/Source/ACE.Server/WorldObjects/Monster_Combat.cs +++ b/Source/ACE.Server/WorldObjects/Monster_Combat.cs @@ -388,6 +388,26 @@ public virtual uint TakeDamage(WorldObject source, DamageType damageType, float DamageHistory.OnHeal((uint)-damage); } + // Step 2: React to the damage source if needed (wake up, target attacker, stop returning) + if (source is Creature attacker) + { + // React if not already awake/fighting OR if was returning home (even if fast healing wasn't active yet) + if (!IsAwake || MonsterState == State.Return) + { + if (DebugMove) + Console.WriteLine($"{Name} ({Guid}) - Reacting to damage from {attacker.Name} ({attacker.Guid}). Current State: {MonsterState}, IsAwake: {IsAwake}"); + + // Stop returning home if that's what we were doing + if (MonsterState == State.Return) + { + CancelMoveTo(); + } + + AttackTarget = attacker; + WakeUp(false); // Wake up, set state to Awake + } + } + if (Health.Current <= 0) { OnDeath(DamageHistory.LastDamager, damageType, crit); diff --git a/Source/ACE.Server/WorldObjects/Monster_Navigation.cs b/Source/ACE.Server/WorldObjects/Monster_Navigation.cs index b0e378998a..b3f9050420 100644 --- a/Source/ACE.Server/WorldObjects/Monster_Navigation.cs +++ b/Source/ACE.Server/WorldObjects/Monster_Navigation.cs @@ -64,6 +64,13 @@ partial class Creature public double NextMoveTime; public double NextCancelTime; + /// + /// Fields for enhanced stuck detection + /// + public Vector3 LastStuckCheckPosition; + public int StuckCounter = 0; + public double LastStuckCheckTime; + /// /// Starts the process of monster turning towards target /// @@ -238,8 +245,7 @@ public Vector3 GetDestination() /// public void Movement() { - //if (!IsRanged) - UpdatePosition(); + UpdatePosition(); if (MonsterState == State.Awake && GetDistanceToTarget() >= MaxChaseRange) { @@ -248,8 +254,64 @@ public void Movement() return; } - if (PhysicsObj.MovementManager.MoveToManager.FailProgressCount > 0 && Timers.RunningTime > NextCancelTime) + // Standard stuck check from MoveToManager + if (!AiImmobile && PhysicsObj.MovementManager.MoveToManager.FailProgressCount > 0 && Timers.RunningTime > NextCancelTime) + { + // Instead of just canceling, also increment stuck counter + StuckCounter++; CancelMoveTo(); + + // If stuck multiple times, force return home - about 15 seconds (5 detections with ~3 seconds each) + if (StuckCounter >= 5) + { + if (DebugMove) + Console.WriteLine($"{Name} ({Guid}) - Stuck multiple times, returning home"); + + StuckCounter = 0; + MoveToHome(); + } + return; + } + + // Additional stuck detection - check if position hasn't changed much over time + if (!AiImmobile && Timers.RunningTime - LastStuckCheckTime >= 5.0) // Check every 5 seconds + { + var currentPos = Location.ToGlobal(); + + if (LastStuckCheckPosition != Vector3.Zero) + { + var distMoved = Vector3.Distance(currentPos, LastStuckCheckPosition); + + // If barely moved in the last 5 seconds but still trying to move + if (distMoved < 1.5f && IsMoving) + { + StuckCounter++; + + if (DebugMove) + Console.WriteLine($"{Name} ({Guid}) - Barely moved ({distMoved:F2} units), StuckCounter={StuckCounter}"); + + // If stuck in place multiple times, force return home - about 15 seconds (3 detections of 5 seconds each) + if (StuckCounter >= 3) + { + if (DebugMove) + Console.WriteLine($"{Name} ({Guid}) - Stuck in place for ~15 seconds, returning home"); + + StuckCounter = 0; + CancelMoveTo(); + MoveToHome(); + return; + } + } + else + { + // Reset counter if making good progress + StuckCounter = 0; + } + } + + LastStuckCheckPosition = currentPos; + LastStuckCheckTime = Timers.RunningTime; + } } public void UpdatePosition(bool netsend = true)