diff --git a/Albion/Merlin/Merlin.csproj b/Albion/Merlin/Merlin.csproj
index 6c2e9b7..5d1aee7 100644
--- a/Albion/Merlin/Merlin.csproj
+++ b/Albion/Merlin/Merlin.csproj
@@ -174,6 +174,7 @@
+
@@ -246,4 +247,4 @@
-
+
\ No newline at end of file
diff --git a/Albion/Merlin/Profiles/Combat.cs b/Albion/Merlin/Profiles/Combat.cs
index 30cc72a..7f59123 100644
--- a/Albion/Merlin/Profiles/Combat.cs
+++ b/Albion/Merlin/Profiles/Combat.cs
@@ -2,94 +2,71 @@
using System.Diagnostics;
using System.Linq;
using Merlin.API;
-using Stateless;
using UnityEngine;
+using Player = LocalPlayerCharacterView;
using SpellCategory = gz.SpellCategory;
using SpellTarget = gz.SpellTarget;
namespace Merlin.Profiles {
- class Combat {
- private readonly LocalPlayerCharacterView _player;
- private readonly List _mobList;
+ public class Combat {
- private StateMachine _state;
-
- private float elapsedSeconds;
- private float secondCount;
-
- public Combat(LocalPlayerCharacterView player) {
- _player = player;
- _mobList = new List();
-
- _state = new StateMachine(State.Idle);
- _state.Configure(State.Idle)
- .Permit(Trigger.Died, State.Respawn)
- .Permit(Trigger.Recover, State.Recover)
- .Permit(Trigger.EncounteredAttacker, State.Combat);
-
- _state.Configure(State.Combat)
- .Permit(Trigger.Finished, State.Idle)
- // .Permit(Trigger.LowHealth, State.Flee) Not working for now.
- .Permit(Trigger.Died, State.Respawn);
-
- _state.Configure(State.Respawn)
- .Permit(Trigger.Finished, State.Idle);
-
- _state.Configure(State.Flee)
- .Permit(Trigger.Died, State.Respawn)
- .Permit(Trigger.Finished, State.Recover);
-
- _state.Configure(State.Recover)
- .Permit(Trigger.EncounteredAttacker, State.Combat)
- .Permit(Trigger.Died, State.Respawn)
- .Permit(Trigger.Finished, State.Idle);
+ public static Combat Instance;
+ private static Player Player => Client.Instance.LocalPlayerCharacter;
+ static Combat() {
+ Instance = new Combat();
}
- public void AddMob(string name) {
- _mobList.Add(new SpecialMob(name));
- }
+ public delegate bool InterruptDelegate(FightingObjectView target);
+ public delegate bool DodgeDelegate(FightingObjectView target, out Evade evade);
- private void AddSpellToMob(SpecialMob mob, DangerousSpell spell) => mob.AddSpell(spell);
+ private InterruptDelegate ShouldInterrupt;
+ private DodgeDelegate ShouldDodge;
- private SpecialMob GetMobByName(string mobName) {
- return _mobList.FirstOrDefault(m => m.GetName().Equals(mobName));
- }
+ private State state;
- public void AddSpellToMob(string mobName, SpellCategory category, SpellTarget target, Evade evade, string spellName = null) {
- DangerousSpell spell = new DangerousSpell(target, category, evade, spellName);
- var mob = GetMobByName(mobName);
- if (mob != null)
- AddSpellToMob(mob, spell);
- }
+ private float elapsedSeconds;
+ private float secondCount;
- private bool IsSpecialMob(FightingObjectView target, out Evade evade) {
- evade = Evade.Tank;
- if (!target.IsCasting()) return false;
+ private bool evading;
- var targetName = target.name;
- var spellName = target.GetSpellCasted().d6;
- var spellTarget = target.GetSpellCasted().d1;
- var spellCategory = target.GetSpellCasted().d4;
+ public void SetState(State newState) => state = newState;
+ public bool IsState(State state) => this.state == state;
+ public State GetState() => state;
- var mob = _mobList.Find(s => s.GetName().Equals(targetName));
- var mobSpell = mob.GetSpell(spellName, spellCategory, spellTarget);
- if (mobSpell == null) return false;
- evade = mobSpell.GetEvadeMethod();
- return true;
+ private Combat() {
+ SetState(State.Idle);
+ ResetDelegates();
}
- public State GetState() => _state.State;
+ public void Debug() {
+ var target = Player.GetAttackTarget();
+ var guiX = 1373;
+ var guiY = 182;
+ var guiW = 304;
+ var guiH = 206; // 9 label lines.
+ var boxGui = new Rect(guiX, guiY, guiW, guiH);
+ GUI.Box(boxGui, "");
+
+ var useSpells = GetUsableSpells();
+ var spellStr = "";
+ for (int i = useSpells.Length - 1; i >= 0; i--) {
+ if (string.IsNullOrEmpty(useSpells[i].Name)) continue;
+ spellStr += useSpells[i].Name + "\n\t";
+ }
+ var dbgStr = $"Target: {target.PrefabName}\nSpells: {spellStr}\n";
+ GUI.Label(new Rect(guiX + 4, guiY + 4, guiW - 8, guiH - 8), dbgStr);
+ }
public void Update() {
var curTime = Stopwatch.GetTimestamp() / Stopwatch.Frequency; // Gets seconds
- elapsedSeconds = secondCount - curTime;
+ elapsedSeconds += curTime - secondCount;
secondCount = curTime;
// Update based on state
// Do nothing while idle.
- switch (_state.State) {
+ switch (state) {
case State.Idle:
Idle();
break;
@@ -108,73 +85,73 @@ public void Update() {
}
}
- public bool HandleAttackers() {
- if (_player.IsUnderAttack(out FightingObjectView attacker)) {
- _player.CreateTextEffect("[Attacked]");
- _state.Fire(Trigger.EncounteredAttacker);
- return true;
- }
-
- return false;
- }
-
private void Idle() {
- if (_player.IsUnderAttack(out FightingObjectView attacker)) {
+ if (Player.IsUnderAttack(out FightingObjectView attacker)) {
Core.Log("[Combat] Attacked");
elapsedSeconds = 0;
- _state.Fire(Trigger.EncounteredAttacker);
+ SetState(State.Combat);
return;
}
- if (_player.GetHealth() <= 0) {
+ if (Player.GetHealth() <= 0) {
Core.Log("[Combat] Player died");
- _state.Fire(Trigger.Died);
+ SetState(State.Respawn);
return;
}
- if (_player.GetHealth() <= _player.GetMaxHealth() * 0.5f) {
+ if (Player.GetHealth() <= Player.GetMaxHealth() * 0.5f) {
Core.Log("[Combat] Recovering");
- _state.Fire(Trigger.Recover);
+ SetState(State.Recover);
return;
}
}
private void Fight() {
- var attackTimer = _player.GetAttackDelay().p();
- var spells = _player.GetSpells().Ready()
- .Ignore("ESCAPE_DUNGEON").Ignore("PLAYER_COUPDEGRACE")
- .Ignore("AMBUSH").Ignore("OUTOFCOMBATHEAL");
-
- var attackTarget = _player.GetAttackTarget();
-
- Evade evade;
- if (attackTarget != null && IsSpecialMob(attackTarget, out evade)) {
- _player.CreateTextEffect("[Dodging]");
- Dodge(attackTarget, evade);
+ var target = Player.GetAttackTarget();
+ if (ShouldDodge(target, out Evade evade)) {
+ Dodge(target, evade, ShouldInterrupt(target));
return;
}
- if (attackTarget != null && elapsedSeconds > attackTimer / 1000f) {
- var selfBuffSpells = spells.Target(SpellTarget.Self).Category(SpellCategory.Damage);
- if (selfBuffSpells.Any() && !_player.IsCastingSpell()) {
- _player.CreateTextEffect("[Casting Buff Spell]");
- _player.CastOnSelf(selfBuffSpells.FirstOrDefault().SpellSlot);
+ if (!evading)
+ Attack(target);
+ }
+
+ private void Attack(FightingObjectView target) {
+ var attackTimer = Player.GetAttackDelay().p();
+ var spells = GetUsableSpells();
+ Player.CreateTextEffect("[Attacking]");
+
+ // enemy not null and player has finished autoAttacking
+ if (target != null && elapsedSeconds > (attackTimer / 1000f) * 2) {
+ var selfBuffSpells = spells.Target(SpellTarget.Self).Category(SpellCategory.Buff);
+ if (selfBuffSpells.Any() && !Player.IsCastingSpell()) {
+ Player.CreateTextEffect("[Casting Buff Spell]");
+ Player.CastOnSelf(selfBuffSpells.FirstOrDefault().SpellSlot);
+ elapsedSeconds = 0;
+ return;
+ }
+
+ var selfDamage = spells.Target(SpellTarget.Self).Category(SpellCategory.Damage);
+ if (selfDamage.Any() && !Player.IsCastingSpell()) {
+ Player.CreateTextEffect("[Casting Buff Spell]");
+ Player.CastOnSelf(selfDamage.FirstOrDefault().SpellSlot);
elapsedSeconds = 0;
return;
}
var enemyBuffSpells = spells.Target(SpellTarget.Enemy).Category(SpellCategory.Buff);
- if (enemyBuffSpells.Any() && !_player.IsCastingSpell()) {
- _player.CreateTextEffect("[Casting Damage Spell]");
- _player.CastOn(enemyBuffSpells.FirstOrDefault().SpellSlot, attackTarget);
+ if (enemyBuffSpells.Any() && !Player.IsCastingSpell()) {
+ Player.CreateTextEffect("[Casting Damage Spell]");
+ Player.CastOn(enemyBuffSpells.FirstOrDefault().SpellSlot, target);
elapsedSeconds = 0;
return;
}
var enemyCCSpells = spells.Target(SpellTarget.Enemy).Category(SpellCategory.CrowdControl);
- if (enemyCCSpells.Any() && !_player.IsCastingSpell()) {
- _player.CreateTextEffect("[Casting CrowdControl Spell]");
- _player.CastOn(enemyCCSpells.FirstOrDefault().SpellSlot, attackTarget);
+ if (enemyCCSpells.Any() && !Player.IsCastingSpell()) {
+ Player.CreateTextEffect("[Casting CrowdControl Spell]");
+ Player.CastOn(enemyCCSpells.FirstOrDefault().SpellSlot, target);
elapsedSeconds = 0;
return;
}
@@ -188,143 +165,162 @@ private void Fight() {
} */
}
-
- if (_player.IsUnderAttack(out FightingObjectView attacker)) {
- _player.SetSelectedObject(attacker);
- _player.AttackSelectedObject();
+ if (Player.IsUnderAttack(out FightingObjectView attacker)) {
+ Player.SetSelectedObject(attacker);
+ Player.AttackSelectedObject();
return;
}
- if (_player.GetHealth() <= (_player.GetMaxHealth() * 0.1f)) {
- _state.Fire(Trigger.LowHealth);
+ if (Player.GetHealth() <= (Player.GetMaxHealth() * 0.1f)) {
+ SetState(State.Flee);
return;
}
- if (_player.IsCasting())
+ if (Player.IsCasting())
return;
- Core.Log("[Expedition] Continuing.");
- _state.Fire(Trigger.Finished);
+ Core.Log("Continuing.");
+ SetState(State.Idle);
}
- private void Dodge(FightingObjectView attackTarget, Evade evadeMethod) {
- Vector3 movePosition;
- switch (evadeMethod) {
+ private void Dodge(FightingObjectView target, Evade evade, bool shouldInterrupt = true) {
+ Player.CreateTextEffect("[Dodging]");
+ var spells = GetUsableSpells();
+ var defensive = spells.FirstOrDefault(s => s.Category.Equals(SpellCategory.Buff_Damageshield));
+ var interrupt = spells.FirstOrDefault(s => s.Category.Equals(SpellCategory.CrowdControl) && !s.Target.Equals(SpellTarget.Ground));
+
+ if (interrupt != null && shouldInterrupt) {
+ Cast(interrupt);
+ Core.Log("Evading by Interrupt");
+ return;
+ }
+ Vector3 movePos;
+ switch (evade) {
case Evade.Behind:
- movePosition = (attackTarget.transform.position - attackTarget.transform.forward * 3);
- _player.RequestMove(movePosition);
+ movePos = target.transform.position - target.transform.forward * 20f;
+ Player.RequestMove(movePos);
+ evading = true;
+ Core.Log("Evading behind");
break;
case Evade.Left:
- movePosition = (attackTarget.transform.position - attackTarget.transform.right * 3);
- _player.RequestMove(movePosition);
+ movePos = target.transform.position - target.transform.right * 20f;
+ Player.RequestMove(movePos);
+ evading = true;
+ Core.Log("Evading left");
break;
- case Evade.Defensive:
- // Not implemented yet.
+ case Evade.Away:
+ movePos = target.transform.position - target.transform.forward * 20f;
+ Player.RequestMove(movePos);
+ evading = true;
+ Core.Log("Evading away");
break;
- case Evade.Tank:
- default:
+ case Evade.Defensive:
+ Cast(defensive);
+ Core.Log("Evading by defensive");
break;
}
}
private void Recover() {
- var recoverySpell = _player.GetSpells().Slot(SpellSlotIndex.Armor).FirstOrDefault();
+ var recoverySpell = Player.GetSpells().Slot(SpellSlotIndex.Armor).FirstOrDefault();
- if (_player.IsUnderAttack(out FightingObjectView attacker)) {
+ if (Player.IsUnderAttack(out FightingObjectView attacker)) {
Core.Log("Attacked");
- _state.Fire(Trigger.EncounteredAttacker);
+ SetState(State.Combat);
return;
}
- if (recoverySpell != null && recoverySpell.Name.Equals("OUTOFCOMBATHEAL") && recoverySpell.IsReady) {
- _player.CastOnSelf(SpellSlotIndex.Armor);
+ if (recoverySpell != null && !Player.IsGettingUpFromKnockDown() &&
+ recoverySpell.Name.Equals("OUTOFCOMBATHEAL") && recoverySpell.IsReady) {
+ Player.CastOnSelf(SpellSlotIndex.Armor);
}
- if (_player.GetHealth() <= 0)
- _state.Fire(Trigger.Died);
+ if (Player.GetHealth() <= 0)
+ SetState(State.Respawn);
- if (_player.GetHealth() > _player.GetMaxHealth() * 0.75f)
- _state.Fire(Trigger.Finished);
+ if (Player.GetHealth() > Player.GetMaxHealth() * 0.75f) {
+ SetState(State.Idle);
+ }
}
- private void Flee() {
- if (_player.GetHealth() <= 0) {
- _state.Fire(Trigger.Died);
- return;
- }
+ /**
+ * Flee is a virtual, but protected, method so it *can* be overridden.
+ * This can be used to implement your own methods in separate classes.
+ */
+ protected virtual void Flee() {
+ if (Player.GetHealth() <= 0)
+ SetState(State.Respawn);
+ if (Player.GetHealth() == Player.GetMaxHealth())
+ SetState(State.Idle);
- /* Not Yet Implemented
- if (_player.IsInCombat())
- path.Flee();
- else
- _state.Fire(Trigger.Finished);
- */
}
private void Respawn() {
var isRespawnShowing = GameGui.Instance.RespawnGui.ExpeditionStart.isActiveAndEnabled;
- if (isRespawnShowing)
- _player.OnRespawn();
+ if (isRespawnShowing) {
+ Player.OnRespawn();
+ SetState(State.Idle);
+ }
}
- private class DangerousSpell {
- private readonly string name;
- private readonly Evade evadeMethod;
- private readonly SpellTarget target;
- private readonly SpellCategory category;
-
- // Name should be the FightingObjectView.GetSpellCasted().d6
- public DangerousSpell(SpellTarget target, SpellCategory category, Evade evadeMethod, string spellName) {
- this.name = spellName;
- this.target = target;
- this.category = category;
- this.evadeMethod = evadeMethod;
+ private Spell[] GetUsableSpells() {
+ List returnSpells = new List();
+ var spells = Player.GetSpells();
+ for (int i = spells.Length - 1; i > 0; i--) {
+ if (!spells[i].IsReady) continue;
+ if (spells[i].Target == SpellTarget.Ground) continue; // Not quite working.
+ if (spells[i].SpellSlot == SpellSlotIndex.Potion || spells[i].SpellSlot == SpellSlotIndex.Food)
+ continue;
+ returnSpells.Add(spells[i]);
}
- public Evade GetEvadeMethod() => evadeMethod;
- public bool IsNamed() => !string.IsNullOrEmpty(name);
- public string GetName() => name;
- public SpellTarget GetTarget() => target;
- public SpellCategory GetCategory() => category;
+ return returnSpells.ToArray();
}
- private class SpecialMob {
-
- private readonly string name;
- public string GetName() => name;
+ private void Cast(Spell spell, FightingObjectView target = null) {
+ switch (spell.Target) {
+ case SpellTarget.Self:
+ Player.CastOnSelf(spell.SpellSlot);
+ return;
+ case SpellTarget.Ground:
+ Player.CastAt(spell.SpellSlot, target == null ? Player.GetPosition() : target.GetPosition());
+ return;
+ case SpellTarget.Enemy:
+ if (target != null)
+ Player.CastOn(spell.SpellSlot, target);
+ return;
+ }
+ }
- private List dangerousSpells;
+ public void ResetDelegates() {
+ ResetInterruptDelegate();
+ ResetDodgeDelegate();
+ }
- public SpecialMob(string name, params DangerousSpell[] dSpells) {
- this.name = name;
- this.dangerousSpells = new List();
+ public void SetInterruptDelegate(InterruptDelegate shouldInterrupt) => ShouldInterrupt = shouldInterrupt;
+ public void SetDodgeDelegate(DodgeDelegate dodgeDelegate) => ShouldDodge = dodgeDelegate;
+ public void ResetInterruptDelegate() => ShouldInterrupt = DefaultShouldInterrupt;
+ public void ResetDodgeDelegate() => ShouldDodge = DefaultShouldDodge;
- foreach (var spell in dSpells)
- dangerousSpells.Add(spell);
- }
+ private bool DefaultShouldDodge(FightingObjectView target, out Evade evade) {
+ evade = Evade.Tank;
+ if (target.IsCasting()) return false;
- public List GetDangerousSpells() => dangerousSpells;
+ var spellCategory = target.GetSpellCasted().d4;
+ var spellTarget = target.GetSpellCasted().d1;
- public DangerousSpell GetSpell(string spellName, SpellCategory category, SpellTarget target) {
- foreach (var spell in dangerousSpells) {
- if (!spell.IsNamed()) continue;
- if (spell.GetCategory().Equals(category) && spell.GetTarget().Equals(target) && spell.GetName().Equals(spellName))
- return spell;
- }
+ if (spellCategory != SpellCategory.Damage) return true;
- return null;
- }
- public DangerousSpell GetSpell(SpellCategory category, SpellTarget target) {
- foreach (var spell in dangerousSpells) {
- if (spell.IsNamed()) continue;
- if (spell.GetCategory().Equals(category) && spell.GetTarget().Equals(target))
- return spell;
- }
+ if (spellTarget == SpellTarget.Enemy)
+ evade = Evade.Defensive;
+ if (spellTarget == SpellTarget.Ground)
+ evade = Evade.Left;
- return null;
- }
+ return true;
+ }
- internal void AddSpell(DangerousSpell spell) => dangerousSpells.Add(spell);
+ private bool DefaultShouldInterrupt(FightingObjectView target) {
+ return true;
}
public enum State {
@@ -340,15 +336,8 @@ public enum Evade {
Behind,
Left,
Defensive,
- Tank
- }
-
- public enum Trigger {
- EncounteredAttacker,
- LowHealth,
- Recover,
- Died,
- Finished,
+ Tank,
+ Away
}
public static string GetStateString(State state) {
diff --git a/Albion/Merlin/Profiles/Killer/Killer.Repair.cs b/Albion/Merlin/Profiles/Killer/Killer.Repair.cs
new file mode 100644
index 0000000..5ff4bdb
--- /dev/null
+++ b/Albion/Merlin/Profiles/Killer/Killer.Repair.cs
@@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using Merlin.Pathing.Worldmap;
+using UnityEngine;
+using WorldMap;
+
+namespace Merlin.Profiles.Killer {
+ partial class Killer {
+
+ private WorldPathingRequest _worldPathingRequest;
+
+ private void Repair() {
+ if (!player.IsMounted) {
+ if (player.IsMounting())
+ return;
+
+ player.MountOrDismount();
+ return;
+ }
+
+ if (_worldPathingRequest != null) {
+ if (_worldPathingRequest.IsRunning) {
+ if (!HandleMounting(Vector3.zero))
+ return;
+
+ _worldPathingRequest.Continue();
+ } else {
+ _worldPathingRequest = null;
+ }
+
+ return;
+ }
+
+ var currentCluster = _world.CurrentCluster;
+ var townCluster = _world.GetCluster("Fort Sterling");
+
+ var path = new List();
+ var pivotPoints = new List();
+
+ var worldPathing = new WorldmapPathfinder();
+
+ if (worldPathing.TryFindPath(currentCluster, townCluster, (cluster) => false, out path, out pivotPoints, true, false))
+ _worldPathingRequest = new WorldPathingRequest(currentCluster, townCluster, path);
+ }
+
+ public bool HandleMounting(Vector3 target) {
+ if (!_localPlayerCharacterView.IsMounted) {
+ if (_localPlayerCharacterView.IsMounting())
+ return false;
+
+ if (_localPlayerCharacterView.GetMount(out MountObjectView mount)) {
+ if (target != Vector3.zero && mount.InRange(target))
+ return true;
+
+ if (mount.IsInUseRange(_localPlayerCharacterView.LocalPlayerCharacter))
+ _localPlayerCharacterView.Interact(mount);
+ else
+ _localPlayerCharacterView.MountOrDismount();
+ } else {
+ _localPlayerCharacterView.MountOrDismount();
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/Albion/Merlin/Profiles/Killer/Killer.cs b/Albion/Merlin/Profiles/Killer/Killer.cs
new file mode 100644
index 0000000..d734184
--- /dev/null
+++ b/Albion/Merlin/Profiles/Killer/Killer.cs
@@ -0,0 +1,225 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Timers;
+using Merlin.MerlinGui;
+using UnityEngine;
+using YinYang.CodeProject.Projects.SimplePathfinding.PathFinders.AStar;
+
+namespace Merlin.Profiles.Killer {
+ partial class Killer : Profile {
+ private static Combat Combat => Combat.Instance;
+ public override string Name => "Killer";
+ private LocalPlayerCharacterView player => _localPlayerCharacterView;
+ private static State state;
+
+ private const float GUI_X = 70;
+ private const float GUI_Y = 135;
+ private const float GUI_W = 296;
+
+ public static void SetState(State newState) => state = newState;
+ public static State GetState() => state;
+
+ private MobView curTarget;
+ private ClusterPathingRequest mobPath;
+ private int mobKillCount = 0;
+
+ private Menu citySelect;
+ private Rect posRect, killsRect, stateRect, combatStateRect, mobNameRect, spellStringRect;
+ private Rect newBox, newStartRect, newStopRect;
+
+ private float standingTime;
+ private float secondCount;
+ private Vector3 oldPosition;
+
+ protected override void OnStart() {
+ var addY = 4;
+ posRect = new Rect(GUI_X + 4, GUI_Y + addY, GUI_W, 20);
+ killsRect = new Rect(GUI_X + 4, GUI_Y + (addY += 22), GUI_W, 20);
+ stateRect = new Rect(GUI_X + 4, GUI_Y + (addY += 22), GUI_W, 20);
+ combatStateRect = new Rect(GUI_X + 4, GUI_Y + (addY += 22), GUI_W, 20);
+ mobNameRect = new Rect(GUI_X + 4, GUI_Y + (addY += 22), GUI_W, 20);
+ spellStringRect = new Rect(GUI_X + 4, GUI_Y + (addY += 22), GUI_W, 20);
+
+ citySelect = new Menu(new Rect(GUI_X + 4, GUI_Y + (addY += 22), GUI_W - 4, 30), "Repair City", "Fort Sterling", "Bridgewatch");
+ Core.Log("Starting Killer");
+
+ var full = citySelect.GetFullHeight();
+ newStartRect = new Rect(GUI_X + 4, GUI_Y + addY + full, GUI_W / 2 - 4, 30);
+ newStopRect = new Rect(GUI_X + (GUI_W / 2) + 4, GUI_Y + addY + full, GUI_W / 2 - 4, 30);
+ newBox = new Rect(GUI_X, GUI_Y, GUI_W + 4, addY + 34 + full);
+ ProfileSelector.SetBoxRect(newBox);
+ ProfileSelector.SetStartButtonRect(newStartRect);
+ ProfileSelector.SetStopButtonRect(newStopRect);
+
+ newBox = new Rect(GUI_X, GUI_Y, GUI_W + 4, addY + 34 + full);
+ }
+
+ protected override void OnStop() {
+
+ }
+
+ protected override void OnUpdate() {
+
+ Combat.Update();
+
+ if (!Combat.IsState(Combat.State.Idle)) return;
+
+ // Do updates.
+ switch (state) {
+ case State.Searching:
+ Search();
+ break;
+ case State.Moving:
+ Move();
+ break;
+ case State.Repair:
+ Repair();
+ break;
+ }
+ }
+
+ private void Search() {
+ if (GetNextTarget(out MobView mob))
+ curTarget = mob;
+
+ if (mob != null && ValidateMob(curTarget)) {
+ Core.Log("Moving");
+ SetState(State.Moving);
+ }
+ }
+
+ private void Move() {
+ // Mob is dead if we are "stuck" in Move state...
+ if (oldPosition == player.GetPosition()) {
+ var curTime = Stopwatch.GetTimestamp() / Stopwatch.Frequency; // Gets seconds
+ standingTime += curTime - secondCount;
+ secondCount = curTime;
+
+ if (standingTime > 20) {
+ standingTime = 0;
+ SetState(State.Searching);
+ curTarget = null;
+ return;
+ }
+ } else
+ standingTime = 0;
+
+ if (!ValidateMob(curTarget)) {
+ Core.Log("Searching");
+ curTarget = null;
+ SetState(State.Searching);
+ return;
+ }
+
+ if (mobPath != null) {
+ if (mobPath.IsRunning)
+ mobPath.Continue();
+ else
+ mobPath = null;
+ return;
+ }
+
+ /* Begin moving closer the target. */
+ var playerCenter = player.transform.position;
+ var targetCenter = curTarget.transform.position;
+
+ var distance = (targetCenter - playerCenter).magnitude;
+ var minimumDistance = curTarget.GetColliderExtents() + player.GetColliderExtents() + 1.5f;
+
+ if (distance >= minimumDistance) {
+ if (player.TryFindPath(new ClusterPathfinder(), curTarget, IsBlocked, out List pathing))
+ mobPath = new ClusterPathingRequest(player, curTarget, pathing);
+ else {
+ SetState(State.Searching);
+ }
+
+ return;
+ }
+
+ if (player.IsInCombat())
+ return;
+
+ player.CreateTextEffect("[Attacking]");
+ player.SetSelectedObject(curTarget);
+ player.AttackSelectedObject();
+ }
+
+ public bool IsBlocked(Vector2 location) {
+ var vector = new Vector3(location.x, 0, location.y);
+
+ if (curTarget != null) {
+ var resourcePosition = new Vector2(curTarget.transform.position.x,
+ curTarget.transform.position.z);
+ var distance = (resourcePosition - location).magnitude;
+
+ if (distance < (curTarget.GetColliderExtents() + player.GetColliderExtents()))
+ return false;
+ }
+
+ if (player != null) {
+ var playerLocation = new Vector2(player.transform.position.x, player.transform.position.z);
+ var distance = (playerLocation - location).magnitude;
+
+ if (distance < 2f)
+ return false;
+ }
+
+ return (_client.Collision.GetFlag(vector, 1.0f) > 0);
+ }
+
+ private bool GetNextTarget(out MobView mob) {
+ List mobs = _client.GetEntities(ValidateMob);
+ mob = mobs.Where(m => m.Mob.sd().d2() == er.MobAlignments.Hostile).OrderBy((view) => {
+ var playerPosition = player.transform.position;
+ var mobPosition = view.transform.position;
+
+ var score = (mobPosition - playerPosition).sqrMagnitude;
+ var yDelta = Math.Abs(_landscape.GetLandscapeHeight(playerPosition.c()) - _landscape.GetLandscapeHeight(mobPosition.c()));
+ score += (yDelta * 10f);
+
+ return (int)score;
+ }).FirstOrDefault();
+
+ return mob != null;
+ }
+
+ private bool ValidateMob(MobView mob) {
+ if (mob.IsDead())
+ return false;
+ return true;
+ }
+
+ private void OnGUI() {
+ var playerPos = player.GetPosition();
+ GUI.Label(posRect, "Position: " + playerPos.x + ", " + playerPos.y + ", " + playerPos.z);
+ GUI.Label(killsRect, "Kills: " + mobKillCount);
+ GUI.Label(stateRect, "State: " + GetStateString(state));
+ GUI.Label(combatStateRect, "Combat State: " + Combat.GetStateString(Combat.GetState()));
+ GUI.Label(mobNameRect, "MOB_NAME: " + Combat.GetCurrentMobName());
+ GUI.Label(spellStringRect, Combat.GetSpellString());
+
+ citySelect.Draw();
+ }
+
+ private string GetStateString(State state) {
+ switch (state) {
+ case State.Searching:
+ return "Searching";
+ case State.Moving:
+ return "Moving";
+ case State.Repair:
+ return "Repairing";
+ }
+
+ return "";
+ }
+
+ internal enum State {
+ Searching,
+ Moving,
+ Repair
+ }
+ }
+}