This commit is contained in:
Leath Cooper 2021-09-11 20:12:32 -04:00
parent 0e84739f2d
commit 3f309edffb
25 changed files with 947 additions and 3 deletions

View file

@ -3,17 +3,34 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Server.Mobiles.BT;
// using Server.Mobiles.BT;
using Server.Targeting;
using Server.Mobiles.BehaviorAI;
using Server.Spells.Third;
namespace Server.Mobiles.AI
{
public class BehaviorTreeAI : BaseAI
{
// private Blackboard blackboard;
private Blackboard blackboard;
private BehaviorTree behaviorTree;
private BehaviorTreeContext behaviorTreeContext;
public BehaviorTreeAI(BaseCreature m) : base(m)
{
// blackboard = new Blackboard();
blackboard = new Blackboard();
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))
);
behaviorTree.Start(behaviorTreeContext);
}
public override bool Think()
@ -23,7 +40,10 @@ namespace Server.Mobiles.AI
return false;
}
MageBehaviorTree.Instance.RunBehavior(m_Mobile, blackboard);
// MageBehaviorTree.Instance.RunBehavior(m_Mobile, blackboard);
m_Mobile.DebugSay("Thinking...");
behaviorTree.Tick(behaviorTreeContext);
return true;
}

View file

@ -11,12 +11,14 @@ namespace Server.Mobiles.BT
AddChild(
new SelectorNode(tree)
.AddChild(new TapNode(tree, (mob, board) => { mob.CurrentSpeed = mob.ActiveSpeed; }))
.AddChild(new CooldownNode(tree, TimeSpan.FromSeconds(0.5), new InverterNode(tree, new KeepRangeNode(tree, 1, 2))))
.AddChild(new CooldownNode(tree, TimeSpan.FromSeconds(0.5), new InverterNode(tree, new KeepRangeNode(tree, 1, 3))))
/*
.AddChild(new MageDebuffNode(tree))
.AddChild(new MageComboNode(tree))
.AddChild(new OwnerConditionNode(tree, (mob, board) => mob.Poison != null, new CureSelfNode(tree, TimeSpan.FromSeconds(6.0))))
.AddChild(new OwnerConditionNode(tree, (mob, board) => mob.HitsMax - mob.Hits >= 40, new HealSelfNode(tree, TimeSpan.FromSeconds(6.0))))
.AddChild(new OwnerConditionNode(tree, shouldMeditate, new MeditateNode(tree, 75)))
*/
);
}

View file

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class Selector : Composite
{
public Selector(BehaviorTree tree) : base(tree)
{
}
public override void OnChildComplete(BehaviorTreeContext context, Result lastResult)
{
if (!currentChildCache.TryGetValue(context.Mobile, out int currentChild))
{
currentChild = 0;
currentChildCache.Add(context.Mobile, currentChild);
}
Behavior child = Children[currentChild];
if (lastResult == Result.Success)
{
currentChildCache[context.Mobile] = 0;
SetResult(context, Result.Success);
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);
}
}
}
}

View file

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class Sequence : Composite
{
public Sequence(BehaviorTree tree) : base(tree)
{
}
public override void OnChildComplete(BehaviorTreeContext context, Result lastResult)
{
if (!currentChildCache.TryGetValue(context.Mobile, out int currentChild))
{
currentChild = 0;
currentChildCache.Add(context.Mobile, currentChild);
}
Behavior child = Children[currentChild];
if (lastResult == Result.Failure)
{
currentChildCache[context.Mobile] = 0;
SetResult(context, Result.Failure);
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);
}
}
}
}

View file

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public delegate void BehaviorObserver(BehaviorTreeContext context, Result lastResult);
public enum Result
{
Success,
Failure,
Running
}
public abstract class Behavior
{
public BehaviorTree Tree { get; protected set; }
public BehaviorObserver Observer { get; protected set; }
private Dictionary<BaseCreature, Result> lastResultCache;
public virtual bool IsRunning(BehaviorTreeContext context) => GetResult(context) == Result.Running;
public Behavior(BehaviorTree tree)
{
Tree = tree;
lastResultCache = new Dictionary<BaseCreature, Result>();
}
public virtual void Tick(BehaviorTreeContext context)
{
}
public virtual void Execute(BehaviorTreeContext context)
{
}
public Result GetResult(BehaviorTreeContext context)
{
if (!lastResultCache.TryGetValue(context.Mobile, out Result result))
{
result = Result.Failure;
lastResultCache.Add(context.Mobile, result);
}
return result;
}
public void SetResult(BehaviorTreeContext context, Result result)
{
if (!lastResultCache.TryGetValue(context.Mobile, out Result old))
{
lastResultCache.Add(context.Mobile, Result.Failure);
}
lastResultCache[context.Mobile] = result;
}
}
}

View file

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class BehaviorQueueEntry
{
public BehaviorTreeContext Context { get; }
public BehaviorObserver Observer { get; }
public Behavior Behavior { get; }
public BehaviorQueueEntry(BehaviorTreeContext context, Behavior behavior, BehaviorObserver observer)
{
Context = context;
Behavior = behavior;
Observer = observer;
}
}
}

View file

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class BehaviorTree
{
public Behavior Root { get; private set; }
private Dictionary<BaseCreature, Queue<BehaviorQueueEntry>> behaviorQueueCache;
private Dictionary<BaseCreature, bool> executingCache;
public BehaviorTree()
{
behaviorQueueCache = new Dictionary<BaseCreature, Queue<BehaviorQueueEntry>>();
executingCache = new Dictionary<BaseCreature, bool>();
}
public bool TryAddRoot(Composite behavior)
{
if (Root == null)
{
Root = behavior;
return true;
}
return false;
}
public void Start(BehaviorTreeContext context)
{
if (Root != null)
{
Enqueue(context, Root, ExecutionFinished);
}
}
public void Stop(BehaviorTreeContext context)
{
getQueue(context).Clear();
}
public virtual void Tick(BehaviorTreeContext context)
{
if (!executingCache.TryGetValue(context.Mobile, out bool executing))
{
executing = false;
executingCache[context.Mobile] = executing;
}
if (!executing)
{
executingCache[context.Mobile] = true;
Queue<BehaviorQueueEntry> queue = getQueue(context);
queue.Enqueue(null);
while (Step(context))
{
}
executingCache[context.Mobile] = false;
}
}
public virtual bool Step(BehaviorTreeContext context)
{
Queue<BehaviorQueueEntry> queue = getQueue(context);
BehaviorQueueEntry current = queue.Dequeue();
if (current == null || current.Behavior == null || current.Context == null)
{
return false;
}
current.Behavior.Tick(current.Context);
if (!current.Behavior.IsRunning(current.Context))
{
if (current.Observer != null)
{
current.Observer(current.Context, current.Behavior.GetResult(current.Context));
}
return true;
}
queue.Enqueue(current);
return false;
}
public virtual void ExecutionFinished(BehaviorTreeContext context, Result result)
{
}
public void Enqueue(BehaviorTreeContext context, Behavior behavior, BehaviorObserver observer)
{
if (!behaviorQueueCache.TryGetValue(context.Mobile, out Queue<BehaviorQueueEntry> queue))
{
queue = new Queue<BehaviorQueueEntry>();
behaviorQueueCache.Add(context.Mobile, queue);
}
queue.Enqueue(new BehaviorQueueEntry(context, behavior, observer));
}
private Queue<BehaviorQueueEntry> getQueue(BehaviorTreeContext context)
{
if (!behaviorQueueCache.TryGetValue(context.Mobile, out Queue<BehaviorQueueEntry> queue))
{
queue = new Queue<BehaviorQueueEntry>();
behaviorQueueCache.Add(context.Mobile, queue);
}
return queue;
}
}
}

View file

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class BehaviorTreeContext
{
public BaseCreature Mobile { get; }
public Blackboard Blackboard { get; }
public BehaviorTreeContext(BaseCreature mob, Blackboard blackboard)
{
Mobile = mob;
Blackboard = blackboard;
}
}
}

View file

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class Blackboard : Dictionary<string, object>
{
}
}

View file

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class Composite : Behavior
{
public List<Behavior> Children { get; private set; }
protected Dictionary<BaseCreature, int> currentChildCache;
public Composite(BehaviorTree tree) : base(tree)
{
Children = new List<Behavior>();
currentChildCache = new Dictionary<BaseCreature, int>();
}
public Composite AddChild(Behavior child)
{
if (Children != null && child != null)
{
Children.Add(child);
}
return this;
}
public override void Tick(BehaviorTreeContext context)
{
if (!currentChildCache.TryGetValue(context.Mobile, out int currentChild))
{
currentChild = 0;
currentChildCache.Add(context.Mobile, currentChild);
}
if (GetResult(context) != Result.Running)
{
currentChild = 0;
currentChildCache[context.Mobile] = currentChild;
SetResult(context, Result.Running);
if (Children[currentChild] == null)
{
throw new NullReferenceException();
}
Tree.Enqueue(context, Children[currentChild], OnChildComplete);
}
}
public virtual void OnChildComplete(BehaviorTreeContext context, Result result)
{
SetResult(context, Result.Failure);
}
}
}

View file

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class Decorator : Behavior
{
public Behavior Child { get; private set; }
public Decorator(BehaviorTree tree) : base(tree)
{
}
public Decorator(BehaviorTree tree, Behavior child) : base(tree)
{
Child = child;
}
public virtual bool AddChild(Behavior child)
{
if (Child == null)
{
Child = child;
return true;
}
return false;
}
public override void Tick(BehaviorTreeContext context)
{
if (Child != null && GetResult(context) != Result.Running)
{
Tree.Enqueue(context, Child, OnChildComplete);
SetResult(context, Result.Running);
return;
}
}
public virtual void OnChildComplete(BehaviorTreeContext context, Result result)
{
SetResult(context, Child.GetResult(context));
}
}
}

View file

@ -0,0 +1,41 @@
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);
public class ConditionalLoop : Decorator
{
public ConditionalLoopPredicate Predicate { get; private set; }
public ConditionalLoop(BehaviorTree tree, ConditionalLoopPredicate predicate) : base(tree)
{
Predicate = predicate;
}
public ConditionalLoop(BehaviorTree tree, ConditionalLoopPredicate predicate, Behavior child) : base(tree, child)
{
Predicate = predicate;
}
public override void Tick(BehaviorTreeContext context)
{
if (Predicate(context))
{
base.Tick(context);
return;
}
SetResult(context, Result.Failure);
return;
}
public override void OnChildComplete(BehaviorTreeContext context, Result result)
{
if (Predicate(context))
{
Tree.Enqueue(context, Child, OnChildComplete);
return;
}
SetResult(context, Result.Success);
}
}
}

View file

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class Cooldown : Decorator
{
public TimeSpan Duration { get; }
private Dictionary<BaseCreature, long> nextCooldownCache;
public Cooldown(BehaviorTree tree, TimeSpan duration) : base(tree)
{
nextCooldownCache = new Dictionary<BaseCreature, long>();
Duration = duration;
}
public Cooldown(BehaviorTree tree, TimeSpan duration, Behavior child) : base(tree, child)
{
nextCooldownCache = new Dictionary<BaseCreature, long>();
Duration = duration;
}
public override void Tick(BehaviorTreeContext context)
{
if(!nextCooldownCache.TryGetValue(context.Mobile, out long nextCooldown))
{
nextCooldown = Core.TickCount;
nextCooldownCache.Add(context.Mobile, nextCooldown);
}
if (Core.TickCount > nextCooldown)
{
base.Tick(context);
}
}
public override void OnChildComplete(BehaviorTreeContext context, Result result)
{
if (!nextCooldownCache.TryGetValue(context.Mobile, out long nextCooldown))
{
nextCooldown = Core.TickCount;
nextCooldownCache.Add(context.Mobile, nextCooldown);
}
if (Child != null)
{
if (Child.GetResult(context) == Result.Success)
{
nextCooldownCache[context.Mobile] = Core.TickCount + Duration.Ticks;
}
SetResult(context, Child.GetResult(context));
return;
}
SetResult(context, Result.Failure);
}
}
}

View file

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class ForceSuccess : Decorator
{
public ForceSuccess(BehaviorTree tree) : base(tree)
{
}
public ForceSuccess(BehaviorTree tree, Behavior child) : base(tree, child)
{
}
public override void OnChildComplete(BehaviorTreeContext context, Result result)
{
SetResult(context, Result.Success);
}
}
}

View file

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class Inverter : Decorator
{
public Inverter(BehaviorTree tree) : base(tree)
{
}
public Inverter(BehaviorTree tree, Behavior child) : base(tree, child)
{
}
public override void OnChildComplete(BehaviorTreeContext context, Result result)
{
if (result == Result.Success)
{
SetResult(context, Result.Failure);
}
else
{
SetResult(context, Result.Success);
}
}
}
}

View file

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class Loop : Decorator
{
private Dictionary<BaseCreature, int> currentIterationCache;
private int totalIterations;
public Loop(BehaviorTree tree, int n) : base(tree)
{
currentIterationCache = new Dictionary<BaseCreature, int>();
totalIterations = n;
}
public override void Tick(BehaviorTreeContext context)
{
if (!currentIterationCache.TryGetValue(context.Mobile, out int iteration))
{
iteration = 0;
currentIterationCache.Add(context.Mobile, iteration);
}
base.Tick(context);
}
public override void OnChildComplete(BehaviorTreeContext context, Result result)
{
if (!currentIterationCache.TryGetValue(context.Mobile, out int iteration))
{
iteration = 0;
currentIterationCache.Add(context.Mobile, iteration);
}
iteration++;
if (iteration >= totalIterations)
{
currentIterationCache[context.Mobile] = 0;
SetResult(context, Result.Success);
return;
}
currentIterationCache[context.Mobile] = iteration;
if (Child != null)
{
Tree.Enqueue(context, Child, OnChildComplete);
return;
}
currentIterationCache[context.Mobile] = 0;
SetResult(context, Result.Failure);
}
}
}

View file

@ -0,0 +1,29 @@
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);
public class OwnerCondition : Decorator
{
public OwnerConditionPredicate Predicate { get; }
public OwnerCondition(BehaviorTree tree, OwnerConditionPredicate predicate) : base(tree)
{
Predicate = predicate;
}
public OwnerCondition(BehaviorTree tree, OwnerConditionPredicate predicate, Behavior child) : base(tree, child)
{
Predicate = predicate;
}
public override void Tick(BehaviorTreeContext context)
{
if (Predicate(context))
{
base.Tick(context);
}
}
}
}

View file

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class UntilFail : Decorator
{
public UntilFail(BehaviorTree tree): base(tree)
{
}
public UntilFail(BehaviorTree tree, Behavior child) : base(tree, child)
{
}
public override void OnChildComplete(BehaviorTreeContext context, Result result)
{
if (Child != null)
{
if (Child.GetResult(context) == Result.Failure)
{
SetResult(context, Result.Success);
return;
}
Tree.Enqueue(context, Child, OnChildComplete);
}
}
}
}

View file

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Server.Targeting;
namespace Server.Mobiles.BehaviorAI
{
public class AutoTarget : Behavior
{
public AutoTarget(BehaviorTree tree) : base(tree)
{
}
public override void Tick(BehaviorTreeContext context)
{
Target target = context.Mobile.Target;
if (target == null)
{
SetResult(context, Result.Failure);
return;
}
Mobile combatant = context.Mobile.Combatant;
if ((target.Flags & TargetFlags.Harmful) != 0 && combatant != null)
{
if (combatant.Deleted)
{
target.Cancel(context.Mobile, TargetCancelType.Canceled);
SetResult(context, Result.Failure);
return;
}
if ((target.Range == -1 || context.Mobile.InRange(combatant, target.Range)) &&
context.Mobile.CanSee(combatant) &&
context.Mobile.InLOS(combatant)
)
{
target.Invoke(context.Mobile, combatant);
SetResult(context, Result.Success);
return;
}
}
else if ((target.Flags & TargetFlags.Beneficial) != 0)
{
target.Invoke(context.Mobile, context.Mobile);
SetResult(context, Result.Success);
return;
}
SetResult(context, Result.Failure);
}
}
}

View file

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Server.Spells;
namespace Server.Mobiles.BehaviorAI
{
public delegate Spell CastSpellCallback(BehaviorTreeContext context);
public class CastSpell : Behavior
{
public CastSpellCallback Callback { get; }
public CastSpell(BehaviorTree tree, CastSpellCallback callback) : base(tree)
{
Callback = callback;
}
public override void Tick(BehaviorTreeContext context)
{
BaseCreature owner = context.Mobile;
if (owner.Spell == null)
{
Spell spell = Callback(context);
if (owner.Mana < spell.GetMana())
{
SetResult(context, Result.Failure);
owner.DebugSay("Not enough mana...");
return;
}
if (Core.TickCount < owner.NextSpellTime)
{
SetResult(context, Result.Running);
owner.DebugSay("On cooldown...");
return;
}
if (!spell.Cast())
{
SetResult(context, Result.Running);
owner.DebugSay("Failed to cast...");
return;
}
owner.DebugSay("Casting {0}...", spell.Name);
if (!string.IsNullOrEmpty(spell.Mantra))
owner.PublicOverheadMessage(Network.MessageType.Spell, owner.SpeechHue, false, spell.Mantra, false);
SetResult(context, Result.Running);
return;
}
else if (owner.Spell.IsCasting)
{
SetResult(context, Result.Running);
owner.DebugSay("Already casting {0}...", ((Spell)owner.Spell).Name);
return;
}
else if (!owner.Spell.IsCasting)
{
SetResult(context, Result.Success);
owner.DebugSay("Finished casting {0}...", ((Spell)owner.Spell).Name);
return;
}
SetResult(context, Result.Failure);
}
}
}

View file

@ -0,0 +1,29 @@
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);
public class Condition : Behavior
{
public ConditionPredicate Predicate { get; }
public Condition(BehaviorTree tree, ConditionPredicate predicate) : base(tree)
{
Predicate = predicate;
}
public override void Tick(BehaviorTreeContext context)
{
if(Predicate(context))
{
SetResult(context, Result.Success);
}
else
{
SetResult(context, Result.Failure);
}
}
}
}

View file

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Server.Targeting;
namespace Server.Mobiles.BehaviorAI
{
public delegate object DynamicTargetCallback(BehaviorTreeContext context);
public class DynamicTarget : Behavior
{
public DynamicTargetCallback Transformation { get; private set; }
public DynamicTarget(BehaviorTree tree, DynamicTargetCallback transformation) : base(tree)
{
Transformation = transformation;
}
public override void Tick(BehaviorTreeContext context)
{
if (Transformation != null)
{
SetResult(context, Result.Failure);
return;
}
Target target = context.Mobile.Target;
if (target == null)
{
SetResult(context, Result.Failure);
return;
}
object targeted = Transformation(context);
if (targeted == null)
{
SetResult(context, Result.Failure);
return;
}
target.Invoke(context.Mobile, targeted);
SetResult(context, Result.Success);
}
}
}

View file

@ -0,0 +1,23 @@
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);
public class Tap : Behavior
{
public TapAction Action { get; private set; }
public Tap(BehaviorTree tree, TapAction action) : base(tree)
{
Action = action;
}
public override void Tick(BehaviorTreeContext context)
{
Action(context);
SetResult(context, Result.Failure);
}
}
}

View file

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorAI
{
public class WaitForTarget : Behavior
{
public WaitForTarget(BehaviorTree tree) : base(tree)
{
}
public override void Tick(BehaviorTreeContext context)
{
if (context.Mobile.Target != null)
{
SetResult(context, Result.Success);
return;
}
SetResult(context, Result.Running);
}
}
}

View file

@ -13,6 +13,8 @@ namespace Server.Mobiles
[Constructible]
public BehaviorMage() : base(AIType.AI_BehaviorTree, FightMode.Evil, 2, 0, 0.01, 3)
{
Debug = true;
InitBody();
SetStr(100, 100);
@ -32,6 +34,7 @@ namespace Server.Mobiles
SetSkill(SkillName.MagicResist, 100.0);
SetSkill(SkillName.Tactics, 100.0);
SetSkill(SkillName.Meditation, 100.0);
SetSkill(SkillName.Wrestling, 100.0);
AddItem(new Backpack() { Movable = false });