more event driven behavior tree

This commit is contained in:
Leath Cooper 2021-09-12 23:38:56 -04:00
parent 391c49553a
commit e0874713d1
29 changed files with 473 additions and 165 deletions

View file

@ -12,7 +12,6 @@ namespace Server.Mobiles.AI
{
public class BehaviorTreeAI : BaseAI
{
// private Blackboard blackboard;
private Blackboard blackboard;
private BehaviorTree behaviorTree;
private BehaviorTreeContext behaviorTreeContext;
@ -23,11 +22,16 @@ namespace Server.Mobiles.AI
behaviorTreeContext = new BehaviorTreeContext(m, blackboard);
behaviorTree = new BehaviorTree();
behaviorTree.TryAddRoot(
new Sequence(behaviorTree)
.AddChild(new Condition(behaviorTree, (context) => context.Mobile.RawStr - context.Mobile.Str == 0))
.AddChild(new CastSpell(behaviorTree, (context) => new BlessSpell(context.Mobile)))
.AddChild(new WaitForTarget(behaviorTree))
.AddChild(new DynamicTarget(behaviorTree, (context) => context.Mobile))
new Selector(behaviorTree)
.AddChild(new MageCombat(behaviorTree))
.AddChild(new MagePassive(behaviorTree))
/*
new Sequence(behaviorTree)
.AddChild(new Condition(behaviorTree, (context) => context.Mobile.RawStr - context.Mobile.Str == 0))
.AddChild(new CastSpell(behaviorTree, (context) => new BlessSpell(context.Mobile)))
.AddChild(new WaitForTarget(behaviorTree))
.AddChild(new DynamicTarget(behaviorTree, (context) => context.Mobile))
*/
);
behaviorTree.Start(behaviorTreeContext);
@ -42,7 +46,6 @@ namespace Server.Mobiles.AI
// MageBehaviorTree.Instance.RunBehavior(m_Mobile, blackboard);
m_Mobile.DebugSay("Thinking...");
behaviorTree.Tick(behaviorTreeContext);
return true;

View file

@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class Selector : Composite
@ -19,8 +13,7 @@ namespace Server.Mobiles.BehaviorAI
currentChildCache.Add(context.Mobile, currentChild);
}
Behavior child = Children[currentChild];
currentChild++;
if (lastResult == Result.Success)
{
currentChildCache[context.Mobile] = 0;
@ -28,23 +21,16 @@ namespace Server.Mobiles.BehaviorAI
return;
}
currentChild++;
if (currentChild >= Children.Count)
{
currentChildCache[context.Mobile] = 0;
SetResult(context, Result.Failure);
return;
}
else
{
currentChildCache[context.Mobile] = currentChild;
SetResult(context, Result.Running);
if (Children[currentChild] == null)
{
throw new NullReferenceException();
}
Tree.Enqueue(context, Children[currentChild], OnChildComplete);
}
currentChildCache[context.Mobile] = currentChild;
SetResult(context, Result.Running);
Tree.Enqueue(context, Children[currentChild], OnChildComplete);
}
}
}

View file

@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class Sequence : Composite
@ -19,8 +13,7 @@ namespace Server.Mobiles.BehaviorAI
currentChildCache.Add(context.Mobile, currentChild);
}
Behavior child = Children[currentChild];
currentChild++;
if (lastResult == Result.Failure)
{
currentChildCache[context.Mobile] = 0;
@ -28,23 +21,16 @@ namespace Server.Mobiles.BehaviorAI
return;
}
currentChild++;
if (currentChild >= Children.Count)
{
currentChildCache[context.Mobile] = 0;
SetResult(context, Result.Success);
return;
}
else
{
SetResult(context, Result.Running);
currentChildCache[context.Mobile] = currentChild;
if (Children[currentChild] == null)
{
throw new NullReferenceException();
}
Tree.Enqueue(context, Children[currentChild], OnChildComplete);
}
currentChildCache[context.Mobile] = currentChild;
SetResult(context, Result.Running);
Tree.Enqueue(context, Children[currentChild], OnChildComplete);
}
}
}

View file

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{

View file

@ -1,8 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{

View file

@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Server.Items;
using Server.Engines.Spawners;
using MoveImpl = Server.Movement.MovementImpl;
namespace Server.Mobiles.BehaviorAI
{
@ -79,7 +79,7 @@ namespace Server.Mobiles.BehaviorAI
queue.Enqueue(current);
return false;
return true;
}
public virtual void ExecutionFinished(BehaviorTreeContext context, Result result)
{
@ -104,5 +104,336 @@ namespace Server.Mobiles.BehaviorAI
return queue;
}
public static void WalkRandomInHome(BehaviorTreeContext context, int steps)
{
BaseCreature mob = context.Mobile;
if (mob.Deleted || mob.DisallowAllMoves)
{
return;
}
if (mob.Home == Point3D.Zero)
{
if (mob.Spawner is RegionSpawner rs)
{
Region region = rs.SpawnRegion;
if (mob.Region.AcceptsSpawnsFrom(region))
{
mob.WalkRegion = region;
WalkRandom(context, steps);
mob.WalkRegion = null;
}
else
{
if (region.GoLocation != Point3D.Zero && Utility.RandomDouble() > 0.5)
{
DoMove(context, mob.GetDirectionTo(region.GoLocation));
}
else
{
WalkRandom(context, steps);
}
}
}
else
{
WalkRandom(context, steps);
}
}
else
{
for (var i = 0; i < steps; i++)
{
if (mob.RangeHome != 0)
{
var currentDistance = (int)mob.GetDistanceToSqrt(mob.Home);
if (currentDistance < mob.RangeHome * 2 / 3)
{
WalkRandom(context, 1);
}
else if (currentDistance > mob.RangeHome)
{
DoMove(context, mob.GetDirectionTo(mob.Home));
}
else
{
if (Utility.RandomDouble() > 0.5)
{
DoMove(context, mob.GetDirectionTo(mob.Home));
}
else
{
WalkRandom(context, 1);
}
}
}
else
{
if (mob.Location != mob.Home)
{
DoMove(context, mob.GetDirectionTo(mob.Home));
}
}
}
}
}
public static MoveResult WalkRandom(BehaviorTreeContext context, int steps, bool run = false)
{
BaseCreature mob = context.Mobile;
if (mob.Deleted || mob.DisallowAllMoves)
{
return MoveResult.BadState;
}
for (var i = 0; i < steps; i++)
{
if (Utility.Random(8) <= 8)
{
var random = Utility.Random(0, 32);
Direction direction;
switch (random)
{
case 0:
direction = Direction.Up;
break;
case 1:
direction = Direction.North;
break;
case 2:
direction = Direction.Left;
break;
case 3:
direction = Direction.West;
break;
case 5:
direction = Direction.Down;
break;
case 6:
direction = Direction.South;
break;
case 7:
direction = Direction.Right;
break;
case 8:
direction = Direction.East;
break;
default:
direction = mob.Direction;
break;
}
DoMove(context, direction, run);
}
}
return MoveResult.Success;
}
public static MoveResult DoMove(BehaviorTreeContext context, Direction d, bool run = false)
{
BaseCreature mob = context.Mobile;
if (mob.Deleted || mob.Frozen || mob.Paralyzed || mob.Spell?.IsCasting == true || mob.DisallowAllMoves)
{
return MoveResult.BadState;
}
Direction direction = d;
if (run)
{
direction |= Direction.Running;
}
mob.Pushing = false;
MoveImpl.IgnoreMovableImpassables = mob.CanMoveOverObstacles && !mob.CanDestroyObstacles;
if ((mob.Direction & Direction.Mask) != (direction & Direction.Mask))
{
bool moved = mob.Move(direction);
MoveImpl.IgnoreMovableImpassables = false;
return moved ? MoveResult.Success : MoveResult.Blocked;
}
if (mob.Move(direction))
{
MoveImpl.IgnoreMovableImpassables = false;
return MoveResult.Success;
}
bool wasPushing = mob.Pushing;
bool blocked = true;
bool canOpenDoors = mob.CanOpenDoors;
bool canDestroyObstacles = mob.CanDestroyObstacles;
if (canOpenDoors || canDestroyObstacles)
{
Map map = mob.Map;
if (map != null)
{
int x = mob.X, y = mob.Y;
Movement.Movement.Offset(direction, ref x, ref y);
int destroyables = 0;
List<Item> obstacles = new List<Item>();
var eable = map.GetItemsInRange(new Point3D(x, y, mob.Location.Z), 1);
foreach (var item in eable)
{
if (canOpenDoors && item is BaseDoor door && door.Z + door.ItemData.Height > mob.Z && mob.Z + 16 > door.Z)
{
if (door.X != x || door.Y != y)
{
continue;
}
if (!door.Locked || !door.UseLocks())
{
obstacles.Add(item);
}
if (!canDestroyObstacles)
{
break;
}
}
else if (canDestroyObstacles && item.Movable && item.ItemData.Impassable && item.Z + item.ItemData.Height > mob.Z && mob.Z + 16 > item.Z)
{
if (!mob.InRange(item.GetWorldLocation(), 1))
{
continue;
}
obstacles.Add(item);
++destroyables;
}
}
eable.Free();
if (destroyables > 0)
{
Effects.PlaySound(new Point3D(x, y, mob.Z), mob.Map, 0x3B3);
}
if (obstacles.Count > 0)
blocked = true;
while (obstacles.Count > 0)
{
Item item = obstacles.First();
if (item is BaseDoor door)
{
mob.DebugSay("Opening door...");
if (!door.Open)
{
door.Use(mob);
}
obstacles.Remove(item);
}
else
{
if (item is Container container)
{
for (var i = 0; i < container.Items.Count; ++i)
{
Item check = container.Items[i];
if (check.Movable && check.ItemData.Impassable && container.Z + check.ItemData.Height > mob.Z)
{
obstacles.Add(check);
}
}
obstacles.Remove(item);
container.Destroy();
}
else
{
obstacles.Remove(item);
item.Delete();
}
}
}
if (!blocked)
{
blocked = !mob.Move(direction);
}
}
}
if (blocked)
{
int offset = Utility.RandomDouble() >= 0.6 ? 1 : -1;
for (var i = 0; i < 2; ++i)
{
mob.TurnInternal(offset);
if (mob.Move(mob.Direction))
{
MoveImpl.IgnoreMovableImpassables = false;
return MoveResult.SuccessAutoTurn;
}
}
MoveImpl.IgnoreMovableImpassables = false;
return wasPushing ? MoveResult.BadState : MoveResult.Blocked;
}
MoveImpl.IgnoreMovableImpassables = false;
return MoveResult.Success;
}
public static bool WalkMobileRange(BehaviorTreeContext context, BaseCreature target, int steps, bool run, int minRange, int maxRange)
{
BaseCreature mob = context.Mobile;
if (mob.Deleted || mob.DisallowAllMoves)
{
return false;
}
if (target == null)
{
return false;
}
for (var i = 0; i < steps; i++)
{
// Get the current distance
var currentDistance = (int)mob.GetDistanceToSqrt(target);
if (currentDistance < minRange || currentDistance > maxRange)
{
var needCloser = currentDistance > maxRange;
var direction = needCloser ?
mob.GetDirectionTo(target, run) : target.GetDirectionTo(mob, run);
DoMove(context, direction, run);
}
else
{
WalkRandom(context, 2, run);
}
return true;
}
// Get the current distance
var newDistance = (int)mob.GetDistanceToSqrt(target);
return newDistance >= minRange && newDistance <= maxRange;
}
}
}

View file

@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class BehaviorTreeContext

View file

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{

View file

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
@ -36,10 +32,6 @@ namespace Server.Mobiles.BehaviorAI
currentChild = 0;
currentChildCache[context.Mobile] = currentChild;
SetResult(context, Result.Running);
if (Children[currentChild] == null)
{
throw new NullReferenceException();
}
Tree.Enqueue(context, Children[currentChild], OnChildComplete);
}
}

View file

@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class Decorator : Behavior
@ -16,14 +10,13 @@ namespace Server.Mobiles.BehaviorAI
{
Child = child;
}
public virtual bool AddChild(Behavior child)
public virtual Decorator AddChild(Behavior child)
{
if (Child == null)
{
Child = child;
return true;
}
return false;
return this;
}
public override void Tick(BehaviorTreeContext context)
{
@ -34,9 +27,9 @@ namespace Server.Mobiles.BehaviorAI
return;
}
}
public virtual void OnChildComplete(BehaviorTreeContext context, Result result)
public virtual void OnChildComplete(BehaviorTreeContext context, Result lastResult)
{
SetResult(context, Child.GetResult(context));
SetResult(context, lastResult);
}
}
}

View file

@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public delegate bool ConditionalLoopPredicate(BehaviorTreeContext context);

View file

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{

View file

@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class ForceSuccess : Decorator

View file

@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class Inverter : Decorator

View file

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{

View file

@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public delegate bool OwnerConditionPredicate(BehaviorTreeContext context);
@ -24,6 +18,7 @@ namespace Server.Mobiles.BehaviorAI
{
base.Tick(context);
}
SetResult(context, Result.Failure);
}
}
}

View file

@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class UntilFail : Decorator

View file

@ -1,8 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Server.Targeting;
namespace Server.Mobiles.BehaviorAI

View file

@ -0,0 +1,22 @@
using Server.Targeting;
namespace Server.Mobiles.BehaviorAI
{
public class CancelTarget : Behavior
{
public CancelTarget(BehaviorTree tree) : base(tree)
{
}
public override void Tick(BehaviorTreeContext context)
{
Target target = context.Mobile.Target;
if (target != null)
{
target.Cancel(context.Mobile, TargetCancelType.Canceled);
}
SetResult(context, Result.Success);
}
}
}

View file

@ -1,8 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Server.Spells;
namespace Server.Mobiles.BehaviorAI
@ -39,7 +34,7 @@ namespace Server.Mobiles.BehaviorAI
if (!spell.Cast())
{
SetResult(context, Result.Running);
SetResult(context, Result.Failure);
owner.DebugSay("Failed to cast...");
return;
}
@ -57,7 +52,7 @@ namespace Server.Mobiles.BehaviorAI
owner.DebugSay("Already casting {0}...", ((Spell)owner.Spell).Name);
return;
}
else if (!owner.Spell.IsCasting)
else if (!owner.Spell.IsCasting && ((Spell)owner.Spell).State == SpellState.Sequencing)
{
SetResult(context, Result.Success);
owner.DebugSay("Finished casting {0}...", ((Spell)owner.Spell).Name);

View file

@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public delegate bool ConditionPredicate(BehaviorTreeContext context);

View file

@ -1,8 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Server.Targeting;
namespace Server.Mobiles.BehaviorAI
@ -10,14 +5,14 @@ namespace Server.Mobiles.BehaviorAI
public delegate object DynamicTargetCallback(BehaviorTreeContext context);
public class DynamicTarget : Behavior
{
public DynamicTargetCallback Transformation { get; private set; }
public DynamicTargetCallback Callback { get; private set; }
public DynamicTarget(BehaviorTree tree, DynamicTargetCallback transformation) : base(tree)
{
Transformation = transformation;
Callback = transformation;
}
public override void Tick(BehaviorTreeContext context)
{
if (Transformation != null)
if (Callback == null)
{
SetResult(context, Result.Failure);
return;
@ -31,7 +26,7 @@ namespace Server.Mobiles.BehaviorAI
return;
}
object targeted = Transformation(context);
object targeted = Callback(context);
if (targeted == null)
{

View file

@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public delegate void TapAction(BehaviorTreeContext context);

View file

@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class WaitForTarget : Behavior

View file

@ -0,0 +1,29 @@
namespace Server.Mobiles.BehaviorAI
{
public class Wander : Behavior
{
public int Range { get; }
public double Chance { get; }
public Wander(BehaviorTree tree) : this(tree, 2)
{
}
public Wander(BehaviorTree tree, int range) : this(tree, range, 1.0)
{
}
public Wander(BehaviorTree tree, int range, double chance) : base(tree)
{
Range = range;
Chance = chance;
}
public override void Tick(BehaviorTreeContext context)
{
if (!context.Mobile.CheckIdle() && Utility.RandomDouble() < Chance)
{
BehaviorTree.WalkRandomInHome(context, Range);
SetResult(context, Result.Success);
return;
}
SetResult(context, Result.Failure);
}
}
}

View file

@ -0,0 +1,37 @@
using Server.Spells;
using Server.Spells.First;
using Server.Spells.Second;
using Server.Spells.Third;
using Server.Spells.Sixth;
namespace Server.Mobiles.BehaviorAI
{
public class MageCombat : OwnerCondition
{
public MageCombat(BehaviorTree tree) : base(tree, canActivate)
{
AddChild(
new Sequence(tree)
.AddChild(new Condition(tree, (context) => context.Mobile.Mana >= 65))
.AddChild(new CastSpell(tree, (context) => new ExplosionSpell(context.Mobile)))
.AddChild(new WaitForTarget(tree))
.AddChild(new AutoTarget(tree))
.AddChild(
new ForceSuccess(tree)
.AddChild(
new Selector(tree)
.AddChild(new OwnerCondition(tree, (context) => Utility.RandomBool(), new CastSpell(tree, (context) => new PoisonSpell(context.Mobile))))
.AddChild(new OwnerCondition(tree, (context) => Utility.RandomBool(), new CastSpell(tree, (context) => new FireballSpell(context.Mobile))))
.AddChild(new CastSpell(tree, (context) => new EnergyBoltSpell(context.Mobile)))
)
)
.AddChild(new WaitForTarget(tree))
.AddChild(new AutoTarget(tree))
);
}
public static bool canActivate(BehaviorTreeContext context)
{
return context.Mobile.Combatant != null;
}
}
}

View file

@ -0,0 +1,14 @@
namespace Server.Mobiles.BehaviorAI
{
public class MagePassive : OwnerCondition
{
public MagePassive(BehaviorTree tree) : base(tree, canActivate)
{
AddChild(new Wander(tree, 2, 0.5));
}
public static bool canActivate(BehaviorTreeContext context)
{
return context.Mobile.Combatant == null;
}
}
}

View file

@ -11,9 +11,9 @@ namespace Server.Mobiles
public class BehaviorMage : BaseCreature
{
[Constructible]
public BehaviorMage() : base(AIType.AI_BehaviorTree, FightMode.Evil, 2, 0, 0.01, 3)
public BehaviorMage() : base(AIType.AI_BehaviorTree, FightMode.Evil, 2, 0, 0.01, 1)
{
Debug = true;
// Debug = true;
InitBody();

View file

@ -52,6 +52,9 @@
<ItemGroup>
<AdditionalFiles Include="Migrations/*.v*.json" />
</ItemGroup>
<ItemGroup>
<Folder Include="Mobiles\BehaviorAI\Sets\" />
</ItemGroup>
<PropertyGroup Condition="'$(RiderVersion)' != '' AND $([MSBuild]::VersionLessThan($(RiderVersion), '2021.2.0'))">
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>