fix: Reverts to RunUO speeds, fixes direction glitching, adds movement speed interpolation (#1695)
> [!IMPORTANT] > Please read through these changes, as they changed certain expectations for how the internal AI thinking/movement work. > Note that some mobs still don't have smooth movement on ClassicUO due to how the client handles animations/movement. ### Summary - **Important Change**: Mobs will now move at a more regular pace. If the OnThink results in a move, but the cooldown would otherwise prevent the move, then the movement is scheduled on another timer. - Added a 400ms delay to mobs turning to face a player to attack in order to avoid glitching between moving and turning. - Reverted AI thinking speeds back to RunUO specific speeds. - Reverted the AI thinking to moving conversion delays back to RunUO. - For thinking speeds that are not exactly the predefined speeds from RunUO, there is a new calculation to determine the correct conversion to movement speed. This stops speeds like `0.35` from being faster than `0.3` > [!NOTE] > **Developer Note** > BaseAI.CheckMove() no longer contains the check for whether or not a mob is on movement cooldown. This function can now be overwritten without causing issues to figuring out that cooldown.
This commit is contained in:
parent
9e37879f19
commit
3332fabc39
20 changed files with 354 additions and 223 deletions
|
|
@ -1,8 +1,14 @@
|
||||||
[
|
[
|
||||||
|
{
|
||||||
|
"level": "VerySlow",
|
||||||
|
"active": 0.4,
|
||||||
|
"passive": 0.8,
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"level": "Slow",
|
"level": "Slow",
|
||||||
"active": 0.35,
|
"active": 0.3,
|
||||||
"passive": 0.5,
|
"passive": 0.6,
|
||||||
"types": [
|
"types": [
|
||||||
"AntLion", "ArcticOgreLord", "BogThing",
|
"AntLion", "ArcticOgreLord", "BogThing",
|
||||||
"Bogle", "BoneKnight", "EarthElemental",
|
"Bogle", "BoneKnight", "EarthElemental",
|
||||||
|
|
@ -20,8 +26,8 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"level": "Medium",
|
"level": "Medium",
|
||||||
"active": 0.275,
|
"active": 0.25,
|
||||||
"passive": 0.45,
|
"passive": 0.5,
|
||||||
"types": [
|
"types": [
|
||||||
"AcidElemental", "AgapiteElemental", "Alligator",
|
"AcidElemental", "AgapiteElemental", "Alligator",
|
||||||
"AncientLich", "Betrayer", "Bird",
|
"AncientLich", "Betrayer", "Bird",
|
||||||
|
|
@ -132,7 +138,7 @@
|
||||||
{
|
{
|
||||||
"level": "VeryFast",
|
"level": "VeryFast",
|
||||||
"active": 0.125,
|
"active": 0.125,
|
||||||
"passive": 0.3,
|
"passive": 0.30,
|
||||||
"types": [
|
"types": [
|
||||||
"Barracoon", "Mephitis", "Neira",
|
"Barracoon", "Mephitis", "Neira",
|
||||||
"Rikktor", "Semidar", "EnergyVortex",
|
"Rikktor", "Semidar", "EnergyVortex",
|
||||||
|
|
|
||||||
|
|
@ -4339,10 +4339,9 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
||||||
if (m_NetState != null)
|
if (m_NetState != null)
|
||||||
{
|
{
|
||||||
m_NetState._nextMovementTime += ComputeMovementSpeed(d);
|
m_NetState._nextMovementTime += ComputeMovementSpeed(d);
|
||||||
|
m_NetState.SendMovementAck(m_NetState.Sequence, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_NetState?.SendMovementAck(m_NetState.Sequence, this);
|
|
||||||
|
|
||||||
SetLocation(newLocation, false);
|
SetLocation(newLocation, false);
|
||||||
SetDirection(d);
|
SetDirection(d);
|
||||||
|
|
||||||
|
|
@ -7282,12 +7281,15 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
||||||
box.Close();
|
box.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_NetState?.ValidateAllTrades();
|
if (m_NetState != null)
|
||||||
|
|
||||||
if (isTeleport && m_NetState != null && (!m_NetState.HighSeas || !NoMoveHS))
|
|
||||||
{
|
{
|
||||||
m_NetState.Sequence = 0;
|
m_NetState.ValidateAllTrades();
|
||||||
m_NetState.SendMobileUpdate(this);
|
|
||||||
|
if (isTeleport && (!m_NetState.HighSeas || !NoMoveHS))
|
||||||
|
{
|
||||||
|
m_NetState.Sequence = 0;
|
||||||
|
m_NetState.SendMobileUpdate(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var map = m_Map;
|
var map = m_Map;
|
||||||
|
|
@ -7367,6 +7369,7 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
||||||
|
|
||||||
m.SendOPLPacketTo(ourState);
|
m.SendOPLPacketTo(ourState);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in map.GetItemsInRange(newLocation, Core.GlobalMaxUpdateRange))
|
foreach (var item in map.GetItemsInRange(newLocation, Core.GlobalMaxUpdateRange))
|
||||||
{
|
{
|
||||||
var range = item.GetUpdateRange(this);
|
var range = item.GetUpdateRange(this);
|
||||||
|
|
@ -8916,7 +8919,12 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret | (run ? Direction.Running : 0);
|
if (run)
|
||||||
|
{
|
||||||
|
ret |= Direction.Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|
|
||||||
|
|
@ -545,11 +545,7 @@ namespace Server.Factions
|
||||||
Action = ActionType.Combat;
|
Action = ActionType.Combat;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_Mobile.CurrentSpeed != m_Mobile.ActiveSpeed)
|
m_Mobile.SetCurrentSpeedToActive();
|
||||||
{
|
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Guard.Warmode = true;
|
m_Guard.Warmode = true;
|
||||||
|
|
||||||
RunTo(toFollow);
|
RunTo(toFollow);
|
||||||
|
|
@ -561,11 +557,7 @@ namespace Server.Factions
|
||||||
Action = ActionType.Wander;
|
Action = ActionType.Wander;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_Mobile.CurrentSpeed != m_Mobile.PassiveSpeed)
|
m_Mobile.SetCurrentSpeedToPassive();
|
||||||
{
|
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Guard.Warmode = false;
|
m_Guard.Warmode = false;
|
||||||
|
|
||||||
WalkRandomInHome(2, 2, 1);
|
WalkRandomInHome(2, 2, 1);
|
||||||
|
|
|
||||||
|
|
@ -186,35 +186,29 @@ namespace Server.Engines.MLQuests.Objectives
|
||||||
quester.ControlSlots = 0;
|
quester.ControlSlots = 0;
|
||||||
quester.SetControlMaster(pm);
|
quester.SetControlMaster(pm);
|
||||||
|
|
||||||
quester.ActiveSpeed = 0.1;
|
|
||||||
quester.PassiveSpeed = 0.2;
|
|
||||||
|
|
||||||
quester.ControlOrder = OrderType.Follow;
|
quester.ControlOrder = OrderType.Follow;
|
||||||
quester.ControlTarget = pm;
|
quester.ControlTarget = pm;
|
||||||
|
|
||||||
quester.CantWalk = false;
|
quester.CantWalk = false;
|
||||||
quester.CurrentSpeed = 0.1;
|
|
||||||
|
quester.SetSpeed(0.1, 0.2, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void EndFollow(BaseCreature quester)
|
public static void EndFollow(BaseCreature quester)
|
||||||
{
|
{
|
||||||
quester.ActiveSpeed = 0.2;
|
|
||||||
quester.PassiveSpeed = 1.0;
|
|
||||||
|
|
||||||
quester.ControlOrder = OrderType.None;
|
quester.ControlOrder = OrderType.None;
|
||||||
quester.ControlTarget = null;
|
quester.ControlTarget = null;
|
||||||
|
|
||||||
quester.CurrentSpeed = 1.0;
|
|
||||||
|
|
||||||
quester.SetControlMaster(null);
|
quester.SetControlMaster(null);
|
||||||
|
|
||||||
|
quester.SetSpeed(0.1, 0.2);
|
||||||
|
|
||||||
(quester as BaseEscortable)?.BeginDelete();
|
(quester as BaseEscortable)?.BeginDelete();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnQuestAccepted()
|
public override void OnQuestAccepted()
|
||||||
{
|
{
|
||||||
var instance = Instance;
|
var pm = Instance.Player;
|
||||||
var pm = instance.Player;
|
|
||||||
|
|
||||||
pm.LastEscortTime = Core.Now;
|
pm.LastEscortTime = Core.Now;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
namespace Server
|
namespace Server
|
||||||
{
|
{
|
||||||
public delegate MoveResult MoveMethod(Direction d);
|
public delegate MoveResult MoveMethod(Direction d, bool badStateOk);
|
||||||
|
|
||||||
public enum MoveResult
|
public enum MoveResult
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ namespace Server
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoveResult Move(Direction d) =>
|
public MoveResult Move(Direction d) =>
|
||||||
Mover?.Invoke(d) ?? (m_From.Move(d) ? MoveResult.Success : MoveResult.Blocked);
|
Mover?.Invoke(d, true) ?? (m_From.Move(d) ? MoveResult.Success : MoveResult.Blocked);
|
||||||
|
|
||||||
public Point3D GetGoalLocation() => (Goal as Item)?.GetWorldLocation() ?? new Point3D(Goal);
|
public Point3D GetGoalLocation() => (Goal as Item)?.GetWorldLocation() ?? new Point3D(Goal);
|
||||||
|
|
||||||
|
|
@ -102,14 +102,14 @@ namespace Server
|
||||||
|
|
||||||
if (!(Enabled && m_Path.Success))
|
if (!(Enabled && m_Path.Success))
|
||||||
{
|
{
|
||||||
d = m_From.GetDirectionTo(goal);
|
d = m_From.GetDirectionTo(goal, run);
|
||||||
m_From.SetDirection(d);
|
m_From.SetDirection(d);
|
||||||
Move(d);
|
|
||||||
|
|
||||||
return Check(m_From.Location, goal, range);
|
return Move(d) is MoveResult.Success or MoveResult.SuccessAutoTurn
|
||||||
|
&& Check(m_From.Location, goal, range);
|
||||||
}
|
}
|
||||||
|
|
||||||
d = m_From.GetDirectionTo(m_Next);
|
d = m_From.GetDirectionTo(m_Next, run);
|
||||||
m_From.SetDirection(d);
|
m_From.SetDirection(d);
|
||||||
var res = Move(d);
|
var res = Move(d);
|
||||||
|
|
||||||
|
|
@ -127,9 +127,9 @@ namespace Server
|
||||||
{
|
{
|
||||||
d = m_From.GetDirectionTo(goal);
|
d = m_From.GetDirectionTo(goal);
|
||||||
m_From.SetDirection(d);
|
m_From.SetDirection(d);
|
||||||
Move(d);
|
|
||||||
|
|
||||||
return Check(m_From.Location, goal, range);
|
return Move(d) is MoveResult.Success or MoveResult.SuccessAutoTurn
|
||||||
|
&& Check(m_From.Location, goal, range);
|
||||||
}
|
}
|
||||||
|
|
||||||
d = m_From.GetDirectionTo(m_Next);
|
d = m_From.GetDirectionTo(m_Next);
|
||||||
|
|
|
||||||
|
|
@ -888,6 +888,8 @@ public abstract partial class BaseWeapon : Item, IWeapon, IFactionItem, ICraftab
|
||||||
|
|
||||||
if (attacker is BaseCreature bc)
|
if (attacker is BaseCreature bc)
|
||||||
{
|
{
|
||||||
|
// Only change direction if they are not a player.
|
||||||
|
attacker.Direction = attacker.GetDirectionTo(defender);
|
||||||
var ab = bc.GetWeaponAbility();
|
var ab = bc.GetWeaponAbility();
|
||||||
|
|
||||||
if (ab != null)
|
if (ab != null)
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,10 @@ public class AnimalAI : BaseAI
|
||||||
m_Mobile.DebugSay($"I should be closer to {combatant.Name}");
|
m_Mobile.DebugSay($"I should be closer to {combatant.Name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (Core.TickCount - m_Mobile.LastMoveTime > 400)
|
||||||
|
{
|
||||||
|
m_Mobile.Direction = m_Mobile.GetDirectionTo(combatant);
|
||||||
|
}
|
||||||
|
|
||||||
if (!m_Mobile.Controlled && !m_Mobile.Summoned && m_Mobile.CanFlee)
|
if (!m_Mobile.Controlled && !m_Mobile.Summoned && m_Mobile.CanFlee)
|
||||||
{
|
{
|
||||||
|
|
@ -99,7 +103,6 @@ public class AnimalAI : BaseAI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_Mobile.Direction = m_Mobile.GetDirectionTo(combatant);
|
|
||||||
if (m_Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, combatant))
|
if (m_Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, combatant))
|
||||||
{
|
{
|
||||||
if (m_Mobile.Debug)
|
if (m_Mobile.Debug)
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,8 @@ public class ArcherAI : BaseAI
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Core.TickCount - m_Mobile.LastMoveTime > 1000 &&
|
if (Core.TickCount - m_Mobile.LastMoveTime > 1000 && !WalkMobileRange(
|
||||||
!WalkMobileRange(
|
combatant,
|
||||||
m_Mobile.Combatant,
|
|
||||||
1,
|
1,
|
||||||
true,
|
true,
|
||||||
m_Mobile.RangeFight,
|
m_Mobile.RangeFight,
|
||||||
|
|
@ -76,8 +75,11 @@ public class ArcherAI : BaseAI
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (Core.TickCount - m_Mobile.LastMoveTime > 400)
|
||||||
|
{
|
||||||
|
m_Mobile.Direction = m_Mobile.GetDirectionTo(combatant);
|
||||||
|
}
|
||||||
|
|
||||||
m_Mobile.Direction = m_Mobile.GetDirectionTo(combatant);
|
|
||||||
if (m_Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, combatant))
|
if (m_Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, combatant))
|
||||||
{
|
{
|
||||||
if (m_Mobile.Debug)
|
if (m_Mobile.Debug)
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,8 @@ public enum ActionType
|
||||||
|
|
||||||
public abstract class BaseAI
|
public abstract class BaseAI
|
||||||
{
|
{
|
||||||
|
// How many milliseconds until our next move can we consider it ok to move without deferring/blocking.
|
||||||
|
private const int FuzzyTimeUntilNextMove = 24;
|
||||||
private static readonly SkillName[] m_KeywordTable =
|
private static readonly SkillName[] m_KeywordTable =
|
||||||
{
|
{
|
||||||
SkillName.Parry,
|
SkillName.Parry,
|
||||||
|
|
@ -96,7 +98,7 @@ public abstract class BaseAI
|
||||||
|
|
||||||
protected ActionType m_Action;
|
protected ActionType m_Action;
|
||||||
|
|
||||||
public BaseCreature m_Mobile;
|
public readonly BaseCreature m_Mobile;
|
||||||
|
|
||||||
private long m_NextDetectHidden;
|
private long m_NextDetectHidden;
|
||||||
private long m_NextStopGuard;
|
private long m_NextStopGuard;
|
||||||
|
|
@ -869,7 +871,7 @@ public abstract class BaseAI
|
||||||
m_Mobile.Warmode = false;
|
m_Mobile.Warmode = false;
|
||||||
m_Mobile.Combatant = null;
|
m_Mobile.Combatant = null;
|
||||||
m_Mobile.FocusMob = null;
|
m_Mobile.FocusMob = null;
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed;
|
m_Mobile.SetCurrentSpeedToPassive();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -877,7 +879,7 @@ public abstract class BaseAI
|
||||||
{
|
{
|
||||||
m_Mobile.Warmode = true;
|
m_Mobile.Warmode = true;
|
||||||
m_Mobile.FocusMob = null;
|
m_Mobile.FocusMob = null;
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed;
|
m_Mobile.SetCurrentSpeedToActive();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -886,9 +888,8 @@ public abstract class BaseAI
|
||||||
m_Mobile.Warmode = true;
|
m_Mobile.Warmode = true;
|
||||||
m_Mobile.FocusMob = null;
|
m_Mobile.FocusMob = null;
|
||||||
m_Mobile.Combatant = null;
|
m_Mobile.Combatant = null;
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed;
|
|
||||||
m_NextStopGuard = Core.TickCount + (int)TimeSpan.FromSeconds(10).TotalMilliseconds;
|
m_NextStopGuard = Core.TickCount + (int)TimeSpan.FromSeconds(10).TotalMilliseconds;
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed;
|
m_Mobile.SetCurrentSpeedToActive();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -896,21 +897,21 @@ public abstract class BaseAI
|
||||||
{
|
{
|
||||||
m_Mobile.Warmode = true;
|
m_Mobile.Warmode = true;
|
||||||
m_Mobile.FocusMob = null;
|
m_Mobile.FocusMob = null;
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed;
|
m_Mobile.SetCurrentSpeedToActive();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionType.Interact:
|
case ActionType.Interact:
|
||||||
{
|
{
|
||||||
m_Mobile.Warmode = false;
|
m_Mobile.Warmode = false;
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed;
|
m_Mobile.SetCurrentSpeedToPassive();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionType.Backoff:
|
case ActionType.Backoff:
|
||||||
{
|
{
|
||||||
m_Mobile.Warmode = false;
|
m_Mobile.Warmode = false;
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed;
|
m_Mobile.SetCurrentSpeedToPassive();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -968,12 +969,9 @@ public abstract class BaseAI
|
||||||
WalkRandomInHome(2, 2, 1);
|
WalkRandomInHome(2, 2, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (CheckMove())
|
else if (CheckMove() && CanMoveNow(out _) && !m_Mobile.CheckIdle())
|
||||||
{
|
{
|
||||||
if (!m_Mobile.CheckIdle())
|
WalkRandomInHome(2, 2, 1);
|
||||||
{
|
|
||||||
WalkRandomInHome(2, 2, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_Mobile.Combatant?.Deleted == false && m_Mobile.Combatant.Alive &&
|
if (m_Mobile.Combatant?.Deleted == false && m_Mobile.Combatant.Alive &&
|
||||||
|
|
@ -1123,7 +1121,7 @@ public abstract class BaseAI
|
||||||
{
|
{
|
||||||
m_Mobile.ControlMaster.RevealingAction();
|
m_Mobile.ControlMaster.RevealingAction();
|
||||||
m_Mobile.Home = m_Mobile.Location;
|
m_Mobile.Home = m_Mobile.Location;
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed;
|
m_Mobile.SetCurrentSpeedToPassive();
|
||||||
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
||||||
m_Mobile.Warmode = false;
|
m_Mobile.Warmode = false;
|
||||||
m_Mobile.Combatant = null;
|
m_Mobile.Combatant = null;
|
||||||
|
|
@ -1133,7 +1131,7 @@ public abstract class BaseAI
|
||||||
case OrderType.Come:
|
case OrderType.Come:
|
||||||
{
|
{
|
||||||
m_Mobile.ControlMaster.RevealingAction();
|
m_Mobile.ControlMaster.RevealingAction();
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed;
|
m_Mobile.SetCurrentSpeedToActive();
|
||||||
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
||||||
m_Mobile.Warmode = false;
|
m_Mobile.Warmode = false;
|
||||||
m_Mobile.Combatant = null;
|
m_Mobile.Combatant = null;
|
||||||
|
|
@ -1143,7 +1141,7 @@ public abstract class BaseAI
|
||||||
case OrderType.Drop:
|
case OrderType.Drop:
|
||||||
{
|
{
|
||||||
m_Mobile.ControlMaster.RevealingAction();
|
m_Mobile.ControlMaster.RevealingAction();
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed;
|
m_Mobile.SetCurrentSpeedToPassive();
|
||||||
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
||||||
m_Mobile.Warmode = false;
|
m_Mobile.Warmode = false;
|
||||||
m_Mobile.Combatant = null;
|
m_Mobile.Combatant = null;
|
||||||
|
|
@ -1160,7 +1158,7 @@ public abstract class BaseAI
|
||||||
case OrderType.Guard:
|
case OrderType.Guard:
|
||||||
{
|
{
|
||||||
m_Mobile.ControlMaster.RevealingAction();
|
m_Mobile.ControlMaster.RevealingAction();
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed;
|
m_Mobile.SetCurrentSpeedToActive();
|
||||||
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
||||||
m_Mobile.Warmode = true;
|
m_Mobile.Warmode = true;
|
||||||
m_Mobile.Combatant = null;
|
m_Mobile.Combatant = null;
|
||||||
|
|
@ -1171,7 +1169,7 @@ public abstract class BaseAI
|
||||||
case OrderType.Attack:
|
case OrderType.Attack:
|
||||||
{
|
{
|
||||||
m_Mobile.ControlMaster.RevealingAction();
|
m_Mobile.ControlMaster.RevealingAction();
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed;
|
m_Mobile.SetCurrentSpeedToActive();
|
||||||
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
||||||
|
|
||||||
m_Mobile.Warmode = true;
|
m_Mobile.Warmode = true;
|
||||||
|
|
@ -1182,7 +1180,7 @@ public abstract class BaseAI
|
||||||
case OrderType.Patrol:
|
case OrderType.Patrol:
|
||||||
{
|
{
|
||||||
m_Mobile.ControlMaster.RevealingAction();
|
m_Mobile.ControlMaster.RevealingAction();
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed;
|
m_Mobile.SetCurrentSpeedToActive();
|
||||||
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
||||||
m_Mobile.Warmode = false;
|
m_Mobile.Warmode = false;
|
||||||
m_Mobile.Combatant = null;
|
m_Mobile.Combatant = null;
|
||||||
|
|
@ -1192,7 +1190,7 @@ public abstract class BaseAI
|
||||||
case OrderType.Release:
|
case OrderType.Release:
|
||||||
{
|
{
|
||||||
m_Mobile.ControlMaster.RevealingAction();
|
m_Mobile.ControlMaster.RevealingAction();
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed;
|
m_Mobile.SetCurrentSpeedToPassive();
|
||||||
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
||||||
m_Mobile.Warmode = false;
|
m_Mobile.Warmode = false;
|
||||||
m_Mobile.Combatant = null;
|
m_Mobile.Combatant = null;
|
||||||
|
|
@ -1202,7 +1200,7 @@ public abstract class BaseAI
|
||||||
case OrderType.Stay:
|
case OrderType.Stay:
|
||||||
{
|
{
|
||||||
m_Mobile.ControlMaster.RevealingAction();
|
m_Mobile.ControlMaster.RevealingAction();
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed;
|
m_Mobile.SetCurrentSpeedToPassive();
|
||||||
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
||||||
m_Mobile.Warmode = false;
|
m_Mobile.Warmode = false;
|
||||||
m_Mobile.Combatant = null;
|
m_Mobile.Combatant = null;
|
||||||
|
|
@ -1213,7 +1211,7 @@ public abstract class BaseAI
|
||||||
{
|
{
|
||||||
m_Mobile.ControlMaster.RevealingAction();
|
m_Mobile.ControlMaster.RevealingAction();
|
||||||
m_Mobile.Home = m_Mobile.Location;
|
m_Mobile.Home = m_Mobile.Location;
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed;
|
m_Mobile.SetCurrentSpeedToPassive();
|
||||||
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
||||||
m_Mobile.Warmode = false;
|
m_Mobile.Warmode = false;
|
||||||
m_Mobile.Combatant = null;
|
m_Mobile.Combatant = null;
|
||||||
|
|
@ -1223,7 +1221,7 @@ public abstract class BaseAI
|
||||||
case OrderType.Follow:
|
case OrderType.Follow:
|
||||||
{
|
{
|
||||||
m_Mobile.ControlMaster.RevealingAction();
|
m_Mobile.ControlMaster.RevealingAction();
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.ActiveSpeed;
|
m_Mobile.SetCurrentSpeedToActive();
|
||||||
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
||||||
|
|
||||||
m_Mobile.Warmode = false;
|
m_Mobile.Warmode = false;
|
||||||
|
|
@ -1234,7 +1232,7 @@ public abstract class BaseAI
|
||||||
case OrderType.Transfer:
|
case OrderType.Transfer:
|
||||||
{
|
{
|
||||||
m_Mobile.ControlMaster.RevealingAction();
|
m_Mobile.ControlMaster.RevealingAction();
|
||||||
m_Mobile.CurrentSpeed = m_Mobile.PassiveSpeed;
|
m_Mobile.SetCurrentSpeedToPassive();
|
||||||
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
m_Mobile.PlaySound(m_Mobile.GetIdleSound());
|
||||||
|
|
||||||
m_Mobile.Warmode = false;
|
m_Mobile.Warmode = false;
|
||||||
|
|
@ -1988,115 +1986,123 @@ public abstract class BaseAI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (iChanceToNotMove <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < iSteps; i++)
|
for (var i = 0; i < iSteps; i++)
|
||||||
{
|
{
|
||||||
if (Utility.Random(8 * iChanceToNotMove) <= 8)
|
if (Utility.Random(1 + iChanceToNotMove) == 0)
|
||||||
{
|
{
|
||||||
var iRndMove = Utility.Random(0, 8 + 9 * iChanceToDir);
|
// iChanceToDir = 2, total weight is 18
|
||||||
|
// 7/26 chance of other direction
|
||||||
switch (iRndMove)
|
var iRndMove = Utility.Random(8 * (iChanceToDir + 1));
|
||||||
{
|
Direction direction = iRndMove < 8 ? (Direction)iRndMove : m_Mobile.Direction;
|
||||||
case 0:
|
DoMove(direction);
|
||||||
{
|
|
||||||
DoMove(Direction.Up);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 1:
|
|
||||||
{
|
|
||||||
DoMove(Direction.North);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 2:
|
|
||||||
{
|
|
||||||
DoMove(Direction.Left);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 3:
|
|
||||||
{
|
|
||||||
DoMove(Direction.West);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 5:
|
|
||||||
{
|
|
||||||
DoMove(Direction.Down);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 6:
|
|
||||||
{
|
|
||||||
DoMove(Direction.South);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 7:
|
|
||||||
{
|
|
||||||
DoMove(Direction.Right);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 8:
|
|
||||||
{
|
|
||||||
DoMove(Direction.East);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
DoMove(m_Mobile.Direction);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public double TransformMoveDelay(double thinkingSpeed)
|
public static double TransformMoveDelay(BaseCreature bc, bool isPassive = false)
|
||||||
{
|
{
|
||||||
var isControlled = m_Mobile.Controlled || m_Mobile.Summoned;
|
if (bc == null)
|
||||||
|
|
||||||
// Monster is passive
|
|
||||||
if (!isControlled && Math.Abs(thinkingSpeed - m_Mobile.PassiveSpeed) < 0.0001)
|
|
||||||
{
|
{
|
||||||
thinkingSpeed *= 3; // Monster passive is 3x slower than thinking
|
return 0.1;
|
||||||
}
|
|
||||||
else if (!isControlled || m_Mobile.ControlOrder != OrderType.Follow || m_Mobile.ControlTarget != m_Mobile.ControlMaster)
|
|
||||||
{
|
|
||||||
thinkingSpeed *= 2; // Monster active speed is 2x slower than thinking
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_Mobile.IsDeadPet && (m_Mobile.ReduceSpeedWithDamage || m_Mobile.IsSubdued))
|
if (bc.MoveSpeedMod > 0)
|
||||||
|
{
|
||||||
|
return bc.MoveSpeedMod;
|
||||||
|
}
|
||||||
|
|
||||||
|
var moveSpeed = bc.CurrentSpeed;
|
||||||
|
var isControlled = bc.Controlled || bc.Summoned;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Movement Speed Table based on RunUO
|
||||||
|
* <0.075 -> 0.0375
|
||||||
|
* 0.075 -> 0.05
|
||||||
|
* 0.1 -> 0.125
|
||||||
|
* 0.2 -> 0.3
|
||||||
|
* 0.25 -> 0.45
|
||||||
|
* 0.3 -> 0.6
|
||||||
|
* 0.4 -> 0.85
|
||||||
|
* 0.5 -> 1.05
|
||||||
|
* 0.6 -> 1.2,
|
||||||
|
* 0.8 -> 1.5
|
||||||
|
* 1.0 -> 1.8
|
||||||
|
* Above 1.0 is linear
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Linear interpolated
|
||||||
|
var movementSpeed = moveSpeed switch
|
||||||
|
{
|
||||||
|
>= 1.0 => 1.8 * moveSpeed,
|
||||||
|
>= 0.5 => 1.05 + (moveSpeed - 0.5) * 1.5,
|
||||||
|
>= 0.4 => 0.85 + (moveSpeed - 0.4) * 2,
|
||||||
|
>= 0.3 => 0.6 + (moveSpeed - 0.3) * 2.5,
|
||||||
|
>= 0.2 => 0.3 + (moveSpeed - 0.2) * 3,
|
||||||
|
>= 0.1 => 0.125 + (moveSpeed - 0.1) * 1.75,
|
||||||
|
>= 0.075 => 0.05 + (moveSpeed - 0.075) * 3,
|
||||||
|
_ => 0.0375 // 30 ticks, 37.5ms
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isPassive)
|
||||||
|
{
|
||||||
|
movementSpeed += 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isControlled)
|
||||||
|
{
|
||||||
|
movementSpeed += 0.1;
|
||||||
|
}
|
||||||
|
else if (!bc.Summoned)
|
||||||
|
{
|
||||||
|
if (bc.ControlOrder == OrderType.Follow && bc.ControlTarget == bc.ControlMaster)
|
||||||
|
{
|
||||||
|
movementSpeed *= 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
movementSpeed -= 0.075;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bc.IsDeadPet && (bc.ReduceSpeedWithDamage || bc.IsSubdued))
|
||||||
{
|
{
|
||||||
int stats, statsMax;
|
int stats, statsMax;
|
||||||
if (Core.HS)
|
if (Core.HS)
|
||||||
{
|
{
|
||||||
stats = m_Mobile.Stam;
|
stats = bc.Stam;
|
||||||
statsMax = m_Mobile.StamMax;
|
statsMax = bc.StamMax;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
stats = m_Mobile.Hits;
|
stats = bc.Hits;
|
||||||
statsMax = m_Mobile.HitsMax;
|
statsMax = bc.HitsMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
var offset = statsMax <= 0 ? 1.0 : Math.Max(0, stats) / (double)statsMax;
|
var offset = statsMax <= 0 ? 1.0 : Math.Max(0, stats) / (double)statsMax;
|
||||||
|
|
||||||
if (offset < 1.0)
|
movementSpeed += (1.0 - offset) * 0.8;
|
||||||
{
|
|
||||||
thinkingSpeed += m_Mobile.PassiveSpeed * (1.0 - offset);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return thinkingSpeed;
|
return movementSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool CheckMove() => Core.TickCount - NextMove >= 0;
|
public bool CanMoveNow(out long delay) => (delay = NextMove - Core.TickCount) <= FuzzyTimeUntilNextMove;
|
||||||
|
|
||||||
|
public virtual bool CheckMove() => true;
|
||||||
|
|
||||||
public virtual bool DoMove(Direction d, bool badStateOk = false)
|
public virtual bool DoMove(Direction d, bool badStateOk = false)
|
||||||
{
|
{
|
||||||
var res = DoMoveImpl(d);
|
var res = DoMoveImpl(d, badStateOk);
|
||||||
|
|
||||||
return res is MoveResult.Success or MoveResult.SuccessAutoTurn || badStateOk && res == MoveResult.BadState;
|
return res is MoveResult.Success or MoveResult.SuccessAutoTurn || badStateOk && res == MoveResult.BadState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual MoveResult DoMoveImpl(Direction d)
|
public virtual MoveResult DoMoveImpl(Direction d, bool badStateOk)
|
||||||
{
|
{
|
||||||
if (m_Mobile.Deleted || m_Mobile.Frozen || m_Mobile.Paralyzed ||
|
if (m_Mobile == null || m_Mobile.Deleted || m_Mobile.Frozen || m_Mobile.Paralyzed ||
|
||||||
m_Mobile.Spell?.IsCasting == true || m_Mobile.DisallowAllMoves)
|
m_Mobile.Spell?.IsCasting == true || m_Mobile.DisallowAllMoves)
|
||||||
{
|
{
|
||||||
return MoveResult.BadState;
|
return MoveResult.BadState;
|
||||||
|
|
@ -2107,11 +2113,22 @@ public abstract class BaseAI
|
||||||
return MoveResult.BadState;
|
return MoveResult.BadState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var delay = (int)(TransformMoveDelay(m_Mobile) * 1000);
|
||||||
|
|
||||||
|
if (!CanMoveNow(out var timeUntilMove))
|
||||||
|
{
|
||||||
|
// When bad state is ok, we can still move if the time until move is less than the fuzzy time
|
||||||
|
if (badStateOk)
|
||||||
|
{
|
||||||
|
AIMovementTimerPool.GetTimer(TimeSpan.FromMilliseconds(timeUntilMove), this, d).Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
return MoveResult.BadState;
|
||||||
|
}
|
||||||
|
|
||||||
// This makes them always move one step, never any direction changes
|
// This makes them always move one step, never any direction changes
|
||||||
// TODO: This is firing off deltas which aren't needed. Look into replacing/removing this
|
// TODO: This is firing off deltas which aren't needed. Look into replacing/removing this
|
||||||
m_Mobile.Direction = d;
|
m_Mobile.Direction = d;
|
||||||
|
|
||||||
var delay = (int)(TransformMoveDelay(m_Mobile.CurrentSpeed) * 1000);
|
|
||||||
NextMove += delay;
|
NextMove += delay;
|
||||||
|
|
||||||
if (Core.TickCount - NextMove > 0)
|
if (Core.TickCount - NextMove > 0)
|
||||||
|
|
@ -2120,21 +2137,31 @@ public abstract class BaseAI
|
||||||
}
|
}
|
||||||
|
|
||||||
m_Mobile.Pushing = false;
|
m_Mobile.Pushing = false;
|
||||||
|
var mobDirection = m_Mobile.Direction;
|
||||||
|
|
||||||
|
// Do the actual move
|
||||||
MoveImpl.IgnoreMovableImpassables = m_Mobile.CanMoveOverObstacles && !m_Mobile.CanDestroyObstacles;
|
MoveImpl.IgnoreMovableImpassables = m_Mobile.CanMoveOverObstacles && !m_Mobile.CanDestroyObstacles;
|
||||||
|
var moveResult = m_Mobile.Move(d);
|
||||||
|
MoveImpl.IgnoreMovableImpassables = false;
|
||||||
|
|
||||||
if ((m_Mobile.Direction & Direction.Mask) != (d & Direction.Mask))
|
if (moveResult)
|
||||||
{
|
{
|
||||||
var v = m_Mobile.Move(d);
|
// If we don't delay combat, then a direction change will happen and cause a glitchy sliding effect.
|
||||||
|
if (m_Mobile.Warmode && m_Mobile.Combatant != null)
|
||||||
MoveImpl.IgnoreMovableImpassables = false;
|
{
|
||||||
return v ? MoveResult.Success : MoveResult.Blocked;
|
var remaining = m_Mobile.NextCombatTime - Core.TickCount;
|
||||||
|
var maxWait = Math.Min(delay, 400);
|
||||||
|
if (remaining < maxWait)
|
||||||
|
{
|
||||||
|
m_Mobile.NextCombatTime = Core.TickCount + maxWait;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MoveResult.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_Mobile.Move(d))
|
if ((mobDirection & Direction.Mask) != (d & Direction.Mask))
|
||||||
{
|
{
|
||||||
MoveImpl.IgnoreMovableImpassables = false;
|
return MoveResult.Blocked;
|
||||||
return MoveResult.Success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var wasPushing = m_Mobile.Pushing;
|
var wasPushing = m_Mobile.Pushing;
|
||||||
|
|
@ -2271,16 +2298,13 @@ public abstract class BaseAI
|
||||||
|
|
||||||
if (m_Mobile.Move(m_Mobile.Direction))
|
if (m_Mobile.Move(m_Mobile.Direction))
|
||||||
{
|
{
|
||||||
MoveImpl.IgnoreMovableImpassables = false;
|
|
||||||
return MoveResult.SuccessAutoTurn;
|
return MoveResult.SuccessAutoTurn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MoveImpl.IgnoreMovableImpassables = false;
|
|
||||||
return wasPushing ? MoveResult.BadState : MoveResult.Blocked;
|
return wasPushing ? MoveResult.BadState : MoveResult.Blocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
MoveImpl.IgnoreMovableImpassables = false;
|
|
||||||
return MoveResult.Success;
|
return MoveResult.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2305,7 +2329,7 @@ public abstract class BaseAI
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (region.GoLocation != Point3D.Zero && Utility.Random(10) > 5)
|
if (region.GoLocation != Point3D.Zero && Utility.RandomBool())
|
||||||
{
|
{
|
||||||
DoMove(m_Mobile.GetDirectionTo(region.GoLocation));
|
DoMove(m_Mobile.GetDirectionTo(region.GoLocation));
|
||||||
}
|
}
|
||||||
|
|
@ -2336,16 +2360,13 @@ public abstract class BaseAI
|
||||||
{
|
{
|
||||||
DoMove(m_Mobile.GetDirectionTo(m_Mobile.Home));
|
DoMove(m_Mobile.GetDirectionTo(m_Mobile.Home));
|
||||||
}
|
}
|
||||||
|
else if (Utility.Random(10) > 5)
|
||||||
|
{
|
||||||
|
DoMove(m_Mobile.GetDirectionTo(m_Mobile.Home));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (Utility.Random(10) > 5)
|
WalkRandom(iChanceToNotMove, iChanceToDir, 1);
|
||||||
{
|
|
||||||
DoMove(m_Mobile.GetDirectionTo(m_Mobile.Home));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
WalkRandom(iChanceToNotMove, iChanceToDir, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -2447,7 +2468,7 @@ public abstract class BaseAI
|
||||||
* iWantDistMax : The maximum distance we want to be
|
* iWantDistMax : The maximum distance we want to be
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public virtual bool WalkMobileRange(Mobile m, int iSteps, bool bRun, int iWantDistMin, int iWantDistMax)
|
public virtual bool WalkMobileRange(Mobile m, int iSteps, bool run, int iWantDistMin, int iWantDistMax)
|
||||||
{
|
{
|
||||||
if (m_Mobile.Deleted || m_Mobile.DisallowAllMoves)
|
if (m_Mobile.Deleted || m_Mobile.DisallowAllMoves)
|
||||||
{
|
{
|
||||||
|
|
@ -2467,11 +2488,10 @@ public abstract class BaseAI
|
||||||
if (iCurrDist < iWantDistMin || iCurrDist > iWantDistMax)
|
if (iCurrDist < iWantDistMin || iCurrDist > iWantDistMax)
|
||||||
{
|
{
|
||||||
var needCloser = iCurrDist > iWantDistMax;
|
var needCloser = iCurrDist > iWantDistMax;
|
||||||
var needFurther = !needCloser;
|
|
||||||
|
|
||||||
if (needCloser && m_Path != null && m_Path.Goal == m)
|
if (needCloser && m_Path != null && m_Path.Goal == m)
|
||||||
{
|
{
|
||||||
if (m_Path.Follow(bRun, 1))
|
if (m_Path.Follow(run, 1))
|
||||||
{
|
{
|
||||||
m_Path = null;
|
m_Path = null;
|
||||||
}
|
}
|
||||||
|
|
@ -2479,13 +2499,13 @@ public abstract class BaseAI
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var dirTo = iCurrDist > iWantDistMax ?
|
var dirTo = iCurrDist > iWantDistMax ?
|
||||||
m_Mobile.GetDirectionTo(m, bRun) : m.GetDirectionTo(m_Mobile, bRun);
|
m_Mobile.GetDirectionTo(m, run) : m.GetDirectionTo(m_Mobile, run);
|
||||||
|
|
||||||
if (!DoMove(dirTo, true) && needCloser)
|
if (!DoMove(dirTo, true) && needCloser)
|
||||||
{
|
{
|
||||||
m_Path = new PathFollower(m_Mobile, m) { Mover = DoMoveImpl };
|
m_Path = new PathFollower(m_Mobile, m) { Mover = DoMoveImpl };
|
||||||
|
|
||||||
if (m_Path.Follow(bRun, 1))
|
if (m_Path.Follow(run, 1))
|
||||||
{
|
{
|
||||||
m_Path = null;
|
m_Path = null;
|
||||||
}
|
}
|
||||||
|
|
@ -2882,9 +2902,6 @@ public abstract class BaseAI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* The mobile changed speeds, we must adjust the timer
|
|
||||||
*/
|
|
||||||
public virtual void OnCurrentSpeedChanged()
|
public virtual void OnCurrentSpeedChanged()
|
||||||
{
|
{
|
||||||
m_Timer.Interval = TimeSpan.FromSeconds(Math.Max(0.008, m_Mobile.CurrentSpeed));
|
m_Timer.Interval = TimeSpan.FromSeconds(Math.Max(0.008, m_Mobile.CurrentSpeed));
|
||||||
|
|
@ -3247,4 +3264,64 @@ public abstract class BaseAI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class AIMovementTimerPool
|
||||||
|
{
|
||||||
|
private const int _poolSize = 1024;
|
||||||
|
private static readonly Queue<AIMovementTimer> _pool = new (_poolSize);
|
||||||
|
|
||||||
|
public static void Configure()
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
while (i++ < _poolSize)
|
||||||
|
{
|
||||||
|
_pool.Enqueue(new AIMovementTimer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AIMovementTimer GetTimer(TimeSpan delay, BaseAI ai, Direction direction)
|
||||||
|
{
|
||||||
|
AIMovementTimer timer;
|
||||||
|
if (_pool.Count > 0)
|
||||||
|
{
|
||||||
|
timer = _pool.Dequeue();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timer = new AIMovementTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.Set(delay, ai, direction);
|
||||||
|
return timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AIMovementTimer : Timer
|
||||||
|
{
|
||||||
|
public BaseAI AI { get; private set; }
|
||||||
|
public Direction Direction { get; private set; }
|
||||||
|
|
||||||
|
public AIMovementTimer() : base(TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Set(TimeSpan delay, BaseAI ai, Direction direction)
|
||||||
|
{
|
||||||
|
if (Running)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Delay = delay;
|
||||||
|
AI = ai;
|
||||||
|
Direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnTick()
|
||||||
|
{
|
||||||
|
AI?.DoMove(Direction);
|
||||||
|
AI = null;
|
||||||
|
_pool.Enqueue(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,11 @@ public class BerserkAI : BaseAI
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (Core.TickCount - m_Mobile.LastMoveTime > 400)
|
||||||
|
{
|
||||||
|
m_Mobile.Direction = m_Mobile.GetDirectionTo(combatant);
|
||||||
|
}
|
||||||
|
|
||||||
m_Mobile.Direction = m_Mobile.GetDirectionTo(combatant);
|
|
||||||
if (m_Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, combatant))
|
if (m_Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, combatant))
|
||||||
{
|
{
|
||||||
if (m_Mobile.Debug)
|
if (m_Mobile.Debug)
|
||||||
|
|
|
||||||
|
|
@ -769,7 +769,6 @@ public class MageAI : BaseAI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_Mobile.Direction = m_Mobile.GetDirectionTo(c);
|
|
||||||
if (m_Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, c))
|
if (m_Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, c))
|
||||||
{
|
{
|
||||||
if (m_Mobile.Debug)
|
if (m_Mobile.Debug)
|
||||||
|
|
@ -840,6 +839,11 @@ public class MageAI : BaseAI
|
||||||
RunTo(c);
|
RunTo(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_Mobile.Spell != null || !m_Mobile.InRange(c, 1) || Core.TickCount - m_Mobile.LastMoveTime > 800)
|
||||||
|
{
|
||||||
|
m_Mobile.Direction = m_Mobile.GetDirectionTo(c);
|
||||||
|
}
|
||||||
|
|
||||||
m_LastTarget = c;
|
m_LastTarget = c;
|
||||||
m_LastTargetLoc = c.Location;
|
m_LastTargetLoc = c.Location;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,6 @@ public class MeleeAI : BaseAI
|
||||||
|
|
||||||
public override bool DoActionWander()
|
public override bool DoActionWander()
|
||||||
{
|
{
|
||||||
if (m_Mobile.Debug)
|
|
||||||
{
|
|
||||||
m_Mobile.DebugSay("I have no combatant");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true))
|
if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true))
|
||||||
{
|
{
|
||||||
if (m_Mobile.Debug)
|
if (m_Mobile.Debug)
|
||||||
|
|
@ -27,6 +22,13 @@ public class MeleeAI : BaseAI
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (m_Mobile.Debug)
|
||||||
|
{
|
||||||
|
m_Mobile.DebugSay("I am wandering");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Mobile.Warmode = false;
|
||||||
|
|
||||||
base.DoActionWander();
|
base.DoActionWander();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,6 +81,7 @@ public class MeleeAI : BaseAI
|
||||||
|
|
||||||
if (!MoveTo(combatant, true, m_Mobile.RangeFight))
|
if (!MoveTo(combatant, true, m_Mobile.RangeFight))
|
||||||
{
|
{
|
||||||
|
m_Mobile.Direction = m_Mobile.GetDirectionTo(combatant);
|
||||||
if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true))
|
if (AcquireFocusMob(m_Mobile.RangePerception, m_Mobile.FightMode, false, false, true))
|
||||||
{
|
{
|
||||||
if (m_Mobile.Debug)
|
if (m_Mobile.Debug)
|
||||||
|
|
@ -107,6 +110,10 @@ public class MeleeAI : BaseAI
|
||||||
m_Mobile.DebugSay($"I cannot find {combatant.Name}, so my guard is up");
|
m_Mobile.DebugSay($"I cannot find {combatant.Name}, so my guard is up");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (Core.TickCount - m_Mobile.LastMoveTime > 400)
|
||||||
|
{
|
||||||
|
m_Mobile.Direction = m_Mobile.GetDirectionTo(combatant);
|
||||||
|
}
|
||||||
|
|
||||||
if (!m_Mobile.Controlled && !m_Mobile.Summoned && m_Mobile.CanFlee)
|
if (!m_Mobile.Controlled && !m_Mobile.Summoned && m_Mobile.CanFlee)
|
||||||
{
|
{
|
||||||
|
|
@ -128,7 +135,6 @@ public class MeleeAI : BaseAI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_Mobile.Direction = m_Mobile.GetDirectionTo(combatant);
|
|
||||||
if (m_Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, combatant))
|
if (m_Mobile.TriggerAbility(MonsterAbilityTrigger.CombatAction, combatant))
|
||||||
{
|
{
|
||||||
if (m_Mobile.Debug)
|
if (m_Mobile.Debug)
|
||||||
|
|
|
||||||
|
|
@ -261,7 +261,14 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
private AIType m_CurrentAI; // The current AI
|
private AIType m_CurrentAI; // The current AI
|
||||||
|
|
||||||
private double m_CurrentSpeed; // The current speed, lets say it could be changed by something;
|
private double _activeSpeed;
|
||||||
|
private double _passiveSpeed;
|
||||||
|
private double _currentSpeed;
|
||||||
|
|
||||||
|
// Herding - Overrides the AI to force the mob to move to a specific location
|
||||||
|
// Thinking: 0.3s, Movement: 0.6s.
|
||||||
|
private IPoint2D _targetLocation;
|
||||||
|
|
||||||
private int m_DamageMax = -1;
|
private int m_DamageMax = -1;
|
||||||
|
|
||||||
private int m_DamageMin = -1;
|
private int m_DamageMin = -1;
|
||||||
|
|
@ -541,9 +548,6 @@ namespace Server.Mobiles
|
||||||
[CommandProperty(AccessLevel.GameMaster)]
|
[CommandProperty(AccessLevel.GameMaster)]
|
||||||
public WayPoint CurrentWayPoint { get; set; }
|
public WayPoint CurrentWayPoint { get; set; }
|
||||||
|
|
||||||
[CommandProperty(AccessLevel.GameMaster)]
|
|
||||||
public IPoint2D TargetLocation { get; set; }
|
|
||||||
|
|
||||||
public virtual Mobile ConstantFocus => null;
|
public virtual Mobile ConstantFocus => null;
|
||||||
|
|
||||||
public virtual bool DisallowAllMoves => false;
|
public virtual bool DisallowAllMoves => false;
|
||||||
|
|
@ -660,32 +664,60 @@ namespace Server.Mobiles
|
||||||
public int RangeHome { get; set; } = 10;
|
public int RangeHome { get; set; } = 10;
|
||||||
|
|
||||||
[CommandProperty(AccessLevel.GameMaster)]
|
[CommandProperty(AccessLevel.GameMaster)]
|
||||||
public double ActiveSpeed { get; set; }
|
public virtual double ActiveSpeed
|
||||||
|
{
|
||||||
|
get => _activeSpeed;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Math.Abs(_activeSpeed - value) > .0001)
|
||||||
|
{
|
||||||
|
_activeSpeed = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[CommandProperty(AccessLevel.GameMaster)]
|
[CommandProperty(AccessLevel.GameMaster)]
|
||||||
public double PassiveSpeed { get; set; }
|
public virtual double PassiveSpeed
|
||||||
|
{
|
||||||
|
get => _passiveSpeed;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_passiveSpeed = value;
|
||||||
|
if (Math.Abs(_passiveSpeed - value) > .0001)
|
||||||
|
{
|
||||||
|
_passiveSpeed = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[CommandProperty(AccessLevel.GameMaster)]
|
[CommandProperty(AccessLevel.GameMaster)]
|
||||||
public double SpeedMod { get; set; }
|
public IPoint2D TargetLocation
|
||||||
|
{
|
||||||
|
get => _targetLocation;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_targetLocation = value;
|
||||||
|
AIObject?.OnCurrentSpeedChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[CommandProperty(AccessLevel.GameMaster)]
|
[CommandProperty(AccessLevel.GameMaster)]
|
||||||
public double CurrentSpeed
|
public double CurrentSpeed
|
||||||
{
|
{
|
||||||
get => TargetLocation != null ? 0.3 : SpeedMod <= 0 ? m_CurrentSpeed : SpeedMod;
|
get => _targetLocation != null ? 0.3 : _currentSpeed;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (m_CurrentSpeed != value)
|
if (Math.Abs(_currentSpeed - value) > 0.0001)
|
||||||
{
|
{
|
||||||
m_CurrentSpeed = value;
|
_currentSpeed = value;
|
||||||
|
AIObject?.OnCurrentSpeedChanged();
|
||||||
if (SpeedMod <= 0)
|
|
||||||
{
|
|
||||||
AIObject?.OnCurrentSpeedChanged();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandProperty(AccessLevel.GameMaster)]
|
||||||
|
public double MoveSpeedMod { get; set; }
|
||||||
|
|
||||||
[CommandProperty(AccessLevel.GameMaster)]
|
[CommandProperty(AccessLevel.GameMaster)]
|
||||||
public Point3D Home
|
public Point3D Home
|
||||||
{
|
{
|
||||||
|
|
@ -1795,9 +1827,9 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
writer.Write(m_Team);
|
writer.Write(m_Team);
|
||||||
|
|
||||||
writer.Write(ActiveSpeed);
|
writer.Write(_activeSpeed);
|
||||||
writer.Write(PassiveSpeed);
|
writer.Write(_passiveSpeed);
|
||||||
writer.Write(m_CurrentSpeed);
|
writer.Write(_currentSpeed);
|
||||||
|
|
||||||
writer.Write(m_Home.X);
|
writer.Write(m_Home.X);
|
||||||
writer.Write(m_Home.Y);
|
writer.Write(m_Home.Y);
|
||||||
|
|
@ -1921,9 +1953,9 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
m_Team = reader.ReadInt();
|
m_Team = reader.ReadInt();
|
||||||
|
|
||||||
ActiveSpeed = reader.ReadDouble();
|
_activeSpeed = reader.ReadDouble();
|
||||||
PassiveSpeed = reader.ReadDouble();
|
_passiveSpeed = reader.ReadDouble();
|
||||||
m_CurrentSpeed = reader.ReadDouble();
|
_currentSpeed = reader.ReadDouble();
|
||||||
|
|
||||||
if (RangePerception == OldRangePerception)
|
if (RangePerception == OldRangePerception)
|
||||||
{
|
{
|
||||||
|
|
@ -4478,13 +4510,19 @@ namespace Server.Mobiles
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetSpeed(double active, double passive)
|
public void SetSpeed(double active, double passive, bool isPassive = true)
|
||||||
{
|
{
|
||||||
ActiveSpeed = active;
|
ActiveSpeed = active;
|
||||||
PassiveSpeed = passive;
|
PassiveSpeed = passive;
|
||||||
CurrentSpeed = PassiveSpeed;
|
CurrentSpeed = isPassive ? PassiveSpeed : ActiveSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetCurrentSpeedToActive() => CurrentSpeed = ActiveSpeed;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void SetCurrentSpeedToPassive() => CurrentSpeed = PassiveSpeed;
|
||||||
|
|
||||||
public void SetDamage(int val)
|
public void SetDamage(int val)
|
||||||
{
|
{
|
||||||
m_DamageMin = val;
|
m_DamageMin = val;
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ public abstract partial class BaseFamiliar : BaseCreature
|
||||||
Warmode = master.Warmode;
|
Warmode = master.Warmode;
|
||||||
Combatant = master.Combatant;
|
Combatant = master.Combatant;
|
||||||
|
|
||||||
CurrentSpeed = 0.10;
|
CurrentSpeed = 0.1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,7 @@ public abstract partial class BaseHealer : BaseVendor
|
||||||
if (!IsInvulnerable)
|
if (!IsInvulnerable)
|
||||||
{
|
{
|
||||||
AI = AIType.AI_Mage;
|
AI = AIType.AI_Mage;
|
||||||
ActiveSpeed = 0.2;
|
SetSpeed(0.2, 0.8);
|
||||||
PassiveSpeed = 0.8;
|
|
||||||
RangePerception = DefaultRangePerception;
|
RangePerception = DefaultRangePerception;
|
||||||
FightMode = FightMode.Aggressor;
|
FightMode = FightMode.Aggressor;
|
||||||
}
|
}
|
||||||
|
|
@ -148,8 +147,7 @@ public abstract partial class BaseHealer : BaseVendor
|
||||||
if (!IsInvulnerable)
|
if (!IsInvulnerable)
|
||||||
{
|
{
|
||||||
AI = AIType.AI_Mage;
|
AI = AIType.AI_Mage;
|
||||||
ActiveSpeed = 0.2;
|
SetSpeed(0.2, 0.8);
|
||||||
PassiveSpeed = 0.8;
|
|
||||||
RangePerception = DefaultRangePerception;
|
RangePerception = DefaultRangePerception;
|
||||||
FightMode = FightMode.Aggressor;
|
FightMode = FightMode.Aggressor;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ namespace Server.Mobiles;
|
||||||
public enum SpeedLevel
|
public enum SpeedLevel
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
|
VerySlow,
|
||||||
Slow,
|
Slow,
|
||||||
Medium,
|
Medium,
|
||||||
Fast,
|
Fast,
|
||||||
|
|
@ -18,8 +19,8 @@ public enum SpeedLevel
|
||||||
public static class NPCSpeeds
|
public static class NPCSpeeds
|
||||||
{
|
{
|
||||||
private const string _tablePath = "Data/npc-speeds.json";
|
private const string _tablePath = "Data/npc-speeds.json";
|
||||||
private static Dictionary<Type, SpeedClassEntry> _speedsByType = new();
|
private static readonly Dictionary<Type, SpeedClassEntry> _speedsByType = new();
|
||||||
private static Dictionary<SpeedLevel, SpeedClassEntry> _speedsByLevel = new();
|
private static readonly Dictionary<SpeedLevel, SpeedClassEntry> _speedsByLevel = new();
|
||||||
|
|
||||||
// Enabled for pets on HS+
|
// Enabled for pets on HS+
|
||||||
public static bool ScaleSpeedByDex { get; private set; }
|
public static bool ScaleSpeedByDex { get; private set; }
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,9 @@ public partial class Neira : BaseChampion
|
||||||
PassiveSpeed *= SpeedBoostScalar;
|
PassiveSpeed *= SpeedBoostScalar;
|
||||||
_speedBoost = false;
|
_speedBoost = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assume active
|
||||||
|
CurrentSpeed = ActiveSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnGaveMeleeAttack(Mobile defender, int damage)
|
public override void OnGaveMeleeAttack(Mobile defender, int damage)
|
||||||
|
|
|
||||||
|
|
@ -509,9 +509,6 @@ public partial class BaseEscortable : BaseCreature
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ActiveSpeed = 0.1;
|
|
||||||
PassiveSpeed = 0.2;
|
|
||||||
|
|
||||||
ControlOrder = OrderType.Follow;
|
ControlOrder = OrderType.Follow;
|
||||||
ControlTarget = escorter;
|
ControlTarget = escorter;
|
||||||
|
|
||||||
|
|
@ -520,18 +517,15 @@ public partial class BaseEscortable : BaseCreature
|
||||||
CantWalk = false;
|
CantWalk = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentSpeed = 0.1;
|
SetSpeed(0.1, 0.2, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void StopFollow()
|
public virtual void StopFollow()
|
||||||
{
|
{
|
||||||
ActiveSpeed = 0.2;
|
|
||||||
PassiveSpeed = 1.0;
|
|
||||||
|
|
||||||
ControlOrder = OrderType.None;
|
ControlOrder = OrderType.None;
|
||||||
ControlTarget = null;
|
ControlTarget = null;
|
||||||
|
|
||||||
CurrentSpeed = 1.0;
|
SetSpeed(0.2, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual Mobile GetEscorter()
|
public virtual Mobile GetEscorter()
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,7 @@ namespace Server.Mobiles
|
||||||
{
|
{
|
||||||
public class CloneAI : BaseAI
|
public class CloneAI : BaseAI
|
||||||
{
|
{
|
||||||
public CloneAI(Clone m) : base(m) => m.CurrentSpeed = m.ActiveSpeed;
|
public CloneAI(Clone m) : base(m) => m.SetCurrentSpeedToActive();
|
||||||
|
|
||||||
public override bool CanDetectHidden => false;
|
public override bool CanDetectHidden => false;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue