-
-
Save Cholik/85546f125b014e6c0a92 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.IO; | |
using System.Linq; | |
using System.Threading.Tasks; | |
using System.Windows.Controls; | |
using System.Windows.Data; | |
using System.Windows.Markup; | |
using Buddy.Coroutines; | |
using log4net; | |
using Loki.Bot; | |
using Loki.Bot.Logic.Bots.OldGrindBot; | |
using Loki.Bot.Pathfinding; | |
using Loki.Game; | |
using Loki.Game.GameData; | |
using Loki.Game.Objects; | |
using Loki.Common; | |
using Loki.Game.Objects.Items; | |
using Utility = Loki.Bot.Logic.Bots.OldGrindBot.Utility; | |
namespace Typhonis | |
{ | |
/// <summary> </summary> | |
public class Typhonis : IRoutine | |
{ | |
private static readonly ILog Log = Logger.GetLoggerInstanceForType(); | |
public static IEnumerable<Item> RarityFlasks | |
{ | |
get | |
{ | |
var inv = LokiPoe.InGameState.QuickFlaskPanel.Flasks; | |
return from item in inv | |
let flask = item as Flask | |
where flask != null && flask.FullName == "Divination Distillate" && flask.CanUse | |
select item; | |
} | |
} | |
void forceFirstWeapon() | |
{ | |
Log.Alert("HAS ENTERED FORCE FIRST WEAPON"); | |
if (Loki.Game.LokiPoe.InGameState.InventoryPanel.LeftHand.Name == "Shadow Axe") | |
{ | |
//Loki.Game.GameData.ActionKeys.weapon_swap(); | |
//Loki.Game.LokiPoe.Input.Binding.weapon_swap | |
Loki.Game.LokiPoe.Input.PressKey(Loki.Game.LokiPoe.Input.Binding.weapon_swap); | |
} | |
} | |
void forceSecondWeapon() | |
{ | |
if (Loki.Game.LokiPoe.InGameState.InventoryPanel.LeftHand.Name != "Shadow Axe") | |
{ | |
Loki.Game.LokiPoe.Input.PressKey(Loki.Game.LokiPoe.Input.Binding.weapon_swap); | |
} | |
} | |
// Auto-set, you do not have to change these. | |
private bool FlaskHelper(Stopwatch sw, int flaskCdMs, IEnumerable<Item> flasks) | |
{ | |
var useFlask = false; | |
if (!sw.IsRunning) | |
{ | |
sw.Start(); | |
useFlask = true; | |
} | |
else if (sw.ElapsedMilliseconds > Loki.Bot.Logic.Bots.OldGrindBot.Utility.LatencySafeValue(flaskCdMs)) | |
{ | |
sw.Restart(); | |
useFlask = true; | |
} | |
if (useFlask) | |
{ | |
var flask = flasks.FirstOrDefault(); | |
if (flask != null) | |
{ | |
var err = LokiPoe.InGameState.QuickFlaskPanel.UseFlask(flask); | |
if (err != LokiPoe.InGameState.UseFlaskError.None) | |
{ | |
Log.ErrorFormat("[FlaskHelper] QuickFlaskPanel.UseFlask returned {0}.", err); | |
} | |
return true; | |
} | |
} | |
return false; | |
} | |
int distanceTest = 12; | |
private int _enduringCrySlot = -1; | |
private int _moltenShellSlot = -1; | |
private int _leapSlamSlot = -1; | |
private int _golemSlot = -1; | |
private int _aaSlot = -1; | |
private int _heraldOfAshSlot = -1; | |
private int _rfSlot = -1; | |
private int _cycloneSlot = -1; | |
private readonly List<int> _curseSlots = new List<int>(); | |
private int _auraSlot = -1; | |
private int _currentLeashRange = -1; | |
private readonly Stopwatch _moltenShellStopwatch = Stopwatch.StartNew(); | |
private readonly List<int> _ignoreAnimatedItems = new List<int>(); | |
private readonly Stopwatch _vaalStopwatch = Stopwatch.StartNew(); | |
private readonly Stopwatch _golemStopwatch = Stopwatch.StartNew(); | |
private readonly Stopwatch _cycloneStopwatch = Stopwatch.StartNew(); | |
private readonly Stopwatch _rarityFlaskCd = new Stopwatch(); | |
private bool _needsUpdate; | |
private readonly Targeting _combatTargeting = new Targeting(); | |
private bool BlacklistedSkill(int id) | |
{ | |
return false; | |
} | |
private Targeting CombatTargeting | |
{ | |
get | |
{ | |
return _combatTargeting; | |
} | |
} | |
// Do not implement a ctor and do stuff in it. | |
#region Targeting | |
private void CombatTargetingOnWeightCalculation(NetworkObject entity, ref float weight) | |
{ | |
weight -= entity.Distance / 2; | |
var m = entity as Monster; | |
if (m == null) | |
return; | |
// If the monster is the source of Allies Cannot Die, we really want to kill it fast. | |
if (m.HasAura("monster_aura_cannot_die")) | |
weight += 40; | |
if (m.IsTargetingMe) | |
{ | |
weight += 20; | |
} | |
if (m.Rarity == Rarity.Magic) | |
{ | |
weight += 5; | |
} | |
else if (m.Rarity == Rarity.Rare) | |
{ | |
weight += 10; | |
} | |
else if (m.Rarity == Rarity.Unique) | |
{ | |
weight += 15; | |
} | |
// Minions that get in the way. | |
switch (m.Name) | |
{ | |
case "Summoned Skeleton": | |
weight -= 15; | |
break; | |
case "Raised Zombie": | |
weight -= 15; | |
break; | |
} | |
if (m.Rarity == Rarity.Normal && m.Type.Contains("/Totems/")) | |
{ | |
weight -= 15; | |
} | |
// Necros | |
if (m.ExplicitAffixes.Any(a => a.InternalName.Contains("RaisesUndead")) || | |
m.ImplicitAffixes.Any(a => a.InternalName.Contains("RaisesUndead"))) | |
{ | |
weight += 30; | |
} | |
// Ignore these mostly, as they just respawn. | |
if (m.Type.Contains("TaniwhaTail")) | |
{ | |
weight -= 30; | |
} | |
} | |
private readonly string[] _aurasToIgnore = new[] | |
{ | |
"shrine_godmode", // Ignore any mob near Divine Shrine | |
"bloodlines_invulnerable", // Ignore Phylacteral Link | |
"god_mode", // Ignore Animated Guardian | |
"bloodlines_necrovigil", | |
}; | |
private bool CombatTargetingOnInclusionCalcuation(NetworkObject entity) | |
{ | |
try | |
{ | |
var m = entity as Monster; | |
if (m == null) | |
return false; | |
if (AreaStateCache.Current.IsBlacklisted(m)) | |
return false; | |
// Do not consider inactive/dead mobs. | |
if (!m.IsActive) | |
return false; | |
// Ignore any mob that cannot die. | |
if (m.CannotDie) | |
return false; | |
// Ignore mobs that are too far to care about. | |
if (m.Distance > (_currentLeashRange != -1 ? TyphonisSettings.Instance.CombatRange : 300)) | |
return false; | |
// Ignore mobs with special aura/buffs | |
if (m.HasAura(_aurasToIgnore)) | |
return false; | |
// Ignore Voidspawn of Abaxoth: thanks ExVault! | |
if (m.ExplicitAffixes.Any(a => a.DisplayName == "Voidspawn of Abaxoth")) | |
return false; | |
// Ignore these mobs when trying to transition in the dom fight. | |
// Flag1 has been seen at 5 or 6 at times, so need to work out something more reliable. | |
if (m.Name == "Miscreation") | |
{ | |
var dom = LokiPoe.ObjectManager.GetObjectByName<Monster>("Dominus, High Templar"); | |
if (dom != null && !dom.IsDead && (dom.Components.TransitionableComponent.Flag1 == 6 || dom.Components.TransitionableComponent.Flag1 == 5)) | |
{ | |
AreaStateCache.Current.Blacklist(m.Id, TimeSpan.FromSeconds(7), "Miscreation"); | |
return false; | |
} | |
} | |
// Ignore Piety's portals. | |
if (m.Name == "Chilling Portal" || m.Name == "Burning Portal") | |
{ | |
AreaStateCache.Current.Blacklist(m.Id, TimeSpan.FromHours(1), "Piety portal"); | |
return false; | |
} | |
} | |
catch (Exception ex) | |
{ | |
Log.Error("[CombatOnInclusionCalcuation]", ex); | |
return false; | |
} | |
return true; | |
} | |
#endregion | |
#region Implementation of IBase | |
/// <summary>Initializes this routine.</summary> | |
public void Initialize() | |
{ | |
Log.DebugFormat("[Typhonis] Initialize"); | |
_combatTargeting.InclusionCalcuation += CombatTargetingOnInclusionCalcuation; | |
_combatTargeting.WeightCalculation += CombatTargetingOnWeightCalculation; | |
} | |
#endregion | |
#region Implementation of IDisposable | |
/// <summary> </summary> | |
/// <summary> </summary> | |
public void Deinitialize() | |
{ | |
} | |
#endregion | |
#region Implementation of IAuthored | |
/// <summary>The name of the routine.</summary> | |
public string Name | |
{ | |
get { return "Typhonis"; } | |
} | |
/// <summary>The description of the routine.</summary> | |
public string Description | |
{ | |
get { return "A Cyclone Routine for Exilebuddy."; } | |
} | |
/// <summary> | |
/// The author of this object. | |
/// </summary> | |
public string Author | |
{ | |
get { return "Tozededao"; } | |
} | |
/// <summary> | |
/// The version of this routone. | |
/// </summary> | |
public string Version | |
{ | |
get { return "0, 0, 0, 1"; } | |
} | |
#endregion | |
#region Implementation of IRunnable | |
/// <summary> The routine start callback. Do any initialization here. </summary> | |
public void Start() | |
{ | |
Log.DebugFormat("[Typhonis] Start"); | |
_needsUpdate = true; | |
} | |
private int EnduranceCharges | |
{ | |
get | |
{ | |
Aura aura = LokiPoe.ObjectManager.Me.Auras.FirstOrDefault(a => a.InternalName == "endurance_charge"); | |
if (aura != null) | |
{ | |
return aura.Charges; | |
} | |
return 0; | |
} | |
} | |
private TimeSpan EnduranceChargesLeft | |
{ | |
get | |
{ | |
Aura aura = LokiPoe.ObjectManager.Me.Auras.FirstOrDefault(a => a.InternalName == "endurance_charge"); | |
if (aura != null) | |
{ | |
return aura.TimeLeft; | |
} | |
return TimeSpan.Zero; | |
} | |
} | |
private bool IsCastableHelper(Skill skill) | |
{ | |
return skill != null && skill.IsCastable && !skill.IsTotem && !skill.IsTrap && !skill.IsMine; | |
} | |
private bool IsAuraName(string name) | |
{ | |
// This makes sure auras on items don't get used, since they don't have skill gems, and won't have an Aura tag. | |
if (!TyphonisSettings.Instance.EnableAurasFromItems) | |
{ | |
return false; | |
} | |
var auraNames = new string[] | |
{ | |
"Anger", "Clarity", "Determination", "Discipline", "Grace", "Haste", "Hatred", "Herald of Ash", "Arctic Armour", "Purity of Elements", | |
"Purity of Fire", "Purity of Ice", "Purity of Lightning", "Vitality", "Wrath" | |
}; | |
return auraNames.Contains(name); | |
} | |
/// <summary> The routine tick callback. Do any update logic here. </summary> | |
public void Tick() | |
{ | |
if (!LokiPoe.IsInGame) | |
return; | |
if (_needsUpdate) | |
{ | |
_enduringCrySlot = -1; | |
_moltenShellSlot = -1; | |
_golemSlot = -1; | |
_auraSlot = -1; | |
_aaSlot = -1; | |
_heraldOfAshSlot = -1; | |
_rfSlot = -1; | |
_leapSlamSlot = -1; | |
_curseSlots.Clear(); | |
// Register curses. | |
foreach (var skill in LokiPoe.InGameState.SkillBarPanel.Skills) | |
{ | |
var tags = skill.SkillTags; | |
var name = skill.Name; | |
if (tags.Contains("curse")) | |
{ | |
var slot = skill.Slot; | |
if (slot != -1 && skill.IsCastable) | |
{ | |
_curseSlots.Add(slot); | |
} | |
} | |
if (_auraSlot == -1 && ((tags.Contains("aura") && !tags.Contains("vaal")) || IsAuraName(name))) | |
{ | |
_auraSlot = skill.Slot; | |
} | |
} | |
var rf = LokiPoe.InGameState.SkillBarPanel.Skills.FirstOrDefault(s => s.Name == "Righteous Fire"); | |
if (IsCastableHelper(rf)) | |
{ | |
_rfSlot = rf.Slot; | |
} | |
var cyclone = LokiPoe.InGameState.SkillBarPanel.Skills.FirstOrDefault(s => s.Name == "Cyclone"); | |
if (IsCastableHelper(cyclone)) | |
{ | |
_cycloneSlot = cyclone.Slot; | |
} | |
var aa = LokiPoe.InGameState.SkillBarPanel.Skills.FirstOrDefault(s => s.Name == "Arctic Armour"); | |
if (IsCastableHelper(aa)) | |
{ | |
_aaSlot = aa.Slot; | |
} | |
var hoa = LokiPoe.InGameState.SkillBarPanel.Skills.FirstOrDefault(s => s.Name == "Herald of Ash"); | |
if (IsCastableHelper(aa)) | |
{ | |
_heraldOfAshSlot = hoa.Slot; | |
} | |
var mc = LokiPoe.InGameState.SkillBarPanel.Skills.FirstOrDefault(s => s.Name == "Molten Shell"); | |
if (IsCastableHelper(mc)) | |
{ | |
_moltenShellSlot = mc.Slot; | |
} | |
var golem = LokiPoe.InGameState.SkillBarPanel.Skills.FirstOrDefault(s => s.Name == "Summon Flame Golem"); | |
if (IsCastableHelper(golem)) | |
{ | |
_golemSlot = golem.Slot; | |
} | |
golem = LokiPoe.InGameState.SkillBarPanel.Skills.FirstOrDefault(s => s.Name == "Summon Ice Golem"); | |
if (IsCastableHelper(golem)) | |
{ | |
_golemSlot = golem.Slot; | |
} | |
golem = LokiPoe.InGameState.SkillBarPanel.Skills.FirstOrDefault(s => s.Name == "Summon Chaos Golem"); | |
if (IsCastableHelper(golem)) | |
{ | |
_golemSlot = golem.Slot; | |
} | |
var ec = LokiPoe.InGameState.SkillBarPanel.Skills.FirstOrDefault(s => s.Name == "Enduring Cry"); | |
if (IsCastableHelper(ec)) | |
{ | |
_enduringCrySlot = ec.Slot; | |
} | |
var ls = LokiPoe.InGameState.SkillBarPanel.Skills.FirstOrDefault(s => s.Name == "Leap Slam"); | |
if (IsCastableHelper(ls)) | |
{ | |
_leapSlamSlot = ls.Slot; | |
} | |
_needsUpdate = false; | |
} | |
} | |
/// <summary> The routine stop callback. Do any pre-dispose cleanup here. </summary> | |
public void Stop() | |
{ | |
Log.DebugFormat("[Typhonis] Stop"); | |
} | |
#endregion | |
#region Implementation of IConfigurable | |
/// <summary> The bot's settings control. This will be added to the Exilebuddy Settings tab.</summary> | |
public UserControl Control | |
{ | |
get | |
{ | |
using (var fs = new FileStream(@"Routines\Typhonis\SettingsGui.xaml", FileMode.Open)) | |
{ | |
var root = (UserControl)XamlReader.Load(fs); | |
// Your settings binding here. | |
if ( | |
!Wpf.SetupCheckBoxBinding(root, "EnableAurasFromItemsCheckBox", "EnableAurasFromItems", | |
BindingMode.TwoWay, TyphonisSettings.Instance)) | |
{ | |
Log.DebugFormat( | |
"[SettingsControl] SetupCheckBoxBinding failed for 'EnableAurasFromItemsCheckBox'."); | |
throw new Exception("The SettingsControl could not be created."); | |
} | |
if ( | |
!Wpf.SetupCheckBoxBinding(root, "AlwaysAttackInPlaceCheckBox", "AlwaysAttackInPlace", | |
BindingMode.TwoWay, TyphonisSettings.Instance)) | |
{ | |
Log.DebugFormat( | |
"[SettingsControl] SetupCheckBoxBinding failed for 'AlwaysAttackInPlaceCheckBox'."); | |
throw new Exception("The SettingsControl could not be created."); | |
} | |
if ( | |
!Wpf.SetupCheckBoxBinding(root, "FarmDominusCheckBox", "FarmDominus", | |
BindingMode.TwoWay, TyphonisSettings.Instance)) | |
{ | |
Log.DebugFormat( | |
"[SettingsControl] SetupCheckBoxBinding failed for 'FarmDominusCheckBox'."); | |
throw new Exception("The SettingsControl could not be created."); | |
} | |
if ( | |
!Wpf.SetupCheckBoxBinding(root, "UseRandomTargetingCheckBox", "UseRandomTargeting", | |
BindingMode.TwoWay, TyphonisSettings.Instance)) | |
{ | |
Log.DebugFormat( | |
"[SettingsControl] SetupCheckBoxBinding failed for 'UseRandomTargetingCheckBox'."); | |
throw new Exception("The SettingsControl could not be created."); | |
} | |
if ( | |
!Wpf.SetupCheckBoxBinding(root, "DebugAurasCheckBox", "DebugAuras", | |
BindingMode.TwoWay, TyphonisSettings.Instance)) | |
{ | |
Log.DebugFormat( | |
"[SettingsControl] SetupCheckBoxBinding failed for 'DebugAurasCheckBox'."); | |
throw new Exception("The SettingsControl could not be created."); | |
} | |
if ( | |
!Wpf.SetupCheckBoxBinding(root, "AutoCastVaalSkillsCheckBox", "AutoCastVaalSkills", | |
BindingMode.TwoWay, TyphonisSettings.Instance)) | |
{ | |
Log.DebugFormat( | |
"[SettingsControl] SetupCheckBoxBinding failed for 'AutoCastVaalSkillsCheckBox'."); | |
throw new Exception("The SettingsControl could not be created."); | |
} | |
if (!Wpf.SetupTextBoxBinding(root, "CombatRangeTextBox", "CombatRange", | |
BindingMode.TwoWay, TyphonisSettings.Instance)) | |
{ | |
Log.DebugFormat("[SettingsControl] SetupTextBoxBinding failed for 'CombatRangeTextBox'."); | |
throw new Exception("The SettingsControl could not be created."); | |
} | |
if (!Wpf.SetupTextBoxBinding(root, "CycloneRangeTextBox", "cycloneRange", | |
BindingMode.TwoWay, TyphonisSettings.Instance)) | |
{ | |
Log.DebugFormat("[SettingsControl] SetupTextBoxBinding failed for 'cycloneRange'."); | |
throw new Exception("The SettingsControl could not be created."); | |
} | |
if (!Wpf.SetupTextBoxBinding(root, "CycloneTargetRangeTextBox", "cycloneTargetRange", | |
BindingMode.TwoWay, TyphonisSettings.Instance)) | |
{ | |
Log.DebugFormat("[SettingsControl] SetupTextBoxBinding failed for 'cycloneTargetRange'."); | |
throw new Exception("The SettingsControl could not be created."); | |
} | |
if (!Wpf.SetupTextBoxBinding(root, "CycloneTargetMaxRangeTextBox", "cycloneTargetMaxRange", | |
BindingMode.TwoWay, TyphonisSettings.Instance)) | |
{ | |
Log.DebugFormat("[SettingsControl] SetupTextBoxBinding failed for 'cycloneTargetMaxRange'."); | |
throw new Exception("The SettingsControl could not be created."); | |
} | |
// Your settings event handlers here. | |
return root; | |
} | |
} | |
} | |
/// <summary>The settings object. This will be registered in the current configuration.</summary> | |
public JsonSettings Settings | |
{ | |
get { return TyphonisSettings.Instance; } | |
} | |
#endregion | |
#region Implementation of IRoutine | |
/// <summary> | |
/// Sends data to the routine with the associated name. | |
/// </summary> | |
/// <param name="name">The name of the configuration.</param> | |
/// <param name="param">The data passed for the configuration.</param> | |
public object Execute(string name, params object[] param) | |
{ | |
if (name == "SetLeash") | |
{ | |
_currentLeashRange = (int)param[0]; | |
} | |
return null; | |
} | |
/// <summary> | |
/// Requests data from the routine with the associated name. | |
/// </summary> | |
/// <param name="name">The name of the configuration.</param> | |
/// <returns>Data from the routine.</returns> | |
public object GetConfiguration(string name) | |
{ | |
return null; | |
} | |
private async Task<bool> CombatLogicEnd() | |
{ | |
await EnableAlwaysHiglight(); | |
return false; | |
} | |
private async Task<bool> HandleShrines() | |
{ | |
// TODO: Shrines need speical CR logic, because it's now the CRs responsibility for handling all combaat situations, | |
// and shrines are now considered a combat situation due their nature. | |
// Check for any active shrines. | |
var shrines = | |
LokiPoe.ObjectManager.Objects.OfType<Shrine>() | |
.Where(s => !s.IsDeactivated && s.Distance < 50) | |
.OrderBy(s => s.Distance) | |
.ToList(); | |
if (!shrines.Any()) | |
return false; | |
// For now, just take the first shrine found. | |
var shrine = shrines[0]; | |
// Handle Skeletal Shrine in a special way, or handle priority between multiple shrines at the same time. | |
var skellyOverride = shrine.ShrineId == "Skeletons"; | |
// Try and only move to touch it when we have a somewhat navigable path. | |
if ((Utility.NumberOfMobsBetween(LokiPoe.Me, shrine) < 5 && | |
Utility.NumberOfMobsNear(LokiPoe.Me, 20) < 3) || skellyOverride) | |
{ | |
Log.DebugFormat("[Logic] Now moving towards the Shrine {0}.", shrine.Id); | |
var myPos = LokiPoe.Me.Position; | |
var pos = ExilePather.WalkablePositionFor(shrine, 10); | |
// We need to filter out based on pathfinding, since otherwise, a large gap will lockup the bot. | |
var pathDistance = ExilePather.PathDistance(myPos, pos); | |
if (pathDistance > 50) | |
{ | |
return false; | |
} | |
var canSee = ExilePather.CanObjectSee(LokiPoe.Me, pos); | |
var inDistance = myPos.Distance(pos) < 20; | |
if (canSee && inDistance) | |
{ | |
Log.DebugFormat("[Logic] Now attempting to interact with the Shrine {0}.", shrine.Id); | |
await Coroutines.FinishCurrentAction(); | |
await Coroutines.InteractWith(shrine); | |
await Coroutine.Sleep(500); | |
} | |
else | |
{ | |
Log.DebugFormat("[HandleShrines] Moving towards {0}. [canSee: {1} | inDistance: {2}]", pos, canSee, | |
inDistance); | |
if (!PlayerMover.MoveTowards(pos)) | |
{ | |
Log.ErrorFormat("[HandleShrines] MoveTowards failed for {0}.", pos); | |
} | |
} | |
return true; | |
} | |
return false; | |
} | |
private bool _needsToDisableAlwaysHighlight; | |
// This logic is now CR specific, because Strongbox gui labels interfere with targeting, | |
// but not general movement using Move only. | |
private async Task DisableAlwaysHiglight() | |
{ | |
if (_needsToDisableAlwaysHighlight && LokiPoe.InGameState.IsAlwaysHighlightEnabled) | |
{ | |
Log.InfoFormat("[DisableAlwaysHiglight] Now disabling Always Highlight to avoid skill use issues."); | |
LokiPoe.Input.PressKey(LokiPoe.Input.Binding.highlight_toggle); | |
await Coroutine.Sleep(16); | |
} | |
} | |
// This logic is now CR specific, because Strongbox gui labels interfere with targeting, | |
// but not general movement using Move only. | |
private async Task EnableAlwaysHiglight() | |
{ | |
if (!LokiPoe.InGameState.IsAlwaysHighlightEnabled) | |
{ | |
Log.InfoFormat("[EnableAlwaysHiglight] Now enabling Always Highlight."); | |
LokiPoe.Input.PressKey(LokiPoe.Input.Binding.highlight_toggle); | |
await Coroutine.Sleep(16); | |
} | |
} | |
/// <summary> | |
/// The routine's coroutine logic to execute. | |
/// </summary> | |
/// <param name="type">The requested type of logic to execute.</param> | |
/// <returns></returns> | |
// ReSharper disable once CSharpWarnings::CS1998 | |
public async Task<bool> Logic(string type, params object[] param) | |
{ | |
if (type == "core_area_changed_event") | |
{ | |
var oldSeed = (uint)param[0]; | |
var newSeed = (uint)param[1]; | |
var oldArea = (DatWorldAreaWrapper)param[2]; | |
var newArea = (DatWorldAreaWrapper)param[3]; | |
_ignoreAnimatedItems.Clear(); | |
return true; | |
} | |
if (type == "core_player_died_event") | |
{ | |
var totalDeathsForInstance = (int)param[0]; | |
return true; | |
} | |
if (type == "core_player_leveled_event") | |
{ | |
Log.InfoFormat("[Logic] We are now level {0}!", (int)param[0]); | |
return true; | |
} | |
if (type == "combat") | |
{ | |
// Update targeting. | |
CombatTargeting.Update(); | |
// We now signal always highlight needs to be disabled, but won't actually do it until we cast something. | |
if ( | |
LokiPoe.ObjectManager.GetObjectsByType<Chest>() | |
.Any(c => c.Distance < 70 && !c.IsOpened && c.IsStrongBox)) | |
{ | |
_needsToDisableAlwaysHighlight = true; | |
} | |
else | |
{ | |
_needsToDisableAlwaysHighlight = false; | |
} | |
var myPos = LokiPoe.Me.Position; | |
#region auras and buffs to cast precombat | |
// Handle aura logic. | |
if (_auraSlot != -1) | |
{ | |
var aura = LokiPoe.InGameState.SkillBarPanel.Slot(_auraSlot); | |
if (!LokiPoe.Me.HasAura(aura.Name) && aura.CanUse(TyphonisSettings.Instance.DebugAuras)) | |
{ | |
await Coroutines.FinishCurrentAction(); | |
await Coroutine.Sleep(Utility.LatencySafeValue(100)); | |
var err1 = LokiPoe.InGameState.SkillBarPanel.Use(_auraSlot, false); | |
if (err1 == LokiPoe.InGameState.UseError.None) | |
{ | |
await Coroutine.Sleep(Utility.LatencySafeValue(250)); | |
await Coroutines.FinishCurrentAction(false); | |
await Coroutine.Sleep(Utility.LatencySafeValue(100)); | |
return true; | |
} | |
Log.ErrorFormat("[Logic] Use returned {0} for {1}.", err1, aura.Name); | |
} | |
foreach (var skill in LokiPoe.InGameState.SkillBarPanel.Skills) | |
{ | |
if ((skill.SkillTags.Contains("aura") && !skill.SkillTags.Contains("vaal")) || IsAuraName(skill.Name)) | |
{ | |
if (!LokiPoe.Me.HasAura(skill.Name) && skill.CanUse(TyphonisSettings.Instance.DebugAuras, true)) | |
{ | |
var doCast = true; | |
while (skill.Slot == -1) | |
{ | |
Log.InfoFormat("[Logic] Now assigning {0} to the skillbar.", skill.Name); | |
var sserr = LokiPoe.InGameState.SkillBarPanel.SetSlot(_auraSlot, skill); | |
if (sserr != LokiPoe.InGameState.SetSlotError.None) | |
{ | |
Log.ErrorFormat("[Logic] SetSlot returned {0}.", sserr); | |
doCast = false; | |
break; | |
} | |
await Coroutine.Sleep(Utility.LatencySafeValue(100)); | |
} | |
if (!doCast) | |
{ | |
continue; | |
} | |
await Coroutines.FinishCurrentAction(); | |
await Coroutine.Sleep(Utility.LatencySafeValue(100)); | |
var err1 = LokiPoe.InGameState.SkillBarPanel.Use(skill.Slot, false); | |
if (err1 == LokiPoe.InGameState.UseError.None) | |
{ | |
await Coroutine.Sleep(Utility.LatencySafeValue(250)); | |
await Coroutines.FinishCurrentAction(false); | |
await Coroutine.Sleep(Utility.LatencySafeValue(100)); | |
return true; | |
} | |
Log.ErrorFormat("[Logic] Use returned {0} for {1}.", err1, skill.Name); | |
} | |
} | |
} | |
} | |
if (_golemSlot != -1 && _golemStopwatch.ElapsedMilliseconds > 10000) | |
{ | |
// See if we can use the skill. | |
var skill = LokiPoe.InGameState.SkillBarPanel.Slot(_golemSlot); | |
if (skill.CanUse() && skill.NumberDeployed < 1) | |
{ | |
var err1 = LokiPoe.InGameState.SkillBarPanel.Use(_golemSlot, true); | |
_golemStopwatch.Restart(); | |
if (err1 == LokiPoe.InGameState.UseError.None) | |
{ | |
//await Coroutine.Sleep(Utility.LatencySafeValue(500)); | |
await Coroutines.FinishCurrentAction(false); | |
await Coroutine.Sleep(Utility.LatencySafeValue(100)); | |
return true; | |
} | |
Log.ErrorFormat("[Logic] Use returned {0} for {1}.", err1, skill.Name); | |
} | |
} | |
#endregion | |
// TODO: _currentLeashRange of -1 means we need to use a cached location system to prevent back and forth issues of mobs despawning. | |
// This is pretty important. Otherwise, components can go invalid and exceptions are thrown. | |
var bestTarget = CombatTargeting.Targets<Monster>().FirstOrDefault(); | |
// No monsters, we can execute non-critical combat logic, like buffs, auras, etc... | |
// For this example, just going to continue executing bot logic. | |
if (bestTarget == null) | |
{ | |
if (await HandleShrines()) | |
{ | |
return true; | |
} | |
return await CombatLogicEnd(); | |
} | |
var cachedPosition = bestTarget.Position; | |
var targetPosition = bestTarget.ModelCenterWorld; | |
var cachedId = bestTarget.Id; | |
var cachedName = bestTarget.Name; | |
var cachedRarity = bestTarget.Rarity; | |
var cachedDistance = bestTarget.Distance; | |
var cachedIsCursable = bestTarget.IsCursable; | |
var cachedCurseCount = bestTarget.CurseCount; | |
var cachedHasCurseFrom = new Dictionary<string, bool>(); | |
var cachedNumberOfMobsNear = Utility.NumberOfMobsNear(bestTarget, 20); | |
var cachedProxShield = bestTarget.HasProximityShield; | |
var cachedMobsNearForAoe = Utility.NumberOfMobsNear(LokiPoe.Me, | |
TyphonisSettings.Instance.MaxMeleeRange); | |
var cachedMobsNearForCurse = Utility.NumberOfMobsNear(bestTarget, 20); | |
foreach (var curseSlot in _curseSlots) | |
{ | |
var skill = LokiPoe.InGameState.SkillBarPanel.Slot(curseSlot); | |
cachedHasCurseFrom.Add(skill.Name, bestTarget.HasCurseFrom(skill.Name)); | |
} | |
var canSee = ExilePather.CanObjectSee(LokiPoe.Me, bestTarget); | |
var pathDistance = ExilePather.PathDistance(myPos, cachedPosition); | |
var blockedByDoor = Utility.ClosedDoorBetween(LokiPoe.Me, bestTarget); | |
if (await HandleShrines()) | |
{ | |
return true; | |
} | |
if (pathDistance.CompareTo(float.MaxValue) == 0) | |
{ | |
Log.ErrorFormat( | |
"[Logic] Could not determine the path distance to the best target. Now blacklisting it."); | |
AreaStateCache.Current.Blacklist(cachedId, TimeSpan.FromMinutes(1), "Unable to pathfind to."); | |
return true; | |
} | |
// Prevent combat loops from happening by preventing combat outside CombatRange. | |
if (pathDistance > TyphonisSettings.Instance.CombatRange) | |
{ | |
await EnableAlwaysHiglight(); | |
return false; | |
} | |
if (!canSee || blockedByDoor) | |
{ | |
Log.InfoFormat( | |
"[Logic] Now moving towards the monster {0} because [canSee: {1}][pathDistance: {2}][blockedByDoor: {3}]", | |
cachedName, canSee, pathDistance, blockedByDoor); | |
if (!PlayerMover.MoveTowards(cachedPosition)) | |
{ | |
Log.ErrorFormat("[Logic] MoveTowards failed for {0}.", cachedPosition); | |
} | |
return true; | |
} | |
int cycloneRange = TyphonisSettings.Instance.cycloneRange; | |
int cycloneTargetRange = TyphonisSettings.Instance.cycloneTargetRange; | |
int cycloneMaxTargetRange = TyphonisSettings.Instance.cycloneTargetMaxRange; | |
bool testeThis = false; | |
// Cyclone to engage the fight | |
if (_cycloneSlot != -1) | |
{ | |
if (Utility.NumberOfMobsNear(LokiPoe.Me, cycloneTargetRange) > 0 && !LokiPoe.Me.IsDead) | |
{ | |
if (cachedDistance < cycloneTargetRange) | |
{ | |
if (testeThis) | |
{ | |
//System.Windows.Forms.Keys cycloneKey = System.Windows.Forms.Keys.Q; | |
//if (cachedDistance < cycloneTargetRange) | |
//{ | |
// Loki.Game.LokiPoe.ProcessHookManager.SetKeyState(cycloneKey, 1); | |
// // Loki.Game.LokiPoe.ProcessHookManager.SetCursorPos(cachedPosition.X, cachedPosition.Y, 1); | |
// Log.ErrorFormat(" Cyclone"); | |
//} | |
//Loki.Game.LokiPoe.ProcessHookManager.SetKeyState(cycloneKey, 0); | |
//Log.ErrorFormat("Stop Cyclone"); | |
if (TyphonisSettings.Instance.FarmDominus) | |
{ | |
forceFirstWeapon(); | |
} | |
Vector2i randomPosition2 = Loki.Bot.Pathfinding.ExilePather.FindWalkableLocationsCloseTo(bestTarget, 4).First(); | |
LokiPoe.InGameState.SkillBarPanel.UseAt(_cycloneSlot, true, randomPosition2); | |
} | |
else | |
{ | |
if (TyphonisSettings.Instance.UseRandomTargeting) | |
{ | |
Vector2i randomPosition; | |
double angle = 2.0 * Math.PI * Loki.Game.LokiPoe.Random.NextDouble(); | |
randomPosition.X = (int)(myPos.X + cycloneMaxTargetRange * Math.Cos(angle)); | |
randomPosition.Y = (int)(myPos.Y + cycloneMaxTargetRange * Math.Sin(angle)); | |
LokiPoe.InGameState.SkillBarPanel.UseAt(_cycloneSlot, true, randomPosition); | |
Log.ErrorFormat("Attack at random pos around me"); | |
} | |
else | |
{ | |
LokiPoe.InGameState.SkillBarPanel.UseAt(_cycloneSlot, true, myPos.GetPointAtDistanceAfterEnd(cachedPosition, cycloneMaxTargetRange)); | |
LokiPoe.InGameState.SkillBarPanel.UseAt(_cycloneSlot, true, myPos.GetPointAtDistanceBeforeThis(cachedPosition, cycloneMaxTargetRange)); | |
Log.ErrorFormat("Attack After End", cachedDistance); | |
} | |
} | |
} | |
else | |
{ | |
Vector2i randomPosition2 = Loki.Bot.Pathfinding.ExilePather.FindWalkableLocationsCloseTo(bestTarget, 4).First(); | |
LokiPoe.InGameState.SkillBarPanel.UseAt(_cycloneSlot, true, randomPosition2); | |
//LokiPoe.InGameState.SkillBarPanel.UseOn(_cycloneSlot, true, bestTarget); | |
//int auxDist = Loki.Game.LokiPoe.Random.Next(1, 5); | |
//LokiPoe.InGameState.SkillBarPanel.UseAt(_cycloneSlot, true, myPos.GetPointAtDistanceAfterThis(cachedPosition, auxDist)); | |
//LokiPoe.InGameState.SkillBarPanel.UseAt(_cycloneSlot, true, cachedPosition); | |
Log.ErrorFormat("Attack at mob pos "); | |
} | |
} | |
else | |
{ | |
if (_leapSlamSlot != -1) | |
{ | |
LokiPoe.InGameState.SkillBarPanel.UseAt(_leapSlamSlot, true, cachedPosition); | |
Log.ErrorFormat("Leap Slam cuz distance -> {0} and targetrage -> {1} ", cachedDistance, cycloneTargetRange); | |
} | |
} | |
} | |
await DisableAlwaysHiglight(); | |
return true; | |
} | |
return false; | |
} | |
#endregion | |
private int EnsurceCast(int slot) | |
{ | |
if (slot == -1) | |
return slot; | |
var slotSkill = LokiPoe.InGameState.SkillBarPanel.Slot(slot); | |
if (slotSkill == null || !slotSkill.CanUse()) | |
{ | |
return -1; | |
} | |
return slot; | |
} | |
#region Override of Object | |
/// <summary> | |
/// | |
/// </summary> | |
/// <returns></returns> | |
public override string ToString() | |
{ | |
return Name + ": " + Description; | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment