From 0e84739f2d4ccf1d7f889b3abda1b187526d5519 Mon Sep 17 00:00:00 2001 From: Leath Cooper Date: Fri, 10 Sep 2021 20:58:02 -0400 Subject: [PATCH] tree stuff --- .../UOContent/Engines/Pathing/MoveResult.cs | 1 - Projects/UOContent/Mobiles/AI/BaseAI.cs | 19 +- .../UOContent/Mobiles/AI/BehaviorTreeAI.cs | 29 +- Projects/UOContent/Mobiles/AI/FireBossAI.cs | 37 ++ .../Mobiles/BT/Actions/CastSpellActionNode.cs | 61 +++ .../Mobiles/BT/Actions/CureSelfNode.cs | 24 ++ .../Mobiles/BT/Actions/EquipItemNode.cs | 28 ++ .../Mobiles/BT/Actions/HealSelfNode.cs | 36 ++ .../Mobiles/BT/Actions/KeepRangeNode.cs | 29 ++ .../Mobiles/BT/Actions/MageComboNode.cs | 140 ++++++ .../Mobiles/BT/Actions/MageDebuffNode.cs | 40 ++ .../Mobiles/BT/Actions/MeditateNode.cs | 20 + .../Mobiles/BT/Actions/PoisonEnemyNode.cs | 34 ++ .../Mobiles/BT/Actions/SetBlackboardData.cs | 33 ++ .../Mobiles/BT/Actions/TargetActionNode.cs | 53 +++ .../Mobiles/BT/Actions/UseSkillNode.cs | 26 ++ .../Mobiles/BT/Actions/WanderNode.cs | 21 + .../Mobiles/BT/BehaviorMageCombatNodeSet.cs | 32 ++ .../Mobiles/BT/BehaviorMagePassiveNodeSet.cs | 25 ++ .../BT/Composite/RandomSelectorNode.cs | 60 +++ .../Mobiles/BT/Composite/SelectorNode.cs | 59 +++ .../Mobiles/BT/Composite/SequenceNode.cs | 59 +++ .../UOContent/Mobiles/BT/Core/BehaviorTree.cs | 400 ++++++++++++++++++ .../Mobiles/BT/Core/BehaviorTreeNode.cs | 27 ++ .../UOContent/Mobiles/BT/Core/Blackboard.cs | 12 + .../Mobiles/BT/Core/CompositeNode.cs | 25 ++ .../Mobiles/BT/Core/DecoratorNode.cs | 28 ++ .../{BehaviorTreeAI => BT/Core}/LeafNode.cs | 2 +- .../Mobiles/BT/Decorator/CheckSkillNode.cs | 32 ++ .../BT/Decorator/ConditionalLoopNode.cs | 36 ++ .../Mobiles/BT/Decorator/CooldownNode.cs | 51 +++ .../Mobiles/BT/Decorator/ForceSuccessNode.cs | 31 ++ .../Mobiles/BT/Decorator/InverterNode.cs | 36 ++ .../Mobiles/BT/Decorator/LoopNode.cs | 42 ++ .../BT/Decorator/OwnerConditionNode.cs | 34 ++ .../BT/Decorator/RandomExecutionNode.cs | 45 ++ .../UOContent/Mobiles/BT/Decorator/TapNode.cs | 29 ++ .../Decorator}/UntilFailNode.cs | 14 +- .../{BehaviorTreeAI => BT/Leaf}/ActionNode.cs | 2 +- .../Mobiles/BT/Leaf/ConditionNode.cs | 26 ++ .../UOContent/Mobiles/BT/MageBehaviorTree.cs | 35 ++ Projects/UOContent/Mobiles/BaseCreature.cs | 1 + Projects/UOContent/Mobiles/BehaviorMage.cs | 91 ++++ .../Actions/CastSpellActionNode.cs | 63 --- .../BehaviorTreeAI/Actions/CureSelfNode.cs | 105 ----- .../Actions/DetectHiddenActionNode.cs | 118 ------ .../Actions/HealSelfActionNode.cs | 58 --- .../BehaviorTreeAI/Actions/MageComboNode.cs | 185 -------- .../BehaviorTreeAI/Actions/SelfHealerNode.cs | 50 --- .../Actions/TargetActionNode.cs | 59 --- .../BehaviorTreeAI/AlwaysSucceedNode.cs | 27 -- .../Mobiles/BehaviorTreeAI/BehaviorTree.cs | 207 --------- .../BehaviorTreeAI/BehaviorTreeNode.cs | 21 - .../Mobiles/BehaviorTreeAI/ConditionNode.cs | 25 -- .../Mobiles/BehaviorTreeAI/DecoratorNode.cs | 21 - .../Mobiles/BehaviorTreeAI/InverterNode.cs | 35 -- .../Mobiles/BehaviorTreeAI/RepeaterNode.cs | 28 -- .../Mobiles/BehaviorTreeAI/SelectorNode.cs | 48 --- .../Mobiles/BehaviorTreeAI/SequenceNode.cs | 48 --- .../Mobiles/BehaviorTreeAI/TapNode.cs | 31 -- Projects/UOContent/Mobiles/FireBoss.cs | 44 ++ .../UOContent/Mobiles/Healers/BaseHealer.cs | 2 +- .../UOContent/Mobiles/Healers/EvilHealer.cs | 4 - .../Mobiles/Vendors/FireBossPriest.cs | 48 +++ Projects/UOContent/Spells/Base/Spell.cs | 6 +- 65 files changed, 1916 insertions(+), 1182 deletions(-) create mode 100644 Projects/UOContent/Mobiles/AI/FireBossAI.cs create mode 100644 Projects/UOContent/Mobiles/BT/Actions/CastSpellActionNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Actions/CureSelfNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Actions/EquipItemNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Actions/HealSelfNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Actions/KeepRangeNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Actions/MageComboNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Actions/MageDebuffNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Actions/MeditateNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Actions/PoisonEnemyNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Actions/SetBlackboardData.cs create mode 100644 Projects/UOContent/Mobiles/BT/Actions/TargetActionNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Actions/UseSkillNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Actions/WanderNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/BehaviorMageCombatNodeSet.cs create mode 100644 Projects/UOContent/Mobiles/BT/BehaviorMagePassiveNodeSet.cs create mode 100644 Projects/UOContent/Mobiles/BT/Composite/RandomSelectorNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Composite/SelectorNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Composite/SequenceNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Core/BehaviorTree.cs create mode 100644 Projects/UOContent/Mobiles/BT/Core/BehaviorTreeNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Core/Blackboard.cs create mode 100644 Projects/UOContent/Mobiles/BT/Core/CompositeNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Core/DecoratorNode.cs rename Projects/UOContent/Mobiles/{BehaviorTreeAI => BT/Core}/LeafNode.cs (86%) create mode 100644 Projects/UOContent/Mobiles/BT/Decorator/CheckSkillNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Decorator/ConditionalLoopNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Decorator/CooldownNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Decorator/ForceSuccessNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Decorator/InverterNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Decorator/LoopNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Decorator/OwnerConditionNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Decorator/RandomExecutionNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/Decorator/TapNode.cs rename Projects/UOContent/Mobiles/{BehaviorTreeAI => BT/Decorator}/UntilFailNode.cs (53%) rename Projects/UOContent/Mobiles/{BehaviorTreeAI => BT/Leaf}/ActionNode.cs (86%) create mode 100644 Projects/UOContent/Mobiles/BT/Leaf/ConditionNode.cs create mode 100644 Projects/UOContent/Mobiles/BT/MageBehaviorTree.cs create mode 100644 Projects/UOContent/Mobiles/BehaviorMage.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/CastSpellActionNode.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/CureSelfNode.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/DetectHiddenActionNode.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/HealSelfActionNode.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/MageComboNode.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/SelfHealerNode.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/TargetActionNode.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/AlwaysSucceedNode.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/BehaviorTree.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/BehaviorTreeNode.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/ConditionNode.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/DecoratorNode.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/InverterNode.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/RepeaterNode.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/SelectorNode.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/SequenceNode.cs delete mode 100644 Projects/UOContent/Mobiles/BehaviorTreeAI/TapNode.cs create mode 100644 Projects/UOContent/Mobiles/FireBoss.cs create mode 100644 Projects/UOContent/Mobiles/Vendors/FireBossPriest.cs diff --git a/Projects/UOContent/Engines/Pathing/MoveResult.cs b/Projects/UOContent/Engines/Pathing/MoveResult.cs index 643f4f19b..e9cf96963 100644 --- a/Projects/UOContent/Engines/Pathing/MoveResult.cs +++ b/Projects/UOContent/Engines/Pathing/MoveResult.cs @@ -1,7 +1,6 @@ namespace Server { public delegate MoveResult MoveMethod(Direction d); - public enum MoveResult { BadState, diff --git a/Projects/UOContent/Mobiles/AI/BaseAI.cs b/Projects/UOContent/Mobiles/AI/BaseAI.cs index 0ad9e0543..2fd898801 100644 --- a/Projects/UOContent/Mobiles/AI/BaseAI.cs +++ b/Projects/UOContent/Mobiles/AI/BaseAI.cs @@ -13,7 +13,6 @@ using Server.Spells; using Server.Spells.Spellweaving; using Server.Targets; using MoveImpl = Server.Movement.MovementImpl; -using Server.Mobiles.BehaviorTreeAI; namespace Server.Mobiles { @@ -29,7 +28,8 @@ namespace Server.Mobiles AI_Berserk, AI_Predator, AI_Thief, - AI_BehaviorTree + AI_BehaviorTree, + AI_FireBoss } public enum ActionType @@ -138,21 +138,6 @@ namespace Server.Mobiles } Action = ActionType.Wander; - - /* - behaviorTree = new BehaviorTree(this.m_Mobile); - - behaviorTree.AddRoot( - new RepeaterNode(behaviorTree, - new SelectorNode(behaviorTree, new BehaviorTreeNode[1] - { - new SelfHealerNode(behaviorTree), - }) - ) - ); - - behaviorTree.Start(); - */ } public ActionType Action diff --git a/Projects/UOContent/Mobiles/AI/BehaviorTreeAI.cs b/Projects/UOContent/Mobiles/AI/BehaviorTreeAI.cs index f9a281bde..0606b97bb 100644 --- a/Projects/UOContent/Mobiles/AI/BehaviorTreeAI.cs +++ b/Projects/UOContent/Mobiles/AI/BehaviorTreeAI.cs @@ -3,31 +3,17 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using Server.Mobiles.BehaviorTreeAI; +using Server.Mobiles.BT; using Server.Targeting; namespace Server.Mobiles.AI { public class BehaviorTreeAI : BaseAI { - private BehaviorTree behaviorTree; + private Blackboard blackboard; public BehaviorTreeAI(BaseCreature m) : base(m) { - behaviorTree = new BehaviorTree(m); - - behaviorTree.AddRoot( - new SelectorNode(behaviorTree, new BehaviorTreeNode[2] - { - new SelfHealerNode(behaviorTree), - // new MageComboNode(behaviorTree), - new TapNode( - behaviorTree, - (mob) => mob.DebugSay("I am taking no action") - ) - }) - ); - - behaviorTree.Start(); + blackboard = new Blackboard(); } public override bool Think() @@ -37,9 +23,14 @@ namespace Server.Mobiles.AI return false; } - // behaviorTree.RunBehavior(); + MageBehaviorTree.Instance.RunBehavior(m_Mobile, blackboard); - return base.Think(); + return true; + } + + public override bool DoActionWander() + { + return true; } } } diff --git a/Projects/UOContent/Mobiles/AI/FireBossAI.cs b/Projects/UOContent/Mobiles/AI/FireBossAI.cs new file mode 100644 index 000000000..7a7e9a187 --- /dev/null +++ b/Projects/UOContent/Mobiles/AI/FireBossAI.cs @@ -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(); + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Actions/CastSpellActionNode.cs b/Projects/UOContent/Mobiles/BT/Actions/CastSpellActionNode.cs new file mode 100644 index 000000000..232331ccc --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Actions/CastSpellActionNode.cs @@ -0,0 +1,61 @@ +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; + } + else if (mob.Spell.IsCasting) + { + mob.DebugSay("Still casting..."); + return Result.Running; + } + else if (!mob.Spell.IsCasting) + { + mob.DebugSay("Finished casting..."); + return Result.Success; + } + + return Result.Failure; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Actions/CureSelfNode.cs b/Projects/UOContent/Mobiles/BT/Actions/CureSelfNode.cs new file mode 100644 index 000000000..0d40b09f9 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Actions/CureSelfNode.cs @@ -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)) + ) + ); + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Actions/EquipItemNode.cs b/Projects/UOContent/Mobiles/BT/Actions/EquipItemNode.cs new file mode 100644 index 000000000..dcc631891 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Actions/EquipItemNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Actions/HealSelfNode.cs b/Projects/UOContent/Mobiles/BT/Actions/HealSelfNode.cs new file mode 100644 index 000000000..2c9775671 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Actions/HealSelfNode.cs @@ -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); + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Actions/KeepRangeNode.cs b/Projects/UOContent/Mobiles/BT/Actions/KeepRangeNode.cs new file mode 100644 index 000000000..d6026c79e --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Actions/KeepRangeNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Actions/MageComboNode.cs b/Projects/UOContent/Mobiles/BT/Actions/MageComboNode.cs new file mode 100644 index 000000000..b6ff1ec06 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Actions/MageComboNode.cs @@ -0,0 +1,140 @@ +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); + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Actions/MageDebuffNode.cs b/Projects/UOContent/Mobiles/BT/Actions/MageDebuffNode.cs new file mode 100644 index 000000000..b98277ac6 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Actions/MageDebuffNode.cs @@ -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)) + ) + ); + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Actions/MeditateNode.cs b/Projects/UOContent/Mobiles/BT/Actions/MeditateNode.cs new file mode 100644 index 000000000..c82a16ad4 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Actions/MeditateNode.cs @@ -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)))) + ); + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Actions/PoisonEnemyNode.cs b/Projects/UOContent/Mobiles/BT/Actions/PoisonEnemyNode.cs new file mode 100644 index 000000000..334b6b7e9 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Actions/PoisonEnemyNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Actions/SetBlackboardData.cs b/Projects/UOContent/Mobiles/BT/Actions/SetBlackboardData.cs new file mode 100644 index 000000000..e7f8a4c1e --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Actions/SetBlackboardData.cs @@ -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 : 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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Actions/TargetActionNode.cs b/Projects/UOContent/Mobiles/BT/Actions/TargetActionNode.cs new file mode 100644 index 000000000..c547e562d --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Actions/TargetActionNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Actions/UseSkillNode.cs b/Projects/UOContent/Mobiles/BT/Actions/UseSkillNode.cs new file mode 100644 index 000000000..f7f9cf1c4 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Actions/UseSkillNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Actions/WanderNode.cs b/Projects/UOContent/Mobiles/BT/Actions/WanderNode.cs new file mode 100644 index 000000000..71630de4f --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Actions/WanderNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/BehaviorMageCombatNodeSet.cs b/Projects/UOContent/Mobiles/BT/BehaviorMageCombatNodeSet.cs new file mode 100644 index 000000000..1f9433943 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/BehaviorMageCombatNodeSet.cs @@ -0,0 +1,32 @@ +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, 2)))) + .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); + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/BehaviorMagePassiveNodeSet.cs b/Projects/UOContent/Mobiles/BT/BehaviorMagePassiveNodeSet.cs new file mode 100644 index 000000000..53c1afbcb --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/BehaviorMagePassiveNodeSet.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Composite/RandomSelectorNode.cs b/Projects/UOContent/Mobiles/BT/Composite/RandomSelectorNode.cs new file mode 100644 index 000000000..334104d65 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Composite/RandomSelectorNode.cs @@ -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 retryCache = new Dictionary(); + 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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Composite/SelectorNode.cs b/Projects/UOContent/Mobiles/BT/Composite/SelectorNode.cs new file mode 100644 index 000000000..127b504dc --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Composite/SelectorNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Composite/SequenceNode.cs b/Projects/UOContent/Mobiles/BT/Composite/SequenceNode.cs new file mode 100644 index 000000000..10e14c2d8 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Composite/SequenceNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Core/BehaviorTree.cs b/Projects/UOContent/Mobiles/BT/Core/BehaviorTree.cs new file mode 100644 index 000000000..06bdb6b45 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Core/BehaviorTree.cs @@ -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 Claims = new Dictionary(); + 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 obstacles = new List(); + + 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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Core/BehaviorTreeNode.cs b/Projects/UOContent/Mobiles/BT/Core/BehaviorTreeNode.cs new file mode 100644 index 000000000..c41605a06 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Core/BehaviorTreeNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Core/Blackboard.cs b/Projects/UOContent/Mobiles/BT/Core/Blackboard.cs new file mode 100644 index 000000000..a75a026f0 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Core/Blackboard.cs @@ -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 + { + } +} diff --git a/Projects/UOContent/Mobiles/BT/Core/CompositeNode.cs b/Projects/UOContent/Mobiles/BT/Core/CompositeNode.cs new file mode 100644 index 000000000..ea9acbfbb --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Core/CompositeNode.cs @@ -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 currentTaskCache; + public List Children { get; protected set; } + public CompositeNode(BehaviorTree tree) : base(tree) + { + currentTaskCache = new Dictionary(); + Children = new List(); + } + public virtual CompositeNode AddChild(BehaviorTreeNode child) + { + child.Parent = this; + Children.Add(child); + return this; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Core/DecoratorNode.cs b/Projects/UOContent/Mobiles/BT/Core/DecoratorNode.cs new file mode 100644 index 000000000..8c9143690 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Core/DecoratorNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/LeafNode.cs b/Projects/UOContent/Mobiles/BT/Core/LeafNode.cs similarity index 86% rename from Projects/UOContent/Mobiles/BehaviorTreeAI/LeafNode.cs rename to Projects/UOContent/Mobiles/BT/Core/LeafNode.cs index 8c84a0bf6..b8c1f2866 100644 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/LeafNode.cs +++ b/Projects/UOContent/Mobiles/BT/Core/LeafNode.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Server.Mobiles.BehaviorTreeAI +namespace Server.Mobiles.BT { public abstract class LeafNode : BehaviorTreeNode { diff --git a/Projects/UOContent/Mobiles/BT/Decorator/CheckSkillNode.cs b/Projects/UOContent/Mobiles/BT/Decorator/CheckSkillNode.cs new file mode 100644 index 000000000..a2ed01dbb --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Decorator/CheckSkillNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Decorator/ConditionalLoopNode.cs b/Projects/UOContent/Mobiles/BT/Decorator/ConditionalLoopNode.cs new file mode 100644 index 000000000..6e3a89381 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Decorator/ConditionalLoopNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Decorator/CooldownNode.cs b/Projects/UOContent/Mobiles/BT/Decorator/CooldownNode.cs new file mode 100644 index 000000000..574450125 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Decorator/CooldownNode.cs @@ -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 cooldowns; + public CooldownNode(BehaviorTree tree, TimeSpan duration) : base(tree) + { + cooldownDuration = duration; + cooldowns = new Dictionary(); + } + public CooldownNode(BehaviorTree tree, TimeSpan duration, BehaviorTreeNode child) : base(tree, child) + { + cooldownDuration = duration; + cooldowns = new Dictionary(); + } + 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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Decorator/ForceSuccessNode.cs b/Projects/UOContent/Mobiles/BT/Decorator/ForceSuccessNode.cs new file mode 100644 index 000000000..4e546328d --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Decorator/ForceSuccessNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Decorator/InverterNode.cs b/Projects/UOContent/Mobiles/BT/Decorator/InverterNode.cs new file mode 100644 index 000000000..a1f5deb46 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Decorator/InverterNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Decorator/LoopNode.cs b/Projects/UOContent/Mobiles/BT/Decorator/LoopNode.cs new file mode 100644 index 000000000..c909d267a --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Decorator/LoopNode.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; + +namespace Server.Mobiles.BT +{ + public class LoopNode : DecoratorNode + { + private int count; + private Dictionary currentCountCache; + public LoopNode(BehaviorTree tree, int n) : base(tree) + { + count = n; + currentCountCache = new Dictionary(); + } + public LoopNode(BehaviorTree tree, int n, BehaviorTreeNode child) : base(tree, child) + { + count = n; + currentCountCache = new Dictionary(); + } + 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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Decorator/OwnerConditionNode.cs b/Projects/UOContent/Mobiles/BT/Decorator/OwnerConditionNode.cs new file mode 100644 index 000000000..bbb4e7d76 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Decorator/OwnerConditionNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Decorator/RandomExecutionNode.cs b/Projects/UOContent/Mobiles/BT/Decorator/RandomExecutionNode.cs new file mode 100644 index 000000000..676e3108d --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Decorator/RandomExecutionNode.cs @@ -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 executingCache = new Dictionary(); + 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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/Decorator/TapNode.cs b/Projects/UOContent/Mobiles/BT/Decorator/TapNode.cs new file mode 100644 index 000000000..2a9c563d8 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Decorator/TapNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/UntilFailNode.cs b/Projects/UOContent/Mobiles/BT/Decorator/UntilFailNode.cs similarity index 53% rename from Projects/UOContent/Mobiles/BehaviorTreeAI/UntilFailNode.cs rename to Projects/UOContent/Mobiles/BT/Decorator/UntilFailNode.cs index 6011f2c50..e5cd471c2 100644 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/UntilFailNode.cs +++ b/Projects/UOContent/Mobiles/BT/Decorator/UntilFailNode.cs @@ -4,26 +4,28 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Server.Mobiles.BehaviorTreeAI +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() + public override Result Execute(BaseCreature mob, Blackboard blackboard) { if (Child != null) { - var result = Child.Execute(); - - if (result == Result.Failure) + if(Child.Execute(mob, blackboard) == Result.Failure) { return Result.Failure; } + return Result.Running; } - return Result.Running; + return Result.Failure; } } } diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/ActionNode.cs b/Projects/UOContent/Mobiles/BT/Leaf/ActionNode.cs similarity index 86% rename from Projects/UOContent/Mobiles/BehaviorTreeAI/ActionNode.cs rename to Projects/UOContent/Mobiles/BT/Leaf/ActionNode.cs index f1cd3da6e..41d59d46d 100644 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/ActionNode.cs +++ b/Projects/UOContent/Mobiles/BT/Leaf/ActionNode.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Server.Mobiles.BehaviorTreeAI +namespace Server.Mobiles.BT { public abstract class ActionNode : LeafNode { diff --git a/Projects/UOContent/Mobiles/BT/Leaf/ConditionNode.cs b/Projects/UOContent/Mobiles/BT/Leaf/ConditionNode.cs new file mode 100644 index 000000000..fc1c3888a --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/Leaf/ConditionNode.cs @@ -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; + } + } +} diff --git a/Projects/UOContent/Mobiles/BT/MageBehaviorTree.cs b/Projects/UOContent/Mobiles/BT/MageBehaviorTree.cs new file mode 100644 index 000000000..6d993d277 --- /dev/null +++ b/Projects/UOContent/Mobiles/BT/MageBehaviorTree.cs @@ -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)) + ); + } + } +} diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index 922fccfee..f7e374687 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -2188,6 +2188,7 @@ namespace Server.Mobiles new MeleeAI(this), AIType.AI_Thief => new ThiefAI(this), AIType.AI_BehaviorTree => new AI.BehaviorTreeAI(this), + AIType.AI_FireBoss => new AI.FireBossAI(this), _ => null }; } diff --git a/Projects/UOContent/Mobiles/BehaviorMage.cs b/Projects/UOContent/Mobiles/BehaviorMage.cs new file mode 100644 index 000000000..370b079b6 --- /dev/null +++ b/Projects/UOContent/Mobiles/BehaviorMage.cs @@ -0,0 +1,91 @@ +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, 3) + { + 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); + + 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); + } + } +} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/CastSpellActionNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/CastSpellActionNode.cs deleted file mode 100644 index 39b81a6ba..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/CastSpellActionNode.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Server.Spells; - -namespace Server.Mobiles.BehaviorTreeAI -{ - public class CastSpellActionNode : ActionNode - { - private Func getSpellCallback; - public CastSpellActionNode(BehaviorTree tree, Func callback) : base(tree) - { - object nextCastTime; - if (!Tree.Blackboard.TryGetValue("nextCastTime", out nextCastTime)) - { - tree.Blackboard.Add("nextCastTime", DateTime.Now); - } - getSpellCallback = callback; - } - - public override Result Execute() - { - object nextCastTime; - - if (Tree.Blackboard.TryGetValue("nextCastTime", out nextCastTime) && DateTime.Now < (DateTime)nextCastTime) - { - Tree.Mobile.DebugSay("Spells are on cooldown..."); - return Result.Failure; - } - - if (Tree.Mobile.Spell == null) - { - Spell spell = getSpellCallback(Tree.Mobile); - - Tree.Mobile.DebugSay("Casting..."); - if (!spell.Cast()) - { - Tree.Mobile.DebugSay("Failed to cast..."); - return Result.Failure; - } - - if (!string.IsNullOrEmpty(spell.Mantra)) - Tree.Mobile.PublicOverheadMessage(Network.MessageType.Spell, 0, false, spell.Mantra, false); - - return Result.Running; - } - else if (Tree.Mobile.Spell.IsCasting) - { - Tree.Mobile.DebugSay("Still casting..."); - return Result.Running; - } - else if (!Tree.Mobile.Spell.IsCasting) - { - Tree.Mobile.DebugSay("Finished casting..."); - return Result.Success; - } - - return Result.Failure; - } - } -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/CureSelfNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/CureSelfNode.cs deleted file mode 100644 index 3e0b48f60..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/CureSelfNode.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using Server.Spells; -using Server.Spells.Second; -using System.Collections.Generic; - -namespace Server.Mobiles.BehaviorTreeAI -{ - public class CureSelfNode : SequenceNode - { - public CureSelfNode(BehaviorTree tree) : base(tree) - { - Children = new List(new BehaviorTreeNode[5] - { - new ConditionNode( - tree, - (mob) => - { - if (mob.Poison == null) - { - mob.DebugSay("I am not poisoned"); - return false; - } - - object nextHealTime; - if (Tree.Blackboard.TryGetValue("nextHealTime", out nextHealTime)) - { - if (DateTime.Now >= (DateTime)nextHealTime) - { - return true; - } - mob.DebugSay("My heal cooldown is active"); - } - - mob.DebugSay("My heal cooldown is not set"); - - return false; - } - ), - new ConditionNode( - tree, - (mob) => - { - return mob.Spell == null || !mob.Spell.IsCasting; - } - ), - new CastSpellActionNode( - tree, - (mob) => new CureSpell(mob) - ), - new AlwaysSucceedNode( - tree, - new TapNode( - tree, - (mob) => - { - Tree.Blackboard.Remove("nextHealTime"); - Tree.Blackboard.Add("nextHealTime", DateTime.Now + TimeSpan.FromSeconds(10.0)); - } - ) - ), - new TargetActionNode(tree), - - }); - } - } - /* - public class CureSelfActionNode : ActionNode - { - public TimeSpan Cooldown { get; private set; } - public CureSelfActionNode(BehaviorTree tree, TimeSpan cooldown) : base(tree) - { - object nextHealTime; - if (!Tree.Blackboard.TryGetValue("nextHealTime", out nextHealTime)) - { - tree.Blackboard.Add("nextHealTime", DateTime.Now); - } - Cooldown = cooldown; - } - - public override Result Execute() - { - object nextHealTime; - if (Tree.Mobile.Poison != null && Tree.Blackboard.TryGetValue("nextHealTime", out nextHealTime)) - { - if (DateTime.Now >= (DateTime)nextHealTime) - { - new CureSpell(Tree.Mobile).Cast(); - Tree.Mobile.DebugSay("Casting cure spell for myself"); - Tree.Blackboard.Remove("nextHealTime"); - Tree.Blackboard.Add("nextHealTime", DateTime.Now + Cooldown); - return Result.Success; - } - - Tree.Mobile.DebugSay("I'm poisoned, but my healing is on cooldown"); - - return Result.Failure; - } - - Tree.Mobile.DebugSay("I'm not poisoned"); - - return Result.Failure; - } - } - */ -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/DetectHiddenActionNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/DetectHiddenActionNode.cs deleted file mode 100644 index afdf15022..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/DetectHiddenActionNode.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Server.Mobiles.BehaviorTreeAI -{ - class DetectHiddenActionNode : BehaviorTreeNode - { - public TimeSpan Cooldown { get; private set; } - public DetectHiddenActionNode(BehaviorTree tree, TimeSpan cooldown) : base(tree) - { - object nextDetectTime; - if (!Tree.Blackboard.TryGetValue("nextDetectTime", out nextDetectTime)) - { - tree.Blackboard.Add("nextDetectTime", DateTime.Now); - } - Cooldown = cooldown; - } - public override Result Execute() - { - /* - if (m_Mobile.Deleted || m_Mobile.Map == null) - { - return; - } - - m_Mobile.DebugSay("Checking for hidden players"); - - var srcSkill = m_Mobile.Skills.DetectHidden.Value; - - if (srcSkill <= 0) - { - return; - } - - var eable = m_Mobile.GetMobilesInRange(m_Mobile.RangePerception); - - foreach (var trg in eable) - { - if (trg != m_Mobile && trg.Player && trg.Alive && trg.Hidden && trg.AccessLevel == AccessLevel.Player && - m_Mobile.InLOS(trg)) - { - m_Mobile.DebugSay("Trying to detect {0}", trg.Name); - - var trgHiding = trg.Skills.Hiding.Value / 2.9; - var trgStealth = trg.Skills.Stealth.Value / 1.8; - - var chance = srcSkill / 1.2 - Math.Min(trgHiding, trgStealth); - - if (chance < srcSkill / 10) - { - chance = srcSkill / 10; - } - - chance /= 100; - - if (chance > Utility.RandomDouble()) - { - trg.RevealingAction(); - trg.SendLocalizedMessage(500814); // You have been revealed! - } - } - } - - eable.Free(); - */ - - Tree.Mobile.DebugSay("DetectHiddenActionNode"); - - object nextDetectTime; - - if (Tree.Blackboard.TryGetValue("nextDetectTime", out nextDetectTime)) - { - if (DateTime.Now >= (DateTime)nextDetectTime) - { - var srcSkill = Tree.Mobile.Skills.DetectHidden.Value; - - var eable = Tree.Mobile.GetMobilesInRange(Tree.Mobile.RangePerception); - - foreach (var trg in eable) - { - if (trg != Tree.Mobile && trg.Player && trg.Alive && trg.Hidden && trg.AccessLevel == AccessLevel.Player && - Tree.Mobile.InLOS(trg)) - { - Tree.Mobile.DebugSay("DetectHiddenActionNode {0}", trg.Name); - - var trgHiding = trg.Skills.Hiding.Value / 2.9; - var trgStealth = trg.Skills.Stealth.Value / 1.8; - - var chance = srcSkill / 1.2 - Math.Min(trgHiding, trgStealth); - - if (chance < srcSkill / 10) - { - chance = srcSkill / 10; - } - - chance /= 100; - - if (chance > Utility.RandomDouble()) - { - trg.RevealingAction(); - trg.SendLocalizedMessage(500814); // You have been revealed! - } - } - } - - return Result.Success; - } - - Tree.Mobile.DebugSay("DetectHiddenActionNode Cooldown"); - } - - return Result.Failure; - } - } -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/HealSelfActionNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/HealSelfActionNode.cs deleted file mode 100644 index 5e8164a5b..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/HealSelfActionNode.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Server.Spells.First; -using Server.Spells.Fourth; - -namespace Server.Mobiles.BehaviorTreeAI -{ - - public class HealSelfActionNode : BehaviorTreeNode - { - public TimeSpan Cooldown { get; private set; } - public HealSelfActionNode(BehaviorTree tree, TimeSpan cooldown) : base(tree) - { - object nextHealTime; - if (!Tree.Blackboard.TryGetValue("nextHealTime", out nextHealTime)) - { - tree.Blackboard.Add("nextHealTime", DateTime.Now); - } - Cooldown = cooldown; - } - public override Result Execute() - { - object nextHealTime; - if (Tree.Mobile.Hits < Tree.Mobile.HitsMax && Tree.Blackboard.TryGetValue("nextHealTime", out nextHealTime)) - { - if (DateTime.Now >= (DateTime)nextHealTime) - { - if (Tree.Mobile.Hits < Tree.Mobile.HitsMax - 50) - { - new GreaterHealSpell(Tree.Mobile).Cast(); - Tree.Mobile.DebugSay("Casting greater heal for myself"); - } - else - { - new HealSpell(Tree.Mobile).Cast(); - Tree.Mobile.DebugSay("Casting heal spell for myself"); - } - - Tree.Blackboard.Remove("nextHealTime"); - Tree.Blackboard.Add("nextHealTime", DateTime.Now + Cooldown); - - return Result.Success; - } - - Tree.Mobile.DebugSay("I'm hurt, but my healing is on cooldown {0}s", ((DateTime)nextHealTime - DateTime.Now).TotalSeconds); - - return Result.Failure; - } - - Tree.Mobile.DebugSay("I'm at full health"); - - return Result.Failure; - } - } -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/MageComboNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/MageComboNode.cs deleted file mode 100644 index d1abef6b9..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/MageComboNode.cs +++ /dev/null @@ -1,185 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Server.Spells.Sixth; -using Server.Spells.Fifth; -using Server.Spells.First; - -namespace Server.Mobiles.BehaviorTreeAI -{ - public class MageComboNode : SequenceNode - { - public MageComboNode(BehaviorTree tree) : base(tree) - { - /* - Children = new List(new BehaviorTreeNode[1] - { - // if we have a combatant - new ConditionNode( - tree, - mob => mob.Combatant != null, - ) - new ConditionNode( - tree, - mob => mob.Combatant != null, - // do the following in sequence - new SelectorNode( - tree, - new BehaviorTreeNode[2] - { - // do the first appropriate debuff until all are done - new SelectorNode( - tree, - new BehaviorTreeNode[3] - { - // weaken if necessary - new ConditionNode( - tree, - mob => mob.Combatant.Str == mob.Combatant.RawStr, - new SequenceNode( - tree, - new BehaviorTreeNode[2] - { - new CastSpellActionNode( - tree, - mob => new WeakenSpell(mob) - ), - new TargetActionNode(tree) - } - ) - ), - // clumbsy if necessary - new ConditionNode( - tree, - mob => mob.Combatant.Dex == mob.Combatant.RawDex, - new SequenceNode( - tree, - new BehaviorTreeNode[2] - { - new CastSpellActionNode( - tree, - mob => new ClumsySpell(mob) - ), - new TargetActionNode(tree) - } - ) - ), - // feeblemind if necessary - new ConditionNode( - tree, - mob => mob.Combatant.Int == mob.Combatant.RawInt, - new SequenceNode( - tree, - new BehaviorTreeNode[2] - { - new CastSpellActionNode( - tree, - mob => new FeeblemindSpell(mob) - ), - new TargetActionNode(tree) - } - ) - ), - } - ), - // do explosion ebolt combo - new SequenceNode( - tree, - new BehaviorTreeNode[2] - { - // explosion - new SequenceNode( - tree, - new BehaviorTreeNode[2] - { - new CastSpellActionNode( - tree, - mob => new ExplosionSpell(mob) - ), - new TargetActionNode(tree) - } - ), - // ebolt - new SequenceNode( - tree, - new BehaviorTreeNode[2] - { - new CastSpellActionNode( - tree, - mob => new EnergyBoltSpell(mob) - ), - new TargetActionNode(tree) - } - ) - } - ), - } - ) - ) - }); - */ - - /* - Children = new List(new BehaviorTreeNode[1] { - new ConditionNode( - tree, - mob => mob.Combatant != null, - new SequenceNode( - tree, - new BehaviorTreeNode[3] { - new AlwaysSucceedNode( - tree, - new ConditionNode( - tree, - mob => mob.Combatant != null && mob.Combatant.Str == mob.Combatant.RawStr, - new SequenceNode( - tree, - new BehaviorTreeNode[2] - { - new CastSpellActionNode( - tree, - mob => new WeakenSpell(mob) - ), - new TargetActionNode(tree) - } - ) - ) - ), - new SequenceNode(tree, new BehaviorTreeNode[2]{ - new CastSpellActionNode( - tree, - mob => new ExplosionSpell(mob) - ), - new TargetActionNode(tree) - }), - new SequenceNode(tree, new BehaviorTreeNode[2]{ - new SelectorNode( - tree, - new BehaviorTreeNode[2] - { - new ConditionNode( - tree, - mob => Utility.RandomDouble() < 0.3, - new CastSpellActionNode( - tree, - mob => new MindBlastSpell(mob) - ) - ), - new CastSpellActionNode( - tree, - mob => new EnergyBoltSpell(mob) - ) - } - ), - new TargetActionNode(tree) - }), - } - ) - ) - }); - */ - } - } -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/SelfHealerNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/SelfHealerNode.cs deleted file mode 100644 index d71644eaf..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/SelfHealerNode.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Server.Mobiles.BehaviorTreeAI -{ - class SelfHealerNode : SelectorNode - { - public SelfHealerNode(BehaviorTree tree) : base(tree) - { - Children = new List(new BehaviorTreeNode[1] - { - new SequenceNode( - tree, - new BehaviorTreeNode[2] - { - new ConditionNode( - tree, - mob => true // Utility.RandomDouble() < 0.5 - ), - new CureSelfNode(tree), - } - ) - /* - new SequenceNode( - tree, - new BehaviorTreeNode[2] - { - new ConditionNode( - tree, - mob => mob.Hits < mob.HitsMax - 20 - ), - new SequenceNode( - tree, - new BehaviorTreeNode[3] - { - new ConditionNode( - tree, - mob => true // Utility.RandomDouble() < 0.2 - ), - new HealSelfActionNode(tree, TimeSpan.FromSeconds(10.0)), - new TargetActionNode(tree) - } - ) - } - ) - */ - }); - } - } -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/TargetActionNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/TargetActionNode.cs deleted file mode 100644 index 32b194622..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/Actions/TargetActionNode.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Server.Spells; -using Server.Targeting; - -namespace Server.Mobiles.BehaviorTreeAI -{ - public class TargetActionNode : ActionNode - { - public TargetActionNode(BehaviorTree tree) : base(tree) - { - } - - public override Result Execute() - { - Target target = Tree.Mobile.Target; - - if (target == null) - { - Tree.Mobile.DebugSay("Waiting for target..."); - return Result.Running; - } - - Mobile mobTarget = Tree.Mobile.Combatant; - - if ((target.Flags & TargetFlags.Harmful) != 0 && mobTarget != null) - { - if (mobTarget.Deleted || !mobTarget.Alive) - { - Tree.Mobile.DebugSay("Canceling my target because my target is dead or does not exist"); - target.Cancel(Tree.Mobile, TargetCancelType.Canceled); - return Result.Success; - } - - if ((target.Range == -1 || - Tree.Mobile.InRange(mobTarget, target.Range)) && - Tree.Mobile.CanSee(mobTarget) && - Tree.Mobile.InLOS(mobTarget) - ) - { - Tree.Mobile.DebugSay("Targeting my combatant"); - target.Invoke(Tree.Mobile, mobTarget); - return Result.Success; - } - } - else if ((target.Flags & TargetFlags.Beneficial) != 0) - { - Tree.Mobile.DebugSay("Targeting myself"); - target.Invoke(Tree.Mobile, Tree.Mobile); - return Result.Success; - } - - return Result.Failure; - } - } -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/AlwaysSucceedNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/AlwaysSucceedNode.cs deleted file mode 100644 index 6228e081c..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/AlwaysSucceedNode.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Server.Mobiles.BehaviorTreeAI -{ - public class AlwaysSucceedNode : DecoratorNode - { - public AlwaysSucceedNode(BehaviorTree tree, BehaviorTreeNode child) : base(tree, child) - { - } - public AlwaysSucceedNode(BehaviorTree tree) : base(tree) - { - } - - public override Result Execute() - { - if (Child != null) - { - Child.Execute(); - } - return Result.Success; - } - } -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/BehaviorTree.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/BehaviorTree.cs deleted file mode 100644 index 20e94994b..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/BehaviorTree.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Server.Mobiles.BehaviorTreeAI -{ - public class BehaviorTree - { - /* - public static BehaviorTree Instance; - private static BehaviorTreeTimer timer; - public static void Initialize() - { - Instance = new BehaviorTree(); - - var seq = new SequenceNode(Instance, new BehaviorTreeNode[3]{ - new ActionNode(Instance, () => { Console.WriteLine("ActionNode1"); return BehaviorTreeNode.Result.Success; }), - new ActionNode(Instance, () => { Console.WriteLine("ActionNode2"); return BehaviorTreeNode.Result.Success; }), - new SequenceNode(Instance, new BehaviorTreeNode[4]{ - new ActionNode(Instance, () => { Console.WriteLine("ActionNode3"); return BehaviorTreeNode.Result.Success; }), - new ActionNode(Instance, () => { Console.WriteLine("ActionNode4"); return BehaviorTreeNode.Result.Success; }), - new ActionNode(Instance, () => { Console.WriteLine("ActionNode5"); return BehaviorTreeNode.Result.Success; }), - new SelectorNode(Instance, new BehaviorTreeNode[3] - { - new ActionNode(Instance, () => { Console.WriteLine("ActionNode6"); return BehaviorTreeNode.Result.Failure; }), - new ActionNode(Instance, () => { Console.WriteLine("ActionNode7"); return BehaviorTreeNode.Result.Success; }), - new ActionNode(Instance, () => { Console.WriteLine("ActionNode8"); return BehaviorTreeNode.Result.Success; }) - }) - }), - }); - - Instance.AddRoot( - new RepeaterNode(Instance, seq) - ); - - Instance.Start(); - } - */ - - private BehaviorTreeNode root; - public Dictionary Blackboard { get; set; } - public BehaviorTreeNode Root { get { return root; } } - - private BehaviorTreeTimer treeTimer; - - public BaseCreature Mobile { get; private set; } - - public BehaviorTree(BaseCreature mob) - { - Blackboard = new Dictionary(); - treeTimer = new BehaviorTreeTimer(this); - Mobile = mob; - } - - public void AddRoot(BehaviorTreeNode rootNode) - { - if (root == null) - { - root = rootNode; - } - } - - public void Start() - { - treeTimer.Start(); - } - - public void Stop() - { - treeTimer.Stop(); - } - - public virtual void Tick() - { - RunBehavior(); - } - - public virtual void RunBehavior() - { - Root.Execute(); - } - - private class BehaviorTreeTimer : Timer - { - private BehaviorTree behaviorTree; - public BehaviorTreeTimer(BehaviorTree tree) : base(TimeSpan.Zero, TimeSpan.FromSeconds(1.0)) - { - behaviorTree = tree; - } - - protected override void OnTick() - { - if (behaviorTree != null) - { - behaviorTree.Tick(); - } - } - } - } - /* - public enum ControlFlowNodeType - { - Sequence, - Fallback, - Parallel, - Decorator - } - - public enum ExecutionNodeType - { - Action, - Condition - } - - public class BehaviorTreeNode - { - private readonly List _children = new List(); - public bool Root { get { return Parent == null; } } - - public BehaviorTreeNode() { } - public BehaviorTreeNode(BehaviorTreeNode parent) : this() - { - Parent = parent; - } - - public BehaviorTreeNode this[int i] - { - get { return _children[i]; } - } - - public BehaviorTreeNode Parent { get; private set; } - - public ReadOnlyCollection Children - { - get { return _children.AsReadOnly(); } - } - - public virtual BehaviorTreeNode AddChild(BehaviorTreeNode node) - { - _children.Add(node); - return node; - } - - public virtual bool Evaluate(BaseCreature creature) - { - return false; - } - - public virtual bool Traverse(BaseCreature creature) - { - foreach (var child in _children) - child.Traverse(creature); - } - } - - public class SequenceNode : BehaviorTreeNode { - public override bool Evaluate(BaseCreature creature) - { - return Children.All((BehaviorTreeNode node) => node.Evaluate(creature)); - } - - public override bool Traverse(BaseCreature creature) - { - if (Evaluate(creature)) return true; - - foreach (var child in Children) - child.Traverse(creature); - } - } - - public class FallbackNode : BehaviorTreeNode - { - public override bool Evaluate(BaseCreature creature) - { - return Children.Any((BehaviorTreeNode node) => node.Evaluate(creature)); - } - - public override bool Traverse(BaseCreature creature) - { - return Evaluate(creature); - } - } - - public class ParallelNode : BehaviorTreeNode - { - - } - - public class DecoratorNode : BehaviorTreeNode - { - - } - - public class ActionNode : BehaviorTreeNode - { - - } - - public class ConditionNode : BehaviorTreeNode - { - - } - */ -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/BehaviorTreeNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/BehaviorTreeNode.cs deleted file mode 100644 index 88256a493..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/BehaviorTreeNode.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Server.Mobiles.BehaviorTreeAI -{ - public abstract class BehaviorTreeNode - { - public enum Result { Running, Failure, Success } - - public BehaviorTree Tree { get; private set; } - - public BehaviorTreeNode(BehaviorTree tree) - { - Tree = tree; - } - public abstract Result Execute(); - } -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/ConditionNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/ConditionNode.cs deleted file mode 100644 index 4105fe62e..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/ConditionNode.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Server.Mobiles.BehaviorTreeAI -{ - public class ConditionNode : LeafNode - { - private Predicate predicate; - public ConditionNode(BehaviorTree tree, Predicate fn) : base(tree) - { - predicate = fn; - } - public override Result Execute() - { - if (predicate(Tree.Mobile)) - { - return Result.Success; - } - return Result.Failure; - } - } -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/DecoratorNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/DecoratorNode.cs deleted file mode 100644 index 124eb14c4..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/DecoratorNode.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Server.Mobiles.BehaviorTreeAI -{ - public abstract class DecoratorNode : BehaviorTreeNode - { - public BehaviorTreeNode Child { get; set; } - - public DecoratorNode(BehaviorTree tree) : this(tree, null) - { - } - public DecoratorNode(BehaviorTree tree, BehaviorTreeNode child) : base(tree) - { - Child = child; - } - } -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/InverterNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/InverterNode.cs deleted file mode 100644 index 1862d44e2..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/InverterNode.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Server.Mobiles.BehaviorTreeAI -{ - public class InverterNode : DecoratorNode - { - public InverterNode(BehaviorTree tree) : base(tree) - { - } - - public override Result Execute() - { - if (Child != null) - { - var result = Child.Execute(); - - if (result == Result.Success) - { - return Result.Failure; - } - else if (result == Result.Failure) - { - return Result.Success; - } - - return Result.Running; - } - return Result.Success; - } - } -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/RepeaterNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/RepeaterNode.cs deleted file mode 100644 index f8c38aca2..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/RepeaterNode.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Server.Mobiles.BehaviorTreeAI -{ - public class RepeaterNode : DecoratorNode - { - public RepeaterNode(BehaviorTree tree) : base(tree) - { - } - - public RepeaterNode(BehaviorTree tree, BehaviorTreeNode child) : base(tree, child) - { - } - - public override Result Execute() - { - if (Child != null) - { - Child.Execute(); - } - return Result.Running; - } - } -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/SelectorNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/SelectorNode.cs deleted file mode 100644 index ce33070b5..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/SelectorNode.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Server.Mobiles.BehaviorTreeAI -{ - class SelectorNode : BehaviorTreeNode - { - int currentTask = 0; - public List Children { get; protected set; } - public SelectorNode(BehaviorTree tree) : this(tree, new BehaviorTreeNode[0]) { } - public SelectorNode(BehaviorTree tree, BehaviorTreeNode[] children) : base(tree) - { - Children = new List(children); - } - - public override Result Execute() - { - if (currentTask < Children.Count) - { - var result = Children[currentTask].Execute(); - - if (result == Result.Success) - { - currentTask = 0; - return Result.Success; - } - else if (result == Result.Running) - { - return Result.Running; - } - - currentTask++; - if (currentTask == Children.Count) - { - currentTask = 0; - return Result.Failure; - } - - return Result.Running; - } - - return Result.Success; - } - } -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/SequenceNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/SequenceNode.cs deleted file mode 100644 index 3ed5a4960..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/SequenceNode.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Server.Mobiles.BehaviorTreeAI -{ - public class SequenceNode : BehaviorTreeNode - { - int currentTask = 0; - public List Children { get; protected set; } - public SequenceNode(BehaviorTree tree) : this(tree, new BehaviorTreeNode[0]) { } - public SequenceNode(BehaviorTree tree, BehaviorTreeNode[] children) : base(tree) - { - Children = new List(children); - } - - public override Result Execute() - { - if(currentTask < Children.Count) - { - var result = Children[currentTask].Execute(); - - if (result == Result.Running) - { - return Result.Running; - } - else if (result == Result.Failure) - { - currentTask = 0; - return Result.Failure; - } - - currentTask++; - if(currentTask == Children.Count) - { - currentTask = 0; - return Result.Success; - } - - return Result.Running; - } - - return Result.Success; - } - } -} diff --git a/Projects/UOContent/Mobiles/BehaviorTreeAI/TapNode.cs b/Projects/UOContent/Mobiles/BehaviorTreeAI/TapNode.cs deleted file mode 100644 index fda030d82..000000000 --- a/Projects/UOContent/Mobiles/BehaviorTreeAI/TapNode.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Server.Mobiles.BehaviorTreeAI -{ - public class TapNode : DecoratorNode - { - public Action Action { get; private set; } - public TapNode(BehaviorTree tree, Action action, BehaviorTreeNode child) : base(tree, child) - { - } - public TapNode(BehaviorTree tree, Action action) : base(tree) - { - Action = action; - } - public override Result Execute() - { - Action(Tree.Mobile); - - if (Child != null) - { - return Child.Execute(); - } - - return Result.Failure; - } - } -} diff --git a/Projects/UOContent/Mobiles/FireBoss.cs b/Projects/UOContent/Mobiles/FireBoss.cs new file mode 100644 index 000000000..b7083440a --- /dev/null +++ b/Projects/UOContent/Mobiles/FireBoss.cs @@ -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(); + } + } +} diff --git a/Projects/UOContent/Mobiles/Healers/BaseHealer.cs b/Projects/UOContent/Mobiles/Healers/BaseHealer.cs index d669d5cc5..dc7efe8b3 100644 --- a/Projects/UOContent/Mobiles/Healers/BaseHealer.cs +++ b/Projects/UOContent/Mobiles/Healers/BaseHealer.cs @@ -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; diff --git a/Projects/UOContent/Mobiles/Healers/EvilHealer.cs b/Projects/UOContent/Mobiles/Healers/EvilHealer.cs index e63990770..6e6fed1b5 100644 --- a/Projects/UOContent/Mobiles/Healers/EvilHealer.cs +++ b/Projects/UOContent/Mobiles/Healers/EvilHealer.cs @@ -7,8 +7,6 @@ namespace Server.Mobiles { AI = AIType.AI_BehaviorTree; - ChangeAIType(AI); - Title = "the healer"; Karma = -10000; @@ -67,8 +65,6 @@ namespace Server.Mobiles { base.Deserialize(reader); - AI = AIType.AI_BehaviorTree; - var version = reader.ReadInt(); } } diff --git a/Projects/UOContent/Mobiles/Vendors/FireBossPriest.cs b/Projects/UOContent/Mobiles/Vendors/FireBossPriest.cs new file mode 100644 index 000000000..ab8c69ef9 --- /dev/null +++ b/Projects/UOContent/Mobiles/Vendors/FireBossPriest.cs @@ -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(); + } + } +} diff --git a/Projects/UOContent/Spells/Base/Spell.cs b/Projects/UOContent/Spells/Base/Spell.cs index 4be93b9b3..a216561e7 100644 --- a/Projects/UOContent/Spells/Base/Spell.cs +++ b/Projects/UOContent/Spells/Base/Spell.cs @@ -84,7 +84,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)) @@ -393,7 +393,7 @@ namespace Server.Spells m_AnimTimer?.Stop(); - if (Core.AOS && Caster.Player && type == DisturbType.Hurt) + if (Core.AOS && (Caster.Player || Caster is BehaviorMage) && type == DisturbType.Hurt) { DoHurtFizzle(); } @@ -414,7 +414,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(); }