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)