Compare commits
8 commits
main
...
behavior-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62c7acf5d3 | ||
|
|
bb2999d3ab | ||
|
|
380f40890c | ||
|
|
e0874713d1 | ||
|
|
391c49553a | ||
|
|
3f309edffb | ||
|
|
0e84739f2d | ||
|
|
53f51d6bde |
75 changed files with 3251 additions and 8 deletions
|
|
@ -1,7 +1,6 @@
|
||||||
namespace Server
|
namespace Server
|
||||||
{
|
{
|
||||||
public delegate MoveResult MoveMethod(Direction d);
|
public delegate MoveResult MoveMethod(Direction d);
|
||||||
|
|
||||||
public enum MoveResult
|
public enum MoveResult
|
||||||
{
|
{
|
||||||
BadState,
|
BadState,
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,9 @@ namespace Server.Mobiles
|
||||||
AI_Mage,
|
AI_Mage,
|
||||||
AI_Berserk,
|
AI_Berserk,
|
||||||
AI_Predator,
|
AI_Predator,
|
||||||
AI_Thief
|
AI_Thief,
|
||||||
|
AI_BehaviorTree,
|
||||||
|
AI_FireBoss
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ActionType
|
public enum ActionType
|
||||||
|
|
@ -2010,7 +2012,7 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
if (canOpenDoors || canDestroyObstacles)
|
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;
|
var map = m_Mobile.Map;
|
||||||
|
|
||||||
|
|
@ -2436,7 +2438,7 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
m_Mobile.NextReacquireTime = Core.TickCount + (int)m_Mobile.ReacquireDelay.TotalMilliseconds;
|
m_Mobile.NextReacquireTime = Core.TickCount + (int)m_Mobile.ReacquireDelay.TotalMilliseconds;
|
||||||
|
|
||||||
m_Mobile.DebugSay("Acquiring...");
|
// m_Mobile.DebugSay("Acquiring...");
|
||||||
|
|
||||||
var map = m_Mobile.Map;
|
var map = m_Mobile.Map;
|
||||||
|
|
||||||
|
|
@ -3069,6 +3071,7 @@ namespace Server.Mobiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
if (m_Owner.CanDetectHidden && Core.TickCount - m_Owner.m_NextDetectHidden >= 0)
|
if (m_Owner.CanDetectHidden && Core.TickCount - m_Owner.m_NextDetectHidden >= 0)
|
||||||
{
|
{
|
||||||
m_Owner.DetectHidden();
|
m_Owner.DetectHidden();
|
||||||
|
|
@ -3082,6 +3085,7 @@ namespace Server.Mobiles
|
||||||
m_Owner.m_NextDetectHidden = Core.TickCount +
|
m_Owner.m_NextDetectHidden = Core.TickCount +
|
||||||
(int)TimeSpan.FromSeconds(Utility.RandomMinMax(min, max)).TotalMilliseconds;
|
(int)TimeSpan.FromSeconds(Utility.RandomMinMax(min, max)).TotalMilliseconds;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
59
Projects/UOContent/Mobiles/AI/BehaviorTreeAI.cs
Normal file
59
Projects/UOContent/Mobiles/AI/BehaviorTreeAI.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
// 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 readonly BehaviorTree behaviorTree;
|
||||||
|
private readonly 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 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Think()
|
||||||
|
{
|
||||||
|
if (m_Mobile.Deleted)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MageBehaviorTree.Instance.RunBehavior(m_Mobile, blackboard);
|
||||||
|
|
||||||
|
behaviorTree.Tick(behaviorTreeContext);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool DoActionWander()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Projects/UOContent/Mobiles/AI/FireBossAI.cs
Normal file
37
Projects/UOContent/Mobiles/AI/FireBossAI.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
using Server.Mobiles.BT;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.AI
|
||||||
|
{
|
||||||
|
public class FireBossAI : BaseAI
|
||||||
|
{
|
||||||
|
private Blackboard blackboard;
|
||||||
|
public BehaviorTree Tree { get; private set; }
|
||||||
|
public FireBossAI(BaseCreature m) : base(m)
|
||||||
|
{
|
||||||
|
blackboard = new Blackboard();
|
||||||
|
m.PassiveSpeed = 0.05;
|
||||||
|
m.ActiveSpeed = 0.05;
|
||||||
|
m.CurrentSpeed = 0.05;
|
||||||
|
|
||||||
|
Tree = new BehaviorTree();
|
||||||
|
Tree.TryAddRoot(
|
||||||
|
new SelectorNode(Tree)
|
||||||
|
.AddChild(
|
||||||
|
new SequenceNode(Tree)
|
||||||
|
.AddChild(new ConditionNode(Tree, (mob, board) => mob.Combatant != null))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public override bool Think()
|
||||||
|
{
|
||||||
|
if (m_Mobile.Deleted || !m_Mobile.Alive)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tree.RunBehavior(m_Mobile, blackboard);
|
||||||
|
|
||||||
|
return base.Think();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -101,12 +101,14 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
base.DoActionWander();
|
base.DoActionWander();
|
||||||
|
|
||||||
|
/*
|
||||||
if (Utility.RandomDouble() < 0.05)
|
if (Utility.RandomDouble() < 0.05)
|
||||||
{
|
{
|
||||||
var spell = CheckCastHealingSpell();
|
var spell = CheckCastHealingSpell();
|
||||||
|
|
||||||
spell?.Cast();
|
spell?.Cast();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -115,10 +117,12 @@ namespace Server.Mobiles
|
||||||
private Spell CheckCastHealingSpell()
|
private Spell CheckCastHealingSpell()
|
||||||
{
|
{
|
||||||
// If I'm poisoned, always attempt to cure.
|
// If I'm poisoned, always attempt to cure.
|
||||||
|
/*
|
||||||
if (m_Mobile.Poisoned)
|
if (m_Mobile.Poisoned)
|
||||||
{
|
{
|
||||||
return new CureSpell(m_Mobile);
|
return new CureSpell(m_Mobile);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Summoned creatures never heal themselves.
|
// Summoned creatures never heal themselves.
|
||||||
if (m_Mobile.Summoned)
|
if (m_Mobile.Summoned)
|
||||||
|
|
@ -151,6 +155,7 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
Spell spell = null;
|
Spell spell = null;
|
||||||
|
|
||||||
|
/*
|
||||||
if (m_Mobile.Hits < m_Mobile.HitsMax - 50)
|
if (m_Mobile.Hits < m_Mobile.HitsMax - 50)
|
||||||
{
|
{
|
||||||
if (UseNecromancy())
|
if (UseNecromancy())
|
||||||
|
|
@ -166,6 +171,7 @@ namespace Server.Mobiles
|
||||||
{
|
{
|
||||||
spell = new HealSpell(m_Mobile);
|
spell = new HealSpell(m_Mobile);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
double delay;
|
double delay;
|
||||||
|
|
||||||
|
|
@ -765,6 +771,7 @@ namespace Server.Mobiles
|
||||||
Spell spell;
|
Spell spell;
|
||||||
var toDispel = FindDispelTarget(true);
|
var toDispel = FindDispelTarget(true);
|
||||||
|
|
||||||
|
/*
|
||||||
if (m_Mobile.Poisoned) // Top cast priority is cure
|
if (m_Mobile.Poisoned) // Top cast priority is cure
|
||||||
{
|
{
|
||||||
m_Mobile.DebugSay("I am going to cure myself");
|
m_Mobile.DebugSay("I am going to cure myself");
|
||||||
|
|
@ -772,6 +779,8 @@ namespace Server.Mobiles
|
||||||
spell = new CureSpell(m_Mobile);
|
spell = new CureSpell(m_Mobile);
|
||||||
}
|
}
|
||||||
else if (toDispel != null) // Something dispellable is attacking us
|
else if (toDispel != null) // Something dispellable is attacking us
|
||||||
|
*/
|
||||||
|
if (toDispel != null)
|
||||||
{
|
{
|
||||||
m_Mobile.DebugSay("I am going to dispel {0}", toDispel);
|
m_Mobile.DebugSay("I am going to dispel {0}", toDispel);
|
||||||
|
|
||||||
|
|
@ -891,10 +900,12 @@ namespace Server.Mobiles
|
||||||
RunFrom(m_Mobile.FocusMob);
|
RunFrom(m_Mobile.FocusMob);
|
||||||
m_Mobile.FocusMob = null;
|
m_Mobile.FocusMob = null;
|
||||||
|
|
||||||
|
/*
|
||||||
if (m_Mobile.Poisoned && Utility.Random(0, 5) == 0)
|
if (m_Mobile.Poisoned && Utility.Random(0, 5) == 0)
|
||||||
{
|
{
|
||||||
new CureSpell(m_Mobile).Cast();
|
new CureSpell(m_Mobile).Cast();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
65
Projects/UOContent/Mobiles/BT/Actions/CastSpellActionNode.cs
Normal file
65
Projects/UOContent/Mobiles/BT/Actions/CastSpellActionNode.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Server.Spells;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class CastSpellActionNode : ActionNode
|
||||||
|
{
|
||||||
|
public delegate Spell GetSpellCallback(BaseCreature mob);
|
||||||
|
private GetSpellCallback getSpellCallback;
|
||||||
|
public CastSpellActionNode(BehaviorTree tree, GetSpellCallback callback) : base(tree)
|
||||||
|
{
|
||||||
|
getSpellCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (mob.Spell == null)
|
||||||
|
{
|
||||||
|
Spell spell = getSpellCallback(mob);
|
||||||
|
|
||||||
|
if (mob.Mana < spell.ScaleMana(spell.GetMana()))
|
||||||
|
{
|
||||||
|
mob.DebugSay("Not enough mana...");
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Core.TickCount - mob.NextSpellTime < 0)
|
||||||
|
{
|
||||||
|
mob.DebugSay("You have not recovered from casting a spell...");
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
mob.DebugSay("Casting...");
|
||||||
|
if (!spell.Cast())
|
||||||
|
{
|
||||||
|
mob.DebugSay("Failed to cast...");
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(spell.Mantra))
|
||||||
|
{
|
||||||
|
mob.PublicOverheadMessage(Network.MessageType.Spell, 0, false, spell.Mantra, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mob.Spell.IsCasting)
|
||||||
|
{
|
||||||
|
mob.DebugSay("Still casting...");
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mob.Spell.IsCasting)
|
||||||
|
{
|
||||||
|
mob.DebugSay("Finished casting...");
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Projects/UOContent/Mobiles/BT/Actions/CureSelfNode.cs
Normal file
24
Projects/UOContent/Mobiles/BT/Actions/CureSelfNode.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Server.Spells.Second;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class CureSelfNode : OwnerConditionNode
|
||||||
|
{
|
||||||
|
public CureSelfNode(BehaviorTree tree, TimeSpan duration) : base(tree, (mob, board) => mob.Poison != null)
|
||||||
|
{
|
||||||
|
AddChild(
|
||||||
|
new CooldownNode(tree, duration)
|
||||||
|
.AddChild(
|
||||||
|
new SequenceNode(tree)
|
||||||
|
.AddChild(new CastSpellActionNode(tree, (mob) => new CureSpell(mob)))
|
||||||
|
.AddChild(new TargetActionNode(tree))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Projects/UOContent/Mobiles/BT/Actions/EquipItemNode.cs
Normal file
28
Projects/UOContent/Mobiles/BT/Actions/EquipItemNode.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class EquipItemNode : ActionNode
|
||||||
|
{
|
||||||
|
private Type itemType;
|
||||||
|
public EquipItemNode(BehaviorTree tree, Type type) : base(tree)
|
||||||
|
{
|
||||||
|
itemType = type;
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
Item item = mob.Backpack.FindItemByType(itemType);
|
||||||
|
|
||||||
|
if (item == null || !mob.EquipItem(item))
|
||||||
|
{
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Projects/UOContent/Mobiles/BT/Actions/HealSelfNode.cs
Normal file
36
Projects/UOContent/Mobiles/BT/Actions/HealSelfNode.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Server.Spells.Fourth;
|
||||||
|
using Server.Spells.First;
|
||||||
|
using Server.Spells;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class HealSelfNode : OwnerConditionNode
|
||||||
|
{
|
||||||
|
public HealSelfNode(BehaviorTree tree, TimeSpan duration) : base(tree, (mob, board) => mob.HitsMax - mob.Hits > 0)
|
||||||
|
{
|
||||||
|
AddChild(
|
||||||
|
new CooldownNode(tree, duration)
|
||||||
|
.AddChild(
|
||||||
|
new SequenceNode(tree)
|
||||||
|
.AddChild(new CastSpellActionNode(tree, GetHealSpell))
|
||||||
|
.AddChild(new TargetActionNode(tree))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Spell GetHealSpell(BaseCreature mob)
|
||||||
|
{
|
||||||
|
if (mob.Mana < 10 || mob.HitsMax - mob.Hits < 15 || Utility.RandomDouble() < 0.3)
|
||||||
|
{
|
||||||
|
return new HealSpell(mob);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GreaterHealSpell(mob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Projects/UOContent/Mobiles/BT/Actions/KeepRangeNode.cs
Normal file
29
Projects/UOContent/Mobiles/BT/Actions/KeepRangeNode.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class KeepRangeNode : ActionNode
|
||||||
|
{
|
||||||
|
private int minRange;
|
||||||
|
private int maxRange;
|
||||||
|
public KeepRangeNode(BehaviorTree tree, int min, int max) : base(tree)
|
||||||
|
{
|
||||||
|
minRange = min;
|
||||||
|
maxRange = max;
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (mob.Combatant != null && !mob.Combatant.Deleted)
|
||||||
|
{
|
||||||
|
BehaviorTree.WalkMobileRange(mob, (BaseCreature)mob.Combatant, 4, true, minRange, maxRange);
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
144
Projects/UOContent/Mobiles/BT/Actions/MageComboNode.cs
Normal file
144
Projects/UOContent/Mobiles/BT/Actions/MageComboNode.cs
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Server.Spells.Sixth;
|
||||||
|
using Server.Spells.First;
|
||||||
|
using Server.Spells.Second;
|
||||||
|
using Server.Spells.Fourth;
|
||||||
|
using Server.Spells.Fifth;
|
||||||
|
using Server.Spells;
|
||||||
|
using Server.Spells.Third;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class MageComboNode : OwnerConditionNode
|
||||||
|
{
|
||||||
|
public MageComboNode(BehaviorTree tree)
|
||||||
|
: base(tree, canActivate)
|
||||||
|
{
|
||||||
|
AddChild(
|
||||||
|
new SequenceNode(tree)
|
||||||
|
.AddChild(new ForceSuccessNode(tree, new RandomExecutionNode(tree, 0.5, new PoisonEnemyNode(tree, TimeSpan.FromSeconds(6.0)))))
|
||||||
|
.AddChild(new ConditionNode(tree, shouldContinueCombo))
|
||||||
|
.AddChild(
|
||||||
|
new ForceSuccessNode(tree)
|
||||||
|
.AddChild(
|
||||||
|
new RandomExecutionNode(tree, 0.5)
|
||||||
|
.AddChild(new SequenceNode(tree)
|
||||||
|
.AddChild(new CastSpellActionNode(tree, getInterruptSpell))
|
||||||
|
.AddChild(new TargetActionNode(tree))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.AddChild(new ForceSuccessNode(tree, new CureSelfNode(tree, TimeSpan.FromSeconds(5.0))))
|
||||||
|
.AddChild(new ConditionNode(tree, shouldContinueCombo))
|
||||||
|
.AddChild(
|
||||||
|
// new ForceSuccessNode(tree)
|
||||||
|
// .AddChild(
|
||||||
|
new SequenceNode(tree)
|
||||||
|
.AddChild(new CastSpellActionNode(tree, getExplosionSpell))
|
||||||
|
.AddChild(new TargetActionNode(tree))
|
||||||
|
.AddChild(new ConditionNode(tree, shouldContinueCombo))
|
||||||
|
.AddChild(new CastSpellActionNode(tree, getEnergyBoltSpell))
|
||||||
|
.AddChild(new TargetActionNode(tree))
|
||||||
|
// )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private static bool canActivate(BaseCreature mob, Blackboard board)
|
||||||
|
{
|
||||||
|
return mob.Combatant != null && !mob.Combatant.Deleted && mob.Mana > 70;
|
||||||
|
}
|
||||||
|
private static bool shouldAttemptInterrupt(BaseCreature mob, Blackboard board)
|
||||||
|
{
|
||||||
|
if (mob.Combatant == null || mob.Combatant.Deleted)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mob.Combatant.Spell != null && mob.Combatant.Spell.IsCasting)
|
||||||
|
{
|
||||||
|
switch(mob.Combatant.Spell)
|
||||||
|
{
|
||||||
|
case GreaterHealSpell:
|
||||||
|
case ExplosionSpell:
|
||||||
|
case EnergyBoltSpell:
|
||||||
|
case CureSpell:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mob.Combatant.Meditating)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
private static bool shouldContinueCombo(BaseCreature mob, Blackboard board)
|
||||||
|
{
|
||||||
|
if (mob.Combatant == null || mob.Combatant.Deleted)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mob.Combatant.HitsMax - mob.Combatant.Hits >= 70)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mob.HitsMax - mob.Hits <= 50)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mob.Mana > 10)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mob.Combatant.Spell != null && mob.Combatant.Poison != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
private static Spell getLightningSpell(BaseCreature mob)
|
||||||
|
{
|
||||||
|
return new LightningSpell(mob);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Spell getExplosionSpell(BaseCreature mob)
|
||||||
|
{
|
||||||
|
return new ExplosionSpell(mob);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Spell getEnergyBoltSpell(BaseCreature mob)
|
||||||
|
{
|
||||||
|
return new EnergyBoltSpell(mob);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Spell getInterruptSpell(BaseCreature mob)
|
||||||
|
{
|
||||||
|
var chance = Utility.RandomDouble();
|
||||||
|
if (chance < 0.1)
|
||||||
|
{
|
||||||
|
new FireballSpell(mob);
|
||||||
|
}
|
||||||
|
else if (chance < 0.3)
|
||||||
|
{
|
||||||
|
return new LightningSpell(mob);
|
||||||
|
}
|
||||||
|
if (chance < 0.5)
|
||||||
|
{
|
||||||
|
return new MagicArrowSpell(mob);
|
||||||
|
}
|
||||||
|
return new HarmSpell(mob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
Projects/UOContent/Mobiles/BT/Actions/MageDebuffNode.cs
Normal file
40
Projects/UOContent/Mobiles/BT/Actions/MageDebuffNode.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Server.Spells.First;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class MageDebuffNode : SelectorNode
|
||||||
|
{
|
||||||
|
public MageDebuffNode(BehaviorTree tree) : base(tree)
|
||||||
|
{
|
||||||
|
AddChild(
|
||||||
|
new OwnerConditionNode(tree, (mob, board) => mob.Combatant != null && mob.Combatant.Str == mob.Combatant.RawStr)
|
||||||
|
.AddChild(
|
||||||
|
new SequenceNode(tree)
|
||||||
|
.AddChild(new CastSpellActionNode(tree, (mob) => new WeakenSpell(mob)))
|
||||||
|
.AddChild(new TargetActionNode(tree))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
AddChild(
|
||||||
|
new OwnerConditionNode(tree, (mob, board) => mob.Combatant != null && mob.Combatant.Dex == mob.Combatant.RawDex)
|
||||||
|
.AddChild(
|
||||||
|
new SequenceNode(tree)
|
||||||
|
.AddChild(new CastSpellActionNode(tree, (mob) => new ClumsySpell(mob)))
|
||||||
|
.AddChild(new TargetActionNode(tree))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
AddChild(
|
||||||
|
new OwnerConditionNode(tree, (mob, board) => mob.Combatant != null && mob.Combatant.Int == mob.Combatant.RawInt)
|
||||||
|
.AddChild(
|
||||||
|
new SequenceNode(tree)
|
||||||
|
.AddChild(new CastSpellActionNode(tree, (mob) => new FeeblemindSpell(mob)))
|
||||||
|
.AddChild(new TargetActionNode(tree))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Projects/UOContent/Mobiles/BT/Actions/MeditateNode.cs
Normal file
20
Projects/UOContent/Mobiles/BT/Actions/MeditateNode.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class MeditateNode : CooldownNode
|
||||||
|
{
|
||||||
|
public MeditateNode(BehaviorTree tree, int desiredMana) : base(tree, TimeSpan.FromSeconds(12.0))
|
||||||
|
{
|
||||||
|
AddChild(
|
||||||
|
new SequenceNode(tree)
|
||||||
|
.AddChild(new UseSkillNode(tree, SkillName.Meditation))
|
||||||
|
.AddChild(new InverterNode(tree, new UntilFailNode(tree, new ConditionNode(tree, (mob, board) => mob.Mana < desiredMana || mob.Meditating))))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Projects/UOContent/Mobiles/BT/Actions/PoisonEnemyNode.cs
Normal file
34
Projects/UOContent/Mobiles/BT/Actions/PoisonEnemyNode.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Server.Spells.Third;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class PoisonEnemyNode : OwnerConditionNode
|
||||||
|
{
|
||||||
|
public PoisonEnemyNode(BehaviorTree tree, TimeSpan duration) : base(tree, (mob, board) => mob.Combatant != null && !mob.Combatant.Deleted && mob.Combatant.Poison == null)
|
||||||
|
{
|
||||||
|
AddChild(
|
||||||
|
new CooldownNode(tree, duration)
|
||||||
|
.AddChild(
|
||||||
|
new SequenceNode(tree)
|
||||||
|
.AddChild(new CastSpellActionNode(tree, (mob) => new PoisonSpell(mob)))
|
||||||
|
.AddChild(new TargetActionNode(tree))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool canActivate(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (mob.Combatant == null || mob.Combatant.Deleted)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mob.Combatant.Poison == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Projects/UOContent/Mobiles/BT/Actions/SetBlackboardData.cs
Normal file
33
Projects/UOContent/Mobiles/BT/Actions/SetBlackboardData.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class SetBlackboardData<T> : ActionNode
|
||||||
|
{
|
||||||
|
public delegate T BlackboardDataTransformation(BaseCreature mob);
|
||||||
|
private BlackboardDataTransformation transformation;
|
||||||
|
private string blackboardKey;
|
||||||
|
public SetBlackboardData(BehaviorTree tree, string key, BlackboardDataTransformation fn) : base(tree)
|
||||||
|
{
|
||||||
|
blackboardKey = key;
|
||||||
|
transformation = fn;
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
T value = transformation(mob);
|
||||||
|
if(blackboard.TryGetValue(blackboardKey, out object oldValue) && (T)oldValue != null)
|
||||||
|
{
|
||||||
|
blackboard[blackboardKey] = (object)value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
blackboard.Add(blackboardKey, (object)value);
|
||||||
|
}
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
Projects/UOContent/Mobiles/BT/Actions/TargetActionNode.cs
Normal file
53
Projects/UOContent/Mobiles/BT/Actions/TargetActionNode.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
using Server.Targeting;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class TargetActionNode : ActionNode
|
||||||
|
{
|
||||||
|
public TargetActionNode(BehaviorTree tree) : base(tree)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
Target target = mob.Target;
|
||||||
|
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
mob.DebugSay("Waiting for target...");
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mobile mobTarget = mob.Combatant;
|
||||||
|
|
||||||
|
if ((target.Flags & TargetFlags.Harmful) != 0 && mobTarget != null)
|
||||||
|
{
|
||||||
|
if (mobTarget.Deleted || !mobTarget.Alive)
|
||||||
|
{
|
||||||
|
mob.DebugSay("Canceling my target because my target is dead or does not exist");
|
||||||
|
target.Cancel(mob, TargetCancelType.Canceled);
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((target.Range == -1 ||
|
||||||
|
mob.InRange(mobTarget, target.Range)) &&
|
||||||
|
mob.CanSee(mobTarget) &&
|
||||||
|
mob.InLOS(mobTarget)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
mob.DebugSay("Targeting my combatant");
|
||||||
|
target.Invoke(mob, mobTarget);
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((target.Flags & TargetFlags.Beneficial) != 0)
|
||||||
|
{
|
||||||
|
mob.DebugSay("Targeting myself");
|
||||||
|
target.Invoke(mob, mob);
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
Projects/UOContent/Mobiles/BT/Actions/UseSkillNode.cs
Normal file
26
Projects/UOContent/Mobiles/BT/Actions/UseSkillNode.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class UseSkillNode : ActionNode
|
||||||
|
{
|
||||||
|
private SkillName skillToUse;
|
||||||
|
public UseSkillNode(BehaviorTree tree, SkillName skill) : base(tree)
|
||||||
|
{
|
||||||
|
skillToUse = skill;
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (mob.UseSkill(skillToUse))
|
||||||
|
{
|
||||||
|
mob.Say("{0}", skillToUse.ToString());
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Projects/UOContent/Mobiles/BT/Actions/WanderNode.cs
Normal file
21
Projects/UOContent/Mobiles/BT/Actions/WanderNode.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class WanderNode : ActionNode
|
||||||
|
{
|
||||||
|
private int stepsPerWander;
|
||||||
|
public WanderNode(BehaviorTree tree, int steps) : base(tree)
|
||||||
|
{
|
||||||
|
stepsPerWander = steps;
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (!mob.CheckIdle())
|
||||||
|
{
|
||||||
|
BehaviorTree.WalkRandomInHome(mob, stepsPerWander);
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Projects/UOContent/Mobiles/BT/BehaviorMageCombatNodeSet.cs
Normal file
34
Projects/UOContent/Mobiles/BT/BehaviorMageCombatNodeSet.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
using Server.Items;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class BehaviorMageCombatNodeSet : OwnerConditionNode
|
||||||
|
{
|
||||||
|
public BehaviorMageCombatNodeSet(BehaviorTree tree)
|
||||||
|
: base(tree, canActivate)
|
||||||
|
{
|
||||||
|
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, 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)))
|
||||||
|
*/
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool canActivate(BaseCreature mob, Blackboard board)
|
||||||
|
{
|
||||||
|
return mob.Combatant != null && !mob.Combatant.Deleted;
|
||||||
|
}
|
||||||
|
private static bool shouldMeditate(BaseCreature mob, Blackboard board)
|
||||||
|
{
|
||||||
|
return mob.Mana <= 30 || (mob.Meditating && mob.Mana < 75);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Projects/UOContent/Mobiles/BT/BehaviorMagePassiveNodeSet.cs
Normal file
25
Projects/UOContent/Mobiles/BT/BehaviorMagePassiveNodeSet.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class BehaviorMagePassiveNodeSet : OwnerConditionNode
|
||||||
|
{
|
||||||
|
public BehaviorMagePassiveNodeSet(BehaviorTree tree)
|
||||||
|
: base(tree, canActivate)
|
||||||
|
{
|
||||||
|
AddChild(
|
||||||
|
new SelectorNode(tree)
|
||||||
|
.AddChild(new TapNode(tree, (mob, board) => { mob.CurrentSpeed = mob.PassiveSpeed; }))
|
||||||
|
.AddChild(new CooldownNode(tree, TimeSpan.FromSeconds(2.0), new WanderNode(tree, 3)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private static bool canActivate(BaseCreature mob, Blackboard board)
|
||||||
|
{
|
||||||
|
return mob.Combatant == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class RandomSelectorNode : CompositeNode
|
||||||
|
{
|
||||||
|
private Dictionary<BaseCreature, int> retryCache = new Dictionary<BaseCreature, int>();
|
||||||
|
public RandomSelectorNode(BehaviorTree tree) : base(tree)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (!Tree.Claims.TryGetValue(mob, out BehaviorTreeNode node) || node != this)
|
||||||
|
{
|
||||||
|
Tree.Claim(mob, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentTaskCache.TryGetValue(mob, out int currentTask))
|
||||||
|
{
|
||||||
|
currentTask = Utility.RandomMinMax(0, Children.Count - 1);
|
||||||
|
currentTaskCache.Add(mob, currentTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!retryCache.TryGetValue(mob, out int currentIteration))
|
||||||
|
{
|
||||||
|
currentIteration = 0;
|
||||||
|
retryCache.Add(mob, currentIteration);
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = Children[currentTask].Execute(mob, blackboard);
|
||||||
|
|
||||||
|
if (result == Result.Running)
|
||||||
|
{
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
else if (result == Result.Failure)
|
||||||
|
{
|
||||||
|
currentIteration++;
|
||||||
|
currentTaskCache[mob] = Utility.RandomMinMax(0, Children.Count - 1);
|
||||||
|
if (currentIteration >= Children.Count)
|
||||||
|
{
|
||||||
|
retryCache[mob] = 0;
|
||||||
|
Tree.Release(mob);
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
retryCache[mob] = currentIteration;
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
retryCache[mob] = 0;
|
||||||
|
currentTaskCache[mob] = 0;
|
||||||
|
Tree.Release(mob);
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
Projects/UOContent/Mobiles/BT/Composite/SelectorNode.cs
Normal file
59
Projects/UOContent/Mobiles/BT/Composite/SelectorNode.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class SelectorNode : CompositeNode
|
||||||
|
{
|
||||||
|
public SelectorNode(BehaviorTree tree): base(tree)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (!Tree.Claims.TryGetValue(mob, out BehaviorTreeNode node) || node != this)
|
||||||
|
{
|
||||||
|
Tree.Claim(mob, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentTaskCache.TryGetValue(mob, out int currentTask))
|
||||||
|
{
|
||||||
|
currentTask = 0;
|
||||||
|
currentTaskCache.Add(mob, currentTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentTask < Children.Count)
|
||||||
|
{
|
||||||
|
var result = Children[currentTask].Execute(mob, blackboard);
|
||||||
|
|
||||||
|
if (result == Result.Success)
|
||||||
|
{
|
||||||
|
currentTaskCache[mob] = 0;
|
||||||
|
Tree.Release(mob);
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
else if (result == Result.Running)
|
||||||
|
{
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTask++;
|
||||||
|
if (currentTask == Children.Count)
|
||||||
|
{
|
||||||
|
currentTask = 0;
|
||||||
|
currentTaskCache[mob] = currentTask;
|
||||||
|
Tree.Release(mob);
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTaskCache[mob] = currentTask;
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tree.Release(mob);
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
Projects/UOContent/Mobiles/BT/Composite/SequenceNode.cs
Normal file
59
Projects/UOContent/Mobiles/BT/Composite/SequenceNode.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class SequenceNode : CompositeNode
|
||||||
|
{
|
||||||
|
public SequenceNode(BehaviorTree tree) : base(tree)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (!Tree.Claims.TryGetValue(mob, out BehaviorTreeNode node) || node != this)
|
||||||
|
{
|
||||||
|
Tree.Claim(mob, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentTaskCache.TryGetValue(mob, out int currentTask))
|
||||||
|
{
|
||||||
|
currentTask = 0;
|
||||||
|
currentTaskCache.Add(mob, currentTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentTask < Children.Count)
|
||||||
|
{
|
||||||
|
var result = Children[currentTask].Execute(mob, blackboard);
|
||||||
|
|
||||||
|
if (result == Result.Running)
|
||||||
|
{
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
else if (result == Result.Failure)
|
||||||
|
{
|
||||||
|
currentTaskCache[mob] = 0;
|
||||||
|
Tree.Release(mob);
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTask++;
|
||||||
|
if (currentTask == Children.Count)
|
||||||
|
{
|
||||||
|
currentTask = 0;
|
||||||
|
currentTaskCache[mob] = currentTask;
|
||||||
|
Tree.Release(mob);
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTaskCache[mob] = currentTask;
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tree.Release(mob);
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
400
Projects/UOContent/Mobiles/BT/Core/BehaviorTree.cs
Normal file
400
Projects/UOContent/Mobiles/BT/Core/BehaviorTree.cs
Normal file
|
|
@ -0,0 +1,400 @@
|
||||||
|
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.BT
|
||||||
|
{
|
||||||
|
public class BehaviorTree
|
||||||
|
{
|
||||||
|
private BehaviorTreeNode root;
|
||||||
|
public BehaviorTreeNode Root { get { return root; } }
|
||||||
|
public Dictionary<BaseCreature, BehaviorTreeNode> Claims = new Dictionary<BaseCreature, BehaviorTreeNode>();
|
||||||
|
public BehaviorTree()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public bool TryAddRoot(CompositeNode rootNode)
|
||||||
|
{
|
||||||
|
if (root == null)
|
||||||
|
{
|
||||||
|
root = rootNode;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public virtual void RunBehavior(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (!Claims.TryGetValue(mob, out BehaviorTreeNode currentNode))
|
||||||
|
{
|
||||||
|
currentNode = null;
|
||||||
|
Claims.Add(mob, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Root != null)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
if (currentNode != null)
|
||||||
|
{
|
||||||
|
currentNode.Execute(mob, blackboard);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Root.Execute(mob, blackboard);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
Root.Execute(mob, blackboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public virtual void Claim(BaseCreature mob, BehaviorTreeNode node)
|
||||||
|
{
|
||||||
|
if (!Claims.TryGetValue(mob, out BehaviorTreeNode foundNode))
|
||||||
|
{
|
||||||
|
Claims.Add(mob, node);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Claims[mob] = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public virtual void Release(BaseCreature mob)
|
||||||
|
{
|
||||||
|
if (!Claims.TryGetValue(mob, out BehaviorTreeNode foundNode))
|
||||||
|
{
|
||||||
|
Claims.Add(mob, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Claims[mob] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WalkRandomInHome(BaseCreature mob, int steps)
|
||||||
|
{
|
||||||
|
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(mob, steps);
|
||||||
|
mob.WalkRegion = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (region.GoLocation != Point3D.Zero && Utility.RandomDouble() > 0.5)
|
||||||
|
{
|
||||||
|
DoMove(mob, mob.GetDirectionTo(region.GoLocation));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WalkRandom(mob, steps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WalkRandom(mob, 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(mob, 1);
|
||||||
|
}
|
||||||
|
else if (currentDistance > mob.RangeHome)
|
||||||
|
{
|
||||||
|
DoMove(mob, mob.GetDirectionTo(mob.Home));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Utility.RandomDouble() > 0.5)
|
||||||
|
{
|
||||||
|
DoMove(mob, mob.GetDirectionTo(mob.Home));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WalkRandom(mob, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (mob.Location != mob.Home)
|
||||||
|
{
|
||||||
|
DoMove(mob, mob.GetDirectionTo(mob.Home));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static MoveResult WalkRandom(BaseCreature mob, int steps, bool run = false)
|
||||||
|
{
|
||||||
|
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(mob, direction, run);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MoveResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MoveResult DoMove(BaseCreature mob, Direction d, bool run = false)
|
||||||
|
{
|
||||||
|
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(BaseCreature mob, BaseCreature target, int steps, bool run, int minRange, int maxRange)
|
||||||
|
{
|
||||||
|
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(mob, direction, run);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WalkRandom(mob, 2, run);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current distance
|
||||||
|
var newDistance = (int)mob.GetDistanceToSqrt(target);
|
||||||
|
|
||||||
|
return newDistance >= minRange && newDistance <= maxRange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Projects/UOContent/Mobiles/BT/Core/BehaviorTreeNode.cs
Normal file
27
Projects/UOContent/Mobiles/BT/Core/BehaviorTreeNode.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public abstract class BehaviorTreeNode
|
||||||
|
{
|
||||||
|
public enum Result { Running, Failure, Success }
|
||||||
|
public BehaviorTreeNode Parent;
|
||||||
|
public BehaviorTree Tree { get; private set; }
|
||||||
|
public BehaviorTreeNode(BehaviorTree tree) : this(tree, null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public BehaviorTreeNode(BehaviorTree tree, BehaviorTreeNode parent)
|
||||||
|
{
|
||||||
|
Parent = parent;
|
||||||
|
Tree = tree;
|
||||||
|
}
|
||||||
|
public virtual Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Projects/UOContent/Mobiles/BT/Core/Blackboard.cs
Normal file
12
Projects/UOContent/Mobiles/BT/Core/Blackboard.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class Blackboard : Dictionary<string, object>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Projects/UOContent/Mobiles/BT/Core/CompositeNode.cs
Normal file
25
Projects/UOContent/Mobiles/BT/Core/CompositeNode.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public abstract class CompositeNode : BehaviorTreeNode
|
||||||
|
{
|
||||||
|
protected Dictionary<BaseCreature, int> currentTaskCache;
|
||||||
|
public List<BehaviorTreeNode> Children { get; protected set; }
|
||||||
|
public CompositeNode(BehaviorTree tree) : base(tree)
|
||||||
|
{
|
||||||
|
currentTaskCache = new Dictionary<BaseCreature, int>();
|
||||||
|
Children = new List<BehaviorTreeNode>();
|
||||||
|
}
|
||||||
|
public virtual CompositeNode AddChild(BehaviorTreeNode child)
|
||||||
|
{
|
||||||
|
child.Parent = this;
|
||||||
|
Children.Add(child);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Projects/UOContent/Mobiles/BT/Core/DecoratorNode.cs
Normal file
28
Projects/UOContent/Mobiles/BT/Core/DecoratorNode.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public abstract class DecoratorNode : BehaviorTreeNode
|
||||||
|
{
|
||||||
|
public BehaviorTreeNode Child;
|
||||||
|
public DecoratorNode(BehaviorTree tree) : base(tree)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public DecoratorNode(BehaviorTree tree, BehaviorTreeNode child) : this(tree)
|
||||||
|
{
|
||||||
|
AddChild(child);
|
||||||
|
}
|
||||||
|
public virtual DecoratorNode AddChild(BehaviorTreeNode child)
|
||||||
|
{
|
||||||
|
if (Child == null)
|
||||||
|
{
|
||||||
|
Child = child;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Projects/UOContent/Mobiles/BT/Core/LeafNode.cs
Normal file
15
Projects/UOContent/Mobiles/BT/Core/LeafNode.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public abstract class LeafNode : BehaviorTreeNode
|
||||||
|
{
|
||||||
|
public LeafNode(BehaviorTree tree) : base(tree)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Projects/UOContent/Mobiles/BT/Decorator/CheckSkillNode.cs
Normal file
32
Projects/UOContent/Mobiles/BT/Decorator/CheckSkillNode.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class CheckSkillNode : DecoratorNode
|
||||||
|
{
|
||||||
|
private SkillName checkSkill;
|
||||||
|
private double minSkillValue;
|
||||||
|
private double maxSkillValue;
|
||||||
|
|
||||||
|
public CheckSkillNode(BehaviorTree tree, SkillName skill, double minSkill, double maxSkill) : base(tree)
|
||||||
|
{
|
||||||
|
checkSkill = skill;
|
||||||
|
minSkillValue = minSkill;
|
||||||
|
maxSkillValue = maxSkill;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if(mob.CheckSkill(checkSkill, minSkillValue, maxSkillValue) && Child != null)
|
||||||
|
{
|
||||||
|
return Child.Execute(mob, blackboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class ConditionalLoopNode : DecoratorNode
|
||||||
|
{
|
||||||
|
public delegate bool ConditionalLoopPredicate(BaseCreature mob, Blackboard blackboard);
|
||||||
|
private ConditionalLoopPredicate predicate;
|
||||||
|
public ConditionalLoopNode(BehaviorTree tree, ConditionalLoopPredicate fn) : base(tree)
|
||||||
|
{
|
||||||
|
predicate = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConditionalLoopNode(BehaviorTree tree, ConditionalLoopPredicate fn, BehaviorTreeNode child) : base(tree, child)
|
||||||
|
{
|
||||||
|
predicate = fn;
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (predicate(mob, blackboard))
|
||||||
|
{
|
||||||
|
if (Child != null)
|
||||||
|
{
|
||||||
|
Child.Execute(mob, blackboard);
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Projects/UOContent/Mobiles/BT/Decorator/CooldownNode.cs
Normal file
51
Projects/UOContent/Mobiles/BT/Decorator/CooldownNode.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class CooldownNode : DecoratorNode
|
||||||
|
{
|
||||||
|
private TimeSpan cooldownDuration;
|
||||||
|
private Dictionary<BaseCreature, DateTime> cooldowns;
|
||||||
|
public CooldownNode(BehaviorTree tree, TimeSpan duration) : base(tree)
|
||||||
|
{
|
||||||
|
cooldownDuration = duration;
|
||||||
|
cooldowns = new Dictionary<BaseCreature, DateTime>();
|
||||||
|
}
|
||||||
|
public CooldownNode(BehaviorTree tree, TimeSpan duration, BehaviorTreeNode child) : base(tree, child)
|
||||||
|
{
|
||||||
|
cooldownDuration = duration;
|
||||||
|
cooldowns = new Dictionary<BaseCreature, DateTime>();
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (Child != null)
|
||||||
|
{
|
||||||
|
if (!cooldowns.TryGetValue(mob, out DateTime nextCooldown))
|
||||||
|
{
|
||||||
|
nextCooldown = DateTime.Now;
|
||||||
|
cooldowns.Add(mob, nextCooldown);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DateTime.Now >= nextCooldown)
|
||||||
|
{
|
||||||
|
Result result = Child.Execute(mob, blackboard);
|
||||||
|
|
||||||
|
if (result == Result.Running)
|
||||||
|
{
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cooldowns[mob] = DateTime.Now + cooldownDuration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
Projects/UOContent/Mobiles/BT/Decorator/ForceSuccessNode.cs
Normal file
31
Projects/UOContent/Mobiles/BT/Decorator/ForceSuccessNode.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class ForceSuccessNode : DecoratorNode
|
||||||
|
{
|
||||||
|
public ForceSuccessNode(BehaviorTree tree) : base(tree)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public ForceSuccessNode(BehaviorTree tree, BehaviorTreeNode child) : base(tree, child)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (Child != null)
|
||||||
|
{
|
||||||
|
if (Child.Execute(mob, blackboard) == Result.Running)
|
||||||
|
{
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Projects/UOContent/Mobiles/BT/Decorator/InverterNode.cs
Normal file
36
Projects/UOContent/Mobiles/BT/Decorator/InverterNode.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class InverterNode : DecoratorNode
|
||||||
|
{
|
||||||
|
public InverterNode(BehaviorTree tree) : base(tree)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public InverterNode(BehaviorTree tree, BehaviorTreeNode child) : base(tree, child)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (Child != null)
|
||||||
|
{
|
||||||
|
var result = Child.Execute(mob, blackboard);
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case Result.Failure:
|
||||||
|
return Result.Success;
|
||||||
|
case Result.Success:
|
||||||
|
return Result.Failure;
|
||||||
|
default:
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Projects/UOContent/Mobiles/BT/Decorator/LoopNode.cs
Normal file
42
Projects/UOContent/Mobiles/BT/Decorator/LoopNode.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class LoopNode : DecoratorNode
|
||||||
|
{
|
||||||
|
private int count;
|
||||||
|
private Dictionary<BaseCreature, int> currentCountCache;
|
||||||
|
public LoopNode(BehaviorTree tree, int n) : base(tree)
|
||||||
|
{
|
||||||
|
count = n;
|
||||||
|
currentCountCache = new Dictionary<BaseCreature, int>();
|
||||||
|
}
|
||||||
|
public LoopNode(BehaviorTree tree, int n, BehaviorTreeNode child) : base(tree, child)
|
||||||
|
{
|
||||||
|
count = n;
|
||||||
|
currentCountCache = new Dictionary<BaseCreature, int>();
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (!currentCountCache.TryGetValue(mob, out int currentCount))
|
||||||
|
{
|
||||||
|
currentCount = 0;
|
||||||
|
currentCountCache.Add(mob, currentCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Child != null && currentCount < count)
|
||||||
|
{
|
||||||
|
Child.Execute(mob, blackboard);
|
||||||
|
currentCountCache[mob]++;
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
else if (currentCount >= count)
|
||||||
|
{
|
||||||
|
currentCountCache[mob] = 0;
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class OwnerConditionNode : DecoratorNode
|
||||||
|
{
|
||||||
|
public delegate bool OwnerConditionPredicate(BaseCreature mob, Blackboard blackboard);
|
||||||
|
private OwnerConditionPredicate predicate;
|
||||||
|
public OwnerConditionNode(BehaviorTree tree, OwnerConditionPredicate fn) : base(tree)
|
||||||
|
{
|
||||||
|
predicate = fn;
|
||||||
|
}
|
||||||
|
public OwnerConditionNode(BehaviorTree tree, OwnerConditionPredicate fn, BehaviorTreeNode child) : base(tree, child)
|
||||||
|
{
|
||||||
|
predicate = fn;
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (predicate(mob, blackboard))
|
||||||
|
{
|
||||||
|
if (Child != null)
|
||||||
|
{
|
||||||
|
return Child.Execute(mob, blackboard);
|
||||||
|
}
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class RandomExecutionNode : DecoratorNode
|
||||||
|
{
|
||||||
|
private double activationChance;
|
||||||
|
private Dictionary<BaseCreature, bool> executingCache = new Dictionary<BaseCreature, bool>();
|
||||||
|
public RandomExecutionNode(BehaviorTree tree, double chance) : base(tree)
|
||||||
|
{
|
||||||
|
activationChance = chance;
|
||||||
|
}
|
||||||
|
public RandomExecutionNode(BehaviorTree tree, double chance, BehaviorTreeNode child) : base(tree, child)
|
||||||
|
{
|
||||||
|
activationChance = chance;
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (!executingCache.TryGetValue(mob, out bool executing))
|
||||||
|
{
|
||||||
|
executing = false;
|
||||||
|
executingCache.Add(mob, executing);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Child != null && (Utility.RandomDouble() < activationChance || executing))
|
||||||
|
{
|
||||||
|
Result result = Child.Execute(mob, blackboard);
|
||||||
|
|
||||||
|
if (result == Result.Running)
|
||||||
|
{
|
||||||
|
executingCache[mob] = true;
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
executingCache[mob] = false;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Projects/UOContent/Mobiles/BT/Decorator/TapNode.cs
Normal file
29
Projects/UOContent/Mobiles/BT/Decorator/TapNode.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class TapNode : DecoratorNode
|
||||||
|
{
|
||||||
|
public delegate void TapNodeAction(BaseCreature mob, Blackboard blackboard);
|
||||||
|
public TapNodeAction Action { get; private set; }
|
||||||
|
public TapNode(BehaviorTree tree, TapNodeAction action) : base(tree)
|
||||||
|
{
|
||||||
|
Action = action;
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
Action(mob, blackboard);
|
||||||
|
|
||||||
|
if (Child != null)
|
||||||
|
{
|
||||||
|
return Child.Execute(mob, blackboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
Projects/UOContent/Mobiles/BT/Decorator/UntilFailNode.cs
Normal file
31
Projects/UOContent/Mobiles/BT/Decorator/UntilFailNode.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public class UntilFailNode : DecoratorNode
|
||||||
|
{
|
||||||
|
public UntilFailNode(BehaviorTree tree) : base(tree)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public UntilFailNode(BehaviorTree tree, BehaviorTreeNode child) : base(tree, child)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (Child != null)
|
||||||
|
{
|
||||||
|
if(Child.Execute(mob, blackboard) == Result.Failure)
|
||||||
|
{
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
return Result.Running;
|
||||||
|
}
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Projects/UOContent/Mobiles/BT/Leaf/ActionNode.cs
Normal file
15
Projects/UOContent/Mobiles/BT/Leaf/ActionNode.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public abstract class ActionNode : LeafNode
|
||||||
|
{
|
||||||
|
public ActionNode(BehaviorTree tree) : base(tree)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
Projects/UOContent/Mobiles/BT/Leaf/ConditionNode.cs
Normal file
26
Projects/UOContent/Mobiles/BT/Leaf/ConditionNode.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public delegate bool ConditionNodePredicate(BaseCreature mob, Blackboard blackboard);
|
||||||
|
public class ConditionNode : LeafNode
|
||||||
|
{
|
||||||
|
private ConditionNodePredicate predicate;
|
||||||
|
public ConditionNode(BehaviorTree tree, ConditionNodePredicate fn) : base(tree)
|
||||||
|
{
|
||||||
|
predicate = fn;
|
||||||
|
}
|
||||||
|
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||||
|
{
|
||||||
|
if (predicate(mob, blackboard))
|
||||||
|
{
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
Projects/UOContent/Mobiles/BT/MageBehaviorTree.cs
Normal file
35
Projects/UOContent/Mobiles/BT/MageBehaviorTree.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BT
|
||||||
|
{
|
||||||
|
public sealed class MageBehaviorTree : BehaviorTree
|
||||||
|
{
|
||||||
|
private static MageBehaviorTree instance;
|
||||||
|
|
||||||
|
private MageBehaviorTree()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public static MageBehaviorTree Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (instance == null)
|
||||||
|
{
|
||||||
|
instance = new MageBehaviorTree();
|
||||||
|
instance.InitTree();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void InitTree()
|
||||||
|
{
|
||||||
|
TryAddRoot(
|
||||||
|
// do the first thing that succeeds of the three children
|
||||||
|
new SelectorNode(this)
|
||||||
|
.AddChild(new TapNode(this, (mob, board) => mob.DebugSay("Thinking...")))
|
||||||
|
.AddChild(new BehaviorMageCombatNodeSet(this))
|
||||||
|
.AddChild(new BehaviorMagePassiveNodeSet(this))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2132,6 +2132,8 @@ namespace Server.Mobiles
|
||||||
// m_AI = new PredatorAI(this);
|
// m_AI = new PredatorAI(this);
|
||||||
new MeleeAI(this),
|
new MeleeAI(this),
|
||||||
AIType.AI_Thief => new ThiefAI(this),
|
AIType.AI_Thief => new ThiefAI(this),
|
||||||
|
AIType.AI_BehaviorTree => new AI.BehaviorTreeAI(this),
|
||||||
|
AIType.AI_FireBoss => new AI.FireBossAI(this),
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
Projects/UOContent/Mobiles/BehaviorAI/Composite/Selector.cs
Normal file
36
Projects/UOContent/Mobiles/BehaviorAI/Composite/Selector.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentChild++;
|
||||||
|
if (lastResult == Result.Success)
|
||||||
|
{
|
||||||
|
currentChildCache[context.Mobile] = 0;
|
||||||
|
SetResult(context, Result.Success);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentChild >= Children.Count)
|
||||||
|
{
|
||||||
|
currentChildCache[context.Mobile] = 0;
|
||||||
|
SetResult(context, Result.Failure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentChildCache[context.Mobile] = currentChild;
|
||||||
|
SetResult(context, Result.Running);
|
||||||
|
Tree.Enqueue(context, Children[currentChild], OnChildComplete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Projects/UOContent/Mobiles/BehaviorAI/Composite/Sequence.cs
Normal file
36
Projects/UOContent/Mobiles/BehaviorAI/Composite/Sequence.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentChild++;
|
||||||
|
if (lastResult == Result.Failure)
|
||||||
|
{
|
||||||
|
currentChildCache[context.Mobile] = 0;
|
||||||
|
SetResult(context, Result.Failure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentChild >= Children.Count)
|
||||||
|
{
|
||||||
|
currentChildCache[context.Mobile] = 0;
|
||||||
|
SetResult(context, Result.Success);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentChildCache[context.Mobile] = currentChild;
|
||||||
|
SetResult(context, Result.Running);
|
||||||
|
Tree.Enqueue(context, Children[currentChild], OnChildComplete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Projects/UOContent/Mobiles/BehaviorAI/Core/Behavior.cs
Normal file
51
Projects/UOContent/Mobiles/BehaviorAI/Core/Behavior.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BehaviorAI
|
||||||
|
{
|
||||||
|
public delegate void BehaviorObserver(BehaviorTreeContext context, Result lastResult);
|
||||||
|
public enum Result
|
||||||
|
{
|
||||||
|
Success,
|
||||||
|
Failure,
|
||||||
|
Running,
|
||||||
|
Terminated
|
||||||
|
}
|
||||||
|
public abstract class Behavior
|
||||||
|
{
|
||||||
|
public BehaviorTree Tree { get; protected set; }
|
||||||
|
public BehaviorObserver Observer { get; protected set; }
|
||||||
|
private readonly 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.Terminated;
|
||||||
|
lastResultCache.Add(context.Mobile, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public void SetResult(BehaviorTreeContext context, Result result)
|
||||||
|
{
|
||||||
|
if (!lastResultCache.TryGetValue(context.Mobile, out _))
|
||||||
|
{
|
||||||
|
lastResultCache.Add(context.Mobile, Result.Failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastResultCache[context.Mobile] = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
418
Projects/UOContent/Mobiles/BehaviorAI/Core/BehaviorTree.cs
Normal file
418
Projects/UOContent/Mobiles/BehaviorAI/Core/BehaviorTree.cs
Normal file
|
|
@ -0,0 +1,418 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Server.Items;
|
||||||
|
using Server.Engines.Spawners;
|
||||||
|
using MoveImpl = Server.Movement.MovementImpl;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BehaviorAI
|
||||||
|
{
|
||||||
|
public class BehaviorTree
|
||||||
|
{
|
||||||
|
public Behavior Root { get; private set; }
|
||||||
|
private readonly Dictionary<BaseCreature, Queue<BehaviorQueueEntry>> _behaviorQueueCache;
|
||||||
|
private readonly 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?.Behavior == null || current.Context == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
current.Behavior.Tick(current.Context);
|
||||||
|
|
||||||
|
if (!current.Behavior.IsRunning(current.Context))
|
||||||
|
{
|
||||||
|
current.Observer?.Invoke(current.Context, current.Behavior.GetResult(current.Context));
|
||||||
|
current.Behavior.SetResult(context, Result.Terminated);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.Enqueue(current);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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 = random switch
|
||||||
|
{
|
||||||
|
0 => Direction.Up,
|
||||||
|
1 => Direction.North,
|
||||||
|
2 => Direction.Left,
|
||||||
|
3 => Direction.West,
|
||||||
|
5 => Direction.Down,
|
||||||
|
6 => Direction.South,
|
||||||
|
7 => Direction.Right,
|
||||||
|
8 => Direction.East,
|
||||||
|
_ => mob.Direction
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Projects/UOContent/Mobiles/BehaviorAI/Core/Blackboard.cs
Normal file
8
Projects/UOContent/Mobiles/BehaviorAI/Core/Blackboard.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BehaviorAI
|
||||||
|
{
|
||||||
|
public class Blackboard : Dictionary<string, object>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Projects/UOContent/Mobiles/BehaviorAI/Core/Composite.cs
Normal file
43
Projects/UOContent/Mobiles/BehaviorAI/Core/Composite.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
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.Terminated)
|
||||||
|
{
|
||||||
|
currentChild = 0;
|
||||||
|
currentChildCache[context.Mobile] = currentChild;
|
||||||
|
SetResult(context, Result.Running);
|
||||||
|
Tree.Enqueue(context, Children[currentChild], OnChildComplete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public virtual void OnChildComplete(BehaviorTreeContext context, Result result)
|
||||||
|
{
|
||||||
|
SetResult(context, Result.Failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
Projects/UOContent/Mobiles/BehaviorAI/Core/Decorator.cs
Normal file
31
Projects/UOContent/Mobiles/BehaviorAI/Core/Decorator.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
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 Decorator AddChild(Behavior child)
|
||||||
|
{
|
||||||
|
Child ??= child;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public override void Tick(BehaviorTreeContext context)
|
||||||
|
{
|
||||||
|
if (Child != null && GetResult(context) != Result.Terminated)
|
||||||
|
{
|
||||||
|
Tree.Enqueue(context, Child, OnChildComplete);
|
||||||
|
SetResult(context, Result.Running);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public virtual void OnChildComplete(BehaviorTreeContext context, Result lastResult)
|
||||||
|
{
|
||||||
|
SetResult(context, lastResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
Projects/UOContent/Mobiles/BehaviorAI/Decorator/Cooldown.cs
Normal file
54
Projects/UOContent/Mobiles/BehaviorAI/Decorator/Cooldown.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
23
Projects/UOContent/Mobiles/BehaviorAI/Decorator/Inverter.cs
Normal file
23
Projects/UOContent/Mobiles/BehaviorAI/Decorator/Inverter.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
Projects/UOContent/Mobiles/BehaviorAI/Decorator/Loop.cs
Normal file
53
Projects/UOContent/Mobiles/BehaviorAI/Decorator/Loop.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
SetResult(context, Result.Failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Projects/UOContent/Mobiles/BehaviorAI/Decorator/UntilFail.cs
Normal file
24
Projects/UOContent/Mobiles/BehaviorAI/Decorator/UntilFail.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Projects/UOContent/Mobiles/BehaviorAI/Leaf/AutoTarget.cs
Normal file
51
Projects/UOContent/Mobiles/BehaviorAI/Leaf/AutoTarget.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
Projects/UOContent/Mobiles/BehaviorAI/Leaf/CancelTarget.cs
Normal file
22
Projects/UOContent/Mobiles/BehaviorAI/Leaf/CancelTarget.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
Projects/UOContent/Mobiles/BehaviorAI/Leaf/CastSpell.cs
Normal file
69
Projects/UOContent/Mobiles/BehaviorAI/Leaf/CastSpell.cs
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
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.Failure);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (owner.Spell.IsCasting)
|
||||||
|
{
|
||||||
|
SetResult(context, Result.Running);
|
||||||
|
owner.DebugSay("Already casting {0}...", ((Spell)owner.Spell).Name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!owner.Spell.IsCasting && ((Spell)owner.Spell).State == SpellState.Sequencing)
|
||||||
|
{
|
||||||
|
SetResult(context, Result.Success);
|
||||||
|
owner.DebugSay("Finished casting {0}...", ((Spell)owner.Spell).Name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetResult(context, Result.Failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
Projects/UOContent/Mobiles/BehaviorAI/Leaf/Condition.cs
Normal file
23
Projects/UOContent/Mobiles/BehaviorAI/Leaf/Condition.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
Projects/UOContent/Mobiles/BehaviorAI/Leaf/DynamicTarget.cs
Normal file
41
Projects/UOContent/Mobiles/BehaviorAI/Leaf/DynamicTarget.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
using Server.Targeting;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.BehaviorAI
|
||||||
|
{
|
||||||
|
public delegate object DynamicTargetCallback(BehaviorTreeContext context);
|
||||||
|
public class DynamicTarget : Behavior
|
||||||
|
{
|
||||||
|
public DynamicTargetCallback Callback { get; private set; }
|
||||||
|
public DynamicTarget(BehaviorTree tree, DynamicTargetCallback transformation) : base(tree)
|
||||||
|
{
|
||||||
|
Callback = transformation;
|
||||||
|
}
|
||||||
|
public override void Tick(BehaviorTreeContext context)
|
||||||
|
{
|
||||||
|
if (Callback == null)
|
||||||
|
{
|
||||||
|
SetResult(context, Result.Failure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Target target = context.Mobile.Target;
|
||||||
|
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
SetResult(context, Result.Failure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
object targeted = Callback(context);
|
||||||
|
|
||||||
|
if (targeted == null)
|
||||||
|
{
|
||||||
|
SetResult(context, Result.Failure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.Invoke(context.Mobile, targeted);
|
||||||
|
SetResult(context, Result.Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Projects/UOContent/Mobiles/BehaviorAI/Leaf/Tap.cs
Normal file
17
Projects/UOContent/Mobiles/BehaviorAI/Leaf/Tap.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Projects/UOContent/Mobiles/BehaviorAI/Leaf/WaitForTarget.cs
Normal file
18
Projects/UOContent/Mobiles/BehaviorAI/Leaf/WaitForTarget.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Projects/UOContent/Mobiles/BehaviorAI/Leaf/Wander.cs
Normal file
29
Projects/UOContent/Mobiles/BehaviorAI/Leaf/Wander.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Projects/UOContent/Mobiles/BehaviorAI/MageCombat.cs
Normal file
37
Projects/UOContent/Mobiles/BehaviorAI/MageCombat.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Projects/UOContent/Mobiles/BehaviorAI/MagePassive.cs
Normal file
14
Projects/UOContent/Mobiles/BehaviorAI/MagePassive.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
94
Projects/UOContent/Mobiles/BehaviorMage.cs
Normal file
94
Projects/UOContent/Mobiles/BehaviorMage.cs
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Server.Mobiles;
|
||||||
|
using Server.Items;
|
||||||
|
|
||||||
|
namespace Server.Mobiles
|
||||||
|
{
|
||||||
|
public class BehaviorMage : BaseCreature
|
||||||
|
{
|
||||||
|
[Constructible]
|
||||||
|
public BehaviorMage() : base(AIType.AI_BehaviorTree, FightMode.Evil, 2, 0, 0.01, 1)
|
||||||
|
{
|
||||||
|
// Debug = true;
|
||||||
|
|
||||||
|
InitBody();
|
||||||
|
|
||||||
|
SetStr(100, 100);
|
||||||
|
SetDex(25, 25);
|
||||||
|
SetInt(100, 100);
|
||||||
|
|
||||||
|
SetDamageType(ResistanceType.Physical, 100);
|
||||||
|
|
||||||
|
SetResistance(ResistanceType.Physical, 40, 50);
|
||||||
|
SetResistance(ResistanceType.Fire, 40, 50);
|
||||||
|
SetResistance(ResistanceType.Cold, 40, 50);
|
||||||
|
SetResistance(ResistanceType.Poison, 40, 50);
|
||||||
|
SetResistance(ResistanceType.Energy, 40, 50);
|
||||||
|
|
||||||
|
SetSkill(SkillName.EvalInt, 100.0);
|
||||||
|
SetSkill(SkillName.Magery, 100.0);
|
||||||
|
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 });
|
||||||
|
|
||||||
|
if (Utility.RandomBool())
|
||||||
|
{
|
||||||
|
SetSkill(SkillName.Swords, 100.0);
|
||||||
|
RangeFight = 1;
|
||||||
|
AddToBackpack(new Halberd() { Quality = WeaponQuality.Exceptional, Crafter = this });
|
||||||
|
AddToBackpack(new Katana() { Quality = WeaponQuality.Exceptional, Crafter = this });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetSkill(SkillName.Archery, 100.0);
|
||||||
|
RangeFight = 6;
|
||||||
|
AddToBackpack(new HeavyCrossbow() { Quality = WeaponQuality.Exceptional, Crafter = this });
|
||||||
|
AddToBackpack(new Bow() { Quality = WeaponQuality.Exceptional, Crafter = this });
|
||||||
|
AddToBackpack(new Bolt() { Amount = 300 });
|
||||||
|
AddToBackpack(new Arrow() { Amount = 300 });
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveSpeed = 0.01;
|
||||||
|
PassiveSpeed = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BehaviorMage(Serial serial) : base(serial)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public override bool CanDestroyObstacles => true;
|
||||||
|
public virtual bool GetGender() => Utility.RandomBool();
|
||||||
|
public virtual void InitBody()
|
||||||
|
{
|
||||||
|
InitStats(100, 100, 25);
|
||||||
|
|
||||||
|
SpeechHue = Utility.RandomDyedHue();
|
||||||
|
Hue = Race.Human.RandomSkinHue();
|
||||||
|
|
||||||
|
if (Female = GetGender())
|
||||||
|
{
|
||||||
|
Body = 0x191;
|
||||||
|
Name = NameList.RandomName("female");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Body = 0x190;
|
||||||
|
Name = NameList.RandomName("male");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public override void Serialize(IGenericWriter writer)
|
||||||
|
{
|
||||||
|
base.Serialize(writer);
|
||||||
|
}
|
||||||
|
public override void Deserialize(IGenericReader reader)
|
||||||
|
{
|
||||||
|
base.Deserialize(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
Projects/UOContent/Mobiles/FireBoss.cs
Normal file
44
Projects/UOContent/Mobiles/FireBoss.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
namespace Server.Mobiles
|
||||||
|
{
|
||||||
|
public class FireBoss : BaseCreature
|
||||||
|
{
|
||||||
|
[Constructible]
|
||||||
|
public FireBoss() : base(AIType.AI_FireBoss, FightMode.Closest, 10, 3, 0.05, 0.05)
|
||||||
|
{
|
||||||
|
Body = 172;
|
||||||
|
Hue = 1360;
|
||||||
|
|
||||||
|
SetStr(400);
|
||||||
|
SetDex(200);
|
||||||
|
SetInt(1000);
|
||||||
|
|
||||||
|
SetHits(300000);
|
||||||
|
|
||||||
|
SetDamage(50);
|
||||||
|
|
||||||
|
SetDamageType(ResistanceType.Fire, 100);
|
||||||
|
|
||||||
|
SetResistance(ResistanceType.Fire, 50);
|
||||||
|
|
||||||
|
Fame = 25000;
|
||||||
|
Karma = -25000;
|
||||||
|
|
||||||
|
VirtualArmor = 92;
|
||||||
|
}
|
||||||
|
public override string CorpseName => "fire boss corpse";
|
||||||
|
public override string DefaultName => "fire boss";
|
||||||
|
public FireBoss(Serial serial) : base(serial)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public override void Serialize(IGenericWriter writer)
|
||||||
|
{
|
||||||
|
base.Serialize(writer);
|
||||||
|
writer.Write(0);
|
||||||
|
}
|
||||||
|
public override void Deserialize(IGenericReader reader)
|
||||||
|
{
|
||||||
|
base.Deserialize(reader);
|
||||||
|
var version = reader.ReadInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -146,7 +146,7 @@ namespace Server.Mobiles
|
||||||
|
|
||||||
if (!IsInvulnerable)
|
if (!IsInvulnerable)
|
||||||
{
|
{
|
||||||
AI = AIType.AI_Mage;
|
AI = AIType.AI_BehaviorTree;
|
||||||
ActiveSpeed = 0.2;
|
ActiveSpeed = 0.2;
|
||||||
PassiveSpeed = 0.8;
|
PassiveSpeed = 0.8;
|
||||||
RangePerception = DefaultRangePerception;
|
RangePerception = DefaultRangePerception;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ namespace Server.Mobiles
|
||||||
[Constructible]
|
[Constructible]
|
||||||
public EvilHealer()
|
public EvilHealer()
|
||||||
{
|
{
|
||||||
|
AI = AIType.AI_BehaviorTree;
|
||||||
|
|
||||||
Title = "the healer";
|
Title = "the healer";
|
||||||
|
|
||||||
Karma = -10000;
|
Karma = -10000;
|
||||||
|
|
|
||||||
48
Projects/UOContent/Mobiles/Vendors/FireBossPriest.cs
Normal file
48
Projects/UOContent/Mobiles/Vendors/FireBossPriest.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Server.Mobiles.Vendors
|
||||||
|
{
|
||||||
|
public class FireBossPriest : BaseCreature
|
||||||
|
{
|
||||||
|
public FireBoss Owner { get; private set; }
|
||||||
|
public FireBossPriest(FireBoss owner) : base(AIType.AI_Animal, FightMode.Closest, 10, 1, 2, 2)
|
||||||
|
{
|
||||||
|
Owner = owner;
|
||||||
|
|
||||||
|
Body = 770;
|
||||||
|
Hue = 1358;
|
||||||
|
|
||||||
|
SetStr(1185);
|
||||||
|
SetDex(255);
|
||||||
|
SetInt(250);
|
||||||
|
|
||||||
|
SetHits(725);
|
||||||
|
|
||||||
|
SetDamage(25);
|
||||||
|
|
||||||
|
Fame = 24000;
|
||||||
|
Karma = -24000;
|
||||||
|
|
||||||
|
VirtualArmor = 90;
|
||||||
|
}
|
||||||
|
public FireBossPriest(Serial serial) : base(serial)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public override void Serialize(IGenericWriter writer)
|
||||||
|
{
|
||||||
|
base.Serialize(writer);
|
||||||
|
writer.Write(0);
|
||||||
|
writer.Write(Owner);
|
||||||
|
}
|
||||||
|
public override void Deserialize(IGenericReader reader)
|
||||||
|
{
|
||||||
|
base.Deserialize(reader);
|
||||||
|
var version = reader.ReadInt();
|
||||||
|
Owner = reader.ReadEntity<FireBoss>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -85,7 +85,7 @@ namespace Server.Spells
|
||||||
public virtual void OnCasterHurt()
|
public virtual void OnCasterHurt()
|
||||||
{
|
{
|
||||||
// Confirm: Monsters and pets cannot be disturbed.
|
// Confirm: Monsters and pets cannot be disturbed.
|
||||||
if (Caster.Player && IsCasting)
|
if ((Caster.Player || Caster is BehaviorMage) && IsCasting)
|
||||||
{
|
{
|
||||||
var hasProtection = ProtectionSpell.Registry.TryGetValue(Caster, out var d);
|
var hasProtection = ProtectionSpell.Registry.TryGetValue(Caster, out var d);
|
||||||
if (!hasProtection || d < 1000 && d < Utility.Random(1000))
|
if (!hasProtection || d < 1000 && d < Utility.Random(1000))
|
||||||
|
|
@ -410,7 +410,7 @@ namespace Server.Spells
|
||||||
_castTimer?.Stop();
|
_castTimer?.Stop();
|
||||||
_animTimer?.Stop();
|
_animTimer?.Stop();
|
||||||
|
|
||||||
if (Core.AOS && Caster.Player && type == DisturbType.Hurt)
|
if (Core.AOS && (Caster.Player || Caster is BehaviorMage) && type == DisturbType.Hurt)
|
||||||
{
|
{
|
||||||
DoHurtFizzle();
|
DoHurtFizzle();
|
||||||
}
|
}
|
||||||
|
|
@ -423,7 +423,7 @@ namespace Server.Spells
|
||||||
|
|
||||||
Target.Cancel(Caster);
|
Target.Cancel(Caster);
|
||||||
|
|
||||||
if (Core.AOS && Caster.Player && type == DisturbType.Hurt)
|
if (Core.AOS && (Caster.Player || Caster is BehaviorMage) && type == DisturbType.Hurt)
|
||||||
{
|
{
|
||||||
DoHurtFizzle();
|
DoHurtFizzle();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue