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
|
||||
{
|
||||
public delegate MoveResult MoveMethod(Direction d);
|
||||
|
||||
public enum MoveResult
|
||||
{
|
||||
BadState,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ namespace Server.Mobiles
|
|||
AI_Mage,
|
||||
AI_Berserk,
|
||||
AI_Predator,
|
||||
AI_Thief
|
||||
AI_Thief,
|
||||
AI_BehaviorTree,
|
||||
AI_FireBoss
|
||||
}
|
||||
|
||||
public enum ActionType
|
||||
|
|
@ -2010,7 +2012,7 @@ namespace Server.Mobiles
|
|||
|
||||
if (canOpenDoors || canDestroyObstacles)
|
||||
{
|
||||
m_Mobile.DebugSay("My movement was blocked, I will try to clear some obstacles.");
|
||||
// m_Mobile.DebugSay("My movement was blocked, I will try to clear some obstacles.");
|
||||
|
||||
var map = m_Mobile.Map;
|
||||
|
||||
|
|
@ -2436,7 +2438,7 @@ namespace Server.Mobiles
|
|||
|
||||
m_Mobile.NextReacquireTime = Core.TickCount + (int)m_Mobile.ReacquireDelay.TotalMilliseconds;
|
||||
|
||||
m_Mobile.DebugSay("Acquiring...");
|
||||
// m_Mobile.DebugSay("Acquiring...");
|
||||
|
||||
var map = m_Mobile.Map;
|
||||
|
||||
|
|
@ -3069,6 +3071,7 @@ namespace Server.Mobiles
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if (m_Owner.CanDetectHidden && Core.TickCount - m_Owner.m_NextDetectHidden >= 0)
|
||||
{
|
||||
m_Owner.DetectHidden();
|
||||
|
|
@ -3082,6 +3085,7 @@ namespace Server.Mobiles
|
|||
m_Owner.m_NextDetectHidden = Core.TickCount +
|
||||
(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();
|
||||
|
||||
/*
|
||||
if (Utility.RandomDouble() < 0.05)
|
||||
{
|
||||
var spell = CheckCastHealingSpell();
|
||||
|
||||
spell?.Cast();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -115,10 +117,12 @@ namespace Server.Mobiles
|
|||
private Spell CheckCastHealingSpell()
|
||||
{
|
||||
// If I'm poisoned, always attempt to cure.
|
||||
/*
|
||||
if (m_Mobile.Poisoned)
|
||||
{
|
||||
return new CureSpell(m_Mobile);
|
||||
}
|
||||
*/
|
||||
|
||||
// Summoned creatures never heal themselves.
|
||||
if (m_Mobile.Summoned)
|
||||
|
|
@ -151,6 +155,7 @@ namespace Server.Mobiles
|
|||
|
||||
Spell spell = null;
|
||||
|
||||
/*
|
||||
if (m_Mobile.Hits < m_Mobile.HitsMax - 50)
|
||||
{
|
||||
if (UseNecromancy())
|
||||
|
|
@ -166,6 +171,7 @@ namespace Server.Mobiles
|
|||
{
|
||||
spell = new HealSpell(m_Mobile);
|
||||
}
|
||||
*/
|
||||
|
||||
double delay;
|
||||
|
||||
|
|
@ -765,6 +771,7 @@ namespace Server.Mobiles
|
|||
Spell spell;
|
||||
var toDispel = FindDispelTarget(true);
|
||||
|
||||
/*
|
||||
if (m_Mobile.Poisoned) // Top cast priority is cure
|
||||
{
|
||||
m_Mobile.DebugSay("I am going to cure myself");
|
||||
|
|
@ -772,6 +779,8 @@ namespace Server.Mobiles
|
|||
spell = new CureSpell(m_Mobile);
|
||||
}
|
||||
else if (toDispel != null) // Something dispellable is attacking us
|
||||
*/
|
||||
if (toDispel != null)
|
||||
{
|
||||
m_Mobile.DebugSay("I am going to dispel {0}", toDispel);
|
||||
|
||||
|
|
@ -891,10 +900,12 @@ namespace Server.Mobiles
|
|||
RunFrom(m_Mobile.FocusMob);
|
||||
m_Mobile.FocusMob = null;
|
||||
|
||||
/*
|
||||
if (m_Mobile.Poisoned && Utility.Random(0, 5) == 0)
|
||||
{
|
||||
new CureSpell(m_Mobile).Cast();
|
||||
}
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
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);
|
||||
new MeleeAI(this),
|
||||
AIType.AI_Thief => new ThiefAI(this),
|
||||
AIType.AI_BehaviorTree => new AI.BehaviorTreeAI(this),
|
||||
AIType.AI_FireBoss => new AI.FireBossAI(this),
|
||||
_ => 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)
|
||||
{
|
||||
AI = AIType.AI_Mage;
|
||||
AI = AIType.AI_BehaviorTree;
|
||||
ActiveSpeed = 0.2;
|
||||
PassiveSpeed = 0.8;
|
||||
RangePerception = DefaultRangePerception;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ namespace Server.Mobiles
|
|||
[Constructible]
|
||||
public EvilHealer()
|
||||
{
|
||||
AI = AIType.AI_BehaviorTree;
|
||||
|
||||
Title = "the healer";
|
||||
|
||||
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()
|
||||
{
|
||||
// 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);
|
||||
if (!hasProtection || d < 1000 && d < Utility.Random(1000))
|
||||
|
|
@ -410,7 +410,7 @@ namespace Server.Spells
|
|||
_castTimer?.Stop();
|
||||
_animTimer?.Stop();
|
||||
|
||||
if (Core.AOS && Caster.Player && type == DisturbType.Hurt)
|
||||
if (Core.AOS && (Caster.Player || Caster is BehaviorMage) && type == DisturbType.Hurt)
|
||||
{
|
||||
DoHurtFizzle();
|
||||
}
|
||||
|
|
@ -423,7 +423,7 @@ namespace Server.Spells
|
|||
|
||||
Target.Cancel(Caster);
|
||||
|
||||
if (Core.AOS && Caster.Player && type == DisturbType.Hurt)
|
||||
if (Core.AOS && (Caster.Player || Caster is BehaviorMage) && type == DisturbType.Hurt)
|
||||
{
|
||||
DoHurtFizzle();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue