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 + } + } +}