behavior tree initial commit

This commit is contained in:
Leath Cooper 2021-09-01 01:04:48 -04:00
parent e7e0151b38
commit 53f51d6bde
25 changed files with 1273 additions and 3 deletions

View file

@ -13,6 +13,7 @@ using Server.Spells;
using Server.Spells.Spellweaving;
using Server.Targets;
using MoveImpl = Server.Movement.MovementImpl;
using Server.Mobiles.BehaviorTreeAI;
namespace Server.Mobiles
{
@ -27,7 +28,8 @@ namespace Server.Mobiles
AI_Mage,
AI_Berserk,
AI_Predator,
AI_Thief
AI_Thief,
AI_BehaviorTree
}
public enum ActionType
@ -136,6 +138,21 @@ namespace Server.Mobiles
}
Action = ActionType.Wander;
/*
behaviorTree = new BehaviorTree(this.m_Mobile);
behaviorTree.AddRoot(
new RepeaterNode(behaviorTree,
new SelectorNode(behaviorTree, new BehaviorTreeNode[1]
{
new SelfHealerNode(behaviorTree),
})
)
);
behaviorTree.Start();
*/
}
public ActionType Action
@ -2019,7 +2036,7 @@ namespace Server.Mobiles
if (canOpenDoors || canDestroyObstacles)
{
m_Mobile.DebugSay("My movement was blocked, I will try to clear some obstacles.");
// m_Mobile.DebugSay("My movement was blocked, I will try to clear some obstacles.");
var map = m_Mobile.Map;
@ -2445,7 +2462,7 @@ namespace Server.Mobiles
m_Mobile.NextReacquireTime = Core.TickCount + (int)m_Mobile.ReacquireDelay.TotalMilliseconds;
m_Mobile.DebugSay("Acquiring...");
// m_Mobile.DebugSay("Acquiring...");
var map = m_Mobile.Map;
@ -3090,6 +3107,7 @@ namespace Server.Mobiles
}
}
/*
if (m_Owner.CanDetectHidden && Core.TickCount - m_Owner.m_NextDetectHidden >= 0)
{
m_Owner.DetectHidden();
@ -3103,6 +3121,7 @@ namespace Server.Mobiles
m_Owner.m_NextDetectHidden = Core.TickCount +
(int)TimeSpan.FromSeconds(Utility.RandomMinMax(min, max)).TotalMilliseconds;
}
*/
}
}
}

View file

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Server.Mobiles.BehaviorTreeAI;
using Server.Targeting;
namespace Server.Mobiles.AI
{
public class BehaviorTreeAI : BaseAI
{
private BehaviorTree behaviorTree;
public BehaviorTreeAI(BaseCreature m) : base(m)
{
behaviorTree = new BehaviorTree(m);
behaviorTree.AddRoot(
new SelectorNode(behaviorTree, new BehaviorTreeNode[2]
{
new SelfHealerNode(behaviorTree),
// new MageComboNode(behaviorTree),
new TapNode(
behaviorTree,
(mob) => mob.DebugSay("I am taking no action")
)
})
);
behaviorTree.Start();
}
public override bool Think()
{
if (m_Mobile.Deleted)
{
return false;
}
// behaviorTree.RunBehavior();
return base.Think();
}
}
}

View file

@ -106,12 +106,14 @@ namespace Server.Mobiles
base.DoActionWander();
/*
if (Utility.RandomDouble() < 0.05)
{
var spell = CheckCastHealingSpell();
spell?.Cast();
}
*/
}
return true;
@ -120,10 +122,12 @@ namespace Server.Mobiles
private Spell CheckCastHealingSpell()
{
// If I'm poisoned, always attempt to cure.
/*
if (m_Mobile.Poisoned)
{
return new CureSpell(m_Mobile);
}
*/
// Summoned creatures never heal themselves.
if (m_Mobile.Summoned)
@ -156,6 +160,7 @@ namespace Server.Mobiles
Spell spell = null;
/*
if (m_Mobile.Hits < m_Mobile.HitsMax - 50)
{
if (UseNecromancy())
@ -171,6 +176,7 @@ namespace Server.Mobiles
{
spell = new HealSpell(m_Mobile);
}
*/
double delay;
@ -775,6 +781,7 @@ namespace Server.Mobiles
Spell spell;
var toDispel = FindDispelTarget(true);
/*
if (m_Mobile.Poisoned) // Top cast priority is cure
{
m_Mobile.DebugSay("I am going to cure myself");
@ -782,6 +789,8 @@ namespace Server.Mobiles
spell = new CureSpell(m_Mobile);
}
else if (toDispel != null) // Something dispellable is attacking us
*/
if (toDispel != null)
{
m_Mobile.DebugSay("I am going to dispel {0}", toDispel);
@ -901,10 +910,12 @@ namespace Server.Mobiles
RunFrom(m_Mobile.FocusMob);
m_Mobile.FocusMob = null;
/*
if (m_Mobile.Poisoned && Utility.Random(0, 5) == 0)
{
new CureSpell(m_Mobile).Cast();
}
*/
}
else
{

View file

@ -2187,6 +2187,7 @@ namespace Server.Mobiles
// m_AI = new PredatorAI(this);
new MeleeAI(this),
AIType.AI_Thief => new ThiefAI(this),
AIType.AI_BehaviorTree => new AI.BehaviorTreeAI(this),
_ => null
};
}

View file

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorTreeAI
{
public abstract class ActionNode : LeafNode
{
public ActionNode(BehaviorTree tree) : base(tree)
{
}
}
}

View file

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Server.Spells;
namespace Server.Mobiles.BehaviorTreeAI
{
public class CastSpellActionNode : ActionNode
{
private Func<BaseCreature, Spell> getSpellCallback;
public CastSpellActionNode(BehaviorTree tree, Func<BaseCreature, Spell> callback) : base(tree)
{
object nextCastTime;
if (!Tree.Blackboard.TryGetValue("nextCastTime", out nextCastTime))
{
tree.Blackboard.Add("nextCastTime", DateTime.Now);
}
getSpellCallback = callback;
}
public override Result Execute()
{
object nextCastTime;
if (Tree.Blackboard.TryGetValue("nextCastTime", out nextCastTime) && DateTime.Now < (DateTime)nextCastTime)
{
Tree.Mobile.DebugSay("Spells are on cooldown...");
return Result.Failure;
}
if (Tree.Mobile.Spell == null)
{
Spell spell = getSpellCallback(Tree.Mobile);
Tree.Mobile.DebugSay("Casting...");
if (!spell.Cast())
{
Tree.Mobile.DebugSay("Failed to cast...");
return Result.Failure;
}
if (!string.IsNullOrEmpty(spell.Mantra))
Tree.Mobile.PublicOverheadMessage(Network.MessageType.Spell, 0, false, spell.Mantra, false);
return Result.Running;
}
else if (Tree.Mobile.Spell.IsCasting)
{
Tree.Mobile.DebugSay("Still casting...");
return Result.Running;
}
else if (!Tree.Mobile.Spell.IsCasting)
{
Tree.Mobile.DebugSay("Finished casting...");
return Result.Success;
}
return Result.Failure;
}
}
}

View file

@ -0,0 +1,105 @@
using System;
using Server.Spells;
using Server.Spells.Second;
using System.Collections.Generic;
namespace Server.Mobiles.BehaviorTreeAI
{
public class CureSelfNode : SequenceNode
{
public CureSelfNode(BehaviorTree tree) : base(tree)
{
Children = new List<BehaviorTreeNode>(new BehaviorTreeNode[5]
{
new ConditionNode(
tree,
(mob) =>
{
if (mob.Poison == null)
{
mob.DebugSay("I am not poisoned");
return false;
}
object nextHealTime;
if (Tree.Blackboard.TryGetValue("nextHealTime", out nextHealTime))
{
if (DateTime.Now >= (DateTime)nextHealTime)
{
return true;
}
mob.DebugSay("My heal cooldown is active");
}
mob.DebugSay("My heal cooldown is not set");
return false;
}
),
new ConditionNode(
tree,
(mob) =>
{
return mob.Spell == null || !mob.Spell.IsCasting;
}
),
new CastSpellActionNode(
tree,
(mob) => new CureSpell(mob)
),
new AlwaysSucceedNode(
tree,
new TapNode(
tree,
(mob) =>
{
Tree.Blackboard.Remove("nextHealTime");
Tree.Blackboard.Add("nextHealTime", DateTime.Now + TimeSpan.FromSeconds(10.0));
}
)
),
new TargetActionNode(tree),
});
}
}
/*
public class CureSelfActionNode : ActionNode
{
public TimeSpan Cooldown { get; private set; }
public CureSelfActionNode(BehaviorTree tree, TimeSpan cooldown) : base(tree)
{
object nextHealTime;
if (!Tree.Blackboard.TryGetValue("nextHealTime", out nextHealTime))
{
tree.Blackboard.Add("nextHealTime", DateTime.Now);
}
Cooldown = cooldown;
}
public override Result Execute()
{
object nextHealTime;
if (Tree.Mobile.Poison != null && Tree.Blackboard.TryGetValue("nextHealTime", out nextHealTime))
{
if (DateTime.Now >= (DateTime)nextHealTime)
{
new CureSpell(Tree.Mobile).Cast();
Tree.Mobile.DebugSay("Casting cure spell for myself");
Tree.Blackboard.Remove("nextHealTime");
Tree.Blackboard.Add("nextHealTime", DateTime.Now + Cooldown);
return Result.Success;
}
Tree.Mobile.DebugSay("I'm poisoned, but my healing is on cooldown");
return Result.Failure;
}
Tree.Mobile.DebugSay("I'm not poisoned");
return Result.Failure;
}
}
*/
}

View file

@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorTreeAI
{
class DetectHiddenActionNode : BehaviorTreeNode
{
public TimeSpan Cooldown { get; private set; }
public DetectHiddenActionNode(BehaviorTree tree, TimeSpan cooldown) : base(tree)
{
object nextDetectTime;
if (!Tree.Blackboard.TryGetValue("nextDetectTime", out nextDetectTime))
{
tree.Blackboard.Add("nextDetectTime", DateTime.Now);
}
Cooldown = cooldown;
}
public override Result Execute()
{
/*
if (m_Mobile.Deleted || m_Mobile.Map == null)
{
return;
}
m_Mobile.DebugSay("Checking for hidden players");
var srcSkill = m_Mobile.Skills.DetectHidden.Value;
if (srcSkill <= 0)
{
return;
}
var eable = m_Mobile.GetMobilesInRange(m_Mobile.RangePerception);
foreach (var trg in eable)
{
if (trg != m_Mobile && trg.Player && trg.Alive && trg.Hidden && trg.AccessLevel == AccessLevel.Player &&
m_Mobile.InLOS(trg))
{
m_Mobile.DebugSay("Trying to detect {0}", trg.Name);
var trgHiding = trg.Skills.Hiding.Value / 2.9;
var trgStealth = trg.Skills.Stealth.Value / 1.8;
var chance = srcSkill / 1.2 - Math.Min(trgHiding, trgStealth);
if (chance < srcSkill / 10)
{
chance = srcSkill / 10;
}
chance /= 100;
if (chance > Utility.RandomDouble())
{
trg.RevealingAction();
trg.SendLocalizedMessage(500814); // You have been revealed!
}
}
}
eable.Free();
*/
Tree.Mobile.DebugSay("DetectHiddenActionNode");
object nextDetectTime;
if (Tree.Blackboard.TryGetValue("nextDetectTime", out nextDetectTime))
{
if (DateTime.Now >= (DateTime)nextDetectTime)
{
var srcSkill = Tree.Mobile.Skills.DetectHidden.Value;
var eable = Tree.Mobile.GetMobilesInRange(Tree.Mobile.RangePerception);
foreach (var trg in eable)
{
if (trg != Tree.Mobile && trg.Player && trg.Alive && trg.Hidden && trg.AccessLevel == AccessLevel.Player &&
Tree.Mobile.InLOS(trg))
{
Tree.Mobile.DebugSay("DetectHiddenActionNode {0}", trg.Name);
var trgHiding = trg.Skills.Hiding.Value / 2.9;
var trgStealth = trg.Skills.Stealth.Value / 1.8;
var chance = srcSkill / 1.2 - Math.Min(trgHiding, trgStealth);
if (chance < srcSkill / 10)
{
chance = srcSkill / 10;
}
chance /= 100;
if (chance > Utility.RandomDouble())
{
trg.RevealingAction();
trg.SendLocalizedMessage(500814); // You have been revealed!
}
}
}
return Result.Success;
}
Tree.Mobile.DebugSay("DetectHiddenActionNode Cooldown");
}
return Result.Failure;
}
}
}

View file

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Server.Spells.First;
using Server.Spells.Fourth;
namespace Server.Mobiles.BehaviorTreeAI
{
public class HealSelfActionNode : BehaviorTreeNode
{
public TimeSpan Cooldown { get; private set; }
public HealSelfActionNode(BehaviorTree tree, TimeSpan cooldown) : base(tree)
{
object nextHealTime;
if (!Tree.Blackboard.TryGetValue("nextHealTime", out nextHealTime))
{
tree.Blackboard.Add("nextHealTime", DateTime.Now);
}
Cooldown = cooldown;
}
public override Result Execute()
{
object nextHealTime;
if (Tree.Mobile.Hits < Tree.Mobile.HitsMax && Tree.Blackboard.TryGetValue("nextHealTime", out nextHealTime))
{
if (DateTime.Now >= (DateTime)nextHealTime)
{
if (Tree.Mobile.Hits < Tree.Mobile.HitsMax - 50)
{
new GreaterHealSpell(Tree.Mobile).Cast();
Tree.Mobile.DebugSay("Casting greater heal for myself");
}
else
{
new HealSpell(Tree.Mobile).Cast();
Tree.Mobile.DebugSay("Casting heal spell for myself");
}
Tree.Blackboard.Remove("nextHealTime");
Tree.Blackboard.Add("nextHealTime", DateTime.Now + Cooldown);
return Result.Success;
}
Tree.Mobile.DebugSay("I'm hurt, but my healing is on cooldown {0}s", ((DateTime)nextHealTime - DateTime.Now).TotalSeconds);
return Result.Failure;
}
Tree.Mobile.DebugSay("I'm at full health");
return Result.Failure;
}
}
}

View file

@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Server.Spells.Sixth;
using Server.Spells.Fifth;
using Server.Spells.First;
namespace Server.Mobiles.BehaviorTreeAI
{
public class MageComboNode : SequenceNode
{
public MageComboNode(BehaviorTree tree) : base(tree)
{
/*
Children = new List<BehaviorTreeNode>(new BehaviorTreeNode[1]
{
// if we have a combatant
new ConditionNode(
tree,
mob => mob.Combatant != null,
)
new ConditionNode(
tree,
mob => mob.Combatant != null,
// do the following in sequence
new SelectorNode(
tree,
new BehaviorTreeNode[2]
{
// do the first appropriate debuff until all are done
new SelectorNode(
tree,
new BehaviorTreeNode[3]
{
// weaken if necessary
new ConditionNode(
tree,
mob => mob.Combatant.Str == mob.Combatant.RawStr,
new SequenceNode(
tree,
new BehaviorTreeNode[2]
{
new CastSpellActionNode(
tree,
mob => new WeakenSpell(mob)
),
new TargetActionNode(tree)
}
)
),
// clumbsy if necessary
new ConditionNode(
tree,
mob => mob.Combatant.Dex == mob.Combatant.RawDex,
new SequenceNode(
tree,
new BehaviorTreeNode[2]
{
new CastSpellActionNode(
tree,
mob => new ClumsySpell(mob)
),
new TargetActionNode(tree)
}
)
),
// feeblemind if necessary
new ConditionNode(
tree,
mob => mob.Combatant.Int == mob.Combatant.RawInt,
new SequenceNode(
tree,
new BehaviorTreeNode[2]
{
new CastSpellActionNode(
tree,
mob => new FeeblemindSpell(mob)
),
new TargetActionNode(tree)
}
)
),
}
),
// do explosion ebolt combo
new SequenceNode(
tree,
new BehaviorTreeNode[2]
{
// explosion
new SequenceNode(
tree,
new BehaviorTreeNode[2]
{
new CastSpellActionNode(
tree,
mob => new ExplosionSpell(mob)
),
new TargetActionNode(tree)
}
),
// ebolt
new SequenceNode(
tree,
new BehaviorTreeNode[2]
{
new CastSpellActionNode(
tree,
mob => new EnergyBoltSpell(mob)
),
new TargetActionNode(tree)
}
)
}
),
}
)
)
});
*/
/*
Children = new List<BehaviorTreeNode>(new BehaviorTreeNode[1] {
new ConditionNode(
tree,
mob => mob.Combatant != null,
new SequenceNode(
tree,
new BehaviorTreeNode[3] {
new AlwaysSucceedNode(
tree,
new ConditionNode(
tree,
mob => mob.Combatant != null && mob.Combatant.Str == mob.Combatant.RawStr,
new SequenceNode(
tree,
new BehaviorTreeNode[2]
{
new CastSpellActionNode(
tree,
mob => new WeakenSpell(mob)
),
new TargetActionNode(tree)
}
)
)
),
new SequenceNode(tree, new BehaviorTreeNode[2]{
new CastSpellActionNode(
tree,
mob => new ExplosionSpell(mob)
),
new TargetActionNode(tree)
}),
new SequenceNode(tree, new BehaviorTreeNode[2]{
new SelectorNode(
tree,
new BehaviorTreeNode[2]
{
new ConditionNode(
tree,
mob => Utility.RandomDouble() < 0.3,
new CastSpellActionNode(
tree,
mob => new MindBlastSpell(mob)
)
),
new CastSpellActionNode(
tree,
mob => new EnergyBoltSpell(mob)
)
}
),
new TargetActionNode(tree)
}),
}
)
)
});
*/
}
}
}

View file

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
namespace Server.Mobiles.BehaviorTreeAI
{
class SelfHealerNode : SelectorNode
{
public SelfHealerNode(BehaviorTree tree) : base(tree)
{
Children = new List<BehaviorTreeNode>(new BehaviorTreeNode[1]
{
new SequenceNode(
tree,
new BehaviorTreeNode[2]
{
new ConditionNode(
tree,
mob => true // Utility.RandomDouble() < 0.5
),
new CureSelfNode(tree),
}
)
/*
new SequenceNode(
tree,
new BehaviorTreeNode[2]
{
new ConditionNode(
tree,
mob => mob.Hits < mob.HitsMax - 20
),
new SequenceNode(
tree,
new BehaviorTreeNode[3]
{
new ConditionNode(
tree,
mob => true // Utility.RandomDouble() < 0.2
),
new HealSelfActionNode(tree, TimeSpan.FromSeconds(10.0)),
new TargetActionNode(tree)
}
)
}
)
*/
});
}
}
}

View file

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Server.Spells;
using Server.Targeting;
namespace Server.Mobiles.BehaviorTreeAI
{
public class TargetActionNode : ActionNode
{
public TargetActionNode(BehaviorTree tree) : base(tree)
{
}
public override Result Execute()
{
Target target = Tree.Mobile.Target;
if (target == null)
{
Tree.Mobile.DebugSay("Waiting for target...");
return Result.Running;
}
Mobile mobTarget = Tree.Mobile.Combatant;
if ((target.Flags & TargetFlags.Harmful) != 0 && mobTarget != null)
{
if (mobTarget.Deleted || !mobTarget.Alive)
{
Tree.Mobile.DebugSay("Canceling my target because my target is dead or does not exist");
target.Cancel(Tree.Mobile, TargetCancelType.Canceled);
return Result.Success;
}
if ((target.Range == -1 ||
Tree.Mobile.InRange(mobTarget, target.Range)) &&
Tree.Mobile.CanSee(mobTarget) &&
Tree.Mobile.InLOS(mobTarget)
)
{
Tree.Mobile.DebugSay("Targeting my combatant");
target.Invoke(Tree.Mobile, mobTarget);
return Result.Success;
}
}
else if ((target.Flags & TargetFlags.Beneficial) != 0)
{
Tree.Mobile.DebugSay("Targeting myself");
target.Invoke(Tree.Mobile, Tree.Mobile);
return Result.Success;
}
return Result.Failure;
}
}
}

View file

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorTreeAI
{
public class AlwaysSucceedNode : DecoratorNode
{
public AlwaysSucceedNode(BehaviorTree tree, BehaviorTreeNode child) : base(tree, child)
{
}
public AlwaysSucceedNode(BehaviorTree tree) : base(tree)
{
}
public override Result Execute()
{
if (Child != null)
{
Child.Execute();
}
return Result.Success;
}
}
}

View file

@ -0,0 +1,207 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorTreeAI
{
public class BehaviorTree
{
/*
public static BehaviorTree Instance;
private static BehaviorTreeTimer timer;
public static void Initialize()
{
Instance = new BehaviorTree();
var seq = new SequenceNode(Instance, new BehaviorTreeNode[3]{
new ActionNode(Instance, () => { Console.WriteLine("ActionNode1"); return BehaviorTreeNode.Result.Success; }),
new ActionNode(Instance, () => { Console.WriteLine("ActionNode2"); return BehaviorTreeNode.Result.Success; }),
new SequenceNode(Instance, new BehaviorTreeNode[4]{
new ActionNode(Instance, () => { Console.WriteLine("ActionNode3"); return BehaviorTreeNode.Result.Success; }),
new ActionNode(Instance, () => { Console.WriteLine("ActionNode4"); return BehaviorTreeNode.Result.Success; }),
new ActionNode(Instance, () => { Console.WriteLine("ActionNode5"); return BehaviorTreeNode.Result.Success; }),
new SelectorNode(Instance, new BehaviorTreeNode[3]
{
new ActionNode(Instance, () => { Console.WriteLine("ActionNode6"); return BehaviorTreeNode.Result.Failure; }),
new ActionNode(Instance, () => { Console.WriteLine("ActionNode7"); return BehaviorTreeNode.Result.Success; }),
new ActionNode(Instance, () => { Console.WriteLine("ActionNode8"); return BehaviorTreeNode.Result.Success; })
})
}),
});
Instance.AddRoot(
new RepeaterNode(Instance, seq)
);
Instance.Start();
}
*/
private BehaviorTreeNode root;
public Dictionary<string, object> Blackboard { get; set; }
public BehaviorTreeNode Root { get { return root; } }
private BehaviorTreeTimer treeTimer;
public BaseCreature Mobile { get; private set; }
public BehaviorTree(BaseCreature mob)
{
Blackboard = new Dictionary<string, object>();
treeTimer = new BehaviorTreeTimer(this);
Mobile = mob;
}
public void AddRoot(BehaviorTreeNode rootNode)
{
if (root == null)
{
root = rootNode;
}
}
public void Start()
{
treeTimer.Start();
}
public void Stop()
{
treeTimer.Stop();
}
public virtual void Tick()
{
RunBehavior();
}
public virtual void RunBehavior()
{
Root.Execute();
}
private class BehaviorTreeTimer : Timer
{
private BehaviorTree behaviorTree;
public BehaviorTreeTimer(BehaviorTree tree) : base(TimeSpan.Zero, TimeSpan.FromSeconds(1.0))
{
behaviorTree = tree;
}
protected override void OnTick()
{
if (behaviorTree != null)
{
behaviorTree.Tick();
}
}
}
}
/*
public enum ControlFlowNodeType
{
Sequence,
Fallback,
Parallel,
Decorator
}
public enum ExecutionNodeType
{
Action,
Condition
}
public class BehaviorTreeNode
{
private readonly List<BehaviorTreeNode> _children = new List<BehaviorTreeNode>();
public bool Root { get { return Parent == null; } }
public BehaviorTreeNode() { }
public BehaviorTreeNode(BehaviorTreeNode parent) : this()
{
Parent = parent;
}
public BehaviorTreeNode this[int i]
{
get { return _children[i]; }
}
public BehaviorTreeNode Parent { get; private set; }
public ReadOnlyCollection<BehaviorTreeNode> Children
{
get { return _children.AsReadOnly(); }
}
public virtual BehaviorTreeNode AddChild(BehaviorTreeNode node)
{
_children.Add(node);
return node;
}
public virtual bool Evaluate(BaseCreature creature)
{
return false;
}
public virtual bool Traverse(BaseCreature creature)
{
foreach (var child in _children)
child.Traverse(creature);
}
}
public class SequenceNode : BehaviorTreeNode {
public override bool Evaluate(BaseCreature creature)
{
return Children.All((BehaviorTreeNode node) => node.Evaluate(creature));
}
public override bool Traverse(BaseCreature creature)
{
if (Evaluate(creature)) return true;
foreach (var child in Children)
child.Traverse(creature);
}
}
public class FallbackNode : BehaviorTreeNode
{
public override bool Evaluate(BaseCreature creature)
{
return Children.Any((BehaviorTreeNode node) => node.Evaluate(creature));
}
public override bool Traverse(BaseCreature creature)
{
return Evaluate(creature);
}
}
public class ParallelNode : BehaviorTreeNode
{
}
public class DecoratorNode : BehaviorTreeNode
{
}
public class ActionNode : BehaviorTreeNode
{
}
public class ConditionNode : BehaviorTreeNode
{
}
*/
}

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.BehaviorTreeAI
{
public abstract class BehaviorTreeNode
{
public enum Result { Running, Failure, Success }
public BehaviorTree Tree { get; private set; }
public BehaviorTreeNode(BehaviorTree tree)
{
Tree = tree;
}
public abstract Result Execute();
}
}

View file

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorTreeAI
{
public class ConditionNode : LeafNode
{
private Predicate<BaseCreature> predicate;
public ConditionNode(BehaviorTree tree, Predicate<BaseCreature> fn) : base(tree)
{
predicate = fn;
}
public override Result Execute()
{
if (predicate(Tree.Mobile))
{
return Result.Success;
}
return Result.Failure;
}
}
}

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.BehaviorTreeAI
{
public abstract class DecoratorNode : BehaviorTreeNode
{
public BehaviorTreeNode Child { get; set; }
public DecoratorNode(BehaviorTree tree) : this(tree, null)
{
}
public DecoratorNode(BehaviorTree tree, BehaviorTreeNode child) : base(tree)
{
Child = child;
}
}
}

View file

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorTreeAI
{
public class InverterNode : DecoratorNode
{
public InverterNode(BehaviorTree tree) : base(tree)
{
}
public override Result Execute()
{
if (Child != null)
{
var result = Child.Execute();
if (result == Result.Success)
{
return Result.Failure;
}
else if (result == Result.Failure)
{
return Result.Success;
}
return Result.Running;
}
return Result.Success;
}
}
}

View file

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorTreeAI
{
public abstract class LeafNode : BehaviorTreeNode
{
public LeafNode(BehaviorTree tree) : base(tree)
{
}
}
}

View file

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorTreeAI
{
public class RepeaterNode : DecoratorNode
{
public RepeaterNode(BehaviorTree tree) : base(tree)
{
}
public RepeaterNode(BehaviorTree tree, BehaviorTreeNode child) : base(tree, child)
{
}
public override Result Execute()
{
if (Child != null)
{
Child.Execute();
}
return Result.Running;
}
}
}

View file

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorTreeAI
{
class SelectorNode : BehaviorTreeNode
{
int currentTask = 0;
public List<BehaviorTreeNode> Children { get; protected set; }
public SelectorNode(BehaviorTree tree) : this(tree, new BehaviorTreeNode[0]) { }
public SelectorNode(BehaviorTree tree, BehaviorTreeNode[] children) : base(tree)
{
Children = new List<BehaviorTreeNode>(children);
}
public override Result Execute()
{
if (currentTask < Children.Count)
{
var result = Children[currentTask].Execute();
if (result == Result.Success)
{
currentTask = 0;
return Result.Success;
}
else if (result == Result.Running)
{
return Result.Running;
}
currentTask++;
if (currentTask == Children.Count)
{
currentTask = 0;
return Result.Failure;
}
return Result.Running;
}
return Result.Success;
}
}
}

View file

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorTreeAI
{
public class SequenceNode : BehaviorTreeNode
{
int currentTask = 0;
public List<BehaviorTreeNode> Children { get; protected set; }
public SequenceNode(BehaviorTree tree) : this(tree, new BehaviorTreeNode[0]) { }
public SequenceNode(BehaviorTree tree, BehaviorTreeNode[] children) : base(tree)
{
Children = new List<BehaviorTreeNode>(children);
}
public override Result Execute()
{
if(currentTask < Children.Count)
{
var result = Children[currentTask].Execute();
if (result == Result.Running)
{
return Result.Running;
}
else if (result == Result.Failure)
{
currentTask = 0;
return Result.Failure;
}
currentTask++;
if(currentTask == Children.Count)
{
currentTask = 0;
return Result.Success;
}
return Result.Running;
}
return Result.Success;
}
}
}

View file

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Server.Mobiles.BehaviorTreeAI
{
public class TapNode : DecoratorNode
{
public Action<BaseCreature> Action { get; private set; }
public TapNode(BehaviorTree tree, Action<BaseCreature> action, BehaviorTreeNode child) : base(tree, child)
{
}
public TapNode(BehaviorTree tree, Action<BaseCreature> action) : base(tree)
{
Action = action;
}
public override Result Execute()
{
Action(Tree.Mobile);
if (Child != null)
{
return Child.Execute();
}
return 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.BehaviorTreeAI
{
public class UntilFailNode : DecoratorNode
{
public UntilFailNode(BehaviorTree tree) : base(tree)
{
}
public override Result Execute()
{
if (Child != null)
{
var result = Child.Execute();
if (result == Result.Failure)
{
return Result.Failure;
}
}
return Result.Running;
}
}
}

View file

@ -5,6 +5,10 @@ namespace Server.Mobiles
[Constructible]
public EvilHealer()
{
AI = AIType.AI_BehaviorTree;
ChangeAIType(AI);
Title = "the healer";
Karma = -10000;
@ -63,6 +67,8 @@ namespace Server.Mobiles
{
base.Deserialize(reader);
AI = AIType.AI_BehaviorTree;
var version = reader.ReadInt();
}
}