diff --git a/Addons/DataToColor/DataToColor.lua b/Addons/DataToColor/DataToColor.lua index 4ea7a6ee..d3ecd1b1 100644 --- a/Addons/DataToColor/DataToColor.lua +++ b/Addons/DataToColor/DataToColor.lua @@ -155,6 +155,7 @@ DataToColor.targetChanged = true DataToColor.autoFollow = false DataToColor.moving = false +DataToColor.channeling = false DataToColor.playerGUID = UnitGUID(DataToColor.C.unitPlayer) DataToColor.petGUID = UnitGUID(DataToColor.C.unitPet) diff --git a/Addons/DataToColor/DataToColor.toc b/Addons/DataToColor/DataToColor.toc index a21a78f5..fb23413e 100644 --- a/Addons/DataToColor/DataToColor.toc +++ b/Addons/DataToColor/DataToColor.toc @@ -3,7 +3,7 @@ ## Title: DataToColor ## Author: FreeHongKongMMO ## Notes: Displays data as colors -## Version: 1.7.59 +## Version: 1.7.60 ## RequiredDeps: ## OptionalDeps: Ace3, LibRangeCheck, LibClassicCasterino ## SavedVariables: diff --git a/Addons/DataToColor/EventHandlers.lua b/Addons/DataToColor/EventHandlers.lua index 6786b796..8368f2f4 100644 --- a/Addons/DataToColor/EventHandlers.lua +++ b/Addons/DataToColor/EventHandlers.lua @@ -99,6 +99,8 @@ function DataToColor:RegisterEvents() DataToColor:RegisterEvent("UNIT_SPELLCAST_SENT", 'OnUnitSpellCastSent') DataToColor:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED", 'OnUnitSpellCastSucceeded') DataToColor:RegisterEvent("UNIT_SPELLCAST_FAILED", 'OnUnitSpellCastFailed') + DataToColor:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START", 'OnUnitSpellCastChannelStart') + DataToColor:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP", 'OnUnitSpellCastChannelStop') --DataToColor:RegisterEvent("UNIT_SPELLCAST_FAILED_QUIET", 'OnUnitSpellCastFailed') DataToColor:RegisterEvent('LOOT_READY', 'OnLootReady') DataToColor:RegisterEvent('LOOT_CLOSED', 'OnLootClosed') @@ -481,6 +483,16 @@ function DataToColor:OnUnitSpellCastFailed(event, unit, castGUID, spellId) DataToColor.lastCastSpellId = spellId end +function DataToColor:OnUnitSpellCastChannelStart(event, unit, castGUID, spellID) + if unit ~= DataToColor.C.unitPlayer then return end + DataToColor.channeling = true +end + +function DataToColor:OnUnitSpellCastChannelStop(event, unit, castGUID, spellID) + if unit ~= DataToColor.C.unitPlayer then return end + DataToColor.channeling = false +end + function DataToColor:SoM_OnCastSuccess(event, unitTarget, castGuid, spellId) if unitTarget ~= DataToColor.C.unitPlayer then return end som_spellId = spellId or 0 diff --git a/Addons/DataToColor/Query.lua b/Addons/DataToColor/Query.lua index 0a42b8a4..70693746 100644 --- a/Addons/DataToColor/Query.lua +++ b/Addons/DataToColor/Query.lua @@ -176,7 +176,8 @@ function DataToColor:Bits3() (UnitIsPlayer(DataToColor.C.unitSoftInteract) and 2 or 0) ^ 3 + (UnitIsTapDenied(DataToColor.C.unitSoftInteract) and 2 or 0) ^ 4 + (UnitAffectingCombat(DataToColor.C.unitSoftInteract) and 2 or 0) ^ 5 + - (DataToColor:IsUnitHostile(DataToColor.C.unitPlayer, DataToColor.C.unitSoftInteract) and 2 or 0) ^ 6 + (DataToColor:IsUnitHostile(DataToColor.C.unitPlayer, DataToColor.C.unitSoftInteract) and 2 or 0) ^ 6 + + ((DataToColor.channeling and 2 or 0)) ^ 7 ) end diff --git a/Core/AddonComponent/AddonBits.cs b/Core/AddonComponent/AddonBits.cs index af82a80d..5e24d314 100644 --- a/Core/AddonComponent/AddonBits.cs +++ b/Core/AddonComponent/AddonBits.cs @@ -97,4 +97,6 @@ public void Update(IAddonDataProvider reader) public bool SoftInteract_Combat() => v3[Mask._5]; public bool SoftInteract_Hostile() => v3[Mask._6]; + + public bool Channeling() => v3[Mask._7]; } \ No newline at end of file diff --git a/Core/GoalsComponent/CastResult.cs b/Core/GoalsComponent/CastResult.cs new file mode 100644 index 00000000..c490f000 --- /dev/null +++ b/Core/GoalsComponent/CastResult.cs @@ -0,0 +1,25 @@ +using System; + +namespace Core.Goals; + +public enum CastResult +{ + Success, + CurrentActionNotDetected, + UIFeedbackNotDetected, + TokenInterrupted, + UIError +} + +public static class CastResult_Extension +{ + public static string ToStringF(this CastResult value) => value switch + { + CastResult.Success => nameof(CastResult.Success), + CastResult.CurrentActionNotDetected => nameof(CastResult.CurrentActionNotDetected), + CastResult.UIFeedbackNotDetected => nameof(CastResult.UIFeedbackNotDetected), + CastResult.TokenInterrupted => nameof(CastResult.TokenInterrupted), + CastResult.UIError => nameof(CastResult.UIError), + _ => throw new ArgumentNullException(nameof(value)), + }; +} diff --git a/Core/GoalsComponent/CastingHandler.cs b/Core/GoalsComponent/CastingHandler.cs index 6728972f..29f1e81c 100644 --- a/Core/GoalsComponent/CastingHandler.cs +++ b/Core/GoalsComponent/CastingHandler.cs @@ -1,4 +1,4 @@ -using Game; +using Game; using Microsoft.Extensions.Logging; @@ -39,7 +39,6 @@ public sealed partial class CastingHandler private readonly ClassConfiguration classConfig; private readonly FormKeyActions forms; - private readonly PlayerDirection direction; private readonly StopMoving stopMoving; private readonly ReactCastError react; @@ -59,16 +58,19 @@ public bool SpellInQueue() => // second cast still problematic private KeyAction? lastAction; - public CastingHandler(ILogger logger, ConfigurableInput input, - ClassConfiguration classConfig, AddonBits bits, + public CastingHandler( + ILogger logger, + ConfigurableInput input, + ClassConfiguration classConfig, + AddonBits bits, ActionBarBits usableAction, ActionBarBits currentAction, Wait wait, PlayerReader playerReader, BagReader bagReader, CombatLog combatLog, - PlayerDirection direction, - StopMoving stopMoving, ReactCastError react, + StopMoving stopMoving, + ReactCastError react, CastingHandlerInterruptWatchdog interruptWatchdog) { this.logger = logger; @@ -88,7 +90,6 @@ public CastingHandler(ILogger logger, ConfigurableInput input, Log = classConfig.Log; forms = classConfig.Form; - this.direction = direction; this.stopMoving = stopMoving; this.react = react; @@ -124,6 +125,7 @@ UI_ERROR.CAST_SUCCESS or } private static float WaitCurrentAction(int duration, Wait wait, + PlayerReader playerReader, KeyAction item, ActionBarBits currentAction, CancellationToken token) { @@ -131,15 +133,16 @@ private static float WaitCurrentAction(int duration, Wait wait, bool Interrupt() => currentAction.Is(item) || + playerReader.CastState == UI_ERROR.CAST_SENT || token.IsCancellationRequested; } - private bool CastInstant(KeyAction item, bool retry, CancellationToken token) + private CastResult CastInstant(KeyAction item, CancellationToken token) { if (!playerReader.IsCasting() && item.BeforeCastStop) { stopMoving.Stop(); - wait.Update(); + wait.Update(playerReader.NetworkLatency); } int beforeCastEventTime = playerReader.UIErrorTime.Value; @@ -155,23 +158,24 @@ private bool CastInstant(KeyAction item, bool retry, CancellationToken token) if (Log && item.Log) LogInstantBaseAction(logger, item.Name, pressMs); - return true; + return CastResult.Success; } float elapsedMs = WaitCurrentAction( - playerReader.DoubleNetworkLatency, wait, item, currentAction, token); + playerReader.DoubleNetworkLatency + playerReader.SpellQueueTimeMs, + wait, playerReader, item, currentAction, token); if (DEBUG && Log && item.Log) LogInstantInput(logger, item.Name, pressMs, playerReader.CastState.ToStringF(), elapsedMs); - if (elapsedMs < 0) + if (elapsedMs < 0 && playerReader.CastState != UI_ERROR.CAST_SUCCESS) { if (!DEBUG || (Log && item.Log)) LogInstantInput(logger, item.Name, pressMs, playerReader.CastState.ToStringF(), elapsedMs); - return false; + return CastResult.CurrentActionNotDetected; } // Melee Swing @@ -232,14 +236,13 @@ static float InstantSpell(int durationMs, beforeCastEventValue.ToStringF(), playerReader.CastState.ToStringF()); - return false; + return CastResult.UIFeedbackNotDetected; } - if (!CastInstantSuccessful(playerReader.CastEvent.Value) && !retry) + if (!CastInstantSuccessful(playerReader.CastEvent.Value)) { LogInstantInputFailed(logger, item.Name, pressMs, playerReader.CastState.ToStringF(), elapsedMs); - react.Do(item); - return false; + return CastResult.UIError; } else if (!DEBUG && Log && item.Log) { @@ -261,10 +264,10 @@ static float InstantSpell(int durationMs, else playerReader.ReadLastCastGCD(); - return true; + return CastResult.Success; } - private bool CastCastbar(KeyAction item, bool retry, CancellationToken token) + private CastResult CastCastbar(KeyAction item, CancellationToken token) { wait.While(bits.Falling); @@ -286,11 +289,12 @@ private bool CastCastbar(KeyAction item, bool retry, CancellationToken token) LogCastbarBaseAction(logger, item.Name, pressMs); item.SetClicked(); - return true; + return CastResult.Success; } float elapsedMs = WaitCurrentAction( - playerReader.DoubleNetworkLatency, wait, item, currentAction, token); + playerReader.DoubleNetworkLatency + playerReader.SpellQueueTimeMs, + wait, playerReader, item, currentAction, token); if (DEBUG && Log && item.Log) LogCastbarInput(logger, item.Name, pressMs, elapsedMs); @@ -300,33 +304,25 @@ private bool CastCastbar(KeyAction item, bool retry, CancellationToken token) if (!DEBUG || (Log && item.Log)) LogCastbarInput(logger, item.Name, pressMs, elapsedMs); - return false; + return CastResult.CurrentActionNotDetected; } - elapsedMs = WaitTilUIErrorChange(playerReader.DoubleNetworkLatency, - beforeCastEventTime, wait, playerReader, token); + elapsedMs = WaitTilUIErrorTimeChange( + playerReader.DoubleNetworkLatency, + beforeCastEventTime, + wait, playerReader, token); if (DEBUG && Log && item.Log) - LogCastbarUsableChange(logger, item.Name, playerReader.IsCasting(), + LogCastbarUsableChange(logger, item.Name, + playerReader.IsCasting(), bits.Channeling(), playerReader.CastCount, beforeUsable, usableAction.Is(item), beforeCastEventValue.ToStringF(), playerReader.CastState.ToStringF()); - if (elapsedMs < 0) + if (playerReader.CastState is < UI_ERROR.MAX_ERROR_RANGE and + not UI_ERROR.NONE) { - if (!DEBUG || (Log && item.Log)) - LogCastbarUsableChange(logger, item.Name, playerReader.IsCasting(), - playerReader.CastCount, beforeUsable, usableAction.Is(item), - beforeCastEventValue.ToStringF(), - playerReader.CastState.ToStringF()); - - return false; - } - - if (playerReader.CastState != UI_ERROR.CAST_START && !retry) - { - react.Do(item); - return false; + return CastResult.UIError; } // at this point the player has castbar @@ -336,13 +332,13 @@ private bool CastCastbar(KeyAction item, bool retry, CancellationToken token) if (item.AfterCastWaitCastbar) { - if (playerReader.IsCasting()) + if (playerReader.IsCasting() || bits.Channeling()) { int remainMs = playerReader.RemainCastMs - playerReader.SpellQueueTimeMs; if (Log && item.Log) LogVisibleAfterCastWaitCastbar(logger, item.Name, remainMs); - WaitTillNoLongerCasting(remainMs, wait, playerReader, RepeatPetAttack, token); + WaitTillNoLongerCastingOrChanneling(remainMs, wait, playerReader, bits, RepeatPetAttack, token); if (token.IsCancellationRequested) { if (playerReader.IsCasting()) @@ -351,7 +347,7 @@ private bool CastCastbar(KeyAction item, bool retry, CancellationToken token) if (Log && item.Log) LogVisibleAfterCastWaitCastbarInterrupted(logger, item.Name); - return false; + return CastResult.TokenInterrupted; } } else if (playerReader.CastState == UI_ERROR.CAST_START) @@ -369,7 +365,7 @@ private bool CastCastbar(KeyAction item, bool retry, CancellationToken token) if (Log && item.Log) LogHiddenAfterCastWaitCastbarInterrupted(logger, item.Name); - return false; + return CastResult.TokenInterrupted; } } @@ -380,10 +376,10 @@ private bool CastCastbar(KeyAction item, bool retry, CancellationToken token) else if (!DEBUG && Log && item.Log) LogCastbarInput(logger, item.Name, pressMs, elapsedMs); - return true; + return CastResult.Success; } - private static float WaitTilUIErrorChange(int durationMs, int beforeCastEventTime, + private static float WaitTilUIErrorTimeChange(int durationMs, int beforeCastEventTime, Wait wait, PlayerReader playerReader, CancellationToken token) { return wait.Until(durationMs, @@ -392,19 +388,19 @@ private static float WaitTilUIErrorChange(int durationMs, int beforeCastEventTim token.IsCancellationRequested); } - private static void WaitTillNoLongerCasting(int remainMs, Wait wait, - PlayerReader playerReader, Action repeat, CancellationToken token) + private static void WaitTillNoLongerCastingOrChanneling(int remainMs, Wait wait, + PlayerReader playerReader, AddonBits bits, Action repeat, CancellationToken token) { wait.Until(remainMs, interrupt: () => - !playerReader.IsCasting() || + (!playerReader.IsCasting() || !bits.Channeling()) || token.IsCancellationRequested, repeat); } - private static void WaitTilCastStateChange(int durationMs, UI_ERROR beforeCastEventValue, + private static float WaitTilCastStateChange(int durationMs, UI_ERROR beforeCastEventValue, Wait wait, PlayerReader playerReader, Action repeat, CancellationToken token) { - wait.Until(durationMs, + return wait.Until(durationMs, interrupt: () => beforeCastEventValue != playerReader.CastState || token.IsCancellationRequested, @@ -416,16 +412,8 @@ public bool CastIfReady(KeyAction item, Func interrupt) return item.CanRun() && Cast(item, interrupt); } - public bool Cast(KeyAction item, Func interrupt) + private bool PreparedForCast(KeyAction item, CancellationToken token) { - CancellationToken token = CancellationToken.None; - - if (item.PressDuration > InputDuration.DefaultPress || - item.HasCastBar) - token = interruptWatchdog.Set(interrupt); - - float elapsedMs = 0; - if (item.HasForm && playerReader.Form != item.FormValue) { bool beforeUsable = usableAction.Is(item); @@ -456,14 +444,16 @@ public bool Cast(KeyAction item, Func interrupt) input.PressStopAttack(); input.PressStopAttack(); - int waitTime = + int waitTimeMs = Max(playerReader.GCD.Value, playerReader.RemainCastMs) + - playerReader.DoubleNetworkLatency; + playerReader.DoubleNetworkLatency; - elapsedMs = wait.Until(waitTime, token); - logger.LogInformation($"Stop {nameof(bits.Shoot)} and wait {waitTime}ms | {elapsedMs}ms"); + float elapsedMs = wait.Until(waitTimeMs, token); + logger.LogInformation($"Stop {nameof(bits.Shoot)} and wait {waitTimeMs}ms | {elapsedMs}ms"); + if (elapsedMs >= 0) { + wait.Update(); return false; } } @@ -478,9 +468,8 @@ public bool Cast(KeyAction item, Func interrupt) if (!playerReader.IsCasting() && (item.BeforeCastStop || item.HasCastBar)) { stopMoving.Stop(); - wait.Update(); + wait.Update(playerReader.NetworkLatency); } - int delay = Random.Shared.Next(item.BeforeCastDelay, item.BeforeCastMaxDelay); if (Log && item.Log) @@ -489,46 +478,11 @@ public bool Cast(KeyAction item, Func interrupt) wait.Until(delay, token); } - int auraHash = playerReader.AuraCount.Hash; - - if (!item.HasCastBar) - { - if (!CastInstant(item, false, token)) - { - // try again after reacted to UI_ERROR - LogFailedAttemptTryAgain(logger, item.Name); - - if (token.IsCancellationRequested || !CastInstant(item, true, token)) - { - return false; - } - } - } - else - { - if (!CastCastbar(item, false, token)) - { - // try again after reacted to UI_ERROR - LogFailedAttemptTryAgain(logger, item.Name); - - if (token.IsCancellationRequested || !CastCastbar(item, true, token)) - { - return false; - } - } - } - - if (!item.BaseAction) - { - lastAction = item; - - int durationMs = UpdateGCD(); - - if (DEBUG && Log && item.Log) - LogWaitForGCD(logger, item.Name, playerReader.LastCastGCD, - playerReader.GCD.Value, playerReader.RemainCastMs, durationMs); - } + return true; + } + private void CheckPostCastFlags(KeyAction item, int auraHash, CancellationToken token) + { int bagHash = bagReader.Hash; if (item.AfterCastWaitBuff) @@ -538,7 +492,7 @@ public bool Cast(KeyAction item, Func interrupt) playerReader.SpellQueueTimeMs + playerReader.NetworkLatency; - elapsedMs = AfterCastWaitBuff(totalTime, + float elapsedMs = AfterCastWaitBuff(totalTime, auraHash, wait, playerReader, combatLog, token); if (Log && item.Log) @@ -571,9 +525,9 @@ static float AfterCastWaitBuff(int totalTime, int auraHash, Wait wait, { int waitTimeMs = playerReader.SpellQueueTimeMs + - playerReader.NetworkLatency; + playerReader.DoubleNetworkLatency; - elapsedMs = AfterCastWaitBag(waitTimeMs, bagHash, wait, bagReader, token); + float elapsedMs = AfterCastWaitBag(waitTimeMs, bagHash, wait, bagReader, token); if (Log && item.Log) LogAfterCastWaitBag(logger, item.Name, bagHash, bagReader.Hash, elapsedMs); @@ -585,7 +539,7 @@ static float AfterCastWaitBag(int totalTime, int bagHash, Wait wait, if (item.AfterCastWaitCombat) { - elapsedMs = AfterCastWaitCombat(2 * GCD, wait, bits, token); + float elapsedMs = AfterCastWaitCombat(2 * GCD, wait, bits, token); if (Log && item.Log) LogAfterCastWaitCombat(logger, item.Name, elapsedMs); @@ -616,19 +570,10 @@ static float AfterCastWaitMeleeRange(int duration, playerReader.HealthCurrent() < lastKnownHealth || !playerReader.WithInPullRange() || token.IsCancellationRequested); - } if (item.AfterCastStepBack != 0) { - if (Log && item.Log) - LogAfterCastStepBack(logger, item.Name, item.AfterCastStepBack); - - input.StartBackward(true); - - if (Random.Shared.Next(3) == 0) - input.PressJump(); - int waitMs = item.AfterCastStepBack != -1 ? item.AfterCastStepBack @@ -636,7 +581,15 @@ static float AfterCastWaitMeleeRange(int duration, ? playerReader.GCD.Value : MIN_GCD - playerReader.SpellQueueTimeMs; - elapsedMs = wait.Until(waitMs, token); + if (Log && item.Log) + LogAfterCastStepBack(logger, item.Name, waitMs); + + input.StartBackward(true); + + if (Random.Shared.Next(3) == 0) + input.PressJump(); + + float elapsedMs = wait.Until(waitMs, token); // todo: does this necessary ? if (Log && item.Log) @@ -660,12 +613,69 @@ static float AfterCastWaitMeleeRange(int duration, if (Log && item.Log) LogAfterCastDelay(logger, item.Name, delay); - elapsedMs = wait.Until(delay, token); + float elapsedMs = wait.Until(delay, token); if (Log && item.Log && elapsedMs >= 0) { LogAfterCastDelayInterrupted(logger, item.Name, elapsedMs); } } + } + + public bool Cast(KeyAction item, Func interrupt) + { + CancellationToken token = CancellationToken.None; + + if (item.PressDuration > InputDuration.DefaultPress || + item.HasCastBar) + token = interruptWatchdog.Set(interrupt); + + if (!PreparedForCast(item, token)) + return false; + + int auraHash = playerReader.AuraCount.Hash; + + Func castStrategy = + item.HasCastBar + ? CastCastbar + : CastInstant; + + CastResult result = castStrategy(item, token); + if (result != CastResult.Success) + { + if (result != CastResult.UIError) + { + LogFailedDueReason(logger, item.Name, result.ToStringF()); + return false; + } + + // instant cast token interrupted? + if (token.IsCancellationRequested) + { + return false; + } + + LogFailedAttemptTryAgain(logger, item.Name); + react.Do(item); + + if (token.IsCancellationRequested || + castStrategy(item, token) != CastResult.Success) + { + return false; + } + } + + if (!item.BaseAction) + { + lastAction = item; + + int durationMs = UpdateGCD(); + + if (DEBUG && Log && item.Log) + LogWaitForGCD(logger, item.Name, playerReader.LastCastGCD, + playerReader.GCD.Value, playerReader.RemainCastMs, durationMs); + } + + CheckPostCastFlags(item, auraHash, token); item.ConsumeCharge(); @@ -674,7 +684,10 @@ static float AfterCastWaitMeleeRange(int duration, public bool WaitForGCD(KeyAction item, bool spellQueue, CancellationToken token) { - int duration = Max(playerReader.GCD.Value, playerReader.RemainCastMs); + int duration = + Max(playerReader.GCD.Value, playerReader.RemainCastMs) + + playerReader.NetworkLatency; + if (spellQueue) duration -= playerReader.SpellQueueTimeMs; @@ -704,7 +717,7 @@ public bool SwitchForm(KeyAction item, CancellationToken token) { return forms.Get(item.FormValue, out KeyAction? formAction) - && CastInstant(formAction!, false, token); + && CastInstant(formAction!, token) == CastResult.Success; } private void RepeatPetAttack() @@ -774,8 +787,8 @@ static partial void LogInstantUsableChange(ILogger logger, string name, [LoggerMessage( EventId = 0077, Level = LogLevel.Information, - Message = "[{name,-17}] ... count: {castCount} | casting: {casting} | usable: {beforeUsable}->{afterUsable} | {beforeCastEvent}->{afterCastEvent}")] - static partial void LogCastbarUsableChange(ILogger logger, string name, bool casting, int castCount, bool beforeUsable, bool afterUsable, string beforeCastEvent, string afterCastEvent); + Message = "[{name,-17}] ... count: {castCount} | casting: {casting} | channeling: {channeling} | usable: {beforeUsable}->{afterUsable} | {beforeCastEvent}->{afterCastEvent}")] + static partial void LogCastbarUsableChange(ILogger logger, string name, bool casting, bool channeling, int castCount, bool beforeUsable, bool afterUsable, string beforeCastEvent, string afterCastEvent); [LoggerMessage( EventId = 0078, @@ -894,10 +907,16 @@ static partial void LogInstantUsableChange(ILogger logger, string name, [LoggerMessage( EventId = 0097, - Level = LogLevel.Trace, + Level = LogLevel.Warning, Message = "[{name,-17}] ... Failed try again!")] static partial void LogFailedAttemptTryAgain(ILogger logger, string name); + [LoggerMessage( + EventId = 0098, + Level = LogLevel.Error, + Message = "[{name,-17}] ... Cast failed due {reason}!")] + static partial void LogFailedDueReason(ILogger logger, string name, string reason); + #endregion } \ No newline at end of file diff --git a/Core/GoalsComponent/ReactCastError.cs b/Core/GoalsComponent/ReactCastError.cs index 7371d533..a8554f23 100644 --- a/Core/GoalsComponent/ReactCastError.cs +++ b/Core/GoalsComponent/ReactCastError.cs @@ -46,7 +46,14 @@ public void Do(KeyAction item) case UI_ERROR.CAST_SUCCESS: WaitForCooldown(item, value); break; + case UI_ERROR.CAST_SENT: + UI_ERROR currentCastState = playerReader.CastState; + int maxTime = Math.Min(playerReader.DoubleNetworkLatency, playerReader.RemainCastMs); + logger.LogInformation($"React to {value.ToStringF()} -- by waiting {maxTime}ms!"); + wait.Until(maxTime, + () => currentCastState != playerReader.CastState); + break; case UI_ERROR.NONE: case UI_ERROR.CAST_START: case UI_ERROR.SPELL_FAILED_TARGETS_DEAD: diff --git a/Json/class/Mage_54_Frost_Arcane.json b/Json/class/Mage_54_Frost_Arcane.json index ef669d0e..16694e42 100644 --- a/Json/class/Mage_54_Frost_Arcane.json +++ b/Json/class/Mage_54_Frost_Arcane.json @@ -155,7 +155,7 @@ "Log": false }, { - "Name": "Conjure Drink", + "Name": "Conjure Water", "HasCastBar": true, "Key": "9", "Requirement": "!BagItem:Item_Conjure_Drink:4", diff --git a/Json/class/Mage_60_Frost_Arcane.json b/Json/class/Mage_60_Frost_Arcane.json new file mode 100644 index 00000000..309908eb --- /dev/null +++ b/Json/class/Mage_60_Frost_Arcane.json @@ -0,0 +1,190 @@ +{ + "ClassName": "Mage", + "Loot": true, + "Mode": "AttendedGrind", + "NPCMaxLevels_Below": 10, + "NPCMaxLevels_Above": 2, + "Mount": { + "Key": "N0" + }, + "PathFilename": "Burning Steppes_2024_08_15_23_32_17.json", + "PathThereAndBack": true, + "PathReduceSteps": true, + "IntVariables": { + "Item_Conjure_Drink": 8079, + "Item_Conjure_Food": 8076, + "MIN_FOOD%": 50, + "MIN_WATER%": 30, + "MIN_MANA_SPELL%": 60 + }, + "Pull": { + "Sequence": [ + { + "Name": "Ice Barrier", + "Key": "7", + "WhenUsable": true, + "Requirement": "!Ice Barrier" + }, + { + "Name": "Frostbolt", + "Key": "1", + "HasCastBar": true, + "Requirements": [ + "SpellInRange:3", + "Mana% > MIN_MANA_SPELL%" + ] + }, + { + "Name": "Shoot", + "Key": "0", + "Item": true, + "BeforeCastStop": true, + "Requirements": [ + "HasRangedWeapon", + "!Shooting", + "SpellInRange:1", + "Mana% < MIN_MANA_SPELL%" + ], + "AfterCastWaitCombat": true, + "AfterCastDelay": 500 + } + ] + }, + "Combat": { + "Sequence": [ + { + "Name": "Ice Barrier", + "Key": "7", + "Cooldown": 30000, + "WhenUsable": true, + "Requirement": "!Ice Barrier" + }, + { + "Name": "Cone of Cold", + "Key": "N8", + "Cooldown": 10000, + "Requirements": [ + "TargetHealth% > 2", + "TargetHealth% < 20", + "InMeleeRange" + ] + }, + { + "Name": "Fire Blast", + "Key": "5", + "Cooldown": 8000, + "Requirements": [ + "TargetHealth% > 2", + "TargetHealth% < 20", + "SpellInRange:4" + ] + }, + { + "Name": "Frost Nova", + "Key": 6, + "Cooldown": 25000, + "WhenUsable": true, + "Requirements": [ + "InMeleeRange" + ], + "AfterCastStepBack": -1 + }, + { + "Name": "Arcane Missiles", + "Key": "2", + "HasCastBar": true, + "Requirements": [ + "SpellInRange:3", + "TargetHealth% > 30", + "Mana% > MIN_MANA_SPELL%" + ]//, + //"AfterCastWaitCastbar": true + }, + { + "Name": "Shoot", + "Key": "0", + "Item": true, + "BeforeCastStop": true, + "Requirements": [ + "HasRangedWeapon", + "!Shooting", + "SpellInRange:1", + "Mana% < MIN_MANA_SPELL%" + ], + "AfterCastDelay": 500 + }, + { + "Name": "AutoAttack", + "Requirements": [ + "!HasRangedWeapon", + "!Casting", + "!Shooting", + "!AutoAttacking" + ] + }, + { + "Name": "Approach", + "Requirements": [ + "!Casting", + "!Shooting" + ] + } + ] + }, + "Parallel": { + "Sequence": [ + { + "Name": "Food", + "Key": "N1", + "Requirement": "Health% < MIN_FOOD%" + }, + { + "Name": "Drink", + "Key": "N2", + "Requirement": "Mana% < MIN_WATER%" + } + ] + }, + "Adhoc": { + "Sequence": [ + { + "Name": "Evocation", + "Key": "N9", + "HasCastBar": true, + "Cooldown": 600000, + "Cost": 2, + "Requirements": [ + "Mana% < 50" + ], + "Interrupt": "Mana% == 100", + "AfterCastWaitCastbar": true + }, + { + "Name": "Ice Armor", + "Key": "3", + "Requirement": "!Frost Armor" + }, + { + "Name": "Arcane Intellect", + "Key": "4", + "Requirement": "!Arcane Intellect" + }, + { + "Name": "Conjure Water", + "HasCastBar": true, + "Key": "9", + "Requirement": "!BagItem:Item_Conjure_Drink:4", + "AfterCastWaitCastbar": true, + "AfterCastWaitBag": true + }, + { + "Name": "Conjure Food", + "HasCastBar": true, + "Key": "8", + "Requirement": "!BagItem:Item_Conjure_Food:4", + "AfterCastWaitCastbar": true, + "AfterCastWaitBag": true + } + ] + } +} \ No newline at end of file diff --git a/Json/path/Burning Steppes_2024_08_15_23_32_17.jpg b/Json/path/Burning Steppes_2024_08_15_23_32_17.jpg new file mode 100644 index 00000000..e6553f99 Binary files /dev/null and b/Json/path/Burning Steppes_2024_08_15_23_32_17.jpg differ diff --git a/Json/path/Burning Steppes_2024_08_15_23_32_17.json b/Json/path/Burning Steppes_2024_08_15_23_32_17.json new file mode 100644 index 00000000..02b3947d --- /dev/null +++ b/Json/path/Burning Steppes_2024_08_15_23_32_17.json @@ -0,0 +1 @@ +[{"X":82.2112,"Y":62.3516,"Z":0.0},{"X":81.93671,"Y":61.7935,"Z":0.0},{"X":81.7506,"Y":61.133,"Z":0.0},{"X":81.487595,"Y":60.526,"Z":0.0},{"X":81.0442,"Y":59.9436,"Z":0.0},{"X":80.769104,"Y":59.349297,"Z":0.0},{"X":80.526695,"Y":58.7313,"Z":0.0},{"X":80.280304,"Y":58.1033,"Z":0.0},{"X":80.0379,"Y":57.4852,"Z":0.0},{"X":79.795395,"Y":56.8671,"Z":0.0},{"X":79.5558,"Y":56.2643,"Z":0.0},{"X":79.3599,"Y":55.6105,"Z":0.0},{"X":79.054596,"Y":55.065998,"Z":0.0},{"X":78.5515,"Y":54.6703,"Z":0.0},{"X":78.0163,"Y":54.3924,"Z":0.0},{"X":77.7119,"Y":53.8401,"Z":0.0},{"X":77.4467,"Y":53.2439,"Z":0.0},{"X":77.2111,"Y":52.6199,"Z":0.0},{"X":76.8601,"Y":51.9514,"Z":0.0},{"X":76.406296,"Y":51.4478,"Z":0.0},{"X":75.883,"Y":51.152,"Z":0.0},{"X":75.3498,"Y":50.753498,"Z":0.0},{"X":75.0565,"Y":50.0388,"Z":0.0},{"X":75.1718,"Y":49.4069,"Z":0.0},{"X":74.9908,"Y":48.7426,"Z":0.0},{"X":74.784195,"Y":48.086,"Z":0.0},{"X":74.2921,"Y":47.6155,"Z":0.0},{"X":74.0377,"Y":47.035698,"Z":0.0},{"X":74.0683,"Y":46.375397,"Z":0.0},{"X":74.3461,"Y":45.7995,"Z":0.0},{"X":74.6499,"Y":45.2501,"Z":0.0},{"X":74.826294,"Y":44.583603,"Z":0.0},{"X":74.9272,"Y":43.8916,"Z":0.0},{"X":74.9896,"Y":43.232597,"Z":0.0},{"X":74.8874,"Y":42.533302,"Z":0.0},{"X":74.7329,"Y":41.841896,"Z":0.0},{"X":74.4708,"Y":41.283398,"Z":0.0},{"X":74.2284,"Y":40.7248,"Z":0.0},{"X":74.098,"Y":40.0712,"Z":0.0},{"X":74.0962,"Y":39.3836,"Z":0.0},{"X":73.985,"Y":38.6939,"Z":0.0},{"X":74.117294,"Y":38.0337,"Z":0.0},{"X":74.6288,"Y":37.595,"Z":0.0},{"X":74.9628,"Y":36.932,"Z":0.0},{"X":75.1325,"Y":36.261,"Z":0.0},{"X":75.1961,"Y":35.5732,"Z":0.0},{"X":75.3934,"Y":34.968903,"Z":0.0},{"X":75.7872,"Y":34.3537,"Z":0.0},{"X":76.3035,"Y":33.903603,"Z":0.0},{"X":76.836,"Y":33.472298,"Z":0.0},{"X":77.2254,"Y":32.881,"Z":0.0},{"X":77.7031,"Y":32.4529,"Z":0.0},{"X":78.381,"Y":32.2502,"Z":0.0},{"X":79.019,"Y":31.9117,"Z":0.0},{"X":79.5879,"Y":31.7197,"Z":0.0},{"X":80.2939,"Y":31.7912,"Z":0.0},{"X":80.9834,"Y":31.8966,"Z":0.0},{"X":81.670105,"Y":31.897099,"Z":0.0},{"X":82.2625,"Y":32.0132,"Z":0.0},{"X":82.8625,"Y":32.1308,"Z":0.0},{"X":83.36,"Y":31.7595,"Z":0.0},{"X":84.029396,"Y":31.6263,"Z":0.0},{"X":84.61211,"Y":31.819698,"Z":0.0},{"X":85.163605,"Y":32.1829,"Z":0.0},{"X":85.61189,"Y":32.7284,"Z":0.0},{"X":86.011894,"Y":33.2665,"Z":0.0},{"X":86.601494,"Y":33.410202,"Z":0.0},{"X":87.1991,"Y":33.552597,"Z":0.0},{"X":87.88161,"Y":33.8037,"Z":0.0},{"X":88.1833,"Y":34.495102,"Z":0.0},{"X":88.0789,"Y":35.1274,"Z":0.0},{"X":87.750595,"Y":35.6652,"Z":0.0},{"X":87.461205,"Y":36.2109,"Z":0.0},{"X":87.254105,"Y":36.8469,"Z":0.0},{"X":86.89629,"Y":37.495,"Z":0.0},{"X":86.65379,"Y":38.1128,"Z":0.0},{"X":86.460304,"Y":38.7529,"Z":0.0},{"X":86.3302,"Y":39.4043,"Z":0.0},{"X":86.401596,"Y":40.0806,"Z":0.0},{"X":86.6148,"Y":40.715797,"Z":0.0},{"X":86.8616,"Y":41.326298,"Z":0.0},{"X":87.1164,"Y":41.877,"Z":0.0},{"X":87.8061,"Y":42.0539,"Z":0.0},{"X":88.192505,"Y":42.6289,"Z":0.0},{"X":88.4768,"Y":43.2019,"Z":0.0},{"X":88.7389,"Y":43.801598,"Z":0.0},{"X":88.9447,"Y":44.4364,"Z":0.0},{"X":88.7469,"Y":45.0427,"Z":0.0},{"X":88.16321,"Y":45.3244,"Z":0.0},{"X":87.6169,"Y":45.654404,"Z":0.0},{"X":87.106895,"Y":46.027,"Z":0.0},{"X":86.391,"Y":45.9804,"Z":0.0},{"X":85.7919,"Y":45.8642,"Z":0.0},{"X":85.1867,"Y":46.0717,"Z":0.0},{"X":85.201195,"Y":46.736103,"Z":0.0},{"X":85.7098,"Y":47.1081,"Z":0.0},{"X":86.069,"Y":47.599102,"Z":0.0},{"X":86.27229,"Y":48.225296,"Z":0.0},{"X":86.37109,"Y":48.926502,"Z":0.0},{"X":86.4725,"Y":49.627197,"Z":0.0},{"X":86.4923,"Y":50.3552,"Z":0.0},{"X":86.3731,"Y":51.0313,"Z":0.0},{"X":86.168,"Y":51.679,"Z":0.0},{"X":85.8797,"Y":52.235798,"Z":0.0},{"X":85.410805,"Y":52.8002,"Z":0.0},{"X":84.8248,"Y":52.9558,"Z":0.0},{"X":84.2358,"Y":52.8238,"Z":0.0},{"X":83.5141,"Y":52.909897,"Z":0.0},{"X":83.0421,"Y":53.418697,"Z":0.0},{"X":82.740295,"Y":53.9748,"Z":0.0},{"X":82.4852,"Y":54.587597,"Z":0.0},{"X":82.4008,"Y":55.288902,"Z":0.0},{"X":82.3053,"Y":55.9879,"Z":0.0},{"X":82.4532,"Y":56.680798,"Z":0.0},{"X":82.6228,"Y":57.3514,"Z":0.0},{"X":82.757996,"Y":58.0326,"Z":0.0},{"X":82.7805,"Y":58.7498,"Z":0.0},{"X":82.802895,"Y":59.4661,"Z":0.0},{"X":82.825806,"Y":60.194,"Z":0.0},{"X":82.8482,"Y":60.9104,"Z":0.0},{"X":82.868004,"Y":61.543198,"Z":0.0}] \ No newline at end of file