tree stuff
This commit is contained in:
parent
53f51d6bde
commit
0e84739f2d
65 changed files with 1916 additions and 1182 deletions
|
|
@ -1,7 +1,6 @@
|
|||
namespace Server
|
||||
{
|
||||
public delegate MoveResult MoveMethod(Direction d);
|
||||
|
||||
public enum MoveResult
|
||||
{
|
||||
BadState,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
37
Projects/UOContent/Mobiles/AI/FireBossAI.cs
Normal file
37
Projects/UOContent/Mobiles/AI/FireBossAI.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using Server.Mobiles.BT;
|
||||
|
||||
namespace Server.Mobiles.AI
|
||||
{
|
||||
public class FireBossAI : BaseAI
|
||||
{
|
||||
private Blackboard blackboard;
|
||||
public BehaviorTree Tree { get; private set; }
|
||||
public FireBossAI(BaseCreature m) : base(m)
|
||||
{
|
||||
blackboard = new Blackboard();
|
||||
m.PassiveSpeed = 0.05;
|
||||
m.ActiveSpeed = 0.05;
|
||||
m.CurrentSpeed = 0.05;
|
||||
|
||||
Tree = new BehaviorTree();
|
||||
Tree.TryAddRoot(
|
||||
new SelectorNode(Tree)
|
||||
.AddChild(
|
||||
new SequenceNode(Tree)
|
||||
.AddChild(new ConditionNode(Tree, (mob, board) => mob.Combatant != null))
|
||||
)
|
||||
);
|
||||
}
|
||||
public override bool Think()
|
||||
{
|
||||
if (m_Mobile.Deleted || !m_Mobile.Alive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Tree.RunBehavior(m_Mobile, blackboard);
|
||||
|
||||
return base.Think();
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Projects/UOContent/Mobiles/BT/Actions/CastSpellActionNode.cs
Normal file
61
Projects/UOContent/Mobiles/BT/Actions/CastSpellActionNode.cs
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Projects/UOContent/Mobiles/BT/Actions/CureSelfNode.cs
Normal file
24
Projects/UOContent/Mobiles/BT/Actions/CureSelfNode.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Server.Spells.Second;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class CureSelfNode : OwnerConditionNode
|
||||
{
|
||||
public CureSelfNode(BehaviorTree tree, TimeSpan duration) : base(tree, (mob, board) => mob.Poison != null)
|
||||
{
|
||||
AddChild(
|
||||
new CooldownNode(tree, duration)
|
||||
.AddChild(
|
||||
new SequenceNode(tree)
|
||||
.AddChild(new CastSpellActionNode(tree, (mob) => new CureSpell(mob)))
|
||||
.AddChild(new TargetActionNode(tree))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Projects/UOContent/Mobiles/BT/Actions/EquipItemNode.cs
Normal file
28
Projects/UOContent/Mobiles/BT/Actions/EquipItemNode.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class EquipItemNode : ActionNode
|
||||
{
|
||||
private Type itemType;
|
||||
public EquipItemNode(BehaviorTree tree, Type type) : base(tree)
|
||||
{
|
||||
itemType = type;
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
Item item = mob.Backpack.FindItemByType(itemType);
|
||||
|
||||
if (item == null || !mob.EquipItem(item))
|
||||
{
|
||||
return Result.Failure;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Projects/UOContent/Mobiles/BT/Actions/HealSelfNode.cs
Normal file
36
Projects/UOContent/Mobiles/BT/Actions/HealSelfNode.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Server.Spells.Fourth;
|
||||
using Server.Spells.First;
|
||||
using Server.Spells;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class HealSelfNode : OwnerConditionNode
|
||||
{
|
||||
public HealSelfNode(BehaviorTree tree, TimeSpan duration) : base(tree, (mob, board) => mob.HitsMax - mob.Hits > 0)
|
||||
{
|
||||
AddChild(
|
||||
new CooldownNode(tree, duration)
|
||||
.AddChild(
|
||||
new SequenceNode(tree)
|
||||
.AddChild(new CastSpellActionNode(tree, GetHealSpell))
|
||||
.AddChild(new TargetActionNode(tree))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private Spell GetHealSpell(BaseCreature mob)
|
||||
{
|
||||
if (mob.Mana < 10 || mob.HitsMax - mob.Hits < 15 || Utility.RandomDouble() < 0.3)
|
||||
{
|
||||
return new HealSpell(mob);
|
||||
}
|
||||
|
||||
return new GreaterHealSpell(mob);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Projects/UOContent/Mobiles/BT/Actions/KeepRangeNode.cs
Normal file
29
Projects/UOContent/Mobiles/BT/Actions/KeepRangeNode.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class KeepRangeNode : ActionNode
|
||||
{
|
||||
private int minRange;
|
||||
private int maxRange;
|
||||
public KeepRangeNode(BehaviorTree tree, int min, int max) : base(tree)
|
||||
{
|
||||
minRange = min;
|
||||
maxRange = max;
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (mob.Combatant != null && !mob.Combatant.Deleted)
|
||||
{
|
||||
BehaviorTree.WalkMobileRange(mob, (BaseCreature)mob.Combatant, 4, true, minRange, maxRange);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
140
Projects/UOContent/Mobiles/BT/Actions/MageComboNode.cs
Normal file
140
Projects/UOContent/Mobiles/BT/Actions/MageComboNode.cs
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Projects/UOContent/Mobiles/BT/Actions/MageDebuffNode.cs
Normal file
40
Projects/UOContent/Mobiles/BT/Actions/MageDebuffNode.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Server.Spells.First;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class MageDebuffNode : SelectorNode
|
||||
{
|
||||
public MageDebuffNode(BehaviorTree tree) : base(tree)
|
||||
{
|
||||
AddChild(
|
||||
new OwnerConditionNode(tree, (mob, board) => mob.Combatant != null && mob.Combatant.Str == mob.Combatant.RawStr)
|
||||
.AddChild(
|
||||
new SequenceNode(tree)
|
||||
.AddChild(new CastSpellActionNode(tree, (mob) => new WeakenSpell(mob)))
|
||||
.AddChild(new TargetActionNode(tree))
|
||||
)
|
||||
);
|
||||
AddChild(
|
||||
new OwnerConditionNode(tree, (mob, board) => mob.Combatant != null && mob.Combatant.Dex == mob.Combatant.RawDex)
|
||||
.AddChild(
|
||||
new SequenceNode(tree)
|
||||
.AddChild(new CastSpellActionNode(tree, (mob) => new ClumsySpell(mob)))
|
||||
.AddChild(new TargetActionNode(tree))
|
||||
)
|
||||
);
|
||||
AddChild(
|
||||
new OwnerConditionNode(tree, (mob, board) => mob.Combatant != null && mob.Combatant.Int == mob.Combatant.RawInt)
|
||||
.AddChild(
|
||||
new SequenceNode(tree)
|
||||
.AddChild(new CastSpellActionNode(tree, (mob) => new FeeblemindSpell(mob)))
|
||||
.AddChild(new TargetActionNode(tree))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Projects/UOContent/Mobiles/BT/Actions/MeditateNode.cs
Normal file
20
Projects/UOContent/Mobiles/BT/Actions/MeditateNode.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class MeditateNode : CooldownNode
|
||||
{
|
||||
public MeditateNode(BehaviorTree tree, int desiredMana) : base(tree, TimeSpan.FromSeconds(12.0))
|
||||
{
|
||||
AddChild(
|
||||
new SequenceNode(tree)
|
||||
.AddChild(new UseSkillNode(tree, SkillName.Meditation))
|
||||
.AddChild(new InverterNode(tree, new UntilFailNode(tree, new ConditionNode(tree, (mob, board) => mob.Mana < desiredMana || mob.Meditating))))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Projects/UOContent/Mobiles/BT/Actions/PoisonEnemyNode.cs
Normal file
34
Projects/UOContent/Mobiles/BT/Actions/PoisonEnemyNode.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Server.Spells.Third;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class PoisonEnemyNode : OwnerConditionNode
|
||||
{
|
||||
public PoisonEnemyNode(BehaviorTree tree, TimeSpan duration) : base(tree, (mob, board) => mob.Combatant != null && !mob.Combatant.Deleted && mob.Combatant.Poison == null)
|
||||
{
|
||||
AddChild(
|
||||
new CooldownNode(tree, duration)
|
||||
.AddChild(
|
||||
new SequenceNode(tree)
|
||||
.AddChild(new CastSpellActionNode(tree, (mob) => new PoisonSpell(mob)))
|
||||
.AddChild(new TargetActionNode(tree))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private bool canActivate(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (mob.Combatant == null || mob.Combatant.Deleted)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return mob.Combatant.Poison == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Projects/UOContent/Mobiles/BT/Actions/SetBlackboardData.cs
Normal file
33
Projects/UOContent/Mobiles/BT/Actions/SetBlackboardData.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class SetBlackboardData<T> : ActionNode
|
||||
{
|
||||
public delegate T BlackboardDataTransformation(BaseCreature mob);
|
||||
private BlackboardDataTransformation transformation;
|
||||
private string blackboardKey;
|
||||
public SetBlackboardData(BehaviorTree tree, string key, BlackboardDataTransformation fn) : base(tree)
|
||||
{
|
||||
blackboardKey = key;
|
||||
transformation = fn;
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
T value = transformation(mob);
|
||||
if(blackboard.TryGetValue(blackboardKey, out object oldValue) && (T)oldValue != null)
|
||||
{
|
||||
blackboard[blackboardKey] = (object)value;
|
||||
}
|
||||
else
|
||||
{
|
||||
blackboard.Add(blackboardKey, (object)value);
|
||||
}
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Projects/UOContent/Mobiles/BT/Actions/TargetActionNode.cs
Normal file
53
Projects/UOContent/Mobiles/BT/Actions/TargetActionNode.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
using Server.Targeting;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class TargetActionNode : ActionNode
|
||||
{
|
||||
public TargetActionNode(BehaviorTree tree) : base(tree)
|
||||
{
|
||||
}
|
||||
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
Target target = mob.Target;
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
mob.DebugSay("Waiting for target...");
|
||||
return Result.Running;
|
||||
}
|
||||
|
||||
Mobile mobTarget = mob.Combatant;
|
||||
|
||||
if ((target.Flags & TargetFlags.Harmful) != 0 && mobTarget != null)
|
||||
{
|
||||
if (mobTarget.Deleted || !mobTarget.Alive)
|
||||
{
|
||||
mob.DebugSay("Canceling my target because my target is dead or does not exist");
|
||||
target.Cancel(mob, TargetCancelType.Canceled);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if ((target.Range == -1 ||
|
||||
mob.InRange(mobTarget, target.Range)) &&
|
||||
mob.CanSee(mobTarget) &&
|
||||
mob.InLOS(mobTarget)
|
||||
)
|
||||
{
|
||||
mob.DebugSay("Targeting my combatant");
|
||||
target.Invoke(mob, mobTarget);
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
else if ((target.Flags & TargetFlags.Beneficial) != 0)
|
||||
{
|
||||
mob.DebugSay("Targeting myself");
|
||||
target.Invoke(mob, mob);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Projects/UOContent/Mobiles/BT/Actions/UseSkillNode.cs
Normal file
26
Projects/UOContent/Mobiles/BT/Actions/UseSkillNode.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class UseSkillNode : ActionNode
|
||||
{
|
||||
private SkillName skillToUse;
|
||||
public UseSkillNode(BehaviorTree tree, SkillName skill) : base(tree)
|
||||
{
|
||||
skillToUse = skill;
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (mob.UseSkill(skillToUse))
|
||||
{
|
||||
mob.Say("{0}", skillToUse.ToString());
|
||||
return Result.Success;
|
||||
}
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Projects/UOContent/Mobiles/BT/Actions/WanderNode.cs
Normal file
21
Projects/UOContent/Mobiles/BT/Actions/WanderNode.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class WanderNode : ActionNode
|
||||
{
|
||||
private int stepsPerWander;
|
||||
public WanderNode(BehaviorTree tree, int steps) : base(tree)
|
||||
{
|
||||
stepsPerWander = steps;
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (!mob.CheckIdle())
|
||||
{
|
||||
BehaviorTree.WalkRandomInHome(mob, stepsPerWander);
|
||||
return Result.Success;
|
||||
}
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Projects/UOContent/Mobiles/BT/BehaviorMageCombatNodeSet.cs
Normal file
32
Projects/UOContent/Mobiles/BT/BehaviorMageCombatNodeSet.cs
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Projects/UOContent/Mobiles/BT/BehaviorMagePassiveNodeSet.cs
Normal file
25
Projects/UOContent/Mobiles/BT/BehaviorMagePassiveNodeSet.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class BehaviorMagePassiveNodeSet : OwnerConditionNode
|
||||
{
|
||||
public BehaviorMagePassiveNodeSet(BehaviorTree tree)
|
||||
: base(tree, canActivate)
|
||||
{
|
||||
AddChild(
|
||||
new SelectorNode(tree)
|
||||
.AddChild(new TapNode(tree, (mob, board) => { mob.CurrentSpeed = mob.PassiveSpeed; }))
|
||||
.AddChild(new CooldownNode(tree, TimeSpan.FromSeconds(2.0), new WanderNode(tree, 3)))
|
||||
);
|
||||
}
|
||||
private static bool canActivate(BaseCreature mob, Blackboard board)
|
||||
{
|
||||
return mob.Combatant == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class RandomSelectorNode : CompositeNode
|
||||
{
|
||||
private Dictionary<BaseCreature, int> retryCache = new Dictionary<BaseCreature, int>();
|
||||
public RandomSelectorNode(BehaviorTree tree) : base(tree)
|
||||
{
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (!Tree.Claims.TryGetValue(mob, out BehaviorTreeNode node) || node != this)
|
||||
{
|
||||
Tree.Claim(mob, this);
|
||||
}
|
||||
|
||||
if (!currentTaskCache.TryGetValue(mob, out int currentTask))
|
||||
{
|
||||
currentTask = Utility.RandomMinMax(0, Children.Count - 1);
|
||||
currentTaskCache.Add(mob, currentTask);
|
||||
}
|
||||
|
||||
if (!retryCache.TryGetValue(mob, out int currentIteration))
|
||||
{
|
||||
currentIteration = 0;
|
||||
retryCache.Add(mob, currentIteration);
|
||||
}
|
||||
|
||||
var result = Children[currentTask].Execute(mob, blackboard);
|
||||
|
||||
if (result == Result.Running)
|
||||
{
|
||||
return Result.Running;
|
||||
}
|
||||
else if (result == Result.Failure)
|
||||
{
|
||||
currentIteration++;
|
||||
currentTaskCache[mob] = Utility.RandomMinMax(0, Children.Count - 1);
|
||||
if (currentIteration >= Children.Count)
|
||||
{
|
||||
retryCache[mob] = 0;
|
||||
Tree.Release(mob);
|
||||
return Result.Failure;
|
||||
}
|
||||
retryCache[mob] = currentIteration;
|
||||
return Result.Running;
|
||||
}
|
||||
|
||||
retryCache[mob] = 0;
|
||||
currentTaskCache[mob] = 0;
|
||||
Tree.Release(mob);
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Projects/UOContent/Mobiles/BT/Composite/SelectorNode.cs
Normal file
59
Projects/UOContent/Mobiles/BT/Composite/SelectorNode.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class SelectorNode : CompositeNode
|
||||
{
|
||||
public SelectorNode(BehaviorTree tree): base(tree)
|
||||
{
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (!Tree.Claims.TryGetValue(mob, out BehaviorTreeNode node) || node != this)
|
||||
{
|
||||
Tree.Claim(mob, this);
|
||||
}
|
||||
|
||||
if (!currentTaskCache.TryGetValue(mob, out int currentTask))
|
||||
{
|
||||
currentTask = 0;
|
||||
currentTaskCache.Add(mob, currentTask);
|
||||
}
|
||||
|
||||
if (currentTask < Children.Count)
|
||||
{
|
||||
var result = Children[currentTask].Execute(mob, blackboard);
|
||||
|
||||
if (result == Result.Success)
|
||||
{
|
||||
currentTaskCache[mob] = 0;
|
||||
Tree.Release(mob);
|
||||
return Result.Success;
|
||||
}
|
||||
else if (result == Result.Running)
|
||||
{
|
||||
return Result.Running;
|
||||
}
|
||||
|
||||
currentTask++;
|
||||
if (currentTask == Children.Count)
|
||||
{
|
||||
currentTask = 0;
|
||||
currentTaskCache[mob] = currentTask;
|
||||
Tree.Release(mob);
|
||||
return Result.Failure;
|
||||
}
|
||||
|
||||
currentTaskCache[mob] = currentTask;
|
||||
return Result.Running;
|
||||
}
|
||||
|
||||
Tree.Release(mob);
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Projects/UOContent/Mobiles/BT/Composite/SequenceNode.cs
Normal file
59
Projects/UOContent/Mobiles/BT/Composite/SequenceNode.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class SequenceNode : CompositeNode
|
||||
{
|
||||
public SequenceNode(BehaviorTree tree) : base(tree)
|
||||
{
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (!Tree.Claims.TryGetValue(mob, out BehaviorTreeNode node) || node != this)
|
||||
{
|
||||
Tree.Claim(mob, this);
|
||||
}
|
||||
|
||||
if (!currentTaskCache.TryGetValue(mob, out int currentTask))
|
||||
{
|
||||
currentTask = 0;
|
||||
currentTaskCache.Add(mob, currentTask);
|
||||
}
|
||||
|
||||
if (currentTask < Children.Count)
|
||||
{
|
||||
var result = Children[currentTask].Execute(mob, blackboard);
|
||||
|
||||
if (result == Result.Running)
|
||||
{
|
||||
return Result.Running;
|
||||
}
|
||||
else if (result == Result.Failure)
|
||||
{
|
||||
currentTaskCache[mob] = 0;
|
||||
Tree.Release(mob);
|
||||
return Result.Failure;
|
||||
}
|
||||
|
||||
currentTask++;
|
||||
if (currentTask == Children.Count)
|
||||
{
|
||||
currentTask = 0;
|
||||
currentTaskCache[mob] = currentTask;
|
||||
Tree.Release(mob);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
currentTaskCache[mob] = currentTask;
|
||||
return Result.Running;
|
||||
}
|
||||
|
||||
Tree.Release(mob);
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
400
Projects/UOContent/Mobiles/BT/Core/BehaviorTree.cs
Normal file
400
Projects/UOContent/Mobiles/BT/Core/BehaviorTree.cs
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Server.Items;
|
||||
using Server.Engines.Spawners;
|
||||
using MoveImpl = Server.Movement.MovementImpl;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class BehaviorTree
|
||||
{
|
||||
private BehaviorTreeNode root;
|
||||
public BehaviorTreeNode Root { get { return root; } }
|
||||
public Dictionary<BaseCreature, BehaviorTreeNode> Claims = new Dictionary<BaseCreature, BehaviorTreeNode>();
|
||||
public BehaviorTree()
|
||||
{
|
||||
}
|
||||
public bool TryAddRoot(CompositeNode rootNode)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
root = rootNode;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public virtual void RunBehavior(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (!Claims.TryGetValue(mob, out BehaviorTreeNode currentNode))
|
||||
{
|
||||
currentNode = null;
|
||||
Claims.Add(mob, null);
|
||||
}
|
||||
|
||||
if (Root != null)
|
||||
{
|
||||
/*
|
||||
if (currentNode != null)
|
||||
{
|
||||
currentNode.Execute(mob, blackboard);
|
||||
}
|
||||
else
|
||||
{
|
||||
Root.Execute(mob, blackboard);
|
||||
}
|
||||
*/
|
||||
Root.Execute(mob, blackboard);
|
||||
}
|
||||
}
|
||||
public virtual void Claim(BaseCreature mob, BehaviorTreeNode node)
|
||||
{
|
||||
if (!Claims.TryGetValue(mob, out BehaviorTreeNode foundNode))
|
||||
{
|
||||
Claims.Add(mob, node);
|
||||
}
|
||||
else
|
||||
{
|
||||
Claims[mob] = node;
|
||||
}
|
||||
}
|
||||
public virtual void Release(BaseCreature mob)
|
||||
{
|
||||
if (!Claims.TryGetValue(mob, out BehaviorTreeNode foundNode))
|
||||
{
|
||||
Claims.Add(mob, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
Claims[mob] = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void WalkRandomInHome(BaseCreature mob, int steps)
|
||||
{
|
||||
if (mob.Deleted || mob.DisallowAllMoves)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (mob.Home == Point3D.Zero)
|
||||
{
|
||||
if (mob.Spawner is RegionSpawner rs)
|
||||
{
|
||||
Region region = rs.SpawnRegion;
|
||||
|
||||
if (mob.Region.AcceptsSpawnsFrom(region))
|
||||
{
|
||||
mob.WalkRegion = region;
|
||||
WalkRandom(mob, steps);
|
||||
mob.WalkRegion = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (region.GoLocation != Point3D.Zero && Utility.RandomDouble() > 0.5)
|
||||
{
|
||||
DoMove(mob, mob.GetDirectionTo(region.GoLocation));
|
||||
}
|
||||
else
|
||||
{
|
||||
WalkRandom(mob, steps);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WalkRandom(mob, steps);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < steps; i++)
|
||||
{
|
||||
if (mob.RangeHome != 0)
|
||||
{
|
||||
var currentDistance = (int)mob.GetDistanceToSqrt(mob.Home);
|
||||
|
||||
if (currentDistance < mob.RangeHome * 2 / 3)
|
||||
{
|
||||
WalkRandom(mob, 1);
|
||||
}
|
||||
else if (currentDistance > mob.RangeHome)
|
||||
{
|
||||
DoMove(mob, mob.GetDirectionTo(mob.Home));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Utility.RandomDouble() > 0.5)
|
||||
{
|
||||
DoMove(mob, mob.GetDirectionTo(mob.Home));
|
||||
}
|
||||
else
|
||||
{
|
||||
WalkRandom(mob, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mob.Location != mob.Home)
|
||||
{
|
||||
DoMove(mob, mob.GetDirectionTo(mob.Home));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static MoveResult WalkRandom(BaseCreature mob, int steps, bool run = false)
|
||||
{
|
||||
if (mob.Deleted || mob.DisallowAllMoves)
|
||||
{
|
||||
return MoveResult.BadState;
|
||||
}
|
||||
|
||||
for (var i = 0; i < steps; i++)
|
||||
{
|
||||
if (Utility.Random(8) <= 8)
|
||||
{
|
||||
var random = Utility.Random(0, 32);
|
||||
|
||||
Direction direction;
|
||||
|
||||
switch (random)
|
||||
{
|
||||
case 0:
|
||||
direction = Direction.Up;
|
||||
break;
|
||||
case 1:
|
||||
direction = Direction.North;
|
||||
break;
|
||||
case 2:
|
||||
direction = Direction.Left;
|
||||
break;
|
||||
case 3:
|
||||
direction = Direction.West;
|
||||
break;
|
||||
case 5:
|
||||
direction = Direction.Down;
|
||||
break;
|
||||
case 6:
|
||||
direction = Direction.South;
|
||||
break;
|
||||
case 7:
|
||||
direction = Direction.Right;
|
||||
break;
|
||||
case 8:
|
||||
direction = Direction.East;
|
||||
break;
|
||||
default:
|
||||
direction = mob.Direction;
|
||||
break;
|
||||
}
|
||||
|
||||
DoMove(mob, direction, run);
|
||||
}
|
||||
}
|
||||
return MoveResult.Success;
|
||||
}
|
||||
|
||||
public static MoveResult DoMove(BaseCreature mob, Direction d, bool run = false)
|
||||
{
|
||||
if (mob.Deleted || mob.Frozen || mob.Paralyzed || mob.Spell?.IsCasting == true || mob.DisallowAllMoves)
|
||||
{
|
||||
return MoveResult.BadState;
|
||||
}
|
||||
|
||||
Direction direction = d;
|
||||
|
||||
if (run)
|
||||
{
|
||||
direction |= Direction.Running;
|
||||
}
|
||||
|
||||
mob.Pushing = false;
|
||||
|
||||
MoveImpl.IgnoreMovableImpassables = mob.CanMoveOverObstacles && !mob.CanDestroyObstacles;
|
||||
|
||||
if ((mob.Direction & Direction.Mask) != (direction & Direction.Mask))
|
||||
{
|
||||
bool moved = mob.Move(direction);
|
||||
|
||||
MoveImpl.IgnoreMovableImpassables = false;
|
||||
return moved ? MoveResult.Success : MoveResult.Blocked;
|
||||
}
|
||||
|
||||
if (mob.Move(direction))
|
||||
{
|
||||
MoveImpl.IgnoreMovableImpassables = false;
|
||||
return MoveResult.Success;
|
||||
}
|
||||
|
||||
bool wasPushing = mob.Pushing;
|
||||
|
||||
bool blocked = true;
|
||||
|
||||
bool canOpenDoors = mob.CanOpenDoors;
|
||||
bool canDestroyObstacles = mob.CanDestroyObstacles;
|
||||
|
||||
if (canOpenDoors || canDestroyObstacles)
|
||||
{
|
||||
Map map = mob.Map;
|
||||
|
||||
if (map != null)
|
||||
{
|
||||
int x = mob.X, y = mob.Y;
|
||||
Movement.Movement.Offset(direction, ref x, ref y);
|
||||
|
||||
int destroyables = 0;
|
||||
List<Item> obstacles = new List<Item>();
|
||||
|
||||
var eable = map.GetItemsInRange(new Point3D(x, y, mob.Location.Z), 1);
|
||||
|
||||
foreach (var item in eable)
|
||||
{
|
||||
if (canOpenDoors && item is BaseDoor door && door.Z + door.ItemData.Height > mob.Z && mob.Z + 16 > door.Z)
|
||||
{
|
||||
if (door.X != x || door.Y != y)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!door.Locked || !door.UseLocks())
|
||||
{
|
||||
obstacles.Add(item);
|
||||
}
|
||||
|
||||
if (!canDestroyObstacles)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (canDestroyObstacles && item.Movable && item.ItemData.Impassable && item.Z + item.ItemData.Height > mob.Z && mob.Z + 16 > item.Z)
|
||||
{
|
||||
if (!mob.InRange(item.GetWorldLocation(), 1))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
obstacles.Add(item);
|
||||
++destroyables;
|
||||
}
|
||||
}
|
||||
|
||||
eable.Free();
|
||||
|
||||
if (destroyables > 0)
|
||||
{
|
||||
Effects.PlaySound(new Point3D(x, y, mob.Z), mob.Map, 0x3B3);
|
||||
}
|
||||
|
||||
if (obstacles.Count > 0)
|
||||
blocked = true;
|
||||
|
||||
while (obstacles.Count > 0)
|
||||
{
|
||||
Item item = obstacles.First();
|
||||
|
||||
if (item is BaseDoor door)
|
||||
{
|
||||
mob.DebugSay("Opening door...");
|
||||
if (!door.Open)
|
||||
{
|
||||
door.Use(mob);
|
||||
}
|
||||
obstacles.Remove(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item is Container container)
|
||||
{
|
||||
for (var i = 0; i < container.Items.Count; ++i)
|
||||
{
|
||||
Item check = container.Items[i];
|
||||
|
||||
if (check.Movable && check.ItemData.Impassable && container.Z + check.ItemData.Height > mob.Z)
|
||||
{
|
||||
obstacles.Add(check);
|
||||
}
|
||||
}
|
||||
|
||||
obstacles.Remove(item);
|
||||
container.Destroy();
|
||||
}
|
||||
else
|
||||
{
|
||||
obstacles.Remove(item);
|
||||
item.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!blocked)
|
||||
{
|
||||
blocked = !mob.Move(direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (blocked)
|
||||
{
|
||||
int offset = Utility.RandomDouble() >= 0.6 ? 1 : -1;
|
||||
|
||||
for (var i = 0; i < 2; ++i)
|
||||
{
|
||||
mob.TurnInternal(offset);
|
||||
|
||||
if (mob.Move(mob.Direction))
|
||||
{
|
||||
MoveImpl.IgnoreMovableImpassables = false;
|
||||
return MoveResult.SuccessAutoTurn;
|
||||
}
|
||||
}
|
||||
|
||||
MoveImpl.IgnoreMovableImpassables = false;
|
||||
return wasPushing ? MoveResult.BadState : MoveResult.Blocked;
|
||||
}
|
||||
|
||||
MoveImpl.IgnoreMovableImpassables = false;
|
||||
return MoveResult.Success;
|
||||
}
|
||||
public static bool WalkMobileRange(BaseCreature mob, BaseCreature target, int steps, bool run, int minRange, int maxRange)
|
||||
{
|
||||
if (mob.Deleted || mob.DisallowAllMoves)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < steps; i++)
|
||||
{
|
||||
// Get the current distance
|
||||
var currentDistance = (int)mob.GetDistanceToSqrt(target);
|
||||
|
||||
if (currentDistance < minRange || currentDistance > maxRange)
|
||||
{
|
||||
var needCloser = currentDistance > maxRange;
|
||||
var direction = needCloser ?
|
||||
mob.GetDirectionTo(target, run) : target.GetDirectionTo(mob, run);
|
||||
|
||||
DoMove(mob, direction, run);
|
||||
}
|
||||
else
|
||||
{
|
||||
WalkRandom(mob, 2, run);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the current distance
|
||||
var newDistance = (int)mob.GetDistanceToSqrt(target);
|
||||
|
||||
return newDistance >= minRange && newDistance <= maxRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Projects/UOContent/Mobiles/BT/Core/BehaviorTreeNode.cs
Normal file
27
Projects/UOContent/Mobiles/BT/Core/BehaviorTreeNode.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public abstract class BehaviorTreeNode
|
||||
{
|
||||
public enum Result { Running, Failure, Success }
|
||||
public BehaviorTreeNode Parent;
|
||||
public BehaviorTree Tree { get; private set; }
|
||||
public BehaviorTreeNode(BehaviorTree tree) : this(tree, null)
|
||||
{
|
||||
}
|
||||
public BehaviorTreeNode(BehaviorTree tree, BehaviorTreeNode parent)
|
||||
{
|
||||
Parent = parent;
|
||||
Tree = tree;
|
||||
}
|
||||
public virtual Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Projects/UOContent/Mobiles/BT/Core/Blackboard.cs
Normal file
12
Projects/UOContent/Mobiles/BT/Core/Blackboard.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class Blackboard : Dictionary<string, object>
|
||||
{
|
||||
}
|
||||
}
|
||||
25
Projects/UOContent/Mobiles/BT/Core/CompositeNode.cs
Normal file
25
Projects/UOContent/Mobiles/BT/Core/CompositeNode.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public abstract class CompositeNode : BehaviorTreeNode
|
||||
{
|
||||
protected Dictionary<BaseCreature, int> currentTaskCache;
|
||||
public List<BehaviorTreeNode> Children { get; protected set; }
|
||||
public CompositeNode(BehaviorTree tree) : base(tree)
|
||||
{
|
||||
currentTaskCache = new Dictionary<BaseCreature, int>();
|
||||
Children = new List<BehaviorTreeNode>();
|
||||
}
|
||||
public virtual CompositeNode AddChild(BehaviorTreeNode child)
|
||||
{
|
||||
child.Parent = this;
|
||||
Children.Add(child);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Projects/UOContent/Mobiles/BT/Core/DecoratorNode.cs
Normal file
28
Projects/UOContent/Mobiles/BT/Core/DecoratorNode.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public abstract class DecoratorNode : BehaviorTreeNode
|
||||
{
|
||||
public BehaviorTreeNode Child;
|
||||
public DecoratorNode(BehaviorTree tree) : base(tree)
|
||||
{
|
||||
}
|
||||
public DecoratorNode(BehaviorTree tree, BehaviorTreeNode child) : this(tree)
|
||||
{
|
||||
AddChild(child);
|
||||
}
|
||||
public virtual DecoratorNode AddChild(BehaviorTreeNode child)
|
||||
{
|
||||
if (Child == null)
|
||||
{
|
||||
Child = child;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
32
Projects/UOContent/Mobiles/BT/Decorator/CheckSkillNode.cs
Normal file
32
Projects/UOContent/Mobiles/BT/Decorator/CheckSkillNode.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class CheckSkillNode : DecoratorNode
|
||||
{
|
||||
private SkillName checkSkill;
|
||||
private double minSkillValue;
|
||||
private double maxSkillValue;
|
||||
|
||||
public CheckSkillNode(BehaviorTree tree, SkillName skill, double minSkill, double maxSkill) : base(tree)
|
||||
{
|
||||
checkSkill = skill;
|
||||
minSkillValue = minSkill;
|
||||
maxSkillValue = maxSkill;
|
||||
}
|
||||
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if(mob.CheckSkill(checkSkill, minSkillValue, maxSkillValue) && Child != null)
|
||||
{
|
||||
return Child.Execute(mob, blackboard);
|
||||
}
|
||||
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class ConditionalLoopNode : DecoratorNode
|
||||
{
|
||||
public delegate bool ConditionalLoopPredicate(BaseCreature mob, Blackboard blackboard);
|
||||
private ConditionalLoopPredicate predicate;
|
||||
public ConditionalLoopNode(BehaviorTree tree, ConditionalLoopPredicate fn) : base(tree)
|
||||
{
|
||||
predicate = fn;
|
||||
}
|
||||
|
||||
public ConditionalLoopNode(BehaviorTree tree, ConditionalLoopPredicate fn, BehaviorTreeNode child) : base(tree, child)
|
||||
{
|
||||
predicate = fn;
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (predicate(mob, blackboard))
|
||||
{
|
||||
if (Child != null)
|
||||
{
|
||||
Child.Execute(mob, blackboard);
|
||||
return Result.Running;
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
Projects/UOContent/Mobiles/BT/Decorator/CooldownNode.cs
Normal file
51
Projects/UOContent/Mobiles/BT/Decorator/CooldownNode.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class CooldownNode : DecoratorNode
|
||||
{
|
||||
private TimeSpan cooldownDuration;
|
||||
private Dictionary<BaseCreature, DateTime> cooldowns;
|
||||
public CooldownNode(BehaviorTree tree, TimeSpan duration) : base(tree)
|
||||
{
|
||||
cooldownDuration = duration;
|
||||
cooldowns = new Dictionary<BaseCreature, DateTime>();
|
||||
}
|
||||
public CooldownNode(BehaviorTree tree, TimeSpan duration, BehaviorTreeNode child) : base(tree, child)
|
||||
{
|
||||
cooldownDuration = duration;
|
||||
cooldowns = new Dictionary<BaseCreature, DateTime>();
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (Child != null)
|
||||
{
|
||||
if (!cooldowns.TryGetValue(mob, out DateTime nextCooldown))
|
||||
{
|
||||
nextCooldown = DateTime.Now;
|
||||
cooldowns.Add(mob, nextCooldown);
|
||||
}
|
||||
|
||||
if (DateTime.Now >= nextCooldown)
|
||||
{
|
||||
Result result = Child.Execute(mob, blackboard);
|
||||
|
||||
if (result == Result.Running)
|
||||
{
|
||||
return Result.Running;
|
||||
}
|
||||
else
|
||||
{
|
||||
cooldowns[mob] = DateTime.Now + cooldownDuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Projects/UOContent/Mobiles/BT/Decorator/ForceSuccessNode.cs
Normal file
31
Projects/UOContent/Mobiles/BT/Decorator/ForceSuccessNode.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class ForceSuccessNode : DecoratorNode
|
||||
{
|
||||
public ForceSuccessNode(BehaviorTree tree) : base(tree)
|
||||
{
|
||||
}
|
||||
public ForceSuccessNode(BehaviorTree tree, BehaviorTreeNode child) : base(tree, child)
|
||||
{
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (Child != null)
|
||||
{
|
||||
if (Child.Execute(mob, blackboard) == Result.Running)
|
||||
{
|
||||
return Result.Running;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Projects/UOContent/Mobiles/BT/Decorator/InverterNode.cs
Normal file
36
Projects/UOContent/Mobiles/BT/Decorator/InverterNode.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class InverterNode : DecoratorNode
|
||||
{
|
||||
public InverterNode(BehaviorTree tree) : base(tree)
|
||||
{
|
||||
}
|
||||
public InverterNode(BehaviorTree tree, BehaviorTreeNode child) : base(tree, child)
|
||||
{
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (Child != null)
|
||||
{
|
||||
var result = Child.Execute(mob, blackboard);
|
||||
switch (result)
|
||||
{
|
||||
case Result.Failure:
|
||||
return Result.Success;
|
||||
case Result.Success:
|
||||
return Result.Failure;
|
||||
default:
|
||||
return Result.Running;
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Projects/UOContent/Mobiles/BT/Decorator/LoopNode.cs
Normal file
42
Projects/UOContent/Mobiles/BT/Decorator/LoopNode.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class LoopNode : DecoratorNode
|
||||
{
|
||||
private int count;
|
||||
private Dictionary<BaseCreature, int> currentCountCache;
|
||||
public LoopNode(BehaviorTree tree, int n) : base(tree)
|
||||
{
|
||||
count = n;
|
||||
currentCountCache = new Dictionary<BaseCreature, int>();
|
||||
}
|
||||
public LoopNode(BehaviorTree tree, int n, BehaviorTreeNode child) : base(tree, child)
|
||||
{
|
||||
count = n;
|
||||
currentCountCache = new Dictionary<BaseCreature, int>();
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (!currentCountCache.TryGetValue(mob, out int currentCount))
|
||||
{
|
||||
currentCount = 0;
|
||||
currentCountCache.Add(mob, currentCount);
|
||||
}
|
||||
|
||||
if (Child != null && currentCount < count)
|
||||
{
|
||||
Child.Execute(mob, blackboard);
|
||||
currentCountCache[mob]++;
|
||||
return Result.Running;
|
||||
}
|
||||
else if (currentCount >= count)
|
||||
{
|
||||
currentCountCache[mob] = 0;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class OwnerConditionNode : DecoratorNode
|
||||
{
|
||||
public delegate bool OwnerConditionPredicate(BaseCreature mob, Blackboard blackboard);
|
||||
private OwnerConditionPredicate predicate;
|
||||
public OwnerConditionNode(BehaviorTree tree, OwnerConditionPredicate fn) : base(tree)
|
||||
{
|
||||
predicate = fn;
|
||||
}
|
||||
public OwnerConditionNode(BehaviorTree tree, OwnerConditionPredicate fn, BehaviorTreeNode child) : base(tree, child)
|
||||
{
|
||||
predicate = fn;
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (predicate(mob, blackboard))
|
||||
{
|
||||
if (Child != null)
|
||||
{
|
||||
return Child.Execute(mob, blackboard);
|
||||
}
|
||||
return Result.Success;
|
||||
}
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class RandomExecutionNode : DecoratorNode
|
||||
{
|
||||
private double activationChance;
|
||||
private Dictionary<BaseCreature, bool> executingCache = new Dictionary<BaseCreature, bool>();
|
||||
public RandomExecutionNode(BehaviorTree tree, double chance) : base(tree)
|
||||
{
|
||||
activationChance = chance;
|
||||
}
|
||||
public RandomExecutionNode(BehaviorTree tree, double chance, BehaviorTreeNode child) : base(tree, child)
|
||||
{
|
||||
activationChance = chance;
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (!executingCache.TryGetValue(mob, out bool executing))
|
||||
{
|
||||
executing = false;
|
||||
executingCache.Add(mob, executing);
|
||||
}
|
||||
|
||||
if (Child != null && (Utility.RandomDouble() < activationChance || executing))
|
||||
{
|
||||
Result result = Child.Execute(mob, blackboard);
|
||||
|
||||
if (result == Result.Running)
|
||||
{
|
||||
executingCache[mob] = true;
|
||||
return Result.Running;
|
||||
}
|
||||
|
||||
executingCache[mob] = false;
|
||||
return result;
|
||||
}
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Projects/UOContent/Mobiles/BT/Decorator/TapNode.cs
Normal file
29
Projects/UOContent/Mobiles/BT/Decorator/TapNode.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public class TapNode : DecoratorNode
|
||||
{
|
||||
public delegate void TapNodeAction(BaseCreature mob, Blackboard blackboard);
|
||||
public TapNodeAction Action { get; private set; }
|
||||
public TapNode(BehaviorTree tree, TapNodeAction action) : base(tree)
|
||||
{
|
||||
Action = action;
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
Action(mob, blackboard);
|
||||
|
||||
if (Child != null)
|
||||
{
|
||||
return Child.Execute(mob, blackboard);
|
||||
}
|
||||
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
26
Projects/UOContent/Mobiles/BT/Leaf/ConditionNode.cs
Normal file
26
Projects/UOContent/Mobiles/BT/Leaf/ConditionNode.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public delegate bool ConditionNodePredicate(BaseCreature mob, Blackboard blackboard);
|
||||
public class ConditionNode : LeafNode
|
||||
{
|
||||
private ConditionNodePredicate predicate;
|
||||
public ConditionNode(BehaviorTree tree, ConditionNodePredicate fn) : base(tree)
|
||||
{
|
||||
predicate = fn;
|
||||
}
|
||||
public override Result Execute(BaseCreature mob, Blackboard blackboard)
|
||||
{
|
||||
if (predicate(mob, blackboard))
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Projects/UOContent/Mobiles/BT/MageBehaviorTree.cs
Normal file
35
Projects/UOContent/Mobiles/BT/MageBehaviorTree.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
|
||||
namespace Server.Mobiles.BT
|
||||
{
|
||||
public sealed class MageBehaviorTree : BehaviorTree
|
||||
{
|
||||
private static MageBehaviorTree instance;
|
||||
|
||||
private MageBehaviorTree()
|
||||
{
|
||||
}
|
||||
public static MageBehaviorTree Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = new MageBehaviorTree();
|
||||
instance.InitTree();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
private void InitTree()
|
||||
{
|
||||
TryAddRoot(
|
||||
// do the first thing that succeeds of the three children
|
||||
new SelectorNode(this)
|
||||
.AddChild(new TapNode(this, (mob, board) => mob.DebugSay("Thinking...")))
|
||||
.AddChild(new BehaviorMageCombatNodeSet(this))
|
||||
.AddChild(new BehaviorMagePassiveNodeSet(this))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
91
Projects/UOContent/Mobiles/BehaviorMage.cs
Normal file
91
Projects/UOContent/Mobiles/BehaviorMage.cs
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<BaseCreature, Spell> getSpellCallback;
|
||||
public CastSpellActionNode(BehaviorTree tree, Func<BaseCreature, Spell> callback) : base(tree)
|
||||
{
|
||||
object nextCastTime;
|
||||
if (!Tree.Blackboard.TryGetValue("nextCastTime", out nextCastTime))
|
||||
{
|
||||
tree.Blackboard.Add("nextCastTime", DateTime.Now);
|
||||
}
|
||||
getSpellCallback = callback;
|
||||
}
|
||||
|
||||
public override Result Execute()
|
||||
{
|
||||
object nextCastTime;
|
||||
|
||||
if (Tree.Blackboard.TryGetValue("nextCastTime", out nextCastTime) && DateTime.Now < (DateTime)nextCastTime)
|
||||
{
|
||||
Tree.Mobile.DebugSay("Spells are on cooldown...");
|
||||
return Result.Failure;
|
||||
}
|
||||
|
||||
if (Tree.Mobile.Spell == null)
|
||||
{
|
||||
Spell spell = getSpellCallback(Tree.Mobile);
|
||||
|
||||
Tree.Mobile.DebugSay("Casting...");
|
||||
if (!spell.Cast())
|
||||
{
|
||||
Tree.Mobile.DebugSay("Failed to cast...");
|
||||
return Result.Failure;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(spell.Mantra))
|
||||
Tree.Mobile.PublicOverheadMessage(Network.MessageType.Spell, 0, false, spell.Mantra, false);
|
||||
|
||||
return Result.Running;
|
||||
}
|
||||
else if (Tree.Mobile.Spell.IsCasting)
|
||||
{
|
||||
Tree.Mobile.DebugSay("Still casting...");
|
||||
return Result.Running;
|
||||
}
|
||||
else if (!Tree.Mobile.Spell.IsCasting)
|
||||
{
|
||||
Tree.Mobile.DebugSay("Finished casting...");
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<BehaviorTreeNode>(new BehaviorTreeNode[5]
|
||||
{
|
||||
new ConditionNode(
|
||||
tree,
|
||||
(mob) =>
|
||||
{
|
||||
if (mob.Poison == null)
|
||||
{
|
||||
mob.DebugSay("I am not poisoned");
|
||||
return false;
|
||||
}
|
||||
|
||||
object nextHealTime;
|
||||
if (Tree.Blackboard.TryGetValue("nextHealTime", out nextHealTime))
|
||||
{
|
||||
if (DateTime.Now >= (DateTime)nextHealTime)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
mob.DebugSay("My heal cooldown is active");
|
||||
}
|
||||
|
||||
mob.DebugSay("My heal cooldown is not set");
|
||||
|
||||
return false;
|
||||
}
|
||||
),
|
||||
new ConditionNode(
|
||||
tree,
|
||||
(mob) =>
|
||||
{
|
||||
return mob.Spell == null || !mob.Spell.IsCasting;
|
||||
}
|
||||
),
|
||||
new CastSpellActionNode(
|
||||
tree,
|
||||
(mob) => new CureSpell(mob)
|
||||
),
|
||||
new AlwaysSucceedNode(
|
||||
tree,
|
||||
new TapNode(
|
||||
tree,
|
||||
(mob) =>
|
||||
{
|
||||
Tree.Blackboard.Remove("nextHealTime");
|
||||
Tree.Blackboard.Add("nextHealTime", DateTime.Now + TimeSpan.FromSeconds(10.0));
|
||||
}
|
||||
)
|
||||
),
|
||||
new TargetActionNode(tree),
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
/*
|
||||
public class CureSelfActionNode : ActionNode
|
||||
{
|
||||
public TimeSpan Cooldown { get; private set; }
|
||||
public CureSelfActionNode(BehaviorTree tree, TimeSpan cooldown) : base(tree)
|
||||
{
|
||||
object nextHealTime;
|
||||
if (!Tree.Blackboard.TryGetValue("nextHealTime", out nextHealTime))
|
||||
{
|
||||
tree.Blackboard.Add("nextHealTime", DateTime.Now);
|
||||
}
|
||||
Cooldown = cooldown;
|
||||
}
|
||||
|
||||
public override Result Execute()
|
||||
{
|
||||
object nextHealTime;
|
||||
if (Tree.Mobile.Poison != null && Tree.Blackboard.TryGetValue("nextHealTime", out nextHealTime))
|
||||
{
|
||||
if (DateTime.Now >= (DateTime)nextHealTime)
|
||||
{
|
||||
new CureSpell(Tree.Mobile).Cast();
|
||||
Tree.Mobile.DebugSay("Casting cure spell for myself");
|
||||
Tree.Blackboard.Remove("nextHealTime");
|
||||
Tree.Blackboard.Add("nextHealTime", DateTime.Now + Cooldown);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
Tree.Mobile.DebugSay("I'm poisoned, but my healing is on cooldown");
|
||||
|
||||
return Result.Failure;
|
||||
}
|
||||
|
||||
Tree.Mobile.DebugSay("I'm not poisoned");
|
||||
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<BehaviorTreeNode>(new BehaviorTreeNode[1]
|
||||
{
|
||||
// if we have a combatant
|
||||
new ConditionNode(
|
||||
tree,
|
||||
mob => mob.Combatant != null,
|
||||
)
|
||||
new ConditionNode(
|
||||
tree,
|
||||
mob => mob.Combatant != null,
|
||||
// do the following in sequence
|
||||
new SelectorNode(
|
||||
tree,
|
||||
new BehaviorTreeNode[2]
|
||||
{
|
||||
// do the first appropriate debuff until all are done
|
||||
new SelectorNode(
|
||||
tree,
|
||||
new BehaviorTreeNode[3]
|
||||
{
|
||||
// weaken if necessary
|
||||
new ConditionNode(
|
||||
tree,
|
||||
mob => mob.Combatant.Str == mob.Combatant.RawStr,
|
||||
new SequenceNode(
|
||||
tree,
|
||||
new BehaviorTreeNode[2]
|
||||
{
|
||||
new CastSpellActionNode(
|
||||
tree,
|
||||
mob => new WeakenSpell(mob)
|
||||
),
|
||||
new TargetActionNode(tree)
|
||||
}
|
||||
)
|
||||
),
|
||||
// clumbsy if necessary
|
||||
new ConditionNode(
|
||||
tree,
|
||||
mob => mob.Combatant.Dex == mob.Combatant.RawDex,
|
||||
new SequenceNode(
|
||||
tree,
|
||||
new BehaviorTreeNode[2]
|
||||
{
|
||||
new CastSpellActionNode(
|
||||
tree,
|
||||
mob => new ClumsySpell(mob)
|
||||
),
|
||||
new TargetActionNode(tree)
|
||||
}
|
||||
)
|
||||
),
|
||||
// feeblemind if necessary
|
||||
new ConditionNode(
|
||||
tree,
|
||||
mob => mob.Combatant.Int == mob.Combatant.RawInt,
|
||||
new SequenceNode(
|
||||
tree,
|
||||
new BehaviorTreeNode[2]
|
||||
{
|
||||
new CastSpellActionNode(
|
||||
tree,
|
||||
mob => new FeeblemindSpell(mob)
|
||||
),
|
||||
new TargetActionNode(tree)
|
||||
}
|
||||
)
|
||||
),
|
||||
}
|
||||
),
|
||||
// do explosion ebolt combo
|
||||
new SequenceNode(
|
||||
tree,
|
||||
new BehaviorTreeNode[2]
|
||||
{
|
||||
// explosion
|
||||
new SequenceNode(
|
||||
tree,
|
||||
new BehaviorTreeNode[2]
|
||||
{
|
||||
new CastSpellActionNode(
|
||||
tree,
|
||||
mob => new ExplosionSpell(mob)
|
||||
),
|
||||
new TargetActionNode(tree)
|
||||
}
|
||||
),
|
||||
// ebolt
|
||||
new SequenceNode(
|
||||
tree,
|
||||
new BehaviorTreeNode[2]
|
||||
{
|
||||
new CastSpellActionNode(
|
||||
tree,
|
||||
mob => new EnergyBoltSpell(mob)
|
||||
),
|
||||
new TargetActionNode(tree)
|
||||
}
|
||||
)
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
});
|
||||
*/
|
||||
|
||||
/*
|
||||
Children = new List<BehaviorTreeNode>(new BehaviorTreeNode[1] {
|
||||
new ConditionNode(
|
||||
tree,
|
||||
mob => mob.Combatant != null,
|
||||
new SequenceNode(
|
||||
tree,
|
||||
new BehaviorTreeNode[3] {
|
||||
new AlwaysSucceedNode(
|
||||
tree,
|
||||
new ConditionNode(
|
||||
tree,
|
||||
mob => mob.Combatant != null && mob.Combatant.Str == mob.Combatant.RawStr,
|
||||
new SequenceNode(
|
||||
tree,
|
||||
new BehaviorTreeNode[2]
|
||||
{
|
||||
new CastSpellActionNode(
|
||||
tree,
|
||||
mob => new WeakenSpell(mob)
|
||||
),
|
||||
new TargetActionNode(tree)
|
||||
}
|
||||
)
|
||||
)
|
||||
),
|
||||
new SequenceNode(tree, new BehaviorTreeNode[2]{
|
||||
new CastSpellActionNode(
|
||||
tree,
|
||||
mob => new ExplosionSpell(mob)
|
||||
),
|
||||
new TargetActionNode(tree)
|
||||
}),
|
||||
new SequenceNode(tree, new BehaviorTreeNode[2]{
|
||||
new SelectorNode(
|
||||
tree,
|
||||
new BehaviorTreeNode[2]
|
||||
{
|
||||
new ConditionNode(
|
||||
tree,
|
||||
mob => Utility.RandomDouble() < 0.3,
|
||||
new CastSpellActionNode(
|
||||
tree,
|
||||
mob => new MindBlastSpell(mob)
|
||||
)
|
||||
),
|
||||
new CastSpellActionNode(
|
||||
tree,
|
||||
mob => new EnergyBoltSpell(mob)
|
||||
)
|
||||
}
|
||||
),
|
||||
new TargetActionNode(tree)
|
||||
}),
|
||||
}
|
||||
)
|
||||
)
|
||||
});
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<BehaviorTreeNode>(new BehaviorTreeNode[1]
|
||||
{
|
||||
new SequenceNode(
|
||||
tree,
|
||||
new BehaviorTreeNode[2]
|
||||
{
|
||||
new ConditionNode(
|
||||
tree,
|
||||
mob => true // Utility.RandomDouble() < 0.5
|
||||
),
|
||||
new CureSelfNode(tree),
|
||||
}
|
||||
)
|
||||
/*
|
||||
new SequenceNode(
|
||||
tree,
|
||||
new BehaviorTreeNode[2]
|
||||
{
|
||||
new ConditionNode(
|
||||
tree,
|
||||
mob => mob.Hits < mob.HitsMax - 20
|
||||
),
|
||||
new SequenceNode(
|
||||
tree,
|
||||
new BehaviorTreeNode[3]
|
||||
{
|
||||
new ConditionNode(
|
||||
tree,
|
||||
mob => true // Utility.RandomDouble() < 0.2
|
||||
),
|
||||
new HealSelfActionNode(tree, TimeSpan.FromSeconds(10.0)),
|
||||
new TargetActionNode(tree)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
*/
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<string, object> Blackboard { get; set; }
|
||||
public BehaviorTreeNode Root { get { return root; } }
|
||||
|
||||
private BehaviorTreeTimer treeTimer;
|
||||
|
||||
public BaseCreature Mobile { get; private set; }
|
||||
|
||||
public BehaviorTree(BaseCreature mob)
|
||||
{
|
||||
Blackboard = new Dictionary<string, object>();
|
||||
treeTimer = new BehaviorTreeTimer(this);
|
||||
Mobile = mob;
|
||||
}
|
||||
|
||||
public void AddRoot(BehaviorTreeNode rootNode)
|
||||
{
|
||||
if (root == null)
|
||||
{
|
||||
root = rootNode;
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
treeTimer.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
treeTimer.Stop();
|
||||
}
|
||||
|
||||
public virtual void Tick()
|
||||
{
|
||||
RunBehavior();
|
||||
}
|
||||
|
||||
public virtual void RunBehavior()
|
||||
{
|
||||
Root.Execute();
|
||||
}
|
||||
|
||||
private class BehaviorTreeTimer : Timer
|
||||
{
|
||||
private BehaviorTree behaviorTree;
|
||||
public BehaviorTreeTimer(BehaviorTree tree) : base(TimeSpan.Zero, TimeSpan.FromSeconds(1.0))
|
||||
{
|
||||
behaviorTree = tree;
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
if (behaviorTree != null)
|
||||
{
|
||||
behaviorTree.Tick();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
public enum ControlFlowNodeType
|
||||
{
|
||||
Sequence,
|
||||
Fallback,
|
||||
Parallel,
|
||||
Decorator
|
||||
}
|
||||
|
||||
public enum ExecutionNodeType
|
||||
{
|
||||
Action,
|
||||
Condition
|
||||
}
|
||||
|
||||
public class BehaviorTreeNode
|
||||
{
|
||||
private readonly List<BehaviorTreeNode> _children = new List<BehaviorTreeNode>();
|
||||
public bool Root { get { return Parent == null; } }
|
||||
|
||||
public BehaviorTreeNode() { }
|
||||
public BehaviorTreeNode(BehaviorTreeNode parent) : this()
|
||||
{
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public BehaviorTreeNode this[int i]
|
||||
{
|
||||
get { return _children[i]; }
|
||||
}
|
||||
|
||||
public BehaviorTreeNode Parent { get; private set; }
|
||||
|
||||
public ReadOnlyCollection<BehaviorTreeNode> Children
|
||||
{
|
||||
get { return _children.AsReadOnly(); }
|
||||
}
|
||||
|
||||
public virtual BehaviorTreeNode AddChild(BehaviorTreeNode node)
|
||||
{
|
||||
_children.Add(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
public virtual bool Evaluate(BaseCreature creature)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool Traverse(BaseCreature creature)
|
||||
{
|
||||
foreach (var child in _children)
|
||||
child.Traverse(creature);
|
||||
}
|
||||
}
|
||||
|
||||
public class SequenceNode : BehaviorTreeNode {
|
||||
public override bool Evaluate(BaseCreature creature)
|
||||
{
|
||||
return Children.All((BehaviorTreeNode node) => node.Evaluate(creature));
|
||||
}
|
||||
|
||||
public override bool Traverse(BaseCreature creature)
|
||||
{
|
||||
if (Evaluate(creature)) return true;
|
||||
|
||||
foreach (var child in Children)
|
||||
child.Traverse(creature);
|
||||
}
|
||||
}
|
||||
|
||||
public class FallbackNode : BehaviorTreeNode
|
||||
{
|
||||
public override bool Evaluate(BaseCreature creature)
|
||||
{
|
||||
return Children.Any((BehaviorTreeNode node) => node.Evaluate(creature));
|
||||
}
|
||||
|
||||
public override bool Traverse(BaseCreature creature)
|
||||
{
|
||||
return Evaluate(creature);
|
||||
}
|
||||
}
|
||||
|
||||
public class ParallelNode : BehaviorTreeNode
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class DecoratorNode : BehaviorTreeNode
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class ActionNode : BehaviorTreeNode
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class ConditionNode : BehaviorTreeNode
|
||||
{
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<BaseCreature> predicate;
|
||||
public ConditionNode(BehaviorTree tree, Predicate<BaseCreature> fn) : base(tree)
|
||||
{
|
||||
predicate = fn;
|
||||
}
|
||||
public override Result Execute()
|
||||
{
|
||||
if (predicate(Tree.Mobile))
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<BehaviorTreeNode> Children { get; protected set; }
|
||||
public SelectorNode(BehaviorTree tree) : this(tree, new BehaviorTreeNode[0]) { }
|
||||
public SelectorNode(BehaviorTree tree, BehaviorTreeNode[] children) : base(tree)
|
||||
{
|
||||
Children = new List<BehaviorTreeNode>(children);
|
||||
}
|
||||
|
||||
public override Result Execute()
|
||||
{
|
||||
if (currentTask < Children.Count)
|
||||
{
|
||||
var result = Children[currentTask].Execute();
|
||||
|
||||
if (result == Result.Success)
|
||||
{
|
||||
currentTask = 0;
|
||||
return Result.Success;
|
||||
}
|
||||
else if (result == Result.Running)
|
||||
{
|
||||
return Result.Running;
|
||||
}
|
||||
|
||||
currentTask++;
|
||||
if (currentTask == Children.Count)
|
||||
{
|
||||
currentTask = 0;
|
||||
return Result.Failure;
|
||||
}
|
||||
|
||||
return Result.Running;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<BehaviorTreeNode> Children { get; protected set; }
|
||||
public SequenceNode(BehaviorTree tree) : this(tree, new BehaviorTreeNode[0]) { }
|
||||
public SequenceNode(BehaviorTree tree, BehaviorTreeNode[] children) : base(tree)
|
||||
{
|
||||
Children = new List<BehaviorTreeNode>(children);
|
||||
}
|
||||
|
||||
public override Result Execute()
|
||||
{
|
||||
if(currentTask < Children.Count)
|
||||
{
|
||||
var result = Children[currentTask].Execute();
|
||||
|
||||
if (result == Result.Running)
|
||||
{
|
||||
return Result.Running;
|
||||
}
|
||||
else if (result == Result.Failure)
|
||||
{
|
||||
currentTask = 0;
|
||||
return Result.Failure;
|
||||
}
|
||||
|
||||
currentTask++;
|
||||
if(currentTask == Children.Count)
|
||||
{
|
||||
currentTask = 0;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
return Result.Running;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<BaseCreature> Action { get; private set; }
|
||||
public TapNode(BehaviorTree tree, Action<BaseCreature> action, BehaviorTreeNode child) : base(tree, child)
|
||||
{
|
||||
}
|
||||
public TapNode(BehaviorTree tree, Action<BaseCreature> action) : base(tree)
|
||||
{
|
||||
Action = action;
|
||||
}
|
||||
public override Result Execute()
|
||||
{
|
||||
Action(Tree.Mobile);
|
||||
|
||||
if (Child != null)
|
||||
{
|
||||
return Child.Execute();
|
||||
}
|
||||
|
||||
return Result.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Projects/UOContent/Mobiles/FireBoss.cs
Normal file
44
Projects/UOContent/Mobiles/FireBoss.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
namespace Server.Mobiles
|
||||
{
|
||||
public class FireBoss : BaseCreature
|
||||
{
|
||||
[Constructible]
|
||||
public FireBoss() : base(AIType.AI_FireBoss, FightMode.Closest, 10, 3, 0.05, 0.05)
|
||||
{
|
||||
Body = 172;
|
||||
Hue = 1360;
|
||||
|
||||
SetStr(400);
|
||||
SetDex(200);
|
||||
SetInt(1000);
|
||||
|
||||
SetHits(300000);
|
||||
|
||||
SetDamage(50);
|
||||
|
||||
SetDamageType(ResistanceType.Fire, 100);
|
||||
|
||||
SetResistance(ResistanceType.Fire, 50);
|
||||
|
||||
Fame = 25000;
|
||||
Karma = -25000;
|
||||
|
||||
VirtualArmor = 92;
|
||||
}
|
||||
public override string CorpseName => "fire boss corpse";
|
||||
public override string DefaultName => "fire boss";
|
||||
public FireBoss(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
writer.Write(0);
|
||||
}
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
var version = reader.ReadInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -146,7 +146,7 @@ namespace Server.Mobiles
|
|||
|
||||
if (!IsInvulnerable)
|
||||
{
|
||||
AI = AIType.AI_Mage;
|
||||
AI = AIType.AI_BehaviorTree;
|
||||
ActiveSpeed = 0.2;
|
||||
PassiveSpeed = 0.8;
|
||||
RangePerception = DefaultRangePerception;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
48
Projects/UOContent/Mobiles/Vendors/FireBossPriest.cs
Normal file
48
Projects/UOContent/Mobiles/Vendors/FireBossPriest.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server.Mobiles.Vendors
|
||||
{
|
||||
public class FireBossPriest : BaseCreature
|
||||
{
|
||||
public FireBoss Owner { get; private set; }
|
||||
public FireBossPriest(FireBoss owner) : base(AIType.AI_Animal, FightMode.Closest, 10, 1, 2, 2)
|
||||
{
|
||||
Owner = owner;
|
||||
|
||||
Body = 770;
|
||||
Hue = 1358;
|
||||
|
||||
SetStr(1185);
|
||||
SetDex(255);
|
||||
SetInt(250);
|
||||
|
||||
SetHits(725);
|
||||
|
||||
SetDamage(25);
|
||||
|
||||
Fame = 24000;
|
||||
Karma = -24000;
|
||||
|
||||
VirtualArmor = 90;
|
||||
}
|
||||
public FireBossPriest(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
writer.Write(0);
|
||||
writer.Write(Owner);
|
||||
}
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
var version = reader.ReadInt();
|
||||
Owner = reader.ReadEntity<FireBoss>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue