diff --git a/CoreAdvanced.cs b/CoreAdvanced.cs index 404f9e852..6a6c0fb59 100644 --- a/CoreAdvanced.cs +++ b/CoreAdvanced.cs @@ -2429,7 +2429,7 @@ public CapeSpecial CurrentCapeSpecial() if (EquippedCape == null) return CapeSpecial.None; int patternId = EquippedCape.EnhancementPatternID; - if (Enum.IsDefined(typeof(EnhancementType), patternId)) + if (!Enum.IsDefined(typeof(CapeSpecial), patternId)) return CapeSpecial.None; return (CapeSpecial)patternId; } @@ -2447,7 +2447,7 @@ public HelmSpecial CurrentHelmSpecial() return HelmSpecial.None; int patternId = EquippedHelm.EnhancementPatternID; - if (Enum.IsDefined(typeof(EnhancementType), patternId)) + if (!Enum.IsDefined(typeof(HelmSpecial), patternId)) return HelmSpecial.None; return (HelmSpecial)patternId; } @@ -2465,7 +2465,7 @@ public WeaponSpecial CurrentWeaponSpecial() return WeaponSpecial.None; int patternId = EquippedWeapon.EnhancementPatternID; - if (Enum.IsDefined(typeof(EnhancementType), patternId)) + if (!Enum.IsDefined(typeof(WeaponSpecial), patternId)) return WeaponSpecial.None; return (WeaponSpecial)patternId; } @@ -2829,7 +2829,19 @@ void _AutoEnhance(InventoryItem item, int shopID, string? map = null, bool loggi bool specialOnCape = item.Category == ItemCategory.Cape && cSpecial != CapeSpecial.None; bool specialOnHelm = item.Category == ItemCategory.Helm && hSpecial != HelmSpecial.None; bool specialOnWeapon = item.ItemGroup == "Weapon" && wSpecial.ToString() != "None"; - string mapName = map ?? Bot.Map?.Name ?? "whitemap"; + string sourceMap = map; + string mapName = + string.IsNullOrWhiteSpace(map) + ? string.IsNullOrWhiteSpace(Bot.Map?.Name) + ? "whitemap" + : Bot.Map.Name + : map; + + if (string.IsNullOrWhiteSpace(sourceMap)) + Core.Logger( + $"Enhance: map input was blank for {item.Name}[{item.ID}], using fallback map '{mapName}'." + ); + List shopItems = Core.GetShopItems(mapName, shopID); // Shopdata complete check diff --git a/CoreBots.cs b/CoreBots.cs index 44ace83db..441504eca 100644 --- a/CoreBots.cs +++ b/CoreBots.cs @@ -2507,6 +2507,12 @@ public void SellItem(int itemID, int quant = 1, bool all = false) /// A list of objects from the specified shop, or an empty list if the shop data could not be loaded. public List GetShopItems(string map, int shopID) { + string originalMap = map; + map = string.IsNullOrWhiteSpace(map) ? "whitemap" : map.Trim(); + + if (!string.Equals(originalMap, map, StringComparison.Ordinal)) + Logger($"GetShopItems: map input '{originalMap}' normalized to '{map}'."); + // Ensure player is in map if (!Bot.Map.Name.Equals(map, StringComparison.OrdinalIgnoreCase)) { @@ -8854,9 +8860,9 @@ public void Join( bool ignoreCheck = false ) { - if (string.IsNullOrEmpty(map)) + if (string.IsNullOrWhiteSpace(map)) { - Logger("Map is null, cannot join."); + Logger("Map is null/blank, cannot join."); return; } @@ -8865,7 +8871,16 @@ public void Join( PrivateRoomNumber = int.Parse(PrivateRoomNumber.ToString()[..6]); } - map = map!.Replace(" ", "").Replace('I', 'i'); + string originalMap = map; + map = map!.Trim().Replace(" ", "").Replace('I', 'i'); + if (!string.Equals(originalMap, map, StringComparison.Ordinal)) + Logger($"Join(): sanitized map from '{originalMap}' to '{map}'."); + + if (string.IsNullOrWhiteSpace(map)) + { + Logger("Join(): map resolved to blank after sanitize, aborting join to avoid invalid /join input."); + return; + } map = map.ToLower() == "tercess" ? "tercessuinotlim" : map.ToLower(); string strippedMap = map.Contains('-') ? map.Split('-').First() : map; cell = @@ -8876,6 +8891,9 @@ public void Join( if (Bot.Map.Name != null && Bot.Map.Name.ToLower() == strippedMap && !ignoreCheck) return; + if (Bot.Map.Name != null && Bot.Map.Name != strippedMap) + Logger($"Join(): transitioning from '{Bot.Map.Name}' to '{strippedMap}'."); + //if aggro/aggroall is enabled when joining a map, disable it [forced] Bot.Options.AggroMonsters = false; Bot.Options.AggroAllMonsters = false; diff --git a/Ultras/0UltraDaily.cs b/Ultras/0UltraDaily.cs new file mode 100644 index 000000000..361669f08 --- /dev/null +++ b/Ultras/0UltraDaily.cs @@ -0,0 +1,400 @@ +/* +name: UltraDaily +description: Do all daily ultras +*/ + +//cs_include Scripts/CoreBots.cs +//cs_include Scripts/CoreAdvanced.cs +//cs_include Scripts/CoreUltra.cs +//cs_include Scripts/Ultras/CoreEngine.cs +//cs_include Scripts/CoreStory.cs +//cs_include Scripts/CoreFarms.cs +//cs_include Scripts/Ultras/UltraEzrajal.cs +//cs_include Scripts/Ultras/UltraWarden.cs +//cs_include Scripts/Ultras/UltraEngineer.cs +//cs_include Scripts/Ultras/UltraAvatarTyndarius.cs +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Skua.Core.Interfaces; +using Skua.Core.Options; + +public class UltraDaily +{ + public string OptionsStorage = "0UltraDaily"; + public bool DontPreconfigure = true; + + private CoreAdvanced Adv => _Adv ??= new CoreAdvanced(); + private static CoreAdvanced? _Adv; + private CoreUltra Ultra = new(); + private CoreBots C => CoreBots.Instance; + + public string[] MultiOptions = + { + "Main", + "CoreSettings", + "Bosses", + "Compositions", + }; + + public List CoreSettings = new() + { + new Option("IgnoreNeeded", "Ignore Needed Check", "Run selected ultras even if quest/state check says account may not need it.", false), + new Option("RestoreGear", "Restore Gear", "Restore gear and consumable state at the end of this handler.", false), + }; + + public List Bosses = new() + { + new Option("RunEzrajal", "Ultra Ezrajal", "Handle Ultra Ezrajal", true), + new Option("RunWarden", "Ultra Warden", "Handle Ultra Warden", true), + new Option("RunEngineer", "Ultra Engineer", "Handle Ultra Engineer", true), + new Option("RunAvatarTyndarius", "Ultra Avatar Tyndarius", "Handle Ultra Avatar Tyndarius", true), + }; + + public List Compositions = new() + { + new Option( + "Ultra Ezrajal Composition", + "Ultra Ezrajal Composition", + "Select the preset composition for Ultra Ezrajal.\n" + + "Safe: Arcana Invoker / Legion Revenant / ArchPaladin / Lord Of Order\n" + + "Fast: Chrono ShadowSlayer / Verus DoomKnight / Legion Revenant / Lord Of Order\n" + + "F2PFastest: Arcana Invoker / Verus DoomKnight / Legion Revenant / Lord Of Order", + UltraEzrajal.EzrajalComp.Safe + ), + new Option( + "Ultra Warden Composition", + "Ultra Warden Composition", + "Select the preset composition for Ultra Warden.\n" + + "Recommended: Legion Revenant / ArchPaladin / Lord Of Order / Verus DoomKnight", + UltraWarden.WardenComp.Recommended + ), + new Option( + "Ultra Engineer Composition", + "Ultra Engineer Composition", + "Select the preset composition for Ultra Engineer.\n" + + "Safe: Legion Revenant / StoneCrusher / ArchPaladin / Lord Of Order\n" + + "Fast: Lich / Legion Revenant / ArchPaladin / Lord Of Order\n" + + "F2PFast: Arcana Invoker / Legion Revenant / ArchPaladin / Lord Of Order", + UltraEngineer.EngineerComp.Safe + ), + new Option( + "Ultra Avatar Tyndarius Composition", + "Ultra Avatar Tyndarius Composition", + "Select the preset composition for Ultra Avatar Tyndarius.\n" + + "Safe: Chaos Avenger / Legion Revenant / ArchPaladin / Lord Of Order\n" + + "Fast: Chrono ShadowSlayer / Legion Revenant / ArchPaladin / Lord Of Order\n" + + "F2PFast: King's Echo / Legion Revenant / ArchPaladin / Lord Of Order", + UltraAvatarTyndarius.TyndariusComp.Safe + ), + }; + + private readonly Dictionary _questIds = new() + { + { "Ultra Ezrajal", 8152 }, + { "Ultra Warden", 8153 }, + { "Ultra Engineer", 8154 }, + { "Ultra Avatar Tyndarius", 8245 }, + }; + private const int DailyArmySize = 4; + + public void ScriptMain(IScriptInterface bot) + { + C.Logger("[0UltraDaily] Bootstrapping daily ultra handler."); + var ignoreNeeded = bot.Config == null ? true : bot.Config.Get("CoreSettings", "IgnoreNeeded"); + var restoreGear = bot.Config == null ? false : bot.Config.Get("CoreSettings", "RestoreGear"); + + var selectedBosses = new[] + { + (Name: "Ultra Ezrajal", Enabled: bot.Config == null ? true : bot.Config.Get("Bosses", "RunEzrajal")), + (Name: "Ultra Warden", Enabled: bot.Config == null ? true : bot.Config.Get("Bosses", "RunWarden")), + (Name: "Ultra Engineer", Enabled: bot.Config == null ? true : bot.Config.Get("Bosses", "RunEngineer")), + (Name: "Ultra Avatar Tyndarius", Enabled: bot.Config == null ? true : bot.Config.Get("Bosses", "RunAvatarTyndarius")), + }; + + var ezrajalComp = bot.Config == null + ? UltraEzrajal.EzrajalComp.Safe + : bot.Config.Get("Compositions", "Ultra Ezrajal Composition"); + var wardenComp = bot.Config == null + ? UltraWarden.WardenComp.Recommended + : bot.Config.Get("Compositions", "Ultra Warden Composition"); + var engineerComp = bot.Config == null + ? UltraEngineer.EngineerComp.Safe + : bot.Config.Get("Compositions", "Ultra Engineer Composition"); + var tyndariusComp = bot.Config == null + ? UltraAvatarTyndarius.TyndariusComp.Safe + : bot.Config.Get("Compositions", "Ultra Avatar Tyndarius Composition"); + + C.Logger( + $"[0UltraDaily] Selected flow => IgnoreNeeded={ignoreNeeded}, RestoreGear={restoreGear}" + ); + var runEzrajal = selectedBosses.First(x => x.Name == "Ultra Ezrajal").Enabled; + var runWarden = selectedBosses.First(x => x.Name == "Ultra Warden").Enabled; + var runEngineer = selectedBosses.First(x => x.Name == "Ultra Engineer").Enabled; + var runAvatar = selectedBosses.First(x => x.Name == "Ultra Avatar Tyndarius").Enabled; + C.Logger( + $"[0UltraDaily] Boss flags => Ezrajal={runEzrajal}, Warden={runWarden}, Engineer={runEngineer}, AvatarTyndarius={runAvatar}" + ); + + CoreBots.Instance.SetOptions(); + Adv.GearStore(); + C.Logger("[0UltraDaily] Core options captured and initial gear stored."); + + try + { + if (selectedBosses.All(x => !x.Item2)) + { + C.Logger("[0UltraDaily] No ultra bosses enabled; nothing to do."); + return; + } + + C.Logger("[0UltraDaily] Starting boss routing loop."); + + if (runEzrajal) + { + C.Logger("[0UltraDaily] Boss enabled: Ultra Ezrajal"); + if (!ignoreNeeded && ShouldSkip("Ultra Ezrajal")) + C.Logger("[0UltraDaily] Skip Ultra Ezrajal (need check says not required)."); + else + { + var script = new UltraEzrajal(); + RunUltra( + () => script.Run( + comp: ezrajalComp, + equipBestGear: true, + doEnhancements: true, + useLifeSteal: true + ), + "Ultra Ezrajal" + ); + } + C.Logger("[0UltraDaily] Boss routing complete: Ultra Ezrajal"); + } + else + { + C.Logger("[0UltraDaily] Boss skipped by config: Ultra Ezrajal"); + } + + if (runWarden) + { + C.Logger("[0UltraDaily] Boss enabled: Ultra Warden"); + if (!ignoreNeeded && ShouldSkip("Ultra Warden")) + C.Logger("[0UltraDaily] Skip Ultra Warden (need check says not required)."); + else + { + var script = new UltraWarden(); + RunUltra( + () => script.Run( + comp: wardenComp, + equipBestGear: true, + useLifeSteal: true + ), + "Ultra Warden" + ); + } + C.Logger("[0UltraDaily] Boss routing complete: Ultra Warden"); + } + else + { + C.Logger("[0UltraDaily] Boss skipped by config: Ultra Warden"); + } + + if (runEngineer) + { + C.Logger("[0UltraDaily] Boss enabled: Ultra Engineer"); + if (!ignoreNeeded && ShouldSkip("Ultra Engineer")) + C.Logger("[0UltraDaily] Skip Ultra Engineer (need check says not required)."); + else + { + var script = new UltraEngineer(); + RunUltra( + () => script.Run( + comp: engineerComp, + equipBestGear: true, + doEnhancements: true, + useLifeSteal: true + ), + "Ultra Engineer" + ); + } + C.Logger("[0UltraDaily] Boss routing complete: Ultra Engineer"); + } + else + { + C.Logger("[0UltraDaily] Boss skipped by config: Ultra Engineer"); + } + + if (runAvatar) + { + C.Logger("[0UltraDaily] Boss enabled: Ultra Avatar Tyndarius"); + if (!ignoreNeeded && ShouldSkip("Ultra Avatar Tyndarius")) + C.Logger("[0UltraDaily] Skip Ultra Avatar Tyndarius (need check says not required)."); + else + { + var script = new UltraAvatarTyndarius(); + RunUltra( + () => script.Run( + comp: tyndariusComp, + equipBestGear: true, + doEnhancements: true, + useLifeSteal: true + ), + "Ultra Avatar Tyndarius" + ); + } + C.Logger("[0UltraDaily] Boss routing complete: Ultra Avatar Tyndarius"); + } + else + { + C.Logger("[0UltraDaily] Boss skipped by config: Ultra Avatar Tyndarius"); + } + } + catch (Exception ex) + { + C.Logger($"[0UltraDaily] Boss routing failed: {ex.GetType().Name}: {ex.Message}"); + throw; + } + finally + { + C.Logger("[0UltraDaily] Entering finalization block."); + if (restoreGear) + Adv.GearStore(true, true); + CoreBots.Instance.SetOptions(false); + bot.StopSync(); + C.Logger("[0UltraDaily] Finalization complete."); + } + } + + private bool ShouldSkip(string bossName) + { + if (!_questIds.TryGetValue(bossName, out var questId)) + { + C.Logger($"[0UltraDaily] No questId mapping for {bossName}. Running by default."); + return false; + } + + bool localShouldRun = Ultra.ShouldRunQuest(questId, bossName); + bool armyShouldRun = ShouldRunQuestForArmy(questId, bossName, localShouldRun); + C.Logger($"[0UltraDaily] Quest check for {bossName} [{questId}] => local={(localShouldRun ? "RUN" : "SKIP")} | army={(armyShouldRun ? "RUN" : "SKIP")}."); + return !armyShouldRun; + } + + private void RunUltra(Action run, string bossName) + { + C.Logger($"[0UltraDaily] Starting {bossName}."); + var started = DateTime.UtcNow; + try + { + run(); + C.Logger( + $"[0UltraDaily] Completed {bossName} in {(DateTime.UtcNow - started).TotalSeconds:F1}s." + ); + } + catch (Exception ex) + { + C.Logger($"[0UltraDaily] {bossName} threw exception after {(DateTime.UtcNow - started).TotalSeconds:F1}s: {ex.GetType().Name}: {ex.Message}"); + throw; + } + } + + private bool ShouldRunQuestForArmy(int questId, string bossName, bool localShouldRun) + { + string syncPath = Ultra.ResolveSyncPath($"daily_need_{questId}.sync"); + string username = (IScriptInterface.Instance.Player?.Username ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(username)) + username = $"unknown_{Environment.TickCount}"; + + UpsertNeedEntry(syncPath, username, localShouldRun); + + DateTime waitUntil = DateTime.UtcNow.AddSeconds(15); + int seen = 0; + while (!IScriptInterface.Instance.ShouldExit && DateTime.UtcNow < waitUntil) + { + var entries = ReadNeedEntries(syncPath); + seen = entries.Count; + if (seen >= DailyArmySize) + break; + + UpsertNeedEntry(syncPath, username, localShouldRun); + IScriptInterface.Instance.Sleep(300); + } + + var finalEntries = ReadNeedEntries(syncPath); + if (finalEntries.Count < DailyArmySize) + { + C.Logger($"[0UltraDaily] Army need check for {bossName} incomplete ({finalEntries.Count}/{DailyArmySize}); running for safety."); + return true; + } + + return finalEntries.Values.Any(v => v); + } + + private Dictionary ReadNeedEntries(string path) + { + Dictionary latest = new(StringComparer.OrdinalIgnoreCase); + string[] lines = Array.Empty(); + try + { + if (File.Exists(path)) + lines = File.ReadAllLines(path); + } + catch + { + return new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + foreach (string line in lines) + { + string[] parts = line.Split(':'); + if (parts.Length < 3) + continue; + string user = parts[0]; + if (string.IsNullOrWhiteSpace(user)) + continue; + if (!int.TryParse(parts[1], out int needInt)) + continue; + if (!long.TryParse(parts[2], out long ts)) + continue; + if (now - ts > 180) + continue; + + bool need = needInt == 1; + if (!latest.TryGetValue(user, out var current) || ts >= current.ts) + latest[user] = (need, ts); + } + + return latest.ToDictionary(k => k.Key, v => v.Value.need, StringComparer.OrdinalIgnoreCase); + } + + private void UpsertNeedEntry(string path, string username, bool need) + { + for (int attempt = 0; attempt < 12; attempt++) + { + try + { + var existing = ReadNeedEntries(path); + existing[username] = need; + long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var outLines = existing.Select(kv => $"{kv.Key}:{(kv.Value ? 1 : 0)}:{now}").ToArray(); + string? dir = Path.GetDirectoryName(path); + if (!string.IsNullOrWhiteSpace(dir)) + Directory.CreateDirectory(dir); + File.WriteAllLines(path, outLines); + return; + } + catch (IOException) + { + IScriptInterface.Instance.Sleep(120); + } + catch + { + return; + } + } + } + + private IScriptInterface bot => IScriptInterface.Instance; + +} diff --git a/Ultras/0UltraWeekly.cs b/Ultras/0UltraWeekly.cs new file mode 100644 index 000000000..379d03a46 --- /dev/null +++ b/Ultras/0UltraWeekly.cs @@ -0,0 +1,444 @@ +/* +name: UltraWeekly +description: Do all weekly ultras +*/ + +//cs_include Scripts/CoreBots.cs +//cs_include Scripts/CoreAdvanced.cs +//cs_include Scripts/CoreUltra.cs +//cs_include Scripts/Ultras/CoreEngine.cs +//cs_include Scripts/CoreStory.cs +//cs_include Scripts/CoreFarms.cs +//cs_include Scripts/Ultras/ChampionDrakath.cs +//cs_include Scripts/Ultras/UltraDrago.cs +//cs_include Scripts/Ultras/UltraDage.cs +//cs_include Scripts/Ultras/UltraDarkon.cs +//cs_include Scripts/Ultras/UltraNulgath.cs +//cs_include Scripts/Ultras/UltraGramiel.cs +//cs_include Scripts/Ultras/UltraSpeaker.cs +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Skua.Core.Interfaces; +using Skua.Core.Options; + +public class UltraWeekly +{ + public string OptionsStorage = "0UltraWeekly"; + public bool DontPreconfigure = true; + + private CoreAdvanced Adv => _Adv ??= new CoreAdvanced(); + private static CoreAdvanced? _Adv; + private CoreUltra Ultra = new(); + private CoreBots C => CoreBots.Instance; + + public string[] MultiOptions = + { + "Main", + "CoreSettings", + "Bosses", + "Compositions", + }; + + public List CoreSettings = new() + { + new Option("IgnoreNeeded", "Ignore Needed Check", "Run selected ultras even if quest/state check says account may not need it.", false), + new Option("RestoreGear", "Restore Gear", "Restore gear and consumable state at the end of this handler.", false), + }; + + public List Bosses = new() + { + new Option("RunChampionDrakath", "Champion Drakath", "Handle Champion Drakath", true), + new Option("RunUltraDrago", "Ultra Drago", "Handle Ultra Drago", true), + new Option("RunUltraDage", "Ultra Dage", "Handle Ultra Dage", true), + new Option("RunUltraDarkon", "Ultra Darkon", "Handle Ultra Darkon", true), + new Option("RunUltraNulgath", "Ultra Nulgath", "Handle Ultra Nulgath", true), + new Option("RunUltraGramiel", "Ultra Gramiel", "Handle Ultra Gramiel", true), + new Option("RunUltraSpeaker", "Ultra Speaker", "Handle Ultra Speaker", true), + }; + + public List Compositions = new() + { + new Option( + "Champion Drakath Composition", + "Champion Drakath Composition", + "Safe: AP / LR / SC / LOO\n" + + "Fast: CSS/CSH / LR / PCM/OPCM / LOO\n" + + "Cheapest: CS / AP / SC / LOO", + ChampionDrakath.DrakathComp.Safe + ), + new Option( + "Ultra Darkon Composition", + "Ultra Darkon Composition", + "Recommended: LC / LR / SC(or IT) / LOO", + UltraDarkon.DarkonComp.Recommended + ), + new Option( + "Ultra Drago Composition", + "Ultra Drago Composition", + "Fast: CSS / LR / AP / LOO\n" + + "Safe: CAv / LR / AP / LOO\n" + + "F2PFast: KE / LR / AP / LOO", + UltraDrago.DragoComp.Safe + ), + new Option( + "Ultra Dage Composition", + "Ultra Dage Composition", + "BestAvailable: CAv / AP / Best DPS / Best DPS", + UltraDage.DageComp.BestAvailable + ), + new Option( + "Ultra Nulgath Composition", + "Ultra Nulgath Composition", + "Fast: CSS / VDK / LR / LOO\n" + + "F2PFast: DoT / DoT / LR / LOO\n" + + "Common: KE / LR / AP / LOO\n" + + "Balanced: LR / AP / SC / LOO", + UltraNulgath.NulgathComp.Fast + ), + new Option( + "Ultra Gramiel Composition", + "Ultra Gramiel Composition", + "Recommended: SC/IT / AP / LOO / VHL\n" + + "Alternate: SC/IT / LC / LOO / VDK", + UltraGramiel.GramielComp.Recommended + ), + new Option( + "Ultra Speaker Composition", + "Ultra Speaker Composition", + "Fast: AP / LR / QCM / LOO\n" + + "Safe: LR / AP / LOO / VDK", + UltraSpeaker.SpeakerComp.Safe + ), + }; + + private readonly Dictionary _questIds = new() + { + { "Champion Drakath", 8300 }, + { "Ultra Drago", 8397 }, + { "Ultra Dage", 8547 }, + { "Ultra Darkon", 8746 }, + { "Ultra Nulgath", 8692 }, + { "Ultra Gramiel", 10301 }, + { "Ultra Speaker", 9173 }, + }; + private const int WeeklyArmySize = 4; + + public void ScriptMain(IScriptInterface bot) + { + C.Logger("[0UltraWeekly] Bootstrapping weekly ultra handler."); + var ignoreNeeded = bot.Config == null ? false : bot.Config.Get("CoreSettings", "IgnoreNeeded"); + var restoreGear = bot.Config == null ? false : bot.Config.Get("CoreSettings", "RestoreGear"); + + var selectedBosses = new[] + { + (Name: "Champion Drakath", Enabled: bot.Config == null ? true : bot.Config.Get("Bosses", "RunChampionDrakath")), + (Name: "Ultra Drago", Enabled: bot.Config == null ? true : bot.Config.Get("Bosses", "RunUltraDrago")), + (Name: "Ultra Dage", Enabled: bot.Config == null ? true : bot.Config.Get("Bosses", "RunUltraDage")), + (Name: "Ultra Darkon", Enabled: bot.Config == null ? true : bot.Config.Get("Bosses", "RunUltraDarkon")), + (Name: "Ultra Nulgath", Enabled: bot.Config == null ? true : bot.Config.Get("Bosses", "RunUltraNulgath")), + (Name: "Ultra Gramiel", Enabled: bot.Config == null ? true : bot.Config.Get("Bosses", "RunUltraGramiel")), + (Name: "Ultra Speaker", Enabled: bot.Config == null ? true : bot.Config.Get("Bosses", "RunUltraSpeaker")), + }; + var drakathComp = bot.Config == null + ? ChampionDrakath.DrakathComp.Safe + : bot.Config.Get("Compositions", "Champion Drakath Composition"); + var darkonComp = bot.Config == null + ? UltraDarkon.DarkonComp.Recommended + : bot.Config.Get("Compositions", "Ultra Darkon Composition"); + var dragoComp = bot.Config == null + ? UltraDrago.DragoComp.Safe + : bot.Config.Get("Compositions", "Ultra Drago Composition"); + var dageComp = bot.Config == null + ? UltraDage.DageComp.BestAvailable + : bot.Config.Get("Compositions", "Ultra Dage Composition"); + var nulgathComp = bot.Config == null + ? UltraNulgath.NulgathComp.Fast + : bot.Config.Get("Compositions", "Ultra Nulgath Composition"); + var gramielComp = bot.Config == null + ? UltraGramiel.GramielComp.Recommended + : bot.Config.Get("Compositions", "Ultra Gramiel Composition"); + var speakerComp = bot.Config == null + ? UltraSpeaker.SpeakerComp.Safe + : bot.Config.Get("Compositions", "Ultra Speaker Composition"); + + var runChampionDrakath = selectedBosses.First(x => x.Name == "Champion Drakath").Enabled; + var runDrago = selectedBosses.First(x => x.Name == "Ultra Drago").Enabled; + var runDage = selectedBosses.First(x => x.Name == "Ultra Dage").Enabled; + var runDarkon = selectedBosses.First(x => x.Name == "Ultra Darkon").Enabled; + var runNulgath = selectedBosses.First(x => x.Name == "Ultra Nulgath").Enabled; + var runGramiel = selectedBosses.First(x => x.Name == "Ultra Gramiel").Enabled; + var runSpeaker = selectedBosses.First(x => x.Name == "Ultra Speaker").Enabled; + + C.Logger( + $"[0UltraWeekly] Selected flow => IgnoreNeeded={ignoreNeeded}, RestoreGear={restoreGear}" + ); + C.Logger( + $"[0UltraWeekly] Boss flags => ChampionDrakath={runChampionDrakath}, Drago={runDrago}, Dage={runDage}, Nulgath={runNulgath}, Darkon={runDarkon}, Gramiel={runGramiel}, Speaker={runSpeaker}" + ); + + CoreBots.Instance.SetOptions(); + Adv.GearStore(); + C.Logger("[0UltraWeekly] Core options captured and initial gear stored."); + + try + { + if (selectedBosses.All(x => !x.Item2)) + { + C.Logger("[0UltraWeekly] No ultra bosses enabled; nothing to do."); + return; + } + + C.Logger("[0UltraWeekly] Starting boss routing loop."); + + if (runChampionDrakath) + { + C.Logger("[0UltraWeekly] Boss enabled: Champion Drakath"); + if (!ignoreNeeded && ShouldSkip("Champion Drakath")) + C.Logger("[0UltraWeekly] Skip Champion Drakath (need check says not required)."); + else + RunUltra(() => new ChampionDrakath().Run(comp: drakathComp, equipBestGear: true, doEnhancements: true, useLifeSteal: true), "Champion Drakath"); + + C.Logger("[0UltraWeekly] Boss routing complete: Champion Drakath"); + } + else + { + C.Logger("[0UltraWeekly] Boss skipped by config: Champion Drakath"); + } + + if (runDrago) + { + C.Logger("[0UltraWeekly] Boss enabled: Ultra Drago"); + if (!ignoreNeeded && ShouldSkip("Ultra Drago")) + C.Logger("[0UltraWeekly] Skip Ultra Drago (need check says not required)."); + else + RunUltra(() => new UltraDrago().Run(comp: dragoComp, doEnhancements: true), "Ultra Drago"); + + C.Logger("[0UltraWeekly] Boss routing complete: Ultra Drago"); + } + else + { + C.Logger("[0UltraWeekly] Boss skipped by config: Ultra Drago"); + } + + if (runDage) + { + C.Logger("[0UltraWeekly] Boss enabled: Ultra Dage"); + if (!ignoreNeeded && ShouldSkip("Ultra Dage")) + C.Logger("[0UltraWeekly] Skip Ultra Dage (need check says not required)."); + else + RunUltra(() => new UltraDage().Run(comp: dageComp, doEnhancements: true), "Ultra Dage"); + + C.Logger("[0UltraWeekly] Boss routing complete: Ultra Dage"); + } + else + { + C.Logger("[0UltraWeekly] Boss skipped by config: Ultra Dage"); + } + + if (runNulgath) + { + C.Logger("[0UltraWeekly] Boss enabled: Ultra Nulgath"); + if (!ignoreNeeded && ShouldSkip("Ultra Nulgath")) + C.Logger("[0UltraWeekly] Skip Ultra Nulgath (need check says not required)."); + else + RunUltra(() => new UltraNulgath().Run(comp: nulgathComp, equipBestGear: true, doEnhancements: true, useLifeSteal: true), "Ultra Nulgath"); + + C.Logger("[0UltraWeekly] Boss routing complete: Ultra Nulgath"); + } + else + { + C.Logger("[0UltraWeekly] Boss skipped by config: Ultra Nulgath"); + } + + if (runDarkon) + { + C.Logger("[0UltraWeekly] Boss enabled: Ultra Darkon"); + if (!ignoreNeeded && ShouldSkip("Ultra Darkon")) + C.Logger("[0UltraWeekly] Skip Ultra Darkon (need check says not required)."); + else + RunUltra(() => new UltraDarkon().Run(comp: darkonComp, equipBestGear: true, doEnhancements: true, useLifeSteal: true), "Ultra Darkon"); + + C.Logger("[0UltraWeekly] Boss routing complete: Ultra Darkon"); + } + else + { + C.Logger("[0UltraWeekly] Boss skipped by config: Ultra Darkon"); + } + + if (runGramiel) + { + C.Logger("[0UltraWeekly] Boss enabled: Ultra Gramiel"); + if (!ignoreNeeded && ShouldSkip("Ultra Gramiel")) + C.Logger("[0UltraWeekly] Skip Ultra Gramiel (need check says not required)."); + else + RunUltra(() => new UltraGramiel().Run(comp: gramielComp, doEnhancements: true), "Ultra Gramiel"); + + C.Logger("[0UltraWeekly] Boss routing complete: Ultra Gramiel"); + } + else + { + C.Logger("[0UltraWeekly] Boss skipped by config: Ultra Gramiel"); + } + + if (runSpeaker) + { + C.Logger("[0UltraWeekly] Boss enabled: Ultra Speaker"); + if (!ignoreNeeded && ShouldSkip("Ultra Speaker")) + C.Logger("[0UltraWeekly] Skip Ultra Speaker (need check says not required)."); + else + RunUltra(() => new UltraSpeaker().Run(comp: speakerComp, doEnhancements: true), "Ultra Speaker"); + + C.Logger("[0UltraWeekly] Boss routing complete: Ultra Speaker"); + } + else + { + C.Logger("[0UltraWeekly] Boss skipped by config: Ultra Speaker"); + } + } + catch (Exception ex) + { + C.Logger($"[0UltraWeekly] Boss routing failed: {ex.GetType().Name}: {ex.Message}"); + throw; + } + finally + { + C.Logger("[0UltraWeekly] Entering finalization block."); + if (restoreGear) + Adv.GearStore(true, true); + CoreBots.Instance.SetOptions(false); + bot.StopSync(); + C.Logger("[0UltraWeekly] Finalization complete."); + } + } + + private bool ShouldSkip(string bossName) + { + if (!_questIds.TryGetValue(bossName, out var questId)) + { + C.Logger($"[0UltraWeekly] No questId mapping for {bossName}. Running by default."); + return false; + } + + bool localShouldRun = Ultra.ShouldRunQuest(questId, bossName); + bool armyShouldRun = ShouldRunQuestForArmy(questId, bossName, localShouldRun); + C.Logger($"[0UltraWeekly] Quest check for {bossName} [{questId}] => local={(localShouldRun ? "RUN" : "SKIP")} | army={(armyShouldRun ? "RUN" : "SKIP")}."); + return !armyShouldRun; + } + + private void RunUltra(Action run, string bossName) + { + C.Logger($"[0UltraWeekly] Starting {bossName}."); + var started = DateTime.UtcNow; + try + { + run(); + C.Logger( + $"[0UltraWeekly] Completed {bossName} in {(DateTime.UtcNow - started).TotalSeconds:F1}s." + ); + } + catch (Exception ex) + { + C.Logger( + $"[0UltraWeekly] {bossName} threw exception after {(DateTime.UtcNow - started).TotalSeconds:F1}s: {ex.GetType().Name}: {ex.Message}" + ); + throw; + } + } + + private bool ShouldRunQuestForArmy(int questId, string bossName, bool localShouldRun) + { + string syncPath = Ultra.ResolveSyncPath($"weekly_need_{questId}.sync"); + string username = (IScriptInterface.Instance.Player?.Username ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(username)) + username = $"unknown_{Environment.TickCount}"; + + UpsertNeedEntry(syncPath, username, localShouldRun); + + DateTime waitUntil = DateTime.UtcNow.AddSeconds(15); + while (!IScriptInterface.Instance.ShouldExit && DateTime.UtcNow < waitUntil) + { + var entries = ReadNeedEntries(syncPath); + if (entries.Count >= WeeklyArmySize) + break; + + UpsertNeedEntry(syncPath, username, localShouldRun); + IScriptInterface.Instance.Sleep(300); + } + + var finalEntries = ReadNeedEntries(syncPath); + if (finalEntries.Count < WeeklyArmySize) + { + C.Logger($"[0UltraWeekly] Army need check for {bossName} incomplete ({finalEntries.Count}/{WeeklyArmySize}); running for safety."); + return true; + } + + return finalEntries.Values.Any(v => v); + } + + private Dictionary ReadNeedEntries(string path) + { + Dictionary latest = new(StringComparer.OrdinalIgnoreCase); + string[] lines = Array.Empty(); + try + { + if (File.Exists(path)) + lines = File.ReadAllLines(path); + } + catch + { + return new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + foreach (string line in lines) + { + string[] parts = line.Split(':'); + if (parts.Length < 3) + continue; + string user = parts[0]; + if (string.IsNullOrWhiteSpace(user)) + continue; + if (!int.TryParse(parts[1], out int needInt)) + continue; + if (!long.TryParse(parts[2], out long ts)) + continue; + if (now - ts > 180) + continue; + + bool need = needInt == 1; + if (!latest.TryGetValue(user, out var current) || ts >= current.ts) + latest[user] = (need, ts); + } + + return latest.ToDictionary(k => k.Key, v => v.Value.need, StringComparer.OrdinalIgnoreCase); + } + + private void UpsertNeedEntry(string path, string username, bool need) + { + for (int attempt = 0; attempt < 12; attempt++) + { + try + { + var existing = ReadNeedEntries(path); + existing[username] = need; + long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var outLines = existing.Select(kv => $"{kv.Key}:{(kv.Value ? 1 : 0)}:{now}").ToArray(); + string? dir = Path.GetDirectoryName(path); + if (!string.IsNullOrWhiteSpace(dir)) + Directory.CreateDirectory(dir); + File.WriteAllLines(path, outLines); + return; + } + catch (IOException) + { + IScriptInterface.Instance.Sleep(120); + } + catch + { + return; + } + } + } +} diff --git a/Ultras/ChampionDrakath.cs b/Ultras/ChampionDrakath.cs index 88ba1c7ef..7b12f607f 100644 --- a/Ultras/ChampionDrakath.cs +++ b/Ultras/ChampionDrakath.cs @@ -4,15 +4,16 @@ tags: Ultra */ -//cs_include Scripts/Ultras/CoreEngine.cs -//cs_include Scripts/Ultras/CoreUltra.cs -//cs_include Scripts/CoreBots.cs -//cs_include Scripts/CoreFarms.cs -//cs_include Scripts/CoreAdvanced.cs -//cs_include Scripts/CoreStory.cs -using Skua.Core.Interfaces; -using Skua.Core.Models.Auras; -using Skua.Core.Options; +//cs_include Scripts/Ultras/CoreEngine.cs +//cs_include Scripts/Ultras/CoreUltra.cs +//cs_include Scripts/CoreBots.cs +//cs_include Scripts/CoreFarms.cs +//cs_include Scripts/CoreAdvanced.cs +//cs_include Scripts/CoreStory.cs +using Skua.Core.Interfaces; +using Skua.Core.Models.Auras; +using Skua.Core.Options; +using System.Collections.Generic; #region Class & Enhancement Setup @@ -130,59 +131,132 @@ private static CoreAdvanced Adv public bool DontPreconfigure = true; - public string OptionsStorage = "ChampionDrakathTauntSelect"; - string a, b, c, d; - int previousHP = 0; - private static int[] hpThresholds = { 18100000, 16100000, 14100000, 12100000, 10100000, 8100000, 6100000, 4100000 }; - - public List Options = new() + public string OptionsStorage = "ChampionDrakath"; + public string[] MultiOptions = { "Main", "CoreSettings", "ClassOverrides" }; + string a, b, c, d; + string overrideA, overrideB, overrideC, overrideD; + int previousHP = 0; + private static int[] hpThresholds = { 18100000, 16100000, 14100000, 12100000, 10100000, 8100000, 6100000, 4100000 }; + bool EquipBestGear; + bool DoEnhancements; + bool RestoreGear; + int TaunterCount = 2; + DrakathComp ActiveComp = DrakathComp.Safe; + + public List Main = new() { - new Option("a", "Taunter Class (Primary)", "", "ArchPaladin"), - new Option("b", "Taunter Class (Secondary)", "", "Legion Revenant"), - new Option("c", "Taunter Class (Tertiary)", "", "StoneCrusher"), - new Option("d", "Taunter Class (Quaternary)", "", "Lord Of Order"), - new Option("SoloTaunt", "Solo Taunt", "Only primary taunter", false), - new Option("DoEnh", "Do Enhancements", "", true), - new Option("HowManyTaunts", "How many taunters", "", HowManyTaunts.Two), + new Option( + "DoEquipClasses", + "Automatically Equip Classes", + "Auto-equip classes across all 4 clients\n" + + "Safe: AP / LR / SC / LOO\n" + + "Fast: CSS/CSH / LR / PCM/OPCM / LOO\n" + + "Cheapest: CS / AP / SC / LOO\n" + + "Unselected = off (use manual classes below).", + DrakathComp.Safe + ), CoreBots.Instance.SkipOptions, }; - bool SoloTaunt; + public List CoreSettings = new() + { + new Option("EquipBestGear", "Equip Best Gear", "Equip best gear for encounter", true), + new Option("DoEnh", "Do Enhancements", "", true), + new Option("RestoreGear", "Restore Gear", "Restore original gear after the script finishes", false), + new Option("UseLifeSteal", "Use LifeSteal", "Non-taunters equip/restock/use Scroll of Life Steal.", true), + new Option("HowManyTaunts", "How many taunters", "", HowManyTaunts.Two), + }; - public void ScriptMain(IScriptInterface bot) + public List ClassOverrides = new() { - if (Bot.Config != null - && Bot.Config.Options.Contains(C.SkipOptions) - && !Bot.Config.Get(C.SkipOptions)) - Bot.Config.Configure(); - - a = (Bot.Config!.Get("a") ?? string.Empty).Trim(); - b = (Bot.Config.Get("b") ?? string.Empty).Trim(); - c = (Bot.Config.Get("c") ?? string.Empty).Trim(); - d = (Bot.Config.Get("d") ?? string.Empty).Trim(); - SoloTaunt = Bot.Config.Get("SoloTaunt"); - - if ((SoloTaunt && string.IsNullOrEmpty(a)) - || (!SoloTaunt && string.IsNullOrEmpty(a) && string.IsNullOrEmpty(b))) - { - Core.Log("Setup", "Primary taunter required."); - Bot.StopSync(); - return; - } + new Option("a", "Primary Class Override", "Blank = use selected comp default for slot 1.", ""), + new Option("b", "Secondary Class Override", "Blank = use selected comp default for slot 2.", ""), + new Option("c", "Tertiary Class Override", "Blank = use selected comp default for slot 3.", ""), + new Option("d", "Quaternary Class Override", "Blank = use selected comp default for slot 4.", ""), + }; - if (SoloTaunt) - { - b = string.Empty; - c = string.Empty; - d = string.Empty; + bool UseLifeSteal; + + public void ScriptMain(IScriptInterface bot) + { + if (Bot.Config != null + && !Bot.Config.Get("Main", "SkipOption")) + Bot.Config.Configure(); + + ActiveComp = Bot.Config == null ? DrakathComp.Safe : Bot.Config.Get("Main", "DoEquipClasses"); + overrideA = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty)).Trim(); + overrideB = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "b") ?? string.Empty)).Trim(); + overrideC = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "c") ?? string.Empty)).Trim(); + overrideD = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "d") ?? string.Empty)).Trim(); + a = overrideA; + b = overrideB; + c = overrideC; + d = overrideD; + EquipBestGear = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "EquipBestGear"); + DoEnhancements = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "DoEnh"); + UseLifeSteal = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "UseLifeSteal"); + RestoreGear = Bot.Config == null ? false : Bot.Config.Get("CoreSettings", "RestoreGear"); + TaunterCount = Bot.Config == null ? 2 : (int)Bot.Config.Get("CoreSettings", "HowManyTaunts"); + + bool usingComp = ActiveComp != DrakathComp.Unselected; + if (!usingComp && ( + string.IsNullOrEmpty(a) + || (TaunterCount >= 2 && string.IsNullOrEmpty(b)) + || (TaunterCount >= 3 && string.IsNullOrEmpty(c)) + || (TaunterCount >= 4 && string.IsNullOrEmpty(d)) + )) + { + Core.Log("Setup", "Fill taunter class overrides for all enabled taunter slots."); + Bot.StopSync(); + return; } - Core.Boot(); - Prep(); - Fight(); - C.JumpWait(); - C.SetOptions(false); - } + Adv.GearStore(); + + try + { + Run(ActiveComp, EquipBestGear, DoEnhancements, UseLifeSteal, TaunterCount, overrideA, overrideB, overrideC, overrideD); + } + finally + { + if (RestoreGear) + Adv.GearStore(true, true); + C.SetOptions(false); + Bot.StopSync(); + } + } + + public void Run( + DrakathComp comp = DrakathComp.Safe, + bool equipBestGear = true, + bool doEnhancements = true, + bool useLifeSteal = true, + int taunterCount = 2, + string? classAOverride = null, + string? classBOverride = null, + string? classCOverride = null, + string? classDOverride = null + ) + { + ActiveComp = comp; + EquipBestGear = equipBestGear; + DoEnhancements = doEnhancements; + UseLifeSteal = useLifeSteal; + TaunterCount = Math.Max(1, Math.Min(4, taunterCount)); + overrideA = classAOverride?.Trim() ?? string.Empty; + overrideB = classBOverride?.Trim() ?? string.Empty; + overrideC = classCOverride?.Trim() ?? string.Empty; + overrideD = classDOverride?.Trim() ?? string.Empty; + a = overrideA; + b = overrideB; + c = overrideC; + d = overrideD; + + Core.Boot(); + Prep(); + Fight(); + C.JumpWait(); + } bool IsTaunter() { @@ -192,32 +266,100 @@ bool IsTaunter() return false; // Check based on HowManyTaunts setting - int taunterCount = (int)Bot.Config!.Get("HowManyTaunts"); - - if (taunterCount >= 1 && !string.IsNullOrEmpty(a) && currentClass.Contains(a)) - return true; - if (taunterCount >= 2 && !string.IsNullOrEmpty(b) && currentClass.Contains(b)) - return true; - if (taunterCount >= 3 && !string.IsNullOrEmpty(c) && currentClass.Contains(c)) - return true; - if (taunterCount >= 4 && !string.IsNullOrEmpty(d) && currentClass.Contains(d)) - return true; + if (TaunterCount >= 1 && !string.IsNullOrEmpty(a) && currentClass.Contains(a)) + return true; + if (TaunterCount >= 2 && !string.IsNullOrEmpty(b) && currentClass.Contains(b)) + return true; + if (TaunterCount >= 3 && !string.IsNullOrEmpty(c) && currentClass.Contains(c)) + return true; + if (TaunterCount >= 4 && !string.IsNullOrEmpty(d) && currentClass.Contains(d)) + return true; return false; } - void Prep() + void Prep() + { + if (ActiveComp != DrakathComp.Unselected) + ApplyCompAndEquip(ActiveComp, overrideA, overrideB, overrideC, overrideD); + + if (EquipBestGear) + EquipBestDmgGear(); + + if (DoEnhancements) + DoEnhs(); + + Ultra.UseAlchemyPotions( + Ultra.GetBestTonicPotion(), + Ultra.GetBestElixirPotion() + ); + Ultra.BuyAlchemyPotion("Potent Honor Potion"); + Core.EquipConsumable("Potent Honor Potion"); + + if (IsTaunter()) + Ultra.GetScrollOfEnrage(); + else if (UseLifeSteal) + Ultra.GetScrollOfLifeSteal(); + } + + void ApplyCompAndEquip(DrakathComp comp, string aOverride, string bOverride, string cOverride, string dOverride) { - if (Bot.Config!.Get("DoEnh")) - DoEnhs(); + string[][] classes; + switch (comp) + { + case DrakathComp.Safe: + a = string.IsNullOrWhiteSpace(aOverride) ? "ArchPaladin" : aOverride; + b = string.IsNullOrWhiteSpace(bOverride) ? "Legion Revenant" : bOverride; + c = string.IsNullOrWhiteSpace(cOverride) ? (C.CheckInventory("Infinity Titan") ? "Infinity Titan" : "StoneCrusher") : cOverride; + d = string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride; + classes = new[] + { + new[] { a }, + new[] { b }, + new[] { c }, + new[] { d } + }; + break; - Ultra.UseAlchemyPotions( - Ultra.GetBestTonicPotion(), - Ultra.GetBestElixirPotion() - ); + case DrakathComp.Fast: + a = string.IsNullOrWhiteSpace(aOverride) ? "Chrono Shadow" : aOverride; + b = string.IsNullOrWhiteSpace(bOverride) ? "Legion Revenant" : bOverride; + c = string.IsNullOrWhiteSpace(cOverride) ? "Paladin Chronomancer" : cOverride; + d = string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride; + classes = new[] + { + string.IsNullOrWhiteSpace(aOverride) + ? new[] { "Chrono ShadowSlayer", "Chrono ShadowHunter" } + : new[] { aOverride }, + new[] { b }, + string.IsNullOrWhiteSpace(cOverride) + ? new[] { "Paladin Chronomancer", "Obsidian Paladin Chronomancer" } + : new[] { cOverride }, + new[] { d } + }; + break; + + case DrakathComp.Cheapest: + a = string.IsNullOrWhiteSpace(aOverride) ? "Chaos Slayer" : aOverride; + b = string.IsNullOrWhiteSpace(bOverride) ? "ArchPaladin" : bOverride; + c = string.IsNullOrWhiteSpace(cOverride) ? (C.CheckInventory("Infinity Titan") ? "Infinity Titan" : "StoneCrusher") : cOverride; + d = string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride; + classes = new[] + { + string.IsNullOrWhiteSpace(aOverride) + ? new[] { "Chaos Slayer", "Chaos Slayer Berserker", "Chaos Slayer Cleric", "Chaos Slayer Mystic", "Chaos Slayer Thief" } + : new[] { aOverride }, + new[] { b }, + new[] { c }, + new[] { d } + }; + break; + + default: + throw new NotImplementedException(); + } - if (IsTaunter()) - Ultra.GetScrollOfEnrage(); + Ultra.EquipClassSync(classes, 4, "champion_drakath_class.sync", allowDuplicates: true); } void Fight() @@ -241,8 +383,14 @@ void Fight() bool[] tauntFired = new bool[8]; // 18M-4M in 2M chunks previousHP = 0; // Reset at fight start - while (!Bot.ShouldExit) - { + while (!Bot.ShouldExit) + { + if (Bot.Map?.Name != map) + { + Core.Join(map); + Core.ChooseBestCell(boss); + Bot.Player.SetSpawnPoint(); + } // Dead → wait for respawn if (!Bot.Player.Alive) { @@ -257,8 +405,6 @@ void Fight() if (!Bot.Quests.IsDailyComplete(8300)) C.EnsureComplete(8300); else Bot.Log("Daily already Complete"); - if (Bot.Config!.Get("DoEnh")) - Adv.GearStore(true, true); break; } @@ -266,6 +412,10 @@ void Fight() Bot.Sleep(500); + // Non-taunter role: use Scroll of Life Steal + if (UseLifeSteal && !IsTaunter() && Bot.Player.HasTarget && Bot.Player.Target?.HP > 0 && Bot.Skills.CanUseSkill(5)) + Bot.Skills.UseSkill(5); + // Only execute taunt logic if this account is a taunter if (IsTaunter() && Bot.Player.HasTarget @@ -311,6 +461,7 @@ void Fight() if (!Bot.Player.HasTarget) break; + // Taunter role: use Scroll of Enrage to apply Focus. if (Bot.Skills.CanUseSkill(5)) Bot.Skills.UseSkill(5); @@ -336,6 +487,7 @@ void Fight() while (!Bot.ShouldExit) { + // Taunter role: keep using Scroll of Enrage below 2M. if (Bot.Skills.CanUseSkill(5)) Bot.Skills.UseSkill(5); Bot.Sleep(500); @@ -356,7 +508,7 @@ void Fight() C.JumpWait(); } - void DoEnhs() + void DoEnhs() { string className = Bot.Player!.CurrentClass?.Name ?? string.Empty; if (string.IsNullOrEmpty(className)) @@ -499,8 +651,22 @@ void DoEnhs() cSpecial: CapeSpecial.Lament // Cape ); break; - } - } + } + } + + void EquipBestDmgGear() + { + C.EquipBestItemsForMeta( + new Dictionary + { + { "Weapon", new[] { "dmgAll", "dmg", "damage" } }, + { "Armor", new[] { "dmgAll", "dmg", "damage" } }, + { "Helm", new[] { "dmgAll", "dmg", "damage" } }, + { "Cape", new[] { "dmgAll", "dmg", "damage" } }, + { "Pet", new[] { "dmgAll", "dmg", "damage" } }, + } + ); + } enum HowManyTaunts @@ -510,4 +676,12 @@ enum HowManyTaunts Three = 3, Four = 4 } + + public enum DrakathComp + { + Unselected, + Safe, + Fast, + Cheapest + } } diff --git a/Ultras/CoreUltra.cs b/Ultras/CoreUltra.cs index 4e62935d5..ffb083970 100644 --- a/Ultras/CoreUltra.cs +++ b/Ultras/CoreUltra.cs @@ -875,6 +875,8 @@ public string EquipClassSync( // Wait for all members to register const int staleThreshold = 600; int lastCount = -1; + Stopwatch registerTimer = Stopwatch.StartNew(); + const int registerTimeoutMs = 120000; while (!Bot!.ShouldExit) { @@ -902,6 +904,18 @@ public string EquipClassSync( if (validCount >= armySize) break; + if (registerTimer.ElapsedMilliseconds >= registerTimeoutMs) + { + C.Logger( + $"[EquipClassSync] Registration timeout ({validCount}/{armySize}). Stopping all clients.", + "EquipClassSync", + true, + true + ); + Bot.StopSync(); + return string.Empty; + } + // Re-poke to keep entry fresh UpdateEntry(syncFile, username, payload); Bot?.Sleep(500); @@ -917,6 +931,8 @@ public string EquipClassSync( Bot?.Log($"[EquipClassSync] {username} marked READY, waiting for all..."); // Wait for all clients to be READY (ensures no more class-list writes) + Stopwatch readyTimer = Stopwatch.StartNew(); + const int readyTimeoutMs = 120000; while (!Bot!.ShouldExit) { string[] lines = ReadLines(syncFile); @@ -943,6 +959,18 @@ public string EquipClassSync( break; } + if (readyTimer.ElapsedMilliseconds >= readyTimeoutMs) + { + C.Logger( + $"[EquipClassSync] READY timeout ({readyCount}/{armySize}). Stopping all clients.", + "EquipClassSync", + true, + true + ); + Bot.StopSync(); + return string.Empty; + } + Bot?.Sleep(300); } @@ -986,8 +1014,7 @@ public string EquipClassSync( Bot?.Log($"[EquipClassSync] {playerClasses.Count} player(s) registered. Assigning classes..."); - // Pre-count how many times each class appears across all slot definitions - // This determines the max allowed duplicates for each class + // Pre-count how many times each class appears across all slot definitions. Dictionary classMaxCount = new(StringComparer.OrdinalIgnoreCase); if (allowDuplicates) { @@ -1002,28 +1029,22 @@ public string EquipClassSync( } } - // Deterministic greedy assignment - // Alpha-sort players so every client computes the identical result. + // Alpha-sort players so every client computes the same assignment. List sortedPlayers = playerClasses .Keys.OrderBy(p => p, StringComparer.OrdinalIgnoreCase) .ToList(); Dictionary assignments = new(StringComparer.OrdinalIgnoreCase); - HashSet assignedPlayers = new(StringComparer.OrdinalIgnoreCase); + bool[] usedPlayers = new bool[sortedPlayers.Count]; Dictionary classUsedCount = new(StringComparer.OrdinalIgnoreCase); - HashSet filledSlots = new(); - for (int s = 0; s < classSlots.Length; s++) + bool CanAssignAllSlots(int slotIndex) { - if (filledSlots.Contains(s)) - continue; - - bool filled = false; + if (slotIndex >= classSlots.Length) + return true; - // Try each accepted class in preference order - foreach (string acceptedClass in classSlots[s]) + foreach (string acceptedClass in classSlots[slotIndex]) { - // Check if this class can still be used int used = classUsedCount.GetValueOrDefault(acceptedClass, 0); if (allowDuplicates) { @@ -1031,60 +1052,66 @@ public string EquipClassSync( if (used >= max) continue; } - else + else if (used >= 1) + continue; + + for (int p = 0; p < sortedPlayers.Count; p++) { - // No duplicates: each class used at most once - if (used >= 1) + if (usedPlayers[p]) continue; - } - List candidates = sortedPlayers - .Where(p => - !assignedPlayers.Contains(p) - && playerClasses[p].Any(c => - c.Equals(acceptedClass, StringComparison.OrdinalIgnoreCase) - ) - ) - .ToList(); + string player = sortedPlayers[p]; + if (!playerClasses[player].Any(c => c.Equals(acceptedClass, StringComparison.OrdinalIgnoreCase))) + continue; - if (candidates.Count == 0) - continue; + usedPlayers[p] = true; + classUsedCount[acceptedClass] = used + 1; + assignments[player] = acceptedClass; - // Most-constrained candidate first (fewest open slots it can fill), - // tiebreak alphabetical. - string best = candidates - .OrderBy(p => - { - int canFill = 0; - for (int s2 = 0; s2 < classSlots.Length; s2++) - { - if (filledSlots.Contains(s2)) - continue; - if ( - classSlots[s2].Any(c => - playerClasses[p].Any(pc => - pc.Equals(c, StringComparison.OrdinalIgnoreCase) - ) - ) - ) - canFill++; - } - return canFill; - }) - .ThenBy(p => p, StringComparer.OrdinalIgnoreCase) - .First(); - - assignments[best] = acceptedClass; - assignedPlayers.Add(best); - classUsedCount[acceptedClass] = used + 1; - filledSlots.Add(s); - Bot?.Log($"[EquipClassSync] Slot {s} > {best} ({acceptedClass})"); - filled = true; - break; + if (CanAssignAllSlots(slotIndex + 1)) + return true; + + assignments.Remove(player); + if (used == 0) + classUsedCount.Remove(acceptedClass); + else + classUsedCount[acceptedClass] = used; + usedPlayers[p] = false; + } } - if (!filled) - Bot?.Log($"[EquipClassSync] WARNING: No candidate for slot {s} ({string.Join("/", classSlots[s])})!"); + return false; + } + + if (!CanAssignAllSlots(0)) + { + string slotSummary = string.Join( + " | ", + classSlots.Select((slot, idx) => $"S{idx}:{string.Join("/", slot)}") + ); + C.Logger( + $"[EquipClassSync] Composition impossible for current group. Needed slots: {slotSummary}. Stopping all clients.", + "EquipClassSync", + true, + true + ); + Bot?.StopSync(); + return string.Empty; + } + + HashSet loggedPlayers = new(StringComparer.OrdinalIgnoreCase); + for (int s = 0; s < classSlots.Length; s++) + { + string? assignedPlayer = sortedPlayers.FirstOrDefault(p => + !loggedPlayers.Contains(p) + && assignments.TryGetValue(p, out string? cls) + && classSlots[s].Any(x => x.Equals(cls, StringComparison.OrdinalIgnoreCase)) + ); + if (!string.IsNullOrEmpty(assignedPlayer)) + { + loggedPlayers.Add(assignedPlayer); + Bot?.Log($"[EquipClassSync] Slot {s} > {assignedPlayer} ({assignments[assignedPlayer]})"); + } } // Find this client's assignment @@ -1151,6 +1178,41 @@ public void GetScrollOfEnrage() Core.EquipEnrage(); } + public void GetScrollOfLifeSteal(int minStock = 10, int restockTo = 50) + { + const string scroll = "Scroll of Life Steal"; + const string shopMap = "terminatemple"; + const int shopId = 2328; + + C.Unbank(scroll); + + int qty = Bot.Inventory.GetQuantity(scroll); + + if (qty < minStock) + { + C.Logger($"LifeSteal: restocking ({qty}/{minStock})..."); + bool buySuccess = Core.BuyItem( + itemKey: scroll, + shopId: shopId, + map: shopMap, + quantity: restockTo + ); + qty = Bot.Inventory.GetQuantity(scroll); + if (!buySuccess && qty < minStock) + C.Logger("LifeSteal: restock failed."); + } + + if (qty > 0) + { + Core.EquipConsumable(scroll); + C.Logger($"LifeSteal: equipped ({qty})."); + } + else + { + C.Logger("LifeSteal: unavailable."); + } + } + public void UseTaunt() { // Dead → wait for respawn @@ -1688,6 +1750,36 @@ public void ArmyHandler( } } + public bool ShouldRunQuest(int questId, string context = "boss", bool log = true) + { + if (questId <= 0) + return false; + + if (Bot.Quests.IsDailyComplete(questId)) + { + if (log) + C.Logger($"[{context}] quest {questId} is already complete today."); + return false; + } + + try + { + if (Core.IsAvailable(questId)) + return true; + } + catch (Exception ex) + { + if (log) + C.Logger($"[{context}] IsAvailable check failed for quest {questId}: {ex.Message}"); + } + + bool completed = C.isCompletedBefore(questId, log: false); + if (log) + C.Logger($"[{context}] Quest {questId} available check fell back to completion check => need={(!completed).ToString().ToLowerInvariant()}."); + + return !completed; + } + public enum CheckType { Bool = 1, diff --git a/Ultras/UltraAvatarTyndarius.cs b/Ultras/UltraAvatarTyndarius.cs index 4aeb5e997..5f335d1d1 100644 --- a/Ultras/UltraAvatarTyndarius.cs +++ b/Ultras/UltraAvatarTyndarius.cs @@ -2,6 +2,15 @@ name: UltraAvatarTyndarius description: Ultra Avatar Tyndarius helper with taunter rotation and orb priority. tags: Ultra + +Fight notes: +- Composition order is [slot 1-4]: Safe = CAv / LR / AP / LOO, Fast = CSS / LR / AP / LOO, F2PFast = KE / LR / AP / LOO. +- Default composition is set to Safe when available. +- Fixed taunt-role assignment is based on the resolved comp slots: + - Slot 2: Ball 1 Taunt + - Slot 3: Must-Taunt Tyndarius + - Slot 4: Focus Tyndarius +- Slot 1 remains Ball 2 support/killer. */ //cs_include Scripts/Ultras/CoreEngine.cs @@ -10,13 +19,9 @@ //cs_include Scripts/CoreFarms.cs //cs_include Scripts/CoreAdvanced.cs //cs_include Scripts/CoreStory.cs -using System.ComponentModel; -using System.Reflection; using Skua.Core.Interfaces; using Skua.Core.Models.Items; -using Skua.Core.Models.Monsters; using Skua.Core.Options; -using Skua.Core.Threading; /* ============================================================================ @@ -72,16 +77,34 @@ private static CoreStory Story public CoreEngine Core = new(); public CoreUltra Ultra = new(); - private string NormalizeString(string input) => (input ?? "").Trim().ToLower(); - bool isBall2killer; bool isBall1Taunter; bool isMustTauntTyn; bool isFocusTyn; - - public bool DontPreconfigure = true; - public string OptionsStorage = "UltraAvatarTyndarius3"; - public List Options = new() + string tauntSlot1 = "Chaos Avenger"; + string tauntSlot2 = "Legion Revenant"; + string tauntSlot3 = "ArchPaladin"; + string tauntSlot4 = "Lord Of Order"; + + public bool DontPreconfigure = true; + public string OptionsStorage = "UltraAvatarTyndarius3"; + + string a, + b, + c, + d, + overrideA, + overrideB, + overrideC, + overrideD; + bool UseLifeSteal; + bool EquipBestGear; + bool DoEnhancements; + bool RestoreGear; + TyndariusComp ActiveComp = TyndariusComp.Safe; + public string[] MultiOptions = { "Main", "CoreSettings", "ClassOverrides" }; + + public List Main = new() { new Option( "DoEquipClasses", @@ -91,97 +114,159 @@ private static CoreStory Story + "Fast: CSS / LR / AP / LOO\n" + "F2PFast: KE / LR / AP / LOO\n" + "Unselected = off (use whatever classes you already have equipped).", - TyndariusComp.Unselected - ), - // Ball 1 Taunter selection - new Option( - "Ball1Taunter", - "Ball 1 Taunter", - "Select which class should taunt Ball 1.", - Ball1Taunter.LegionRevenant - ), - // Ball 2 Taunter selection - new Option( - "Ball2killer", - "Ball 2 killer", - "Select which class should kill Ball 2.", - Ball2killer.ChaosAvenger - ), - // Must Taunt Tyndarius selection - new Option( - "MustTauntTyndarius", - "Must Taunt Tyndarius", - "Select which class must taunt Tyndarius.", - MustTauntTyndarius.ArchPaladin - ), - // Focus Tyndarius selection - new Option( - "DebuffTyndarius", - "Focus Tyndarius", - "Select which class should focus Tyndarius.", - DebuffTyndarius.LordOfOrder + TyndariusComp.Safe ), - new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), CoreBots.Instance.SkipOptions, }; - public void ScriptMain(IScriptInterface bot) + public List CoreSettings = new() { - C.Join("whitemap"); - if ( - Bot.Config != null - && Bot.Config.Options.Contains(C.SkipOptions) - && !Bot.Config.Get(C.SkipOptions) - ) - Bot.Config.Configure(); - - if ( - NormalizeString(GetDescription(Bot.Config!.Get("Ball1Taunter"))) == "Dragon of Time" - && NormalizeString(GetDescription(Bot.Config!.Get("Ball2killer"))) == "Dragon of Time" - ) - C.Logger("Ball1Taunter & Ball2Killer are set to Dragon of Time, choose something else", "Fix This", true, true); - - isBall1Taunter = - NormalizeString(Bot.Player.CurrentClass!.Name) - == NormalizeString(GetDescription(Bot.Config!.Get("Ball1Taunter"))); - isBall2killer = - NormalizeString(Bot.Player.CurrentClass.Name) - == NormalizeString(GetDescription(Bot.Config!.Get("Ball2killer"))); - isMustTauntTyn = - NormalizeString(Bot.Player.CurrentClass.Name) - == NormalizeString(GetDescription(Bot.Config!.Get("MustTauntTyndarius"))); - isFocusTyn = - NormalizeString(Bot.Player.CurrentClass.Name) - == NormalizeString(GetDescription(Bot.Config!.Get("DebuffTyndarius"))); - - Core.Boot(); - Prep(); - Fight(); - Bot.StopSync(); + new Option("EquipBestGear", "Equip Best Gear", "Equip best gear for encounter", true), + new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), + new Option("RestoreGear", "Restore Gear", "Restore original gear after the script finishes", false), + new Option("UseLifeSteal", "Use LifeSteal", "Non-taunters equip/restock/use Scroll of Life Steal.", true), + }; + + public List ClassOverrides = new() + { + new Option("a", "Primary Class Override", "Blank = use selected comp default for slot 1.", ""), + new Option("b", "Secondary Class Override", "Blank = use selected comp default for slot 2.", ""), + new Option("c", "Tertiary Class Override", "Blank = use selected comp default for slot 3.", ""), + new Option("d", "Quaternary Class Override", "Blank = use selected comp default for slot 4.", ""), + }; + + public void ScriptMain(IScriptInterface bot) + { + C.Join("whitemap"); + if (Bot.Config != null && !Bot.Config.Get("Main", "SkipOption")) + Bot.Config.Configure(); + + UseLifeSteal = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "UseLifeSteal"); + EquipBestGear = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "EquipBestGear"); + DoEnhancements = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "DoEnh"); + RestoreGear = Bot.Config == null ? false : Bot.Config.Get("CoreSettings", "RestoreGear"); + ActiveComp = Bot.Config == null ? TyndariusComp.Safe : Bot.Config.Get("Main", "DoEquipClasses"); + + overrideA = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty)).Trim(); + overrideB = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "b") ?? string.Empty)).Trim(); + overrideC = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "c") ?? string.Empty)).Trim(); + overrideD = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "d") ?? string.Empty)).Trim(); + + Adv.GearStore(); + + try + { + Run(ActiveComp, EquipBestGear, DoEnhancements, UseLifeSteal, overrideA, overrideB, overrideC, overrideD); + } + finally + { + if (RestoreGear) + Adv.GearStore(true, true); + C.SetOptions(false); + Bot.StopSync(); + } + } + + public void Run( + TyndariusComp comp = TyndariusComp.Safe, + bool equipBestGear = true, + bool doEnhancements = true, + bool useLifeSteal = true, + string? classAOverride = null, + string? classBOverride = null, + string? classCOverride = null, + string? classDOverride = null + ) + { + ActiveComp = comp; + EquipBestGear = equipBestGear; + DoEnhancements = doEnhancements; + UseLifeSteal = useLifeSteal; + overrideA = classAOverride?.Trim() ?? string.Empty; + overrideB = classBOverride?.Trim() ?? string.Empty; + overrideC = classCOverride?.Trim() ?? string.Empty; + overrideD = classDOverride?.Trim() ?? string.Empty; + a = overrideA; + b = overrideB; + c = overrideC; + d = overrideD; + + Core.Boot(); + Prep(); + Fight(); } void Prep() { // Sync-equip classes if a comp is selected - TyndariusComp comp = Bot.Config!.Get("DoEquipClasses"); - if (comp != TyndariusComp.Unselected) - { - string[] classes = comp switch + ApplyCompAndEquip(ActiveComp, overrideA, overrideB, overrideC, overrideD); + + if (EquipBestGear) + EquipBestDmgGear(); + + if (DoEnhancements) + DoEnh(); + Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); + Ultra.BuyAlchemyPotion("Potent Honor Potion"); + Core.EquipConsumable("Potent Honor Potion"); + if (isBall1Taunter || isMustTauntTyn || isFocusTyn) + Ultra.GetScrollOfEnrage(); + else if (UseLifeSteal) + Ultra.GetScrollOfLifeSteal(); + } + + void EquipBestDmgGear() + { + C.EquipBestItemsForMeta( + new Dictionary { - TyndariusComp.Safe => new[] { "Chaos Avenger", "Legion Revenant", "ArchPaladin", "Lord Of Order" }, - TyndariusComp.Fast => new[] { "Chrono ShadowSlayer", "Legion Revenant", "ArchPaladin", "Lord Of Order" }, - TyndariusComp.F2PFast => new[] { "King's Echo", "Legion Revenant", "ArchPaladin", "Lord Of Order" }, - _ => throw new InvalidOperationException($"Unhandled TyndariusComp value: {comp}"), - }; + { "Weapon", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Armor", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Helm", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Cape", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Pet", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + } + ); + } - Ultra.EquipClassSync(classes, 4, "tyndarius_class.sync"); - } + void ApplyCompAndEquip(TyndariusComp comp, string aOverride, string bOverride, string cOverride, string dOverride) + { + if (comp == TyndariusComp.Unselected) + return; - if (Bot.Config!.Get("DoEnh")) - DoEnh(); - Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); - if (isBall1Taunter || isMustTauntTyn || isFocusTyn) - Ultra.GetScrollOfEnrage(); + string[] classes = comp switch + { + TyndariusComp.Safe => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "Chaos Avenger" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "Legion Revenant" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "ArchPaladin" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride, + }, + TyndariusComp.Fast => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "Chrono ShadowSlayer" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "Legion Revenant" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "ArchPaladin" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride, + }, + TyndariusComp.F2PFast => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "King's Echo" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "Legion Revenant" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "ArchPaladin" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride, + }, + _ => throw new InvalidOperationException($"Unhandled TyndariusComp value: {comp}"), + }; + + tauntSlot1 = classes[0]; + tauntSlot2 = classes[1]; + tauntSlot3 = classes[2]; + tauntSlot4 = classes[3]; + + Ultra.EquipClassSync(classes, 4, "tyndarius_class.sync"); + SetRoleAllocations(); } void Fight() @@ -209,14 +294,15 @@ void Fight() continue; } + if (UseLifeSteal && Bot.Skills.CanUseSkill(5)) + Bot.Skills.UseSkill(5); + if (Ultra.CheckArmyProgressBool(() => Bot.TempInv.Contains("Ultra Avatar Tyndarius Defeated", 1), syncPath)) { C.Jump("Enter", "Spawn"); C.Logger("All players finished farm."); C.EnsureComplete(8245); - if (Bot.Config!.Get("DoEnh")) - Adv.GearStore(true, true); break; } if (Bot.Map.Name != map) @@ -251,8 +337,6 @@ void Fight() { Bot.Combat.Attack(1); } - if (Bot.Skills.CanUseSkill(5) && !Bot.Target.Auras.Any(a => a.Name == "Focus")) - Bot.Skills.UseSkill(5); Bot.Sleep(500); } if (isBall2killer) @@ -278,8 +362,6 @@ void Fight() { Bot.Combat.Attack(2); Bot.Sleep(500); - if (Bot.Skills.CanUseSkill(5) && !Bot.Target.Auras.Any(a => a.Name == "Focus")) - Bot.Skills.UseSkill(5); } // ====================================================== // FOCUS TYN (semi-taunt) @@ -294,14 +376,12 @@ void Fight() } } - public static string GetDescription(Enum value) + void SetRoleAllocations() { - FieldInfo? field = value.GetType().GetField(value.ToString()); - DescriptionAttribute? attribute = - field?.GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault() - as DescriptionAttribute; - - return attribute?.Description ?? value.ToString(); + isBall1Taunter = Core.HasClassEquipped(tauntSlot2); + isBall2killer = Core.HasClassEquipped(tauntSlot1); + isMustTauntTyn = Core.HasClassEquipped(tauntSlot3); + isFocusTyn = Core.HasClassEquipped(tauntSlot4); } void DoEnh() @@ -406,72 +486,4 @@ public enum TyndariusComp F2PFast, } - public enum Ball2killer - { - // In order of fast > safe > f2p fast > other - [Description("Chrono ShadowSlayer")] - ChronoShadowSlayer, - - [Description("Chrono ShadowHunter")] - ChronoShadowHunter, - - [Description("Chaos Avenger")] - ChaosAvenger, - - [Description("King's Echo")] - KingsEcho, - - [Description("StoneCrusher")] - StoneCrusher, - - [Description("Arcana Invoker")] - ArcanaInvoker, - - [Description("Dragon of Time")] - DragonofTime, - - [Description("Current Class | Ball2killer")] - Ball2killer_CurrentClass, - } - - public enum Ball1Taunter - { - // In order of fast > safe > f2p fast > other - [Description("Legion Revenant")] - LegionRevenant, - - [Description("Lich")] - Lich, - - [Description("Current Class | Ball1Taunter")] - Ball1Taunter_Current, - } - - public enum DebuffTyndarius - { - // In order of fast > safe > f2p fast > other - [Description("Lord Of Order")] - LordOfOrder, - - [Description("Dragon of Time")] - DragonofTime, - - [Description("Current Class | DebuffTyndarius")] - DebuffTyndarius_Current, - } - - public enum MustTauntTyndarius - { - // In order of fast > safe > f2p fast > other - [Description("ArchPaladin")] - ArchPaladin, - - [Description("Verus DoomKnight")] - VerusDoomknight, - - [Description("Current Class | MustTauntTyndarius")] - MustTauntTyndarius_Current, - } - - } diff --git a/Ultras/UltraDage.cs b/Ultras/UltraDage.cs index 900e50fe8..d588878b4 100644 --- a/Ultras/UltraDage.cs +++ b/Ultras/UltraDage.cs @@ -2,6 +2,10 @@ name: UltraDage description: Two-taunter strategy for Ultra Dage with aura-based taunting and army synchronization. tags: Ultra + +Fight notes: +- Composition order is [slot 1-4]: BestAvailable = CAv / AP / DPS / DPS. +- Fixed taunter slots are slot 1 and slot 2. */ //cs_include Scripts/Ultras/CoreEngine.cs @@ -11,31 +15,11 @@ //cs_include Scripts/CoreFarms.cs //cs_include Scripts/CoreAdvanced.cs -#region Required Taunters -// Chaos Avenger: Lucky | Anima | Dauntless/HealthVamp | Vainglory -// ArchPaladin: Lucky | Forge | Dauntless/HealthVamp | Lament -#endregion - -#region DPS (No Deaths) -// Lich: Lucky | Examen | Ravenous | Penitence -// Legion Revenant: Wizard | Pneuma | Dauntless/HealthVamp | Vainglory -// Great Thief: Lucky | Forge | Dauntless/HealthVamp | Vainglory -// Hollowborn Vindicator: Lucky | Forge | Dauntless/HealthVamp | Penitence -// Quantum Chronomancer: Lucky | Anima | Dauntless/HealthVamp | Vainglory -// Phantom Chronomancer: Wizard | Pneuma | Dauntless/HealthVamp | Vainglory -// Verus DoomKnight: Lucky | Anima | Dauntless/HealthVamp | Vainglory -// King's Echo: Lucky | Pneuma | Dauntless/HealthVamp | Lament -#endregion - -#region DPS (Works with Deaths) -// Arachnomancer: Lucky | Anima | Dauntless/HealthVamp | Vainglory -// Archfiend: Lucky | Forge | Dauntless/HealthVamp | Vainglory -// Infinity Knight: Wizard | Pneuma | Dauntless/HealthVamp | Vainglory -// StoneCrusher: Wizard | Pneuma | Dauntless/HealthVamp | Vainglory -#endregion - +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Skua.Core.Interfaces; -using Skua.Core.Models.Items; using Skua.Core.Options; public class UltraDage @@ -56,116 +40,184 @@ private static CoreStory Story private static CoreStory _Story; public CoreEngine Core = new(); public CoreUltra Ultra = new(); - string a, - b; + public bool DontPreconfigure = true; public string OptionsStorage = "UltraDage"; - public List Options = new() + public string[] MultiOptions = { "Main", "CoreSettings", "ClassOverrides" }; + + public List Main = new() { new Option( "DoEquipClasses", "Automatically Equip Classes", "Auto-equip classes across all 4 clients\n" + "BestAvailable: CAv / AP / Best DPS / Best DPS\n" - + "Unselected = off (use whatever classes you already have equipped).", - DageComp.Unselected - ), - new Option( - "a", - "First Taunter Class", - "Insert the name of the class that will taunt ( examples: AP, Cav, LR, KE(?))", - "Chaos Avenger" + + "Unselected = off (use current classes).", + DageComp.BestAvailable ), - new Option( - "b", - "Second Taunter Class", - "Insert the name of the class that will taunt ( examples: AP, Cav, LR, KE(?))", - "ArchPaladin" - ), - new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), CoreBots.Instance.SkipOptions, }; + public List CoreSettings = new() + { + new Option("EquipBestGear", "Equip Best Gear", "Equip best gear for encounter", true), + new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), + new Option("RestoreGear", "Restore Gear", "Restore original gear after the script finishes", false), + new Option("UseLifeSteal", "Use LifeSteal", "Non-taunters equip/restock/use Scroll of Life Steal.", true), + }; + + public List ClassOverrides = new() + { + new Option("a", "Primary Class Override (T)", "Blank = use selected comp default for slot 1 (taunter).", ""), + new Option("b", "Secondary Class Override (T)", "Blank = use selected comp default for slot 2 (taunter).", ""), + new Option("c", "Tertiary Class Override", "Blank = use selected comp default for slot 3 (dps).", ""), + new Option("d", "Quaternary Class Override", "Blank = use selected comp default for slot 4 (dps).", ""), + }; + + private string tauntSlot1 = "chaos avenger"; + private string tauntSlot2 = "archpaladin"; + private string overrideA = string.Empty; + private string overrideB = string.Empty; + private string overrideC = string.Empty; + private string overrideD = string.Empty; + private DageComp ActiveComp = DageComp.BestAvailable; + private bool EquipBestGear; + private bool DoEnhancements; + private bool RestoreGear; + private bool UseLifeSteal; + private string NormalizeString(string input) => (input ?? "").Trim().ToLower(); public void ScriptMain(IScriptInterface bot) { if (!C.isCompletedBefore(793)) - C.Logger( - @"player is not part of the legion, you will not be able to turn the quest in. though u cna prolly do the kill." - ); + C.Logger("player is not part of the legion, you may not be able to turn in."); C.Join("whitemap"); - if ( - Bot.Config != null - && Bot.Config.Options.Contains(C.SkipOptions) - && !Bot.Config.Get(C.SkipOptions) - ) + if (Bot.Config != null && !Bot.Config.Get("Main", "SkipOption")) Bot.Config.Configure(); - a = NormalizeString(Bot.Config!.Get("a")!); - b = NormalizeString(Bot.Config.Get("b")!); - if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b)) + + ActiveComp = Bot.Config == null ? DageComp.BestAvailable : Bot.Config.Get("Main", "DoEquipClasses"); + overrideA = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty)).Trim(); + overrideB = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "b") ?? string.Empty)).Trim(); + overrideC = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "c") ?? string.Empty)).Trim(); + overrideD = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "d") ?? string.Empty)).Trim(); + EquipBestGear = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "EquipBestGear"); + DoEnhancements = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "DoEnh"); + RestoreGear = Bot.Config == null ? false : Bot.Config.Get("CoreSettings", "RestoreGear"); + UseLifeSteal = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "UseLifeSteal"); + + Adv.GearStore(); + try + { + Run(ActiveComp, EquipBestGear, DoEnhancements, UseLifeSteal, overrideA, overrideB, overrideC, overrideD); + } + finally { - Core.Log("Setup", "Fill both taunter classes in Script Options."); + if (RestoreGear) + Adv.GearStore(true, true); + C.SetOptions(false); Bot.StopSync(); - return; } - Core.Boot(); + } + + public void Run( + DageComp comp = DageComp.BestAvailable, + bool equipBestGear = true, + bool doEnhancements = true, + bool useLifeSteal = true, + string? classAOverride = null, + string? classBOverride = null, + string? classCOverride = null, + string? classDOverride = null + ) + { + ActiveComp = comp; + EquipBestGear = equipBestGear; + DoEnhancements = doEnhancements; + UseLifeSteal = useLifeSteal; + overrideA = classAOverride?.Trim() ?? string.Empty; + overrideB = classBOverride?.Trim() ?? string.Empty; + overrideC = classCOverride?.Trim() ?? string.Empty; + overrideD = classDOverride?.Trim() ?? string.Empty; - Adv.GearStore(EnhAfter: true); + Core.Boot(); Prep(); Bot.Events.ExtensionPacketReceived += UltraDageListener; - Fight(); - Bot.Events.ExtensionPacketReceived -= UltraDageListener; - - if (Bot.Config!.Get("DoEnh")) - Adv.GearStore(true, true); - Bot.StopSync(); + try + { + Fight(); + } + finally + { + Bot.Events.ExtensionPacketReceived -= UltraDageListener; + } } - bool IsTaunter() => Core.HasClassEquipped(a) || Core.HasClassEquipped(b); + bool IsTaunter() => Core.HasClassEquipped(tauntSlot1) || Core.HasClassEquipped(tauntSlot2); void Prep() { - // UpdateQuest to `Fail to the king` to unlock ultra dage Bot.Quests.UpdateQuest(793); + ApplyCompAndEquip(ActiveComp, overrideA, overrideB, overrideC, overrideD); - // Sync-equip classes if a comp is selected - DageComp comp = Bot.Config!.Get("DoEquipClasses"); - if (comp != DageComp.Unselected) - { - // DPS priority: no-death classes first, then death-tolerant as fallback - string[] dpsOptions = new[] { - "Lich", - "Legion Revenant", - "Great Thief", - "Hollowborn Vindicator", - "Quantum Chronomancer", - "Phantom Chronomancer", - "Verus DoomKnight", - "King's Echo", - "Arachnomancer", - "Archfiend", - "Infinity Knight", - "StoneCrusher" - }; - - string[][] classes = new[] { - new[] { "Chaos Avenger" }, - new[] { "ArchPaladin" }, - dpsOptions, - dpsOptions - }; - - Ultra.EquipClassSync(classes, 4, "dage_class.sync"); - } + if (EquipBestGear) + EquipBestDmgGear(); - if (Bot.Config!.Get("DoEnh")) + if (DoEnhancements) DoEnh(); + Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); + Ultra.BuyAlchemyPotion("Potent Honor Potion"); + Core.EquipConsumable("Potent Honor Potion"); + if (IsTaunter()) Ultra.GetScrollOfEnrage(); + else if (UseLifeSteal) + Ultra.GetScrollOfLifeSteal(); + } + + void ApplyCompAndEquip(DageComp comp, string aOverride, string bOverride, string cOverride, string dOverride) + { + if (comp == DageComp.Unselected) + { + tauntSlot1 = NormalizeString(string.IsNullOrWhiteSpace(aOverride) ? "Chaos Avenger" : aOverride); + tauntSlot2 = NormalizeString(string.IsNullOrWhiteSpace(bOverride) ? "ArchPaladin" : bOverride); + return; + } + + string[] dpsOptions = + { + "Lich", "Legion Revenant", "Great Thief", "Hollowborn Vindicator", "Quantum Chronomancer", "Phantom Chronomancer", + "Verus DoomKnight", "King's Echo", "Arachnomancer", "Archfiend", "Infinity Knight", "StoneCrusher" + }; + + string[][] classes = new[] + { + new[] { string.IsNullOrWhiteSpace(aOverride) ? "Chaos Avenger" : aOverride }, + new[] { string.IsNullOrWhiteSpace(bOverride) ? "ArchPaladin" : bOverride }, + string.IsNullOrWhiteSpace(cOverride) ? dpsOptions : new[] { cOverride }, + string.IsNullOrWhiteSpace(dOverride) ? dpsOptions : new[] { dOverride } + }; + + tauntSlot1 = NormalizeString(classes[0][0]); + tauntSlot2 = NormalizeString(classes[1][0]); + Ultra.EquipClassSync(classes, 4, "dage_class.sync"); + } + + void EquipBestDmgGear() + { + C.EquipBestItemsForMeta( + new Dictionary + { + { "Weapon", new[] { "dmgAll", "dmg", "damage" } }, + { "Armor", new[] { "dmgAll", "dmg", "damage" } }, + { "Helm", new[] { "dmgAll", "dmg", "damage" } }, + { "Cape", new[] { "dmgAll", "dmg", "damage" } }, + { "Pet", new[] { "dmgAll", "dmg", "damage" } }, + } + ); } void Fight() @@ -187,7 +239,13 @@ void Fight() while (!Bot.ShouldExit) { - // Dead → wait for respawn + if (Bot.Map?.Name != map) + { + Core.Join(map); + Core.ChooseBestCell(boss); + Bot.Player.SetSpawnPoint(); + } + if (!Bot.Player.Alive) { Bot.Wait.ForTrue(() => Bot.Player.Alive, 20); @@ -202,7 +260,11 @@ void Fight() C.EnsureComplete(8547); break; } - if (Core.HasClassEquipped(a) || Core.HasClassEquipped(b) && !Bot.Target.Auras.Any(a => a.Name == "Focus")) + + if (UseLifeSteal && !IsTaunter() && Bot.Skills.CanUseSkill(5)) + Bot.Skills.UseSkill(5); + + if (Core.HasClassEquipped(tauntSlot1) || Core.HasClassEquipped(tauntSlot2) && !Bot.Target.Auras.Any(a => a.Name == "Focus")) { if (Bot.Skills.CanUseSkill(5)) Bot.Skills.UseSkill(5); @@ -251,140 +313,51 @@ void DoEnh() switch (className.ToLower()) { case "chaos avenger": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Anima, - wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, - cSpecial: CapeSpecial.Vainglory - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Anima, wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, cSpecial: CapeSpecial.Vainglory); break; - case "archpaladin": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, - cSpecial: CapeSpecial.Lament - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Forge, wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, cSpecial: CapeSpecial.Lament); break; - case "legion revenant": - Adv.EnhanceEquipped( - type: EnhancementType.Wizard, - hSpecial: HelmSpecial.Pneuma, - wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, - cSpecial: CapeSpecial.Vainglory - ); + Adv.EnhanceEquipped(type: EnhancementType.Wizard, hSpecial: HelmSpecial.Pneuma, wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, cSpecial: CapeSpecial.Vainglory); break; - case "archfiend": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, - cSpecial: CapeSpecial.Vainglory - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Forge, wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, cSpecial: CapeSpecial.Vainglory); break; - case "arachnomancer": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Anima, - wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, - cSpecial: CapeSpecial.Vainglory - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Anima, wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, cSpecial: CapeSpecial.Vainglory); break; - case "king's echo": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Pneuma, - wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, - cSpecial: CapeSpecial.Lament - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Pneuma, wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, cSpecial: CapeSpecial.Lament); break; - case "chrono shadowslayer": case "chrono shadowhunter": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Vim, - wSpecial: WeaponSpecial.Health_Vamp, - cSpecial: CapeSpecial.Lament - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Vim, wSpecial: WeaponSpecial.Health_Vamp, cSpecial: CapeSpecial.Lament); break; - case "quantum chronomancer": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Anima, - wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, - cSpecial: CapeSpecial.Vainglory - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Anima, wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, cSpecial: CapeSpecial.Vainglory); break; - case "phantom chronomancer": case "phantasm chronomancer": - Adv.EnhanceEquipped( - type: EnhancementType.Wizard, - hSpecial: HelmSpecial.Pneuma, - wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, - cSpecial: CapeSpecial.Vainglory - ); + Adv.EnhanceEquipped(type: EnhancementType.Wizard, hSpecial: HelmSpecial.Pneuma, wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, cSpecial: CapeSpecial.Vainglory); break; - case "infinity knight": - Adv.EnhanceEquipped( - type: EnhancementType.Wizard, - hSpecial: HelmSpecial.Pneuma, - wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, - cSpecial: CapeSpecial.Vainglory - ); + Adv.EnhanceEquipped(type: EnhancementType.Wizard, hSpecial: HelmSpecial.Pneuma, wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, cSpecial: CapeSpecial.Vainglory); break; - case "lich": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Examen, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Penitence - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Examen, wSpecial: WeaponSpecial.Ravenous, cSpecial: CapeSpecial.Penitence); break; - case "verus doomknight": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Anima, - wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, - cSpecial: CapeSpecial.Vainglory - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Anima, wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, cSpecial: CapeSpecial.Vainglory); break; - case "hollowborn vindicator": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, - cSpecial: CapeSpecial.Penitence - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Forge, wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, cSpecial: CapeSpecial.Penitence); break; - case "great thief": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, - cSpecial: CapeSpecial.Vainglory - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Forge, wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, cSpecial: CapeSpecial.Vainglory); break; - case "stonecrusher": - Adv.EnhanceEquipped( - type: EnhancementType.Wizard, - hSpecial: HelmSpecial.Pneuma, - wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, - cSpecial: CapeSpecial.Vainglory - ); + Adv.EnhanceEquipped(type: EnhancementType.Wizard, hSpecial: HelmSpecial.Pneuma, wSpecial: Adv.uDauntless() ? WeaponSpecial.Dauntless : WeaponSpecial.Health_Vamp, cSpecial: CapeSpecial.Vainglory); break; } } diff --git a/Ultras/UltraDarkon.cs b/Ultras/UltraDarkon.cs index ac957ad30..cedd960a4 100644 --- a/Ultras/UltraDarkon.cs +++ b/Ultras/UltraDarkon.cs @@ -1,7 +1,7 @@ /* name: UltraDarkon -description: Ultra Darkon spam taunt -tags: ultra, darkon, taunt, spam, Ultra Darkon, ultra darkon +description: Ultra Darkon spam taunt helper. +tags: ultra, darkon, taunt */ //cs_include Scripts/Ultras/CoreEngine.cs @@ -13,11 +13,18 @@ using System; using System.Collections.Generic; -using System.Dynamic; -using System.Threading.Tasks; +using System.Linq; using Skua.Core.Interfaces; using Skua.Core.Options; +// NOTE: In all compositions below, slot 2 and slot 4 are the taunter roles. +// +// Recommended Comp +// 1) LightCaster +// 2) Legion Revenant (Taunter) +// 3) StoneCrusher +// 4) Lord Of Order (Tauner) + // Light Caster: // Weapon: Ravenous / Praxis // Class: Lucky @@ -47,6 +54,15 @@ // Scroll: Enrage // Potion: Divine Elixir +// Alternate DPS Options (fallback order when LightCaster is unavailable): +// 1) Chrono ShadowSlayer / Chrono ShadowHunter +// 2) Arcana Invoker +// 3) King's Echo +// 4) Lich +// 5) Hollowborn Vindicator +// 6) Alpha Omega / Alpha DOOMmega + + public class UltraDarkon { private static CoreAdvanced Adv @@ -59,83 +75,224 @@ private static CoreAdvanced Adv public IScriptInterface Bot => IScriptInterface.Instance; public CoreEngine Core = new(); public CoreUltra Ultra = new(); - string? className = null; - public bool DontPreconfigure = true; public string OptionsStorage = "UltraDarkon"; - // User options - public List Options = new() + public string[] MultiOptions = { "Main", "CoreSettings", "ClassOverrides" }; + + string a, b, c, d; + string overrideA, overrideB, overrideC, overrideD; + bool UseLifeSteal; + bool EquipBestGear; + bool DoEnhancements; + bool RestoreGear; + DarkonComp ActiveComp = DarkonComp.Recommended; + + public List Main = new() { new Option( "DoEquipClasses", "Automatically Equip Classes", "Auto-equip classes across all 4 clients\n" - + "Recommended: LC / LR / LOO / SC\n" - + "Unselected = off (use whatever classes you already have equipped).", - DarkonComp.Unselected + + "Slots 2 and 4 are always taunters.\n" + + "Recommended: LC / LR / SC / LOO\n" + + "Unselected = off (use manual classes below).", + DarkonComp.Recommended ), - new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), CoreBots.Instance.SkipOptions, }; - bool taunting = true; + + public List CoreSettings = new() + { + new Option("EquipBestGear", "Equip Best Gear", "Equip best gear for encounter", true), + new Option("DoEnh", "Do Enhancements", "Auto-enhance for the currently equipped class", true), + new Option("RestoreGear", "Restore Gear", "Restore original gear after the script finishes", false), + new Option("UseLifeSteal", "Use LifeSteal", "Non-taunters equip/restock/use Scroll of Life Steal.", true), + }; + + public List ClassOverrides = new() + { + new Option("a", "Primary Class Override", "Blank = use selected comp default for slot 1.", ""), + new Option("b", "Secondary Class Override (T)", "Blank = use selected comp default for slot 2 (taunter).", ""), + new Option("c", "Tertiary Class Override", "Blank = use selected comp default for slot 3.", ""), + new Option("d", "Quaternary Class Override (T)", "Blank = use selected comp default for slot 4 (taunter).", ""), + }; public void ScriptMain(IScriptInterface bot) { - C.Logger("This script uses the `spam taunt method.. and works..maybe ^_^"); - className = Bot.Player.CurrentClass?.Name?.ToLower(); + C.Logger("This script uses the spam taunt method."); + + if (Bot.Config != null && !Bot.Config.Get("Main", "SkipOption")) + Bot.Config.Configure(); + + ActiveComp = Bot.Config == null ? DarkonComp.Recommended : Bot.Config.Get("Main", "DoEquipClasses"); + overrideA = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty)).Trim(); + overrideB = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "b") ?? string.Empty)).Trim(); + overrideC = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "c") ?? string.Empty)).Trim(); + overrideD = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "d") ?? string.Empty)).Trim(); + EquipBestGear = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "EquipBestGear"); + DoEnhancements = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "DoEnh"); + UseLifeSteal = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "UseLifeSteal"); + RestoreGear = Bot.Config == null ? false : Bot.Config.Get("CoreSettings", "RestoreGear"); + + a = overrideA; + b = overrideB; + c = overrideC; + d = overrideD; + + bool usingComp = ActiveComp != DarkonComp.Unselected; + if (!usingComp && (string.IsNullOrEmpty(b) || string.IsNullOrEmpty(d))) + { + C.Logger("Setup", "Fill taunter class overrides for slots 2 and 4."); + Bot.StopSync(); + return; + } + + Adv.GearStore(); + + try + { + Run(ActiveComp, EquipBestGear, DoEnhancements, UseLifeSteal, overrideA, overrideB, overrideC, overrideD); + } + finally + { + if (RestoreGear) + Adv.GearStore(true, true); + C.SetOptions(false); + Bot.StopSync(); + } + } + + public void Run( + DarkonComp comp = DarkonComp.Recommended, + bool equipBestGear = true, + bool doEnhancements = true, + bool useLifeSteal = true, + string? classAOverride = null, + string? classBOverride = null, + string? classCOverride = null, + string? classDOverride = null + ) + { + ActiveComp = comp; + EquipBestGear = equipBestGear; + DoEnhancements = doEnhancements; + UseLifeSteal = useLifeSteal; + overrideA = classAOverride?.Trim() ?? string.Empty; + overrideB = classBOverride?.Trim() ?? string.Empty; + overrideC = classCOverride?.Trim() ?? string.Empty; + overrideD = classDOverride?.Trim() ?? string.Empty; + a = overrideA; + b = overrideB; + c = overrideC; + d = overrideD; + Core.Boot(); Prep(); - Kill(); - C.SetOptions(false); + Fight(); } - void Prep() + bool IsTaunter() { - if ( - Bot.Config != null - && Bot.Config.Options.Contains(C.SkipOptions) - && !Bot.Config.Get(C.SkipOptions) - ) - Bot.Config.Configure(); + string currentClass = Bot.Player.CurrentClass?.Name ?? string.Empty; + if (string.IsNullOrWhiteSpace(currentClass)) + return false; - // Sync-equip classes if a comp is selected - DarkonComp comp = Bot.Config!.Get("DoEquipClasses"); - if (comp != DarkonComp.Unselected) - { - string[] classes = comp switch - { - DarkonComp.Recommended => new[] { "LightCaster", "Legion Revenant", "Lord Of Order", "StoneCrusher" }, - _ => throw new InvalidOperationException($"Unhandled DarkonComp value: {comp}") - }; + if (!string.IsNullOrWhiteSpace(b) && currentClass.Equals(b, StringComparison.OrdinalIgnoreCase)) + return true; + if (!string.IsNullOrWhiteSpace(d) && currentClass.Equals(d, StringComparison.OrdinalIgnoreCase)) + return true; - Ultra.EquipClassSync(classes, 4, "darkon_class.sync"); - } + return false; + } + + void Prep() + { + if (ActiveComp != DarkonComp.Unselected) + ApplyCompAndEquip(ActiveComp, overrideA, overrideB, overrideC, overrideD); - if (Bot.Player.CurrentClass?.Name == "Alpha Omega" || Bot.Player.CurrentClass?.Name == "Alpha DOOMmega") - taunting = false; + if (EquipBestGear) + EquipBestDmgGear(); - Adv.GearStore(EnhAfter: true); - if (Bot.Config!.Get("DoEnh")) + if (DoEnhancements) DoEnhs(); - if (Bot.Player.CurrentClass!.Name == "Stonecrusher") + if (Bot.Player.CurrentClass?.Name == "StoneCrusher" || Bot.Player.CurrentClass?.Name == "Infinity Titan") { C.HuntMonster("poisonforest", "Xavier Lionfang", "Divine Elixir", 10, isTemp: false); Ultra.UseAlchemyPotions("Divine Elixir"); } else Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); - Ultra.GetScrollOfEnrage(); + Ultra.BuyAlchemyPotion("Potent Honor Potion"); + Core.EquipConsumable("Potent Honor Potion"); + + if (IsTaunter()) + Ultra.GetScrollOfEnrage(); + else if (UseLifeSteal) + Ultra.GetScrollOfLifeSteal(); + Bot.Sleep(2500); } - void Kill() + void ApplyCompAndEquip(DarkonComp comp, string aOverride, string bOverride, string cOverride, string dOverride) { - if (!C.isCompletedBefore(8733)) + string[][] classes; + switch (comp) { - C.Logger("Quest 8733 (\"The World\") not completed. Run: `\"Story\\ElegyofMadness(Darkon)\\0CompleteAll.cs` to be able to complete the quest, or just let this run to help get the kill."); + case DarkonComp.Recommended: + if (!string.IsNullOrWhiteSpace(aOverride)) + { + a = aOverride; + } + else if (C.CheckInventory("LightCaster")) + { + a = "LightCaster"; + } + else + { + string[] fallbackOrder = + { + "Chrono ShadowSlayer", + "Chrono ShadowHunter", + "Arcana Invoker", + "King's Echo", + "Lich", + "Hollowborn Vindicator", + "Alpha Omega", + "Alpha DOOMmega", + }; + + a = fallbackOrder.FirstOrDefault(x => C.CheckInventory(x)) ?? "LightCaster"; + C.Logger($"LightCaster not found. Using fallback DPS for slot 1: {a}"); + } + b = string.IsNullOrWhiteSpace(bOverride) ? "Legion Revenant" : bOverride; + c = string.IsNullOrWhiteSpace(cOverride) + ? (C.CheckInventory("Infinity Titan") ? "Infinity Titan" : "StoneCrusher") + : cOverride; + d = string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride; + classes = new[] + { + new[] { a }, + new[] { b }, + new[] { c }, + new[] { d } + }; + break; + + default: + throw new InvalidOperationException($"Unhandled DarkonComp value: {comp}"); + } + + Ultra.EquipClassSync(classes, 4, "darkon_class.sync", allowDuplicates: true); + } + + void Fight() + { + if (!C.isCompletedBefore(8733)) + { + C.Logger("Quest 8733 (The World) not completed. Run Story/ElegyofMadness(Darkon)/0CompleteAll.cs or use this script for kill support only."); Bot.Quests.UpdateQuest(8733); } @@ -144,50 +301,66 @@ void Kill() Bot.Sleep(2500); C.EnsureAccept(8746); - C.AddDrop("Darkon Insignia"); Core.Join("ultradarkon"); Ultra.WaitForArmy(3, "Ultra_Darkon.sync"); Core.ChooseBestCell("Darkon the Conductor"); + Bot.Player.SetSpawnPoint(); Core.EnableSkills(); + while (!Bot.ShouldExit) { + if (Bot.Map?.Name != "ultradarkon") + { + Core.Join("ultradarkon"); + Core.ChooseBestCell("Darkon the Conductor"); + Bot.Player.SetSpawnPoint(); + } + if (Ultra.CheckArmyProgressBool(() => Bot.TempInv.Contains("Darkon the Conductor Defeated", 1), syncPath)) { C.Jump("Enter", "Spawn"); C.Logger("All players finished farm."); C.EnsureComplete(8746); Bot.Wait.ForPickup("Darkon Insignia"); - C.Logger("Restoring enhancements!"); - - if (Bot.Config!.Get("DoEnh")) - Adv.GearStore(true, true); break; } - // Dead → wait for respawn if (Bot.Player?.Alive == false) { Bot.Wait.ForTrue(() => Bot.Player.Alive, 20); - if (Bot.Player!.CurrentClass?.Name == "Stonecrusher") + if (Bot.Player.CurrentClass?.Name == "StoneCrusher" || Bot.Player.CurrentClass?.Name == "Infinity Titan") { Ultra.UseAlchemyPotions("Divine Elixir"); + Ultra.BuyAlchemyPotion("Potent Honor Potion"); + Core.EquipConsumable("Potent Honor Potion"); Bot.Sleep(2500); - Core.EquipEnrage(); } + + if (IsTaunter()) + Core.EquipEnrage(); + else if (UseLifeSteal) + Ultra.GetScrollOfLifeSteal(); + continue; } if (!Bot.Player!.HasTarget) Bot.Combat.Attack("*"); + Bot.Sleep(200); - // Spam Taunt here - if (taunting && - !Bot.Target.Auras.Any(x => x != null && x.Name == "Focus") - && Bot.Skills.CanUseSkill(5) - ) + // Non-taunter role: use Scroll of Life Steal (equipped in Prep via CoreUltra helper). + if (UseLifeSteal && !IsTaunter() && Bot.Player.HasTarget && Bot.Player.Target?.HP > 0 && Bot.Skills.CanUseSkill(5)) + Bot.Skills.UseSkill(5); + + // Taunter role: use Scroll of Enrage to apply Focus. + if (IsTaunter() + && Bot.Player?.Target != null + && Bot.Player.Target.HP > 0 + && !Bot.Target.Auras.Any(x => x != null && x.Name == "Focus") + && Bot.Skills.CanUseSkill(5)) Bot.Skills.UseSkill(5); } } @@ -200,121 +373,124 @@ void DoEnhs() switch (className) { - // Light Caster case "LightCaster": Adv.EnhanceEquipped( - type: EnhancementType.Lucky, // Class - hSpecial: HelmSpecial.Pneuma, // Helm - wSpecial: WeaponSpecial.Ravenous, // Weapon - cSpecial: CapeSpecial.Penitence // Cape + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Pneuma, + wSpecial: WeaponSpecial.Ravenous, + cSpecial: CapeSpecial.Penitence ); break; - // Legion Revenant case "Legion Revenant": Adv.EnhanceEquipped( - type: EnhancementType.Wizard, // Class - hSpecial: HelmSpecial.Pneuma, // Helm - wSpecial: WeaponSpecial.Valiance, // Weapon - cSpecial: CapeSpecial.Penitence // Cape + type: EnhancementType.Wizard, + hSpecial: HelmSpecial.Pneuma, + wSpecial: WeaponSpecial.Valiance, + cSpecial: CapeSpecial.Penitence ); break; - // Lord Of Order case "Lord Of Order": Adv.EnhanceEquipped( - type: EnhancementType.Lucky, // Class - hSpecial: HelmSpecial.Forge, // Helm - wSpecial: WeaponSpecial.Valiance, // Weapon - cSpecial: CapeSpecial.Absolution // Cape + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Forge, + wSpecial: WeaponSpecial.Valiance, + cSpecial: CapeSpecial.Absolution ); break; - // StoneCrusher case "StoneCrusher": + case "Infinity Titan": Adv.EnhanceEquipped( - type: EnhancementType.Fighter, // Class - hSpecial: HelmSpecial.Anima, // Helm - wSpecial: WeaponSpecial.Valiance, // Weapon - cSpecial: CapeSpecial.Absolution // Cape + type: EnhancementType.Fighter, + hSpecial: HelmSpecial.Anima, + wSpecial: WeaponSpecial.Valiance, + cSpecial: CapeSpecial.Absolution ); break; - // ===== NEW ADDITIONS ===== - // Chrono ShadowSlayer / Chrono ShadowHunter case "Chrono ShadowSlayer": case "Chrono ShadowHunter": Adv.EnhanceEquipped( - type: EnhancementType.Lucky, // Class - hSpecial: HelmSpecial.Forge, // Helm - wSpecial: WeaponSpecial.Arcanas_Concerto, // Weapon - cSpecial: CapeSpecial.Lament // Cape + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Forge, + wSpecial: WeaponSpecial.Arcanas_Concerto, + cSpecial: CapeSpecial.Lament ); break; - // Paladin Chronomancer case "Paladin Chronomancer": Adv.EnhanceEquipped( - type: EnhancementType.Wizard, // Class - hSpecial: HelmSpecial.None, // Helm - wSpecial: WeaponSpecial.Mana_Vamp, // Weapon - cSpecial: CapeSpecial.Absolution // Cape + type: EnhancementType.Wizard, + hSpecial: HelmSpecial.None, + wSpecial: WeaponSpecial.Mana_Vamp, + cSpecial: CapeSpecial.Absolution ); break; - // Alpha Omega / Alpha DOOMmega case "Alpha Omega": case "Alpha DOOMmega": Adv.EnhanceEquipped( - type: EnhancementType.Lucky, // Class - hSpecial: HelmSpecial.Vim, // Helm - wSpecial: WeaponSpecial.Praxis, // Weapon - cSpecial: CapeSpecial.Avarice // Cape + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Vim, + wSpecial: WeaponSpecial.Praxis, + cSpecial: CapeSpecial.Avarice ); break; - // Arcana Invoker case "Arcana Invoker": Adv.EnhanceEquipped( - type: EnhancementType.Lucky, // Class - hSpecial: HelmSpecial.Forge, // Helm - wSpecial: WeaponSpecial.Ravenous, // Weapon - cSpecial: CapeSpecial.Penitence // Cape + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Forge, + wSpecial: WeaponSpecial.Ravenous, + cSpecial: CapeSpecial.Penitence ); break; - // Lich case "Lich": Adv.EnhanceEquipped( - type: EnhancementType.Lucky, // Class - hSpecial: HelmSpecial.Examen, // Helm - wSpecial: WeaponSpecial.Ravenous, // Weapon - cSpecial: CapeSpecial.Penitence // Cape + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Examen, + wSpecial: WeaponSpecial.Ravenous, + cSpecial: CapeSpecial.Penitence ); break; - // Hollowborn VIndicator case "Hollowborn Vindicator": Adv.EnhanceEquipped( - type: EnhancementType.Lucky, // Class - hSpecial: HelmSpecial.Forge, // Helm - wSpecial: WeaponSpecial.Dauntless, // Weapon - cSpecial: CapeSpecial.Penitence // Cape + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Forge, + wSpecial: WeaponSpecial.Dauntless, + cSpecial: CapeSpecial.Penitence ); break; - // King's Echo case "King's Echo": Adv.EnhanceEquipped( - type: EnhancementType.Lucky, // Class - hSpecial: HelmSpecial.Examen, // Helm - wSpecial: WeaponSpecial.Ravenous, // Weapon - cSpecial: CapeSpecial.Lament // Cape + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Examen, + wSpecial: WeaponSpecial.Ravenous, + cSpecial: CapeSpecial.Lament ); break; } } + void EquipBestDmgGear() + { + C.EquipBestItemsForMeta( + new Dictionary + { + { "Weapon", new[] { "dmgAll", "dmg", "damage" } }, + { "Armor", new[] { "dmgAll", "dmg", "damage" } }, + { "Helm", new[] { "dmgAll", "dmg", "damage" } }, + { "Cape", new[] { "dmgAll", "dmg", "damage" } }, + { "Pet", new[] { "dmgAll", "dmg", "damage" } }, + } + ); + } + public enum DarkonComp { Unselected, diff --git a/Ultras/UltraDrago.cs b/Ultras/UltraDrago.cs index e7cfe70c8..556c6caff 100644 --- a/Ultras/UltraDrago.cs +++ b/Ultras/UltraDrago.cs @@ -1,455 +1,350 @@ -/* -name: UltraDrago -description: Ultra King Drago helper with taunter classes and priority adds. -tags: Ultra -*/ - -//cs_include Scripts/Ultras/CoreEngine.cs -//cs_include Scripts/Ultras/CoreUltra.cs -//cs_include Scripts/CoreBots.cs -//cs_include Scripts/CoreFarms.cs -//cs_include Scripts/CoreAdvanced.cs -//cs_include Scripts/CoreStory.cs -//cs_include Scripts/Story/ElegyofMadness(Darkon)/CoreAstravia.cs - -using System.ComponentModel; -using System.Reflection; -using Skua.Core.Interfaces; -using Skua.Core.Models.Items; -using Skua.Core.Options; - -#region Fast Comp - -/// -/// Fast Composition - Maximum damage output for speed -/// -// Chrono ShadowSlayer -// ├─ Class: Lucky -// ├─ Helm: Vim / Forge -// ├─ Weapon: Valiance -// └─ Cape: Vainglory / Lament -// -// Legion Revenant -// ├─ Class: Wizard -// ├─ Helm: Pneuma -// ├─ Weapon: Valiance / Ravenous / Arcana -// └─ Cape: Vainglory -// -// ArchPaladin -// ├─ Class: Lucky -// ├─ Helm: Forge -// ├─ Weapon: Valiance -// └─ Cape: Lament -// -// Lord Of Order -// ├─ Class: Lucky -// ├─ Helm: Forge -// ├─ Weapon: Awe Blast / Valiance -// └─ Cape: Absolution - -#endregion - -#region Safe Comp - -/// -/// Safe Composition - Balanced survivability and damage -/// -// Chaos Avenger -// ├─ Class: Lucky -// ├─ Helm: Anima -// ├─ Weapon: Valiance -// └─ Cape: Vainglory -// -// Legion Revenant -// ├─ Class: Wizard -// ├─ Helm: Pneuma -// ├─ Weapon: Valiance / Ravenous / Arcana -// └─ Cape: Vainglory -// -// ArchPaladin -// ├─ Class: Lucky -// ├─ Helm: Forge -// ├─ Weapon: Valiance -// └─ Cape: Lament -// -// Lord Of Order -// ├─ Class: Lucky -// ├─ Helm: Forge -// ├─ Weapon: Awe Blast / Valiance -// └─ Cape: Absolution - -#endregion - -#region F2P Fast - -/// -/// F2P Fast Composition - Budget-friendly speed setup -/// -// King's Echo -// ├─ Class: Lucky -// ├─ Helm: Examen -// ├─ Weapon: Ravenous -// └─ Cape: Vainglory -// -// Legion Revenant -// ├─ Class: Wizard -// ├─ Helm: Pneuma -// ├─ Weapon: Valiance / Ravenous / Arcana -// └─ Cape: Vainglory -// -// ArchPaladin -// ├─ Class: Lucky -// ├─ Helm: Forge -// ├─ Weapon: Valiance -// └─ Cape: Lament -// -// Lord Of Order -// ├─ Class: Lucky -// ├─ Helm: Forge -// ├─ Weapon: Awe Blast / Valiance -// └─ Cape: Absolution - -#endregion - -#region Other DPS Options - -/// -/// Other DPS Options - Alternative single-class configurations -/// -// Arcana Invoker -// ├─ Class: Lucky -// ├─ Helm: Examen / Forge -// ├─ Weapon: Ravenous / Valiance -// └─ Cape: Vainglory -// -// Archfiend -// ├─ Class: Lucky -// ├─ Helm: Forge -// ├─ Weapon: Ravenous -// └─ Cape: Vainglory -// -// Lich -// ├─ Class: Lucky -// ├─ Helm: Examen -// ├─ Weapon: Ravenous -// └─ Cape: Penitence -// -// Sentinel -// ├─ Class: Lucky -// ├─ Helm: Anima -// ├─ Weapon: Ravenous -// └─ Cape: Vainglory - -#endregion - -public class UltraDrago -{ - private static CoreAdvanced Adv - { - get => _Adv ??= new CoreAdvanced(); - set => _Adv = value; - } - private static CoreAdvanced _Adv; - private CoreBots C => CoreBots.Instance; - private static CoreAstravia Astravia - { - get => _Astravia ??= new CoreAstravia(); - set => _Astravia = value; - } - private static CoreAstravia _Astravia; - public IScriptInterface Bot => IScriptInterface.Instance; - public CoreEngine Core = new(); - public CoreUltra Ultra = new(); - - public bool DontPreconfigure = true; - public string OptionsStorage = "UltraDrago"; - - // User options - public List Options = new() - { - new Option( - "DoEquipClasses", - "Automatically Equip Classes", - "Auto-equip classes across all 4 clients\n" - + "Fast: CSS / LR / AP / LOO\n" - + "Safe: CAv / LR / AP / LOO\n" - + "F2P Fast: KE / LR / AP / LOO\n" - + "Unselected = off (use whatever classes you already have equipped).", - DragoComp.Unselected - ), - new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), - CoreBots.Instance.SkipOptions, - }; - - List TaunterGroup1 = new[] { "ArchPaladin" }.ToList(); - bool isTaunterGroup1 = false; - List TaunterGroup2 = new[] { "Lord Of Order", "Lich", "Sentinel" }.ToList(); - bool isTaunterGroup2 = false; - public void ScriptMain(IScriptInterface bot) - { - C.OneTimeMessage( - "WARNING", - "Please use the classes in the options to ensure proper role functionality.\n" - + "We've allowed you to choose 'Current Class', but it's recommended to select a specific role for optimal (safe) performance.\n" - + "Current Class will Focus Boss -> Left Summon -> Right Summon", - true, - true - ); - - if ( - Bot.Config != null - && Bot.Config.Options.Contains(C.SkipOptions) - && !Bot.Config.Get(C.SkipOptions) - ) - Bot.Config.Configure(); - - Core.Boot(); - Prep(); - - // Detect taunter role AFTER sync-equip so the class is correct - if (TaunterGroup1.Contains(Bot.Player.CurrentClass!.Name)) - isTaunterGroup1 = true; - if (TaunterGroup2.Contains(Bot.Player.CurrentClass.Name)) - isTaunterGroup2 = true; - - C.EnsureComplete(8397); - Fight(); - } - - - void Prep() - { - C.Join("whitemap"); - Astravia.AstraviaJudgement(); - - // Sync-equip classes if a comp is selected - DragoComp comp = Bot.Config!.Get("DoEquipClasses"); - if (comp != DragoComp.Unselected) - { - string[] classes = comp switch - { - DragoComp.Fast => new[] { "Chrono ShadowSlayer", "Legion Revenant", "ArchPaladin", "Lord Of Order" }, - DragoComp.Safe => new[] { "Chaos Avenger", "Legion Revenant", "ArchPaladin", "Lord Of Order" }, - DragoComp.F2PFast => new[] { "King's Echo", "Legion Revenant", "ArchPaladin", "Lord Of Order" }, - _ => new[] { "ArchPaladin", "Legion Revenant", "Chaos Avenger", "Lord Of Order" }, - }; - - Ultra.EquipClassSync(classes, 4, "drago_class.sync"); - } - - Adv.GearStore(EnhAfter: true); - if (Bot.Config!.Get("DoEnh")) - DoEnhs(); - - Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); - Ultra.BuyAlchemyPotion("Potent Honor Potion"); - Core.EquipConsumable("Potent Honor Potion"); - - // Taunter check uses current (possibly sync-assigned) class - if (TaunterGroup1.Contains(Bot.Player.CurrentClass?.Name ?? string.Empty) - || TaunterGroup2.Contains(Bot.Player.CurrentClass?.Name ?? string.Empty)) - Ultra.GetScrollOfEnrage(); - } - - void Fight() - { - const string map = "ultradrago"; - const string boss = "King Drago"; - const string leftSummon = "Bowmaster Algie"; // Right summon (Bow) - const string rightSummon = "Executioner Dene"; // Left summon (Axe) - - string syncPath = Ultra.ResolveSyncPath("UltraItemCheck.sync"); - Ultra.ClearSyncFile(syncPath); - Bot.Sleep(2500); - if (!Bot.Quests.IsUnlocked(8397)) - Bot.Quests.UpdateQuest(8395); - C.AddDrop("King Drago Insignia"); - - Core.Join(map); - C.EnsureAccept(8397); - Ultra.WaitForArmy(3, "ultra_drago.sync"); - Core.ChooseBestCell(boss); - Bot.Player.SetSpawnPoint(); - Core.EnableSkills(); - - // ===== MAIN LOOP ===== - while (!Bot.ShouldExit) - { - // Dead → wait for respawn - if (!Bot.Player!.Alive) - { - Bot.Wait.ForTrue(() => Bot.Player.Alive, 20); - continue; - } - - if (Ultra.CheckArmyProgressBool(() => Bot.TempInv.Contains("Drago Dethroned", 1), syncPath)) - { - C.Jump("Enter", "Spawn"); - C.Logger("All players finished farm."); - if (Bot.Quests.IsUnlocked(8397)) - C.EnsureComplete(8397); - Bot.Wait.ForPickup("King Drago Insignia"); - if (Bot.Config!.Get("DoEnh")) - Adv.GearStore(true, true); - break; - } - - if (isTaunterGroup1 && Ultra.MonsterAlive(rightSummon)) - { - // ArchPaladin taunts the left summon (Axe) - while (!Bot.ShouldExit) - { - Ultra.Taunt( - Bot.Player?.CurrentClass?.Name!, - rightSummon, - "aura", - 250, - "Focus" - ); - if (!Ultra.MonsterAlive(rightSummon)) - break; - } - continue; - } - - if (isTaunterGroup2 && Ultra.MonsterAlive(rightSummon)) - { - // LordOfOrder loops taunt with ArchPaladin (left summon) - while (!Bot.ShouldExit) - { - Ultra.Taunt(Bot.Player?.CurrentClass?.Name!, - rightSummon, - "aura", - 700, - "Focus" - ); - if (!Ultra.MonsterAlive(rightSummon)) - break; - } - continue; - } - - Core.KillWithPriority(boss, leftSummon, rightSummon); - Bot.Skills.UseSkill(5); - } - } - - - void DoEnhs() - { - string className = Bot.Player!.CurrentClass?.Name ?? string.Empty; - if (string.IsNullOrEmpty(className)) - return; - - switch (className) - { - // Chrono ShadowSlayer - case "Chrono ShadowSlayer": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Vim, - wSpecial: WeaponSpecial.Valiance, - cSpecial: CapeSpecial.Vainglory - ); - break; - - // Legion Revenant - case "Legion Revenant": - Adv.EnhanceEquipped( - type: EnhancementType.Wizard, - hSpecial: HelmSpecial.Pneuma, - wSpecial: WeaponSpecial.Valiance, - cSpecial: CapeSpecial.Vainglory - ); - break; - - // ArchPaladin - case "ArchPaladin": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: WeaponSpecial.Valiance, - cSpecial: CapeSpecial.Lament - ); - break; - - // Lord Of Order - case "Lord Of Order": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: WeaponSpecial.Awe_Blast, - cSpecial: CapeSpecial.Absolution - ); - break; - - // Chaos Avenger - case "Chaos Avenger": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Anima, - wSpecial: WeaponSpecial.Valiance, - cSpecial: CapeSpecial.Vainglory - ); - break; - - // King's Echo - case "King's Echo": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Examen, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Vainglory - ); - break; - - // Arcana Invoker - case "Arcana Invoker": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Examen, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Vainglory - ); - break; - - // Archfiend - case "Archfiend": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Vainglory - ); - break; - - // Lich - case "Lich": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Examen, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Penitence - ); - break; - - // Sentinel - case "Sentinel": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Anima, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Vainglory - ); - break; - } - } - - public enum DragoComp - { - Unselected, - Fast, - Safe, - F2PFast, - } +/* +name: UltraDrago +description: Ultra King Drago helper with taunter classes and priority adds. +tags: Ultra + +Fight notes: +- Composition order is [slot 1-4]: Fast = CSS / LR / AP / LOO, Safe = CAv / LR / AP / LOO, F2PFast = KE / LR / AP / LOO. +- Fixed taunter slots are slot 3 and slot 4. +- Slot 3 handles Group 1 taunt timing, slot 4 handles Group 2 taunt timing. +*/ + +//cs_include Scripts/Ultras/CoreEngine.cs +//cs_include Scripts/Ultras/CoreUltra.cs +//cs_include Scripts/CoreBots.cs +//cs_include Scripts/CoreFarms.cs +//cs_include Scripts/CoreAdvanced.cs +//cs_include Scripts/CoreStory.cs +//cs_include Scripts/Story/ElegyofMadness(Darkon)/CoreAstravia.cs + +using System.Collections.Generic; +using System.Linq; +using Skua.Core.Interfaces; +using Skua.Core.Options; + +public class UltraDrago +{ + private static CoreAdvanced Adv + { + get => _Adv ??= new CoreAdvanced(); + set => _Adv = value; + } + private static CoreAdvanced _Adv; + private CoreBots C => CoreBots.Instance; + private static CoreAstravia Astravia + { + get => _Astravia ??= new CoreAstravia(); + set => _Astravia = value; + } + private static CoreAstravia _Astravia; + public IScriptInterface Bot => IScriptInterface.Instance; + public CoreEngine Core = new(); + public CoreUltra Ultra = new(); + + public bool DontPreconfigure = true; + public string OptionsStorage = "UltraDrago"; + public string[] MultiOptions = { "Main", "CoreSettings", "ClassOverrides" }; + + public List Main = new() + { + new Option( + "DoEquipClasses", + "Automatically Equip Classes", + "Auto-equip classes across all 4 clients\n" + + "Fast: CSS / LR / AP / LOO\n" + + "Safe: CAv / LR / AP / LOO\n" + + "F2PFast: KE / LR / AP / LOO\n" + + "Unselected = off (use whatever classes you already have equipped).", + DragoComp.Safe + ), + CoreBots.Instance.SkipOptions, + }; + + public List CoreSettings = new() + { + new Option("EquipBestGear", "Equip Best Gear", "Equip best gear for encounter", true), + new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), + new Option("RestoreGear", "Restore Gear", "Restore original gear after the script finishes", false), + new Option("UseLifeSteal", "Use LifeSteal", "Non-taunters equip/restock/use Scroll of Life Steal.", true), + }; + + public List ClassOverrides = new() + { + new Option("a", "Primary Class Override", "Blank = use selected comp default for slot 1.", ""), + new Option("b", "Secondary Class Override", "Blank = use selected comp default for slot 2.", ""), + new Option("c", "Tertiary Class Override (T)", "Blank = use selected comp default for slot 3 (taunter).", ""), + new Option("d", "Quaternary Class Override (T)", "Blank = use selected comp default for slot 4 (taunter).", ""), + }; + + string overrideA = string.Empty; + string overrideB = string.Empty; + string overrideC = string.Empty; + string overrideD = string.Empty; + bool UseLifeSteal; + bool EquipBestGear; + bool DoEnhancements; + bool RestoreGear; + DragoComp ActiveComp = DragoComp.Safe; + + string tauntSlot3 = "ArchPaladin"; + string tauntSlot4 = "Lord Of Order"; + bool isTaunterGroup1; + bool isTaunterGroup2; + + public void ScriptMain(IScriptInterface bot) + { + if (Bot.Config != null && !Bot.Config.Get("Main", "SkipOption")) + Bot.Config.Configure(); + + ActiveComp = Bot.Config == null ? DragoComp.Safe : Bot.Config.Get("Main", "DoEquipClasses"); + overrideA = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty)).Trim(); + overrideB = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "b") ?? string.Empty)).Trim(); + overrideC = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "c") ?? string.Empty)).Trim(); + overrideD = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "d") ?? string.Empty)).Trim(); + EquipBestGear = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "EquipBestGear"); + DoEnhancements = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "DoEnh"); + UseLifeSteal = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "UseLifeSteal"); + RestoreGear = Bot.Config == null ? false : Bot.Config.Get("CoreSettings", "RestoreGear"); + + Adv.GearStore(); + try + { + Run(ActiveComp, EquipBestGear, DoEnhancements, UseLifeSteal, overrideA, overrideB, overrideC, overrideD); + } + finally + { + if (RestoreGear) + Adv.GearStore(true, true); + C.SetOptions(false); + Bot.StopSync(); + } + } + + public void Run( + DragoComp comp = DragoComp.Safe, + bool equipBestGear = true, + bool doEnhancements = true, + bool useLifeSteal = true, + string? classAOverride = null, + string? classBOverride = null, + string? classCOverride = null, + string? classDOverride = null + ) + { + ActiveComp = comp; + EquipBestGear = equipBestGear; + DoEnhancements = doEnhancements; + UseLifeSteal = useLifeSteal; + overrideA = classAOverride?.Trim() ?? string.Empty; + overrideB = classBOverride?.Trim() ?? string.Empty; + overrideC = classCOverride?.Trim() ?? string.Empty; + overrideD = classDOverride?.Trim() ?? string.Empty; + + Core.Boot(); + Prep(); + + isTaunterGroup1 = Core.HasClassEquipped(tauntSlot3); + isTaunterGroup2 = Core.HasClassEquipped(tauntSlot4); + + C.EnsureComplete(8397); + Fight(); + } + + void Prep() + { + C.Join("whitemap"); + Astravia.AstraviaJudgement(); + + ApplyCompAndEquip(ActiveComp, overrideA, overrideB, overrideC, overrideD); + + if (EquipBestGear) + EquipBestDmgGear(); + + if (DoEnhancements) + DoEnhs(); + + Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); + Ultra.BuyAlchemyPotion("Potent Honor Potion"); + Core.EquipConsumable("Potent Honor Potion"); + + if (Core.HasClassEquipped(tauntSlot3) || Core.HasClassEquipped(tauntSlot4)) + Ultra.GetScrollOfEnrage(); + else if (UseLifeSteal) + Ultra.GetScrollOfLifeSteal(); + } + + void ApplyCompAndEquip(DragoComp comp, string aOverride, string bOverride, string cOverride, string dOverride) + { + if (comp == DragoComp.Unselected) + return; + + string[] classes = comp switch + { + DragoComp.Fast => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "Chrono ShadowSlayer" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "Legion Revenant" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "ArchPaladin" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride, + }, + DragoComp.Safe => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "Chaos Avenger" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "Legion Revenant" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "ArchPaladin" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride, + }, + DragoComp.F2PFast => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "King's Echo" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "Legion Revenant" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "ArchPaladin" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride, + }, + _ => throw new System.InvalidOperationException($"Unhandled DragoComp value: {comp}") + }; + + tauntSlot3 = classes[2]; + tauntSlot4 = classes[3]; + Ultra.EquipClassSync(classes, 4, "drago_class.sync"); + } + + void EquipBestDmgGear() + { + C.EquipBestItemsForMeta( + new Dictionary + { + { "Weapon", new[] { "dmgAll", "dmg", "damage" } }, + { "Armor", new[] { "dmgAll", "dmg", "damage" } }, + { "Helm", new[] { "dmgAll", "dmg", "damage" } }, + { "Cape", new[] { "dmgAll", "dmg", "damage" } }, + { "Pet", new[] { "dmgAll", "dmg", "damage" } }, + } + ); + } + + void Fight() + { + const string map = "ultradrago"; + const string boss = "King Drago"; + const string leftSummon = "Bowmaster Algie"; + const string rightSummon = "Executioner Dene"; + + string syncPath = Ultra.ResolveSyncPath("UltraItemCheck.sync"); + Ultra.ClearSyncFile(syncPath); + Bot.Sleep(2500); + if (!Bot.Quests.IsUnlocked(8397)) + Bot.Quests.UpdateQuest(8395); + C.AddDrop("King Drago Insignia"); + + Core.Join(map); + C.EnsureAccept(8397); + Ultra.WaitForArmy(3, "ultra_drago.sync"); + Core.ChooseBestCell(boss); + Bot.Player.SetSpawnPoint(); + Core.EnableSkills(); + + while (!Bot.ShouldExit) + { + if (Bot.Map?.Name != map) + { + Core.Join(map); + Core.ChooseBestCell(boss); + Bot.Player.SetSpawnPoint(); + } + + if (!Bot.Player.Alive) + { + Bot.Wait.ForTrue(() => Bot.Player.Alive, 20); + continue; + } + + if (Ultra.CheckArmyProgressBool(() => Bot.TempInv.Contains("Drago Dethroned", 1), syncPath)) + { + C.Jump("Enter", "Spawn"); + C.Logger("All players finished farm."); + if (Bot.Quests.IsUnlocked(8397)) + C.EnsureComplete(8397); + Bot.Wait.ForPickup("King Drago Insignia"); + break; + } + + if (UseLifeSteal && !isTaunterGroup1 && !isTaunterGroup2 && Bot.Skills.CanUseSkill(5)) + Bot.Skills.UseSkill(5); + + if (isTaunterGroup1 && Ultra.MonsterAlive(rightSummon)) + { + while (!Bot.ShouldExit) + { + Ultra.Taunt(Bot.Player?.CurrentClass?.Name!, rightSummon, "aura", 250, "Focus"); + if (!Ultra.MonsterAlive(rightSummon)) + break; + } + continue; + } + + if (isTaunterGroup2 && Ultra.MonsterAlive(rightSummon)) + { + while (!Bot.ShouldExit) + { + Ultra.Taunt(Bot.Player?.CurrentClass?.Name!, rightSummon, "aura", 700, "Focus"); + if (!Ultra.MonsterAlive(rightSummon)) + break; + } + continue; + } + + Core.KillWithPriority(boss, leftSummon, rightSummon); + Bot.Skills.UseSkill(5); + } + } + + void DoEnhs() + { + string className = Bot.Player!.CurrentClass?.Name ?? string.Empty; + if (string.IsNullOrEmpty(className)) + return; + + switch (className) + { + case "Chrono ShadowSlayer": + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Vim, wSpecial: WeaponSpecial.Valiance, cSpecial: CapeSpecial.Vainglory); + break; + case "Legion Revenant": + Adv.EnhanceEquipped(type: EnhancementType.Wizard, hSpecial: HelmSpecial.Pneuma, wSpecial: WeaponSpecial.Valiance, cSpecial: CapeSpecial.Vainglory); + break; + case "ArchPaladin": + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Forge, wSpecial: WeaponSpecial.Valiance, cSpecial: CapeSpecial.Lament); + break; + case "Lord Of Order": + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Forge, wSpecial: WeaponSpecial.Awe_Blast, cSpecial: CapeSpecial.Absolution); + break; + case "Chaos Avenger": + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Anima, wSpecial: WeaponSpecial.Valiance, cSpecial: CapeSpecial.Vainglory); + break; + case "King's Echo": + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Examen, wSpecial: WeaponSpecial.Ravenous, cSpecial: CapeSpecial.Vainglory); + break; + case "Arcana Invoker": + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Examen, wSpecial: WeaponSpecial.Ravenous, cSpecial: CapeSpecial.Vainglory); + break; + case "Archfiend": + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Forge, wSpecial: WeaponSpecial.Ravenous, cSpecial: CapeSpecial.Vainglory); + break; + case "Lich": + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Examen, wSpecial: WeaponSpecial.Ravenous, cSpecial: CapeSpecial.Penitence); + break; + case "Sentinel": + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Anima, wSpecial: WeaponSpecial.Ravenous, cSpecial: CapeSpecial.Vainglory); + break; + } + } + + public enum DragoComp + { + Unselected, + Fast, + Safe, + F2PFast, + } } diff --git a/Ultras/UltraEngineer.cs b/Ultras/UltraEngineer.cs index 91e6a8334..be9b26e9e 100644 --- a/Ultras/UltraEngineer.cs +++ b/Ultras/UltraEngineer.cs @@ -2,6 +2,12 @@ name: UltraEngineer description: Ultra Engineer helper prioritizing drones with army synchronization and consumables. tags: Ultra + +Fight notes: +- Composition order is [slot 1-4]: Fast = Lich / LR / AP / LOO, Safe = LR / SC / AP / LOO, F2PFast = AI / LR / AP / LOO. +- Default composition is set to Safe when selected. +- Recommended fixed comp taunter classes are slot 1 = Lich and slot 2 = Legion Revenant. +- No explicit script-level taunt-role logic is used; scripts rely on combat/party behavior. */ //cs_include Scripts/Ultras/CoreEngine.cs @@ -151,9 +157,26 @@ private static CoreAdvanced Adv public CoreEngine Core = new(); public CoreUltra Ultra = new(); - public bool DontPreconfigure = true; - public string OptionsStorage = "UltraEngineer"; - public List Options = new() + public bool DontPreconfigure = true; + public string OptionsStorage = "UltraEngineer"; + + string a, + b, + c, + d, + overrideA, + overrideB, + overrideC, + overrideD; + bool UseLifeSteal; + bool EquipBestGear; + bool DoEnhancements; + bool RestoreGear; + EngineerComp ActiveComp = EngineerComp.Safe; + + public string[] MultiOptions = { "Main", "CoreSettings", "ClassOverrides" }; + + public List Main = new() { new Option( "DoEquipClasses", @@ -163,77 +186,182 @@ private static CoreAdvanced Adv + "Safe: LR / SC / AP / LOO\n" + "F2PFast: AI / LR / AP / LOO\n" + "Unselected = off (use whatever classes you already have equipped).", - EngineerComp.Unselected + EngineerComp.Safe ), - new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), CoreBots.Instance.SkipOptions, }; - public void ScriptMain(IScriptInterface bot) + public List CoreSettings = new() + { + new Option("EquipBestGear", "Equip Best Gear", "Equip best gear for encounter", true), + new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), + new Option("RestoreGear", "Restore Gear", "Restore original gear after the script finishes", false), + new Option("UseLifeSteal", "Use LifeSteal", "Equip/restock/use Scroll of Life Steal.", true), + }; + + public List ClassOverrides = new() { - if ( - Bot.Config != null - && Bot.Config.Options.Contains(C.SkipOptions) - && !Bot.Config.Get(C.SkipOptions) - ) - Bot.Config.Configure(); - - Core.Boot(); - Prep(); - Fight(); - if (Bot.Config!.Get("DoEnh")) - Adv.GearStore(true, true); - - Bot.StopSync(); + new Option("a", "Primary Class Override", "Blank = use selected comp default for slot 1.", ""), + new Option("b", "Secondary Class Override", "Blank = use selected comp default for slot 2.", ""), + new Option("c", "Tertiary Class Override", "Blank = use selected comp default for slot 3.", ""), + new Option("d", "Quaternary Class Override", "Blank = use selected comp default for slot 4.", ""), + }; + + public void ScriptMain(IScriptInterface bot) + { + if (Bot.Config != null && !Bot.Config.Get("Main", "SkipOption")) + Bot.Config.Configure(); + + overrideA = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty)).Trim(); + overrideB = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "b") ?? string.Empty)).Trim(); + overrideC = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "c") ?? string.Empty)).Trim(); + overrideD = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "d") ?? string.Empty)).Trim(); + EquipBestGear = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "EquipBestGear"); + DoEnhancements = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "DoEnh"); + UseLifeSteal = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "UseLifeSteal"); + RestoreGear = Bot.Config == null ? false : Bot.Config.Get("CoreSettings", "RestoreGear"); + ActiveComp = Bot.Config == null ? EngineerComp.Safe : Bot.Config.Get("Main", "DoEquipClasses"); + + Adv.GearStore(); + + try + { + Run(ActiveComp, EquipBestGear, DoEnhancements, UseLifeSteal, overrideA, overrideB, overrideC, overrideD); + } + finally + { + if (RestoreGear) + Adv.GearStore(true, true); + C.SetOptions(false); + Bot.StopSync(); + } } - void Prep() + public void Run( + EngineerComp comp = EngineerComp.Safe, + bool equipBestGear = true, + bool doEnhancements = true, + bool useLifeSteal = true, + string? classAOverride = null, + string? classBOverride = null, + string? classCOverride = null, + string? classDOverride = null + ) + { + ActiveComp = comp; + EquipBestGear = equipBestGear; + DoEnhancements = doEnhancements; + UseLifeSteal = useLifeSteal; + overrideA = classAOverride?.Trim() ?? string.Empty; + overrideB = classBOverride?.Trim() ?? string.Empty; + overrideC = classCOverride?.Trim() ?? string.Empty; + overrideD = classDOverride?.Trim() ?? string.Empty; + a = overrideA; + b = overrideB; + c = overrideC; + d = overrideD; + + Core.Boot(); + Prep(); + Fight(); + } + + void Prep() + { + C.Logger($"UltraEngineer prep: {ActiveComp}"); + ApplyCompAndEquip(ActiveComp, overrideA, overrideB, overrideC, overrideD); + + if (EquipBestGear) + EquipBestDmgGear(); + + if (DoEnhancements) + DoEnhs(); + + Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); + Ultra.BuyAlchemyPotion("Potent Honor Potion"); + Core.EquipConsumable("Potent Honor Potion"); + if (UseLifeSteal) + Ultra.GetScrollOfLifeSteal(); + C.Logger("Potions/consumables prepared."); + } + + void EquipBestDmgGear() { - // Sync-equip classes if a comp is selected - EngineerComp comp = Bot.Config!.Get("DoEquipClasses"); - if (comp != EngineerComp.Unselected) - { - string[] classes = comp switch + C.EquipBestItemsForMeta( + new Dictionary { - EngineerComp.Fast => new[] { "Lich", "Legion Revenant", "ArchPaladin", "Lord Of Order" }, - EngineerComp.Safe => new[] { "Legion Revenant", "StoneCrusher", "ArchPaladin", "Lord Of Order" }, - EngineerComp.F2PFast => new[] { "Arcana Invoker", "Legion Revenant", "ArchPaladin", "Lord Of Order" }, - _ => throw new InvalidOperationException($"Unhandled EngineerComp value: {comp}") - }; + { "Weapon", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Armor", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Helm", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Cape", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Pet", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + } + ); + } - Ultra.EquipClassSync(classes, 4, "engineer_class.sync"); - } + void ApplyCompAndEquip(EngineerComp comp, string aOverride, string bOverride, string cOverride, string dOverride) + { + if (comp == EngineerComp.Unselected) + return; - if (Bot.Config!.Get("DoEnh")) + string[] classes = comp switch { - Adv.GearStore(false, true); - DoEnhs(); - } - Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); - Ultra.BuyAlchemyPotion("Potent Honor Potion"); - Core.EquipConsumable("Potent Honor Potion"); + EngineerComp.Fast => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "Lich" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "Legion Revenant" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "ArchPaladin" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride, + }, + EngineerComp.Safe => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "Legion Revenant" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "StoneCrusher" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "ArchPaladin" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride, + }, + EngineerComp.F2PFast => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "Arcana Invoker" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "Legion Revenant" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "ArchPaladin" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride, + }, + _ => throw new InvalidOperationException($"Unhandled EngineerComp value: {comp}") + }; + + Ultra.EquipClassSync(classes, 4, "engineer_class.sync"); + C.Logger( + $"Engineer classes selected => [1]={classes[0]}, [2]={classes[1]}, [3]={classes[2]}, [4]={classes[3]}" + ); } void Fight() { - const string map = "ultraengineer"; - const string boss = "Ultra Engineer"; - const string priority1 = "Defense Drone"; - const string priority2 = "Attack Drone"; + const string map = "ultraengineer"; + const string boss = "Ultra Engineer"; + const string priority1 = "Defense Drone"; + const string priority2 = "Attack Drone"; + C.Logger("UltraEngineer fight start."); string syncPath = Ultra.ResolveSyncPath("UltraItemCheck.sync"); Ultra.ClearSyncFile(syncPath); Bot.Sleep(2500); - C.EnsureAccept(8154); - C.AddDrop("Engineer Insignia"); - Core.Join(map); - Ultra.WaitForArmy(3, "ultra_engineer.sync"); - Core.ChooseBestCell(boss); - Bot.Player.SetSpawnPoint(); - Core.EnableSkills(); - - while (!Bot.ShouldExit) - { + C.EnsureAccept(8154); + C.AddDrop("Engineer Insignia"); + Core.Join(map); + if (Bot.Map.Name != map) + Core.Join(map); + Ultra.WaitForArmy(3, "ultra_engineer.sync"); + Core.ChooseBestCell(boss); + Bot.Player.SetSpawnPoint(); + Core.EnableSkills(); + + while (!Bot.ShouldExit) + { + if (Bot.Map.Name != map) + Core.Join(map); + // Dead → wait for respawn if (!Bot.Player.Alive) { @@ -246,21 +374,19 @@ void Fight() { C.Logger("All players finished farm."); C.EnsureComplete(8154); - if (Bot.Config!.Get("DoEnh")) - Adv.GearStore(true, true); break; } + if (UseLifeSteal && Bot.Skills.CanUseSkill(5)) + Bot.Skills.UseSkill(5); Ultra.KillWithPriority(boss, 3, priority1, 2, priority2, 1); - Bot.Skills.UseSkill(5); } } - void DoEnhs() - { - string className = Bot.Player!.CurrentClass?.Name ?? string.Empty; - if (string.IsNullOrEmpty(className)) - return; - Adv.GearStore(EnhAfter: true); + void DoEnhs() + { + string className = Bot.Player!.CurrentClass?.Name ?? string.Empty; + if (string.IsNullOrEmpty(className)) + return; switch (className) { @@ -363,8 +489,11 @@ void DoEnhs() cSpecial: CapeSpecial.Vainglory ); break; - } - } + + default: + break; + } + } public enum EngineerComp { diff --git a/Ultras/UltraEzrajal.cs b/Ultras/UltraEzrajal.cs index 97aa097ab..fddad4231 100644 --- a/Ultras/UltraEzrajal.cs +++ b/Ultras/UltraEzrajal.cs @@ -2,6 +2,12 @@ name: UltraEzrajal description: Ultra Ezrajal helper handling Counter Attack windows with army sync. tags: Ultra + +Fight notes: +- Composition order is [slot 1-4]: Fast = CSS / VDK / LR / LOO, Safe = AI / LR / AP / LOO, F2PFastest = AI / VDK / LR / LOO. +- Default composition is set to Safe when selected. +- No dedicated taunter role is configured in this script. +- Taunt/taunter setup is intentionally not used here; combat is handled via counter-attack windows. */ //cs_include Scripts/Ultras/CoreEngine.cs @@ -158,13 +164,32 @@ private static CoreAdvanced Adv } private CoreBots C => CoreBots.Instance; private static CoreAdvanced _Adv; - public IScriptInterface Bot => IScriptInterface.Instance; - public CoreEngine Core = new(); - public CoreUltra Ultra = new(); - - public string OptionsStorage = "UltraEzrajal2"; - public bool DontPreconfigure = true; - public List Options = new() + public IScriptInterface Bot => IScriptInterface.Instance; + public CoreEngine Core = new(); + public CoreUltra Ultra = new(); + + public string OptionsStorage = "UltraEzrajal2"; + public bool DontPreconfigure = true; + + string a, + b, + c, + d, + overrideA, + overrideB, + overrideC, + overrideD; + bool UseLifeSteal; + bool EquipBestGear; + bool DoEnhancements; + bool RestoreGear; + private EzrajalComp ActiveComp = EzrajalComp.Safe; + private bool wasCounterAttackDetected; + private int counterAttackCycles; + + public string[] MultiOptions = { "Main", "CoreSettings", "ClassOverrides" }; + + public List Main = new() { new Option( "DoEquipClasses", @@ -174,59 +199,160 @@ private static CoreAdvanced Adv + "Safe: AI / LR / AP / LOO\n" + "F2PFastest: AI / VDK / LR / LOO\n" + "Unselected = off (use whatever classes you already have equipped).", - EzrajalComp.Unselected + EzrajalComp.Safe ), - new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), CoreBots.Instance.SkipOptions, }; - public void ScriptMain(IScriptInterface bot) + public List CoreSettings = new() + { + new Option("EquipBestGear", "Equip Best Gear", "Equip best gear for encounter", true), + new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), + new Option("RestoreGear", "Restore Gear", "Restore original gear after the script finishes", false), + new Option("UseLifeSteal", "Use LifeSteal", "Equip/restock/use Scroll of Life Steal.", true), + }; + + public List ClassOverrides = new() { - if ( - Bot.Config != null - && Bot.Config.Options.Contains(C.SkipOptions) - && !Bot.Config.Get(C.SkipOptions) - ) - Bot.Config.Configure(); - Core.Boot(); - Bot.UltraBossHelper.EnableCounterAttack(); - C.AddDrop("Ezrajal Insignia"); + new Option("a", "Primary Class Override", "Blank = use selected comp default for slot 1.", ""), + new Option("b", "Secondary Class Override", "Blank = use selected comp default for slot 2.", ""), + new Option("c", "Tertiary Class Override", "Blank = use selected comp default for slot 3.", ""), + new Option("d", "Quaternary Class Override", "Blank = use selected comp default for slot 4.", ""), + }; + + public void ScriptMain(IScriptInterface bot) + { + if (Bot.Config != null && !Bot.Config.Get("Main", "SkipOption")) + Bot.Config.Configure(); + + overrideA = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty)).Trim(); + overrideB = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "b") ?? string.Empty)).Trim(); + overrideC = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "c") ?? string.Empty)).Trim(); + overrideD = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "d") ?? string.Empty)).Trim(); + EquipBestGear = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "EquipBestGear"); + DoEnhancements = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "DoEnh"); + UseLifeSteal = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "UseLifeSteal"); + RestoreGear = Bot.Config == null ? false : Bot.Config.Get("CoreSettings", "RestoreGear"); + ActiveComp = Bot.Config == null ? EzrajalComp.Safe : Bot.Config.Get("Main", "DoEquipClasses"); + Adv.GearStore(); + + try + { + Run(ActiveComp, EquipBestGear, DoEnhancements, UseLifeSteal, overrideA, overrideB, overrideC, overrideD); + } + finally + { + if (RestoreGear) + Adv.GearStore(true, true); + C.SetOptions(false); + Bot.StopSync(); + } + } + + public void Run( + EzrajalComp comp = EzrajalComp.Safe, + bool equipBestGear = true, + bool doEnhancements = true, + bool useLifeSteal = true, + string? classAOverride = null, + string? classBOverride = null, + string? classCOverride = null, + string? classDOverride = null + ) + { + ActiveComp = comp; + EquipBestGear = equipBestGear; + DoEnhancements = doEnhancements; + UseLifeSteal = useLifeSteal; + overrideA = classAOverride?.Trim() ?? string.Empty; + overrideB = classBOverride?.Trim() ?? string.Empty; + overrideC = classCOverride?.Trim() ?? string.Empty; + overrideD = classDOverride?.Trim() ?? string.Empty; + a = overrideA; + b = overrideB; + c = overrideC; + d = overrideD; + + Core.Boot(); + Bot.UltraBossHelper.EnableCounterAttack(); + C.AddDrop("Ezrajal Insignia"); Prep(); Fight(); - if (Bot.Config!.Get("DoEnh")) - Adv.GearStore(true, true); - Bot.StopSync(); } - void Prep() - { - // Sync-equip classes if a comp is selected - EzrajalComp comp = Bot.Config!.Get("DoEquipClasses"); - if (comp != EzrajalComp.Unselected) - { - string[] classes = comp switch - { - EzrajalComp.Fast => new[] { "Chrono ShadowSlayer", "Verus DoomKnight", "Legion Revenant", "Lord Of Order" }, - EzrajalComp.Safe => new[] { "Arcana Invoker", "Legion Revenant", "ArchPaladin", "Lord Of Order" }, - EzrajalComp.F2PFastest => new[] { "Arcana Invoker", "Verus DoomKnight", "Legion Revenant", "Lord Of Order" }, - _ => throw new InvalidOperationException($"Unhandled EzrajalComp value: {comp}") - }; + void Prep() + { + C.Logger($"[UltraEzrajal] Prep: {ActiveComp}"); + // Sync-equip classes if a comp is selected + ApplyCompAndEquip(ActiveComp, overrideA, overrideB, overrideC, overrideD); - Ultra.EquipClassSync(classes, 4, "ezrajal_class.sync"); - } + if (EquipBestGear) + EquipBestDmgGear(); - if (Bot.Config!.Get("DoEnh")) + if (DoEnhancements) { - Adv.GearStore(false, true); DoEnhs(); } - Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); - Ultra.BuyAlchemyPotion("Potent Honor Potion"); - Core.EquipConsumable("Potent Honor Potion"); + + Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); + Ultra.BuyAlchemyPotion("Potent Honor Potion"); + Core.EquipConsumable("Potent Honor Potion"); + + if (UseLifeSteal) + Ultra.GetScrollOfLifeSteal(); + } + + void EquipBestDmgGear() + { + C.EquipBestItemsForMeta( + new Dictionary + { + { "Weapon", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Armor", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Helm", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Cape", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Pet", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + } + ); + } + + void ApplyCompAndEquip(EzrajalComp comp, string aOverride, string bOverride, string cOverride, string dOverride) + { + if (comp == EzrajalComp.Unselected) + return; + + string[] classes = comp switch + { + EzrajalComp.Fast => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "Chrono ShadowSlayer" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "Verus DoomKnight" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "Legion Revenant" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride, + }, + EzrajalComp.Safe => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "Arcana Invoker" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "Legion Revenant" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "ArchPaladin" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride, + }, + EzrajalComp.F2PFastest => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "Arcana Invoker" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "Verus DoomKnight" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "Legion Revenant" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride, + }, + _ => throw new InvalidOperationException($"Unhandled EzrajalComp value: {comp}") + }; + + Ultra.EquipClassSync(classes, 4, "ezrajal_class.sync"); } void Fight() { + C.Logger("[UltraEzrajal] Fight start."); const string map = "ultraezrajal"; const string boss = "Ultra Ezrajal"; @@ -257,6 +383,9 @@ void Fight() continue; } + if (UseLifeSteal && Bot.Skills.CanUseSkill(5)) + Bot.Skills.UseSkill(5); + // Check if the whole army has finished if (Ultra.CheckArmyProgressBool(() => Bot.TempInv.Contains("Ultra Ezrajal Defeated", 1), syncPath)) { @@ -269,10 +398,25 @@ void Fight() // --------------------------- // COUNTER ATTACK HANDLER // --------------------------- - if ( - Bot.Player.HasTarget - && Bot.Target?.Auras?.Any(a => a != null && a?.Name == "Counter Attack") == true - ) + bool hasCounterAttackAura = Core.HasAura("Counter Attack"); + bool counterAttackStateChanged = hasCounterAttackAura != wasCounterAttackDetected; + if (counterAttackStateChanged) + { + wasCounterAttackDetected = hasCounterAttackAura; + if (hasCounterAttackAura) + { + counterAttackCycles++; + C.Logger( + $"[UltraEzrajal] Counter Attack aura detected; entering evade cycle #{counterAttackCycles}." + ); + } + else + { + C.Logger("[UltraEzrajal] Counter Attack aura no longer active; resuming normal attacks."); + } + } + + if (hasCounterAttackAura) { Bot.Combat.CancelAutoAttack(); @@ -283,9 +427,9 @@ void Fight() Bot.Combat.Attack(boss); } - Bot.Sleep(500); // slightly lower, smoother attacks - } - } + Bot.Sleep(500); // slightly lower, smoother attacks + } + } void DoEnhs() { diff --git a/Ultras/UltraGramiel.cs b/Ultras/UltraGramiel.cs index 6361edb70..78c40533e 100644 --- a/Ultras/UltraGramiel.cs +++ b/Ultras/UltraGramiel.cs @@ -1,7 +1,12 @@ /* name: UltraGramiel -description: Ultra Gramiel - auto-assigns role by class: SC/IT→Left T1, LC→Left T2, LOO→Right T1, VDK→Right T2. Off-comp classes use the "Custom Role" dropdown. -tags: ultra, gramiel, Ultra Gramiel +description: Ultra Gramiel helper with fixed taunt-role assignments and crystal-phase recovery. +tags: Ultra + +Fight notes: +- Composition order is [slot 1-4]: Recommended = SC/IT / AP / LOO / VHL, Alternate = SC/IT / LC / LOO / VDK. +- Fixed taunter slots are slot 1 (Left T1), slot 2 (Left T2), slot 3 (Right T1), slot 4 (Right T2). +- If you run off-comp classes, set your role manually with Custom Role. */ //cs_include Scripts/Ultras/CoreEngine.cs @@ -13,82 +18,10 @@ using System; using System.Collections.Generic; +using System.Linq; using Skua.Core.Interfaces; using Skua.Core.Options; -#region Recommended Comp - -/// -/// Recommended -/// -// LEFT CRYSTAL (MapID 2) - T1 & T2 Taunters -// ────────────────────────────────────────── -// StoneCrusher / Infinity Titan (T1) -// ├─ Class: Fighter -// ├─ Helm: Anima -// ├─ Weapon: Valiance -// ├─ Cape: Absolution -// └─ Potions: Sage Tonic + Crusader Elixir -// ArchPaladin (T2) -// ├─ Class: Lucky -// ├─ Helm: Lucky -// ├─ Weapon: Awe Blast -// ├─ Cape: Penitence -// └─ Potions: Potent Malevolence Elixir + Sage Tonic -// -// RIGHT CRYSTAL (MapID 3) - T1 & T2 Taunters -// ────────────────────────────────────────── -// Lord Of Order (T1) -// ├─ Class: Lucky -// ├─ Helm: Forge -// ├─ Weapon: Arcanas Concerto / Valiance / Awe Blast -// ├─ Cape: Penitence -// └─ Potions: Divine Elixir + Sage Tonic -// Void Highlord (T2) -// ├─ Class: Lucky -// ├─ Helm: Lucky -// ├─ Weapon: Valiance -// ├─ Cape: Lament -// └─ Potions: Potent Battle Elixir + Sage Tonic - -#endregion - -#region Alternate Comp - -/// -/// Alternate -/// -// LEFT CRYSTAL (MapID 2) - T1 & T2 Taunters -// ────────────────────────────────────────── -// StoneCrusher (T1) -// ├─ Class: Fighter -// ├─ Helm: Wizard -// ├─ Weapon: Valiance -// ├─ Cape: Absolution -// └─ Potions: Sage Tonic + Crusader Elixir -// LightCaster (T2) -// ├─ Class: Lucky -// ├─ Helm: Forge / Pneuma -// ├─ Weapon: Valiance / Awe Blast -// ├─ Cape: Penitence / Lament -// └─ Potions: Potent Malevolence Elixir + Sage Tonic -// RIGHT CRYSTAL (MapID 3) - T1 & T2 Taunters -// ────────────────────────────────────────── -// Lord Of Order (T1) -// ├─ Class: Lucky -// ├─ Helm: Forge -// ├─ Weapon: Arcanas / Valiance / Awe Blast -// ├─ Cape: Penitence / Absolution -// └─ Potions: Divine Elixir + Sage Tonic -// Verus DoomKnight (T2) -// ├─ Class: Lucky -// ├─ Helm: Anima / Forge -// ├─ Weapon: Valiance / Ravenous -// ├─ Cape: Vainglory / Lament -// └─ Potions: Potent Battle Elixir + Sage Tonic - -#endregion - public class UltraGramiel { private static CoreAdvanced Adv @@ -104,70 +37,140 @@ private static CoreAdvanced Adv public bool DontPreconfigure = true; public string OptionsStorage = "UltraGramiel"; + public string[] MultiOptions = { "Main", "CoreSettings", "ClassOverrides" }; - // Gramiel warning tracking - private int tauntCounter = 0; - private DateTime lastTauntWarningTime = DateTime.MinValue; - private bool shouldExecuteTaunt = false; - - // Gramiel boss taunt timer (4 rotation, 5 seconds per taunt) - private DateTime gramielFightStartTime = DateTime.MinValue; - private double tauntOffsetSeconds = 0; - private const double TauntIntervalSeconds = 20.0; // Full 4-taunt cycle - private const double TauntWindowSeconds = 4.0; // Window to execute taunt - - // Player role assignment (determined once during prep) - private int crystalMapId = 2; - private bool isT1Taunter = false; - private int crystalDeathCount = 0; - - public List Options = new() + public List Main = new() { new Option( "DoEquipClasses", "Automatically Equip Classes", "Auto-equip classes across all 4 clients\n" - + "Recommended: SC / IT, LoO, AP, VHL\n" + + "Recommended: SC / IT, AP, LOO, VHL\n" + "Alternate: SC / IT, LC, LOO, VDK\n" - + "Unselected = off (use whatever classes you already have equipped).", - GramielComp.Unselected + + "Unselected = off (use current classes).", + GramielComp.Recommended ), new Option( "CustomRole", "Custom Role", - "Used if you are not using a pre-defined comp. Pick which slot you're filling.", + "Used only when your class is off-comp and cannot be auto-mapped.", CustomRole.Unselected ), - new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), CoreBots.Instance.SkipOptions, }; + public List CoreSettings = new() + { + new Option("EquipBestGear", "Equip Best Gear", "Equip best gear for encounter", true), + new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), + new Option("RestoreGear", "Restore Gear", "Restore original gear after the script finishes", false), + new Option("UseLifeSteal", "Use LifeSteal", "Unused here because every fixed role taunts with Enrage.", true), + }; + + public List ClassOverrides = new() + { + new Option("a", "Slot 1 Class Override (Left T1)", "Blank = use selected comp default for slot 1.", ""), + new Option("b", "Slot 2 Class Override (Left T2)", "Blank = use selected comp default for slot 2.", ""), + new Option("c", "Slot 3 Class Override (Right T1)", "Blank = use selected comp default for slot 3.", ""), + new Option("d", "Slot 4 Class Override (Right T2)", "Blank = use selected comp default for slot 4.", ""), + }; + + private int tauntCounter; + private DateTime lastTauntWarningTime = DateTime.MinValue; + private bool shouldExecuteTaunt; + private DateTime gramielFightStartTime = DateTime.MinValue; + private double tauntOffsetSeconds; + private const double TauntIntervalSeconds = 20.0; + private const double TauntWindowSeconds = 4.0; + + private int crystalMapId = 2; + private bool isT1Taunter; + private int crystalDeathCount; + + private GramielComp ActiveComp = GramielComp.Recommended; + private CustomRole ActiveCustomRole = CustomRole.Unselected; + private bool EquipBestGear; + private bool DoEnhancements; + private bool UseLifeSteal; + private bool RestoreGear; + private string overrideA = string.Empty; + private string overrideB = string.Empty; + private string overrideC = string.Empty; + private string overrideD = string.Empty; + public void ScriptMain(IScriptInterface bot) { - C.OneTimeMessage("Ultra Gramiel", - "This is a technical fight requiring optimal enhancements and classes.\n" - + "Recommended comp: SC / IT, LoO, AP, VHL.\n" - + "Alternate comp: SC / IT, LC, LOO, VDK.\n" - + "The crystal phase is RNG and deaths will occur.\n" - + "If you are not prepared, please do not run this script.", + if (Bot.Config != null && !Bot.Config.Get("Main", "SkipOption")) + Bot.Config.Configure(); + + C.OneTimeMessage( + "Ultra Gramiel", + "This encounter requires synchronized taunts and role fidelity.\n" + + "Recommended comp: SC/IT, AP, LOO, VHL.\n" + + "Alternate comp: SC/IT, LC, LOO, VDK.", true, true ); - Bot.Options.LagKiller = true; - if ( - Bot.Config != null - && Bot.Config.Options.Contains(C.SkipOptions) - && !Bot.Config.Get(C.SkipOptions) - ) - Bot.Config.Configure(); + ActiveComp = Bot.Config == null ? GramielComp.Recommended : Bot.Config.Get("Main", "DoEquipClasses"); + ActiveCustomRole = Bot.Config == null ? CustomRole.Unselected : Bot.Config.Get("Main", "CustomRole"); + overrideA = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty)).Trim(); + overrideB = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "b") ?? string.Empty)).Trim(); + overrideC = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "c") ?? string.Empty)).Trim(); + overrideD = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "d") ?? string.Empty)).Trim(); + EquipBestGear = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "EquipBestGear"); + DoEnhancements = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "DoEnh"); + UseLifeSteal = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "UseLifeSteal"); + RestoreGear = Bot.Config == null ? false : Bot.Config.Get("CoreSettings", "RestoreGear"); + + Adv.GearStore(); + try + { + Run( + ActiveComp, + EquipBestGear, + DoEnhancements, + UseLifeSteal, + ActiveCustomRole, + overrideA, + overrideB, + overrideC, + overrideD + ); + } + finally + { + if (RestoreGear) + Adv.GearStore(true, true); + C.SetOptions(false); + Bot.StopSync(); + } + } + + public void Run( + GramielComp comp = GramielComp.Recommended, + bool equipBestGear = true, + bool doEnhancements = true, + bool useLifeSteal = true, + CustomRole customRole = CustomRole.Unselected, + string? classAOverride = null, + string? classBOverride = null, + string? classCOverride = null, + string? classDOverride = null + ) + { + ActiveComp = comp; + EquipBestGear = equipBestGear; + DoEnhancements = doEnhancements; + UseLifeSteal = useLifeSteal; + ActiveCustomRole = customRole; + overrideA = classAOverride?.Trim() ?? string.Empty; + overrideB = classBOverride?.Trim() ?? string.Empty; + overrideC = classCOverride?.Trim() ?? string.Empty; + overrideD = classDOverride?.Trim() ?? string.Empty; Core.Boot(); - Adv.GearStore(EnhAfter: true); - - // Register packet handler for Gramiel warnings Bot.Events.ExtensionPacketReceived += GramielMessageListener; - try { Prep(); @@ -175,45 +178,62 @@ public void ScriptMain(IScriptInterface bot) } finally { - // Unregister packet handler Bot.Events.ExtensionPacketReceived -= GramielMessageListener; } - - if (Bot.Config!.Get("DoEnh")) - Adv.GearStore(true, true); - Bot.StopSync(); } void Prep(bool skipEnhancements = false) { - // Sync-equip classes if a comp is selected - GramielComp comp = Bot.Config!.Get("DoEquipClasses"); - if (comp != GramielComp.Unselected) - { - string[][] classes = comp switch - { - GramielComp.Recommended => new[] { - new[] { "StoneCrusher", "Infinity Titan" }, - new[] { "ArchPaladin" }, - new[] { "Lord Of Order" }, - new[] { "Void Highlord" } - }, - GramielComp.Alternate => new[] { - new[] { "StoneCrusher", "Infinity Titan" }, - new[] { "LightCaster" }, - new[] { "Lord Of Order" }, - new[] { "Verus DoomKnight" } - }, - _ => throw new NotImplementedException(), - }; - - Ultra.EquipClassSync(classes, 4, "gramiel_class.sync"); - } + ApplyCompAndEquip(ActiveComp, overrideA, overrideB, overrideC, overrideD); - if (Bot.Config!.Get("DoEnh") && !skipEnhancements) + if (EquipBestGear) + EquipBestDmgGear(); + + if (DoEnhancements && !skipEnhancements) DoEnhs(); - // Auto-detect role from equipped class; fall back to dropdown for off-meta + AssignRoleFromClassOrCustom(); + + Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); + Ultra.BuyAlchemyPotion("Potent Honor Potion"); + Core.EquipConsumable("Potent Honor Potion"); + + if (UseLifeSteal) + C.Logger("UseLifeSteal is enabled, but Gramiel uses Enrage on all fixed taunt roles."); + Ultra.GetScrollOfEnrage(); + + Bot.Sleep(2500); + } + + void ApplyCompAndEquip(GramielComp comp, string aOverride, string bOverride, string cOverride, string dOverride) + { + if (comp == GramielComp.Unselected) + return; + + string[] classes = comp switch + { + GramielComp.Recommended => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "StoneCrusher" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "ArchPaladin" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "Lord Of Order" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Void Highlord" : dOverride, + }, + GramielComp.Alternate => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "StoneCrusher" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "LightCaster" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "Lord Of Order" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Verus DoomKnight" : dOverride, + }, + _ => throw new InvalidOperationException($"Unhandled GramielComp value: {comp}") + }; + + Ultra.EquipClassSync(classes, 4, "gramiel_class.sync"); + } + + void AssignRoleFromClassOrCustom() + { string className = Bot.Player.CurrentClass?.Name ?? string.Empty; switch (className) { @@ -237,68 +257,59 @@ void Prep(bool skipEnhancements = false) isT1Taunter = false; break; default: - // Off-comp class — use the "Custom Role" dropdown - var role = Bot.Config!.Get("CustomRole"); - if (role == CustomRole.Unselected) + if (ActiveCustomRole == CustomRole.Unselected) { - C.Logger($"Your class \"{className}\" isn't auto-mapped. Please select a Custom Role in the options.", "Fix This", true, true); + C.Logger($"Your class '{className}' is not auto-mapped. Set Main > Custom Role.", "Fix This", true, true); return; } - switch (role) + + switch (ActiveCustomRole) { case CustomRole.LeftCrystalT1: - crystalMapId = 2; isT1Taunter = true; break; + crystalMapId = 2; + isT1Taunter = true; + break; case CustomRole.LeftCrystalT2: - crystalMapId = 2; isT1Taunter = false; break; + crystalMapId = 2; + isT1Taunter = false; + break; case CustomRole.RightCrystalT1: - crystalMapId = 3; isT1Taunter = true; break; + crystalMapId = 3; + isT1Taunter = true; + break; case CustomRole.RightCrystalT2: - crystalMapId = 3; isT1Taunter = false; break; + crystalMapId = 3; + isT1Taunter = false; + break; } - C.Logger($"Off-comp class \"{className}\" — using Custom Role: {role}"); + C.Logger($"Off-comp class '{className}' using Custom Role: {ActiveCustomRole}"); break; } - C.Logger($"Assigned to crystal ID '{crystalMapId}' as {(isT1Taunter ? "T1" : "T2")}"); - - // Calculate taunt offset for timer-based rotation - // Left T1: 0s, Left T2: 5s, Right T1: 10s, Right T2: 15s if (crystalMapId == 2 && isT1Taunter) tauntOffsetSeconds = 0; else if (crystalMapId == 2 && !isT1Taunter) tauntOffsetSeconds = 5; else if (crystalMapId == 3 && isT1Taunter) tauntOffsetSeconds = 10; - else // Right T2 + else tauntOffsetSeconds = 15; - C.Logger($"Gramiel taunt offset: {tauntOffsetSeconds}s"); - - // Potions based on equipped class - switch (className) - { - case "StoneCrusher": - case "Infinity Titan": - Ultra.UseAlchemyPotions("Sage Tonic", "Crusader Elixir"); - break; - case "LightCaster": - case "ArchPaladin": - Ultra.UseAlchemyPotions("Potent Malevolence Elixir", "Sage Tonic"); - break; - case "Lord Of Order": - Ultra.UseAlchemyPotions("Divine Elixir", "Sage Tonic"); - break; - case "Verus DoomKnight": - case "Void Highlord": - Ultra.UseAlchemyPotions("Potent Battle Elixir", "Sage Tonic"); - break; - default: - Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); - break; - } + C.Logger($"Assigned crystal role: mapId={crystalMapId}, slot={(isT1Taunter ? "T1" : "T2")}, offset={tauntOffsetSeconds}s."); + } - Ultra.GetScrollOfEnrage(); - Bot.Sleep(2500); + void EquipBestDmgGear() + { + C.EquipBestItemsForMeta( + new Dictionary + { + { "Weapon", new[] { "dmgAll", "dmg", "damage" } }, + { "Armor", new[] { "dmgAll", "dmg", "damage" } }, + { "Helm", new[] { "dmgAll", "dmg", "damage" } }, + { "Cape", new[] { "dmgAll", "dmg", "damage" } }, + { "Pet", new[] { "dmgAll", "dmg", "damage" } }, + } + ); } void Fight() @@ -309,37 +320,24 @@ void Fight() Ultra.ClearSyncFile(syncPath); Bot.Sleep(2500); - // --------------------------- - // WHITEMAP STAGING - // --------------------------- Core.Join("whitemap"); Bot.Wait.ForMapLoad("whitemap"); - - // Wait for army to gather Ultra.WaitForArmy(3, "UltraItemCheck.sync"); Bot.Sleep(1500); - // --------------------------- - // MAP SETUP - // --------------------------- C.EnsureAccept(10301); C.AddDrop("Gramiel the Graceful Vanquished"); - + Core.Join(map); Ultra.WaitForArmy(3, "ultra_gramiel.sync"); Core.ChooseBestCell("*"); Bot.Player.SetSpawnPoint(); Core.EnableSkills(); - // --------------------------- - // MAIN COMBAT LOOP - // --------------------------- while (!Bot.ShouldExit) { - bool anyCrystalAlive = Bot.Monsters.CurrentAvailableMonsters - .Any(x => x != null && x.Alive && (x.MapID == 2 || x.MapID == 3)); + bool anyCrystalAlive = Bot.Monsters.CurrentAvailableMonsters.Any(x => x != null && x.Alive && (x.MapID == 2 || x.MapID == 3)); - // player death during crystal phase if (!Bot.Player.Alive && anyCrystalAlive) { crystalDeathCount++; @@ -347,20 +345,16 @@ void Fight() while (!Bot.Player.Alive && !Bot.ShouldExit) Bot.Sleep(500); Bot.Sleep(250); - - // 1st death: respawn and continue fighting + if (crystalDeathCount < 2) - { continue; - } - - // 2nd death: leave room and restart + Core.DisableSkills(); - C.Logger("2nd crystal phase death — leaving room to restart and avoid desync."); + C.Logger("2nd crystal phase death: restarting room to avoid desync."); tauntCounter = 0; crystalDeathCount = 0; gramielFightStartTime = DateTime.MinValue; - + Core.Join("whitemap"); Bot.Wait.ForMapLoad("whitemap"); Ultra.ClearSyncFile(syncPath); @@ -377,20 +371,19 @@ void Fight() continue; } - // restart if any army member is missing -- catches deaths during crystal phase that would cause desync if (Bot.Map.PlayerCount < 3) { Core.DisableSkills(); - C.Logger("Army member missing (during crystal phase?) - restarting!"); + C.Logger("Army member missing; restarting room."); tauntCounter = 0; crystalDeathCount = 0; gramielFightStartTime = DateTime.MinValue; - + Core.Join("whitemap"); Bot.Wait.ForMapLoad("whitemap"); Prep(skipEnhancements: true); Ultra.WaitForArmy(3, "ultra_gramiel.sync"); - + Core.Join(map); Bot.Wait.ForMapLoad(map); Core.ChooseBestCell("*"); @@ -399,7 +392,6 @@ void Fight() continue; } - // Dead during Gramiel phase → just respawn if (!Bot.Player.Alive) { Bot.Wait.ForTrue(() => Bot.Player.Alive, 20); @@ -407,7 +399,6 @@ void Fight() continue; } - // Check if the whole army has finished if (Ultra.CheckArmyProgressBool(() => C.CheckInventory("Gramiel the Graceful Vanquished", 1), syncPath)) { Core.DisableSkills(); @@ -418,9 +409,6 @@ void Fight() break; } - // --------------------------- - // CRYSTAL & BOSS COMBAT - // --------------------------- AttackWithPriority(); Bot.Sleep(250); } @@ -429,70 +417,30 @@ void Fight() void DoEnhs() { string className = Bot.Player.CurrentClass?.Name.ToLower() ?? string.Empty; - if (string.IsNullOrEmpty(className)) return; - // Enhance based on currently equipped class switch (className) { - // StoneCrusher / Infinity Titan case "stonecrusher": case "infinity titan": - Adv.EnhanceEquipped( - type: EnhancementType.Fighter, - hSpecial: HelmSpecial.Anima, - wSpecial: WeaponSpecial.Valiance, - cSpecial: CapeSpecial.Absolution - ); + Adv.EnhanceEquipped(type: EnhancementType.Fighter, hSpecial: HelmSpecial.Anima, wSpecial: WeaponSpecial.Valiance, cSpecial: CapeSpecial.Absolution); break; - - // Lord Of Order case "lord of order": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: WeaponSpecial.Arcanas_Concerto, - cSpecial: CapeSpecial.Penitence - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Forge, wSpecial: WeaponSpecial.Arcanas_Concerto, cSpecial: CapeSpecial.Penitence); break; - - // LightCaster case "lightcaster": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Pneuma, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Penitence - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Pneuma, wSpecial: WeaponSpecial.Ravenous, cSpecial: CapeSpecial.Penitence); break; - - // Verus DoomKnight case "verus doomknight": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Anima, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Vainglory - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Anima, wSpecial: WeaponSpecial.Ravenous, cSpecial: CapeSpecial.Vainglory); break; - case "archpaladin": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - wSpecial: WeaponSpecial.Awe_Blast, - cSpecial: CapeSpecial.Penitence - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, wSpecial: WeaponSpecial.Awe_Blast, cSpecial: CapeSpecial.Penitence); break; - case "void highlord": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - wSpecial: WeaponSpecial.Valiance, - cSpecial: CapeSpecial.Lament - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, wSpecial: WeaponSpecial.Valiance, cSpecial: CapeSpecial.Lament); break; - default: Adv.SmartEnhance(Bot.Player.CurrentClass!.Name); break; @@ -502,17 +450,16 @@ void DoEnhs() void AttackWithPriority() { string className = Bot.Player.CurrentClass?.Name ?? string.Empty; - int gramielMapId = 1; // Gramiel MapID - - // Execute taunt if flagged (synchronously, blocking other actions) + const int gramielMapId = 1; + if (shouldExecuteTaunt) { shouldExecuteTaunt = false; Core.DisableSkills(); Bot.Sleep(500); - - C.Logger($"{className} executing taunt #{tauntCounter}!"); - + + C.Logger($"{className} executing taunt #{tauntCounter}."); + int attempts = 0; bool tauntLanded = false; while (!Bot.ShouldExit && attempts < 15) @@ -521,18 +468,16 @@ void AttackWithPriority() break; if (!Bot.Player.HasTarget) - Bot.Combat.Attack(crystalMapId); // Re-target crystal - + Bot.Combat.Attack(crystalMapId); + if (Bot.Skills.CanUseSkill(5)) Bot.Skills.UseSkill(5); Bot.Sleep(500); attempts++; - // Check if Focus aura appeared (taunt landed) if (Bot.Player.HasTarget && Bot.Target?.Auras?.Any(a => a?.Name == "Focus") == true) { - C.Logger($"Taunt #{tauntCounter} landed after {attempts} attempts"); tauntLanded = true; Bot.Sleep(500); break; @@ -540,45 +485,28 @@ void AttackWithPriority() } if (!tauntLanded) - { - C.Logger($"WARNING: Taunt #{tauntCounter} FAILED after {attempts} attempts!"); - if (Bot.Player.HasTarget && Bot.Target?.Auras != null) - { - string auraNames = string.Join(", ", Bot.Target.Auras.Select(a => a?.Name ?? "null")); - C.Logger($"Target auras present: {auraNames}"); - } - } + C.Logger($"Taunt #{tauntCounter} did not land after {attempts} attempts."); Core.EnableSkills(); Bot.Sleep(300); return; } - // Check if primary crystal is alive - bool primaryCrystalAlive = Bot.Monsters.CurrentAvailableMonsters - .Any(x => x != null && x.Alive && x.MapID == crystalMapId); - - // If primary crystal is dead, switch to the other crystal + bool primaryCrystalAlive = Bot.Monsters.CurrentAvailableMonsters.Any(x => x != null && x.Alive && x.MapID == crystalMapId); + int targetCrystalMapId = crystalMapId; if (!primaryCrystalAlive) { int otherCrystalMapId = crystalMapId == 2 ? 3 : 2; - bool otherCrystalAlive = Bot.Monsters.CurrentAvailableMonsters - .Any(x => x != null && x.Alive && x.MapID == otherCrystalMapId); - + bool otherCrystalAlive = Bot.Monsters.CurrentAvailableMonsters.Any(x => x != null && x.Alive && x.MapID == otherCrystalMapId); if (otherCrystalAlive) - { targetCrystalMapId = otherCrystalMapId; - C.Logger($"Primary crystal down! Switching to other crystal (MapID {otherCrystalMapId})"); - } } - - bool anyCrystalAlive = Bot.Monsters.CurrentAvailableMonsters - .Any(x => x != null && x.Alive && (x.MapID == 2 || x.MapID == 3)); + + bool anyCrystalAlive = Bot.Monsters.CurrentAvailableMonsters.Any(x => x != null && x.Alive && (x.MapID == 2 || x.MapID == 3)); if (anyCrystalAlive) { - // Attack crystal Bot.Combat.Attack(targetCrystalMapId); } else @@ -586,47 +514,40 @@ void AttackWithPriority() if (gramielFightStartTime == DateTime.MinValue) { gramielFightStartTime = DateTime.Now; - C.Logger("Both crystals down! Starting Gramiel fight timer."); + C.Logger("Both crystals are down; starting Gramiel taunt timer."); } - // No crystal - attack Gramiel with timer-based taunts Bot.Combat.Attack(gramielMapId); - - // Timer-based taunt rotation (staggered 5-second intervals) + TimeSpan timeSinceFightStart = DateTime.Now - gramielFightStartTime; double currentTime = timeSinceFightStart.TotalSeconds; double timeInCycle = (currentTime - tauntOffsetSeconds) % TauntIntervalSeconds; - - // Check if we're in our taunt window (0-4 seconds into our slot) + bool inTauntWindow = timeInCycle >= 0 && timeInCycle < TauntWindowSeconds; - bool noFocusAura = Bot.Player.HasTarget && - (Bot.Target?.Auras?.Any(a => a?.Name == "Focus") != true); - + bool noFocusAura = Bot.Player.HasTarget && (Bot.Target?.Auras?.Any(a => a?.Name == "Focus") != true); + if (inTauntWindow && noFocusAura) { Core.DisableSkills(); Bot.Sleep(500); - - C.Logger($"Gramiel taunt window ({currentTime:F1}s into fight, offset {tauntOffsetSeconds}s)"); - + int attempts = 0; while (!Bot.ShouldExit && attempts < 15) { if (!Bot.Player.Alive) break; - + if (!Bot.Player.HasTarget) Bot.Combat.Attack(gramielMapId); - + if (Bot.Skills.CanUseSkill(5)) Bot.Skills.UseSkill(5); - + Bot.Sleep(500); attempts++; - + if (Bot.Player.HasTarget && Bot.Target?.Auras?.Any(a => a?.Name == "Focus") == true) { - C.Logger("Gramiel taunt landed - Focus aura detected!"); Bot.Sleep(500); break; } @@ -638,7 +559,6 @@ void AttackWithPriority() } } - // Packet Handler for Gramiel Warning Messages private void GramielMessageListener(dynamic packet) { try @@ -646,53 +566,37 @@ private void GramielMessageListener(dynamic packet) string type = packet["params"].type; if (type is not "json") return; - + if (!Bot.Player.Alive) return; - + dynamic data = packet["params"].dataObj; string cmd = data.cmd.ToString(); - if (cmd != "ct") return; - - // Check for messages in anims array (boss messages appear here) + if (data.anims is null) return; - + foreach (dynamic anim in data.anims) { if (anim is null || anim.msg is null) continue; - + string message = (string)anim.msg; - - // Check for crystal defense shattering attack warning - if (message.Contains("The Grace Crystal prepares a defense shattering attack!", StringComparison.OrdinalIgnoreCase)) - { - // Debounce: Ignore if we received this message within the last 2 seconds -- avoid double taunts - TimeSpan timeSinceLastWarning = DateTime.Now - lastTauntWarningTime; - if (timeSinceLastWarning.TotalSeconds < 2) - { - return; - } - - lastTauntWarningTime = DateTime.Now; - tauntCounter++; - C.Logger($"Crystal attack warning detected! (Taunt #{tauntCounter})"); - - // Determine if this player should taunt - // T1 taunters taunt on odd counts (1, 3, 5...) - // T2 taunters taunt on even counts (2, 4, 6...) - bool shouldTaunt = (isT1Taunter && tauntCounter % 2 == 1) || - (!isT1Taunter && tauntCounter % 2 == 0); - - if (shouldTaunt) - { - C.Logger($"Taunt #{tauntCounter} assigned to {(isT1Taunter ? "T1" : "T2")}"); - shouldExecuteTaunt = true; - } - } + if (!message.Contains("The Grace Crystal prepares a defense shattering attack!", StringComparison.OrdinalIgnoreCase)) + continue; + + TimeSpan timeSinceLastWarning = DateTime.Now - lastTauntWarningTime; + if (timeSinceLastWarning.TotalSeconds < 2) + return; + + lastTauntWarningTime = DateTime.Now; + tauntCounter++; + + bool shouldTaunt = (isT1Taunter && tauntCounter % 2 == 1) || (!isT1Taunter && tauntCounter % 2 == 0); + if (shouldTaunt) + shouldExecuteTaunt = true; } } catch { } diff --git a/Ultras/UltraNulgath.cs b/Ultras/UltraNulgath.cs index e8b6cde68..fbe324854 100644 --- a/Ultras/UltraNulgath.cs +++ b/Ultras/UltraNulgath.cs @@ -1,375 +1,522 @@ -/* -name: UltraNulgath -description: Nulgath the Archfiend helper with taunter rotation and blade priority. -tags: Ultra -*/ - -//cs_include Scripts/Ultras/CoreEngine.cs -//cs_include Scripts/Ultras/CoreUltra.cs -//cs_include Scripts/CoreBots.cs -//cs_include Scripts/CoreFarms.cs -//cs_include Scripts/CoreAdvanced.cs -//cs_include Scripts/CoreStory.cs -using Skua.Core.Interfaces; -using Skua.Core.Options; - -#region Fast Comp -// Chrono ShadowSlayer: Lucky | Vim | Valiance | Vainglory -// Verus DoomKnight: Lucky | Anima | Ravenous | Vainglory -// Legion Revenant: Wizard | Pneuma | Valiance/Ravenous/Arcana | Vainglory -// Lord Of Order: Lucky | Forge | Awe Blast/Valiance | Absolution -#endregion - -#region F2P Fast -// Dragon of Time (x2): Wizard | Pneuma | Elysium | Vainglory -// Legion Revenant: Wizard | Pneuma | Valiance/Ravenous/Arcana | Vainglory -// Lord Of Order: Lucky | Forge | Awe Blast/Valiance | Absolution -#endregion - -#region Common Comp -// King's Echo: Lucky | Examen | Ravenous | Vainglory -// Legion Revenant: Wizard | Pneuma | Valiance/Ravenous/Arcana | Vainglory -// ArchPaladin: Lucky | Forge | Valiance | Lament -// Lord Of Order: Lucky | Forge | Awe Blast/Valiance | Absolution -#endregion - -#region Balanced Comp -// Legion Revenant: Wizard | Pneuma | Valiance/Ravenous/Arcana | Vainglory -// ArchPaladin: Lucky | Forge | Valiance | Lament -// StoneCrusher: Wizard | Pneuma | Valiance | Vainglory -// Lord Of Order: Lucky | Forge | Awe Blast/Valiance | Absolution -#endregion - -#region Other DPS Options -// Arcana Invoker: Lucky | Examen | Ravenous | Vainglory -// Archfiend: Lucky | Forge | Ravenous | Vainglory -// Lich: Lucky | Examen | Ravenous | Vainglory -#endregion - -public class UltraNulgath -{ - private static CoreAdvanced Adv - { - get => _Adv ??= new CoreAdvanced(); - set => _Adv = value; - } - private CoreBots C => CoreBots.Instance; - private static CoreAdvanced _Adv; - public IScriptInterface Bot => IScriptInterface.Instance; - public CoreEngine Core = new(); - public CoreUltra Ultra = new(); - - public bool DontPreconfigure = true; - public string OptionsStorage = "UltraNulgath"; - public List Options = new() - { - new Option( - "DoEquipClasses", - "Automatically Equip Classes", - "Auto-equip classes across all 4 clients\n" - + "Fast: CSS / VDK / LR / LOO\n" - + "F2PFast: DoT / DoT / LR / LOO\n" - + "Common: KE / LR / AP / LOO\n" - + "Balanced: LR / AP / SC / LOO\n" - + "Unselected = off (use whatever classes you already have equipped).", - NulgathComp.Unselected - ), - new Option( "a", "Taunter 1 ClassName", "Names must be exact including punctuation, spelling, and captitalization", "ArchPaladin"), - new Option( "b", "Taunter 2 ClassName", "Names must be exact including punctuation, spelling, and captitalization", "Lord Of Order"), - new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), - CoreBots.Instance.SkipOptions, - }; - - public void ScriptMain(IScriptInterface bot) - { - C.OneTimeMessage( - "Ultra Nulgath", - "Deaths more then likely will happen, Suggested class and thier enhs are in the script at the top" - ); - - if ( - Bot.Config != null - && Bot.Config.Options.Contains(C.SkipOptions) - && !Bot.Config.Get(C.SkipOptions) - ) - Bot.Config.Configure(); - - a = (Bot.Config!.Get("a") ?? "").Trim(); - b = (Bot.Config!.Get("b") ?? "").Trim(); - if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b)) - { - C.Logger("Setup", "Fill both taunter classes in Script Options."); - C.SetOptions(false); - } - - Core.Boot(); - Prep(); - Fight(); - C.SetOptions(false); - } - - string a, b; - // Overfiend Blade = 1 - // Nulgath = 2 - void Fight() - { - #region ignore this - const string map = "ultranulgath"; - const string boss = "Nulgath the Archfiend"; - string syncPath = Ultra.ResolveSyncPath("UltraItemCheck.sync"); - Ultra.ClearSyncFile(syncPath); - Bot.Sleep(2500); - C.EnsureAccept(8692); - C.AddDrop("Nulgath Insignia"); - Core.Join(map); - Ultra.WaitForArmy(3, "ultra_nulgath.sync"); - - Core.ChooseBestCell(boss); - Bot.Player.SetSpawnPoint(); - Core.EnableSkills(); - #endregion - while (!Bot.ShouldExit) - { - if (!Bot.Player.Alive) - { - Bot.Wait.ForTrue(() => Bot.Player.Alive, 20); - continue; - } - - if (Ultra.CheckArmyProgressBool(() => Bot.TempInv.Contains("Nulgath the Archfiend Defeated?", 1), syncPath)) - { - C.Logger("All players finished farm."); - Core.DisableSkills(); - Bot.Sleep(200); - C.Jump("Enter", "Spawn"); - C.Join("whitemap"); - if (!Bot.Quests.IsDailyComplete(8692)) - C.EnsureComplete(8692); - if (Bot.Config!.Get("DoEnh")) - Adv.GearStore(true, true); - break; - } - - // Taunters focus nulgath - if (Bot.Player.CurrentClass?.Name == a || Bot.Player.CurrentClass?.Name == b) - { - //taunters focus Nulgath (MID 2) - Bot.Combat.Attack(2); - Bot.Sleep(200); - } - else - { - //DPSers attack the Overfiend Blade(1) and when it dies, swap to nulgath(2), (refocusing "Overfiend Blade" when it respawns) - if (Bot.Monsters.MapMonsters.Any(x => x != null && x.MapID == 1 && x.HP > 0)) - Bot.Combat.Attack(1); // Overfiend Blade - else - Bot.Combat.Attack(2); // Nulgath - Bot.Sleep(200); - } - - // Taunter logic - if (Bot.Player.Alive && (Bot.Player.CurrentClass?.Name == a || Bot.Player.CurrentClass?.Name == b) && !Bot.Target.Auras.Any(x => x?.Name == "Focus") - && Bot.Monsters.MapMonsters.Any(x => (x?.MapID == 2 || x?.MapID == 1) && x.HP > 0)) - { - Core.DisableSkills(); - while (!Bot.ShouldExit && !Bot.Target.Auras.Any(x => x?.Name == "Focus")) - { - if (!Bot.Player.Alive) - { - Bot.Wait.ForTrue(() => Bot.Player.Alive, 20); - continue; - } - - if (!Bot.Target.Auras.Any(x => x != null && x?.Name == "Focus")) - Bot.Skills.UseSkill(5); - else - break; - - Bot.Sleep(500); - } - Core.EnableSkills(); - } - - } - } - - - void Prep() - { - // Sync-equip classes if a comp is selected - NulgathComp comp = Bot.Config!.Get("DoEquipClasses"); - if (comp != NulgathComp.Unselected) - { - string[][] classes = comp switch - { - NulgathComp.Fast => new[] { - new[] { C.CheckInventory("Chrono ShadowSlayer") ? "Chrono ShadowSlayer" : "Chrono ShadowHunter" }, - new[] { "Verus DoomKnight" }, - new[] { "Legion Revenant" }, - new[] { "Lord Of Order" } - }, - NulgathComp.F2PFast => new[] { - new[] { "Dragon of Time" }, - new[] { "Dragon of Time" }, - new[] { "Legion Revenant" }, - new[] { "Lord Of Order" } - }, - NulgathComp.Common => new[] { - new[] { "King's Echo" }, - new[] { "Legion Revenant" }, - new[] { "ArchPaladin" }, - new[] { "Lord Of Order" } - }, - NulgathComp.Balanced => new[] { - new[] { "Legion Revenant" }, - new[] { "ArchPaladin" }, - new[] { C.CheckInventory("Infinity Titan") ? "Infinity Titan" : "StoneCrusher" }, - new[] { "Lord Of Order" } - }, - _ => throw new NotImplementedException(), - }; - - Ultra.EquipClassSync(classes, 4, "nulgath_class.sync", allowDuplicates: true); - } - - if (Bot.Config!.Get("DoEnh")) - { - Adv.GearStore(false, true); - DoEnhs(); - } - Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); - if (Bot.Inventory.Items.Any(x => x != null && x.Equipped && (x.Name == a || x.Name == b))) - { - Ultra.GetScrollOfEnrage(); - Core.EquipEnrage(); - } - } - void DoEnhs() - { - string className = Bot.Player!.CurrentClass?.Name ?? string.Empty; - if (string.IsNullOrEmpty(className)) - return; - - switch (className) - { - // Chrono ShadowSlayer - case "Chrono ShadowSlayer": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Vim, - wSpecial: WeaponSpecial.Valiance, - cSpecial: CapeSpecial.Vainglory - ); - break; - - // Verus DoomKnight - case "Verus DoomKnight": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Anima, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Vainglory - ); - break; - - // Legion Revenant - case "Legion Revenant": - Adv.EnhanceEquipped( - type: EnhancementType.Wizard, - hSpecial: HelmSpecial.Pneuma, - wSpecial: WeaponSpecial.Valiance, - cSpecial: CapeSpecial.Vainglory - ); - break; - - // Lord Of Order - case "Lord Of Order": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: WeaponSpecial.Awe_Blast, - cSpecial: CapeSpecial.Absolution - ); - break; - - // Dragon of Time - case "Dragon of Time": - Adv.EnhanceEquipped( - type: EnhancementType.Wizard, - hSpecial: HelmSpecial.Pneuma, - wSpecial: WeaponSpecial.Elysium, - cSpecial: CapeSpecial.Vainglory - ); - break; - - // King's Echo - case "King's Echo": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Examen, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Vainglory - ); - break; - - // Arcana Invoker - case "Arcana Invoker": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Examen, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Vainglory - ); - break; - - // Archfiend - case "Archfiend": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Vainglory - ); - break; - - // Lich - case "Lich": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Examen, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Vainglory - ); - break; - - // ArchPaladin - case "ArchPaladin": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: WeaponSpecial.Valiance, - cSpecial: CapeSpecial.Lament - ); - break; - - // StoneCrusher - case "StoneCrusher": - Adv.EnhanceEquipped( - type: EnhancementType.Wizard, - hSpecial: HelmSpecial.Pneuma, - wSpecial: WeaponSpecial.Valiance, - cSpecial: CapeSpecial.Vainglory - ); - break; - } - } - - public enum NulgathComp - { - Unselected, - Fast, - F2PFast, - Common, - Balanced, - } -} +/* +name: UltraNulgath +description: Nulgath the Archfiend helper with taunter rotation and blade priority. +tags: Ultra +*/ + +//cs_include Scripts/Ultras/CoreEngine.cs +//cs_include Scripts/Ultras/CoreUltra.cs +//cs_include Scripts/CoreBots.cs +//cs_include Scripts/CoreFarms.cs +//cs_include Scripts/CoreAdvanced.cs +//cs_include Scripts/CoreStory.cs +using System; +using System.Collections.Generic; +using System.Linq; +using Skua.Core.Interfaces; +using Skua.Core.Options; + +// NOTE: In all compositions below, slot 1 and slot 2 are the taunter roles. + +#region Fast Comp +// Chrono ShadowSlayer: Lucky | Vim | Valiance | Vainglory +// Verus DoomKnight: Lucky | Anima | Ravenous | Vainglory +// Legion Revenant: Wizard | Pneuma | Valiance/Ravenous/Arcana | Vainglory +// Lord Of Order: Lucky | Forge | Awe Blast/Valiance | Absolution +#endregion + +#region F2P Fast +// Dragon of Time (x2): Wizard | Pneuma | Elysium | Vainglory +// Legion Revenant: Wizard | Pneuma | Valiance/Ravenous/Arcana | Vainglory +// Lord Of Order: Lucky | Forge | Awe Blast/Valiance | Absolution +#endregion + +#region Common Comp +// King's Echo: Lucky | Examen | Ravenous | Vainglory +// Legion Revenant: Wizard | Pneuma | Valiance/Ravenous/Arcana | Vainglory +// ArchPaladin: Lucky | Forge | Valiance | Lament +// Lord Of Order: Lucky | Forge | Awe Blast/Valiance | Absolution +#endregion + +#region Balanced Comp +// Legion Revenant: Wizard | Pneuma | Valiance/Ravenous/Arcana | Vainglory +// ArchPaladin: Lucky | Forge | Valiance | Lament +// StoneCrusher: Wizard | Pneuma | Valiance | Vainglory +// Lord Of Order: Lucky | Forge | Awe Blast/Valiance | Absolution +#endregion + +#region Other DPS Options +// Arcana Invoker: Lucky | Examen | Ravenous | Vainglory +// Archfiend: Lucky | Forge | Ravenous | Vainglory +// Lich: Lucky | Examen | Ravenous | Vainglory +#endregion + +public class UltraNulgath +{ + private static CoreAdvanced Adv + { + get => _Adv ??= new CoreAdvanced(); + set => _Adv = value; + } + private CoreBots C => CoreBots.Instance; + private static CoreAdvanced _Adv; + public IScriptInterface Bot => IScriptInterface.Instance; + public CoreEngine Core = new(); + public CoreUltra Ultra = new(); + + public bool DontPreconfigure = true; + public string OptionsStorage = "UltraNulgath"; + public string[] MultiOptions = { "Main", "CoreSettings", "ClassOverrides" }; + + string a, b, c, d; + string overrideA, overrideB, overrideC, overrideD; + bool UseLifeSteal; + bool EquipBestGear; + bool DoEnhancements; + bool RestoreGear; + NulgathComp ActiveComp = NulgathComp.Fast; + + public List Main = new() + { + new Option( + "DoEquipClasses", + "Automatically Equip Classes", + "Auto-equip classes across all 4 clients\n" + + "Slots 1 and 2 are always taunters.\n" + + "Fast: CSS / VDK / LR / LOO\n" + + "F2PFast: DoT / DoT / LR / LOO\n" + + "Common: KE / LR / AP / LOO\n" + + "Balanced: LR / AP / SC / LOO\n" + + "Unselected = off (use manual classes below).", + NulgathComp.Fast + ), + CoreBots.Instance.SkipOptions, + }; + + public List CoreSettings = new() + { + new Option("EquipBestGear", "Equip Best Gear", "Equip best gear for encounter", true), + new Option("DoEnh", "Do Enhancements", "Auto-enhance for the currently equipped class", true), + new Option("RestoreGear", "Restore Gear", "Restore original gear after the script finishes", false), + new Option("UseLifeSteal", "Use LifeSteal", "Non-taunters equip/restock/use Scroll of Life Steal.", true), + }; + + public List ClassOverrides = new() + { + new Option("a", "Primary Class Override", "Blank = use selected comp default for slot 1 (taunter).", ""), + new Option("b", "Secondary Class Override", "Blank = use selected comp default for slot 2 (taunter).", ""), + new Option("c", "Tertiary Class Override", "Blank = use selected comp default for slot 3.", ""), + new Option("d", "Quaternary Class Override", "Blank = use selected comp default for slot 4.", ""), + }; + + public void ScriptMain(IScriptInterface bot) + { + C.OneTimeMessage( + "Ultra Nulgath", + "Deaths more then likely will happen, Suggested class and their enhs are in the script at the top" + ); + + if (Bot.Config != null && !Bot.Config.Get("Main", "SkipOption")) + Bot.Config.Configure(); + + ActiveComp = Bot.Config == null ? NulgathComp.Fast : Bot.Config.Get("Main", "DoEquipClasses"); + overrideA = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty)).Trim(); + overrideB = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "b") ?? string.Empty)).Trim(); + overrideC = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "c") ?? string.Empty)).Trim(); + overrideD = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "d") ?? string.Empty)).Trim(); + EquipBestGear = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "EquipBestGear"); + DoEnhancements = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "DoEnh"); + UseLifeSteal = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "UseLifeSteal"); + RestoreGear = Bot.Config == null ? false : Bot.Config.Get("CoreSettings", "RestoreGear"); + + a = overrideA; + b = overrideB; + c = overrideC; + d = overrideD; + + bool usingComp = ActiveComp != NulgathComp.Unselected; + if (!usingComp && (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b))) + { + C.Logger("Setup", "Fill taunter class overrides for slots 1 and 2."); + Bot.StopSync(); + return; + } + + Adv.GearStore(); + + try + { + Run(ActiveComp, EquipBestGear, DoEnhancements, UseLifeSteal, overrideA, overrideB, overrideC, overrideD); + } + finally + { + if (RestoreGear) + Adv.GearStore(true, true); + C.SetOptions(false); + Bot.StopSync(); + } + } + + public void Run( + NulgathComp comp = NulgathComp.Fast, + bool equipBestGear = true, + bool doEnhancements = true, + bool useLifeSteal = true, + string? classAOverride = null, + string? classBOverride = null, + string? classCOverride = null, + string? classDOverride = null + ) + { + ActiveComp = comp; + EquipBestGear = equipBestGear; + DoEnhancements = doEnhancements; + UseLifeSteal = useLifeSteal; + overrideA = classAOverride?.Trim() ?? string.Empty; + overrideB = classBOverride?.Trim() ?? string.Empty; + overrideC = classCOverride?.Trim() ?? string.Empty; + overrideD = classDOverride?.Trim() ?? string.Empty; + a = overrideA; + b = overrideB; + c = overrideC; + d = overrideD; + + Core.Boot(); + Prep(); + Fight(); + } + + bool IsTaunter() + { + string currentClass = Bot.Player.CurrentClass?.Name ?? string.Empty; + if (string.IsNullOrWhiteSpace(currentClass)) + return false; + + if (!string.IsNullOrWhiteSpace(a) && currentClass.Equals(a, StringComparison.OrdinalIgnoreCase)) + return true; + if (!string.IsNullOrWhiteSpace(b) && currentClass.Equals(b, StringComparison.OrdinalIgnoreCase)) + return true; + + return false; + } + + void Prep() + { + if (ActiveComp != NulgathComp.Unselected) + ApplyCompAndEquip(ActiveComp, overrideA, overrideB, overrideC, overrideD); + + if (EquipBestGear) + EquipBestDmgGear(); + + if (DoEnhancements) + DoEnhs(); + + Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); + Ultra.BuyAlchemyPotion("Potent Honor Potion"); + Core.EquipConsumable("Potent Honor Potion"); + + if (IsTaunter()) + Ultra.GetScrollOfEnrage(); + else if (UseLifeSteal) + Ultra.GetScrollOfLifeSteal(); + } + + void ApplyCompAndEquip(NulgathComp comp, string aOverride, string bOverride, string cOverride, string dOverride) + { + string[][] classes; + switch (comp) + { + case NulgathComp.Fast: + a = string.IsNullOrWhiteSpace(aOverride) + ? (C.CheckInventory("Chrono ShadowSlayer") ? "Chrono ShadowSlayer" : "Chrono ShadowHunter") + : aOverride; + b = string.IsNullOrWhiteSpace(bOverride) ? "Verus DoomKnight" : bOverride; + c = string.IsNullOrWhiteSpace(cOverride) ? "Legion Revenant" : cOverride; + d = string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride; + classes = new[] + { + string.IsNullOrWhiteSpace(aOverride) + ? new[] { "Chrono ShadowSlayer", "Chrono ShadowHunter" } + : new[] { aOverride }, + new[] { b }, + new[] { c }, + new[] { d } + }; + break; + + case NulgathComp.F2PFast: + a = string.IsNullOrWhiteSpace(aOverride) ? "Dragon of Time" : aOverride; + b = string.IsNullOrWhiteSpace(bOverride) ? "Dragon of Time" : bOverride; + c = string.IsNullOrWhiteSpace(cOverride) ? "Legion Revenant" : cOverride; + d = string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride; + classes = new[] + { + new[] { a }, + new[] { b }, + new[] { c }, + new[] { d } + }; + break; + + case NulgathComp.Common: + a = string.IsNullOrWhiteSpace(aOverride) ? "King's Echo" : aOverride; + b = string.IsNullOrWhiteSpace(bOverride) ? "Legion Revenant" : bOverride; + c = string.IsNullOrWhiteSpace(cOverride) ? "ArchPaladin" : cOverride; + d = string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride; + classes = new[] + { + new[] { a }, + new[] { b }, + new[] { c }, + new[] { d } + }; + break; + + case NulgathComp.Balanced: + a = string.IsNullOrWhiteSpace(aOverride) ? "Legion Revenant" : aOverride; + b = string.IsNullOrWhiteSpace(bOverride) ? "ArchPaladin" : bOverride; + c = string.IsNullOrWhiteSpace(cOverride) ? (C.CheckInventory("Infinity Titan") ? "Infinity Titan" : "StoneCrusher") : cOverride; + d = string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride; + classes = new[] + { + new[] { a }, + new[] { b }, + new[] { c }, + new[] { d } + }; + break; + + default: + throw new NotImplementedException(); + } + + Ultra.EquipClassSync(classes, 4, "nulgath_class.sync", allowDuplicates: true); + } + + // Overfiend Blade = 1 + // Nulgath = 2 + void Fight() + { + const string map = "ultranulgath"; + const string boss = "Nulgath the Archfiend"; + + string syncPath = Ultra.ResolveSyncPath("UltraItemCheck.sync"); + Ultra.ClearSyncFile(syncPath); + Bot.Sleep(2500); + + C.EnsureAccept(8692); + C.AddDrop("Nulgath Insignia"); + + Core.Join(map); + Ultra.WaitForArmy(3, "ultra_nulgath.sync"); + + Core.ChooseBestCell(boss); + Bot.Player.SetSpawnPoint(); + Core.EnableSkills(); + + while (!Bot.ShouldExit) + { + if (Bot.Map?.Name != map) + { + Core.Join(map); + Core.ChooseBestCell(boss); + Bot.Player.SetSpawnPoint(); + } + + if (!Bot.Player.Alive) + { + Bot.Wait.ForTrue(() => Bot.Player.Alive, 20); + continue; + } + + if (Ultra.CheckArmyProgressBool(() => Bot.TempInv.Contains("Nulgath the Archfiend Defeated?", 1), syncPath)) + { + C.Logger("All players finished farm."); + Core.DisableSkills(); + Bot.Sleep(200); + C.Jump("Enter", "Spawn"); + C.Join("whitemap"); + if (!Bot.Quests.IsDailyComplete(8692)) + C.EnsureComplete(8692); + break; + } + + if (IsTaunter()) + { + Bot.Combat.Attack(2); + Bot.Sleep(200); + } + else + { + if (Bot.Monsters.MapMonsters.Any(x => x != null && x.MapID == 1 && x.HP > 0)) + Bot.Combat.Attack(1); + else + Bot.Combat.Attack(2); + Bot.Sleep(200); + } + + // Non-taunter role: use Scroll of Life Steal + if (UseLifeSteal && !IsTaunter() && Bot.Player.HasTarget && Bot.Player.Target?.HP > 0 && Bot.Skills.CanUseSkill(5)) + Bot.Skills.UseSkill(5); + + if (Bot.Player.Alive + && IsTaunter() + && !Bot.Target.Auras.Any(x => x?.Name == "Focus") + && Bot.Monsters.MapMonsters.Any(x => (x?.MapID == 2 || x?.MapID == 1) && x.HP > 0)) + { + Core.DisableSkills(); + while (!Bot.ShouldExit && !Bot.Target.Auras.Any(x => x?.Name == "Focus")) + { + if (!Bot.Player.Alive) + { + Bot.Wait.ForTrue(() => Bot.Player.Alive, 20); + continue; + } + + // Taunter role: use Scroll of Enrage to apply Focus. + if (!Bot.Target.Auras.Any(x => x != null && x.Name == "Focus") && Bot.Skills.CanUseSkill(5)) + Bot.Skills.UseSkill(5); + else + break; + + Bot.Sleep(500); + } + Core.EnableSkills(); + } + } + } + + void DoEnhs() + { + string className = Bot.Player!.CurrentClass?.Name ?? string.Empty; + if (string.IsNullOrEmpty(className)) + return; + + switch (className) + { + case "Chrono ShadowSlayer": + case "Chrono ShadowHunter": + Adv.EnhanceEquipped( + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Vim, + wSpecial: WeaponSpecial.Valiance, + cSpecial: CapeSpecial.Vainglory + ); + break; + + case "Verus DoomKnight": + Adv.EnhanceEquipped( + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Anima, + wSpecial: WeaponSpecial.Ravenous, + cSpecial: CapeSpecial.Vainglory + ); + break; + + case "Legion Revenant": + Adv.EnhanceEquipped( + type: EnhancementType.Wizard, + hSpecial: HelmSpecial.Pneuma, + wSpecial: WeaponSpecial.Valiance, + cSpecial: CapeSpecial.Vainglory + ); + break; + + case "Lord Of Order": + Adv.EnhanceEquipped( + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Forge, + wSpecial: WeaponSpecial.Awe_Blast, + cSpecial: CapeSpecial.Absolution + ); + break; + + case "Dragon of Time": + Adv.EnhanceEquipped( + type: EnhancementType.Wizard, + hSpecial: HelmSpecial.Pneuma, + wSpecial: WeaponSpecial.Elysium, + cSpecial: CapeSpecial.Vainglory + ); + break; + + case "King's Echo": + Adv.EnhanceEquipped( + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Examen, + wSpecial: WeaponSpecial.Ravenous, + cSpecial: CapeSpecial.Vainglory + ); + break; + + case "Arcana Invoker": + Adv.EnhanceEquipped( + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Examen, + wSpecial: WeaponSpecial.Ravenous, + cSpecial: CapeSpecial.Vainglory + ); + break; + + case "Archfiend": + Adv.EnhanceEquipped( + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Forge, + wSpecial: WeaponSpecial.Ravenous, + cSpecial: CapeSpecial.Vainglory + ); + break; + + case "Lich": + Adv.EnhanceEquipped( + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Examen, + wSpecial: WeaponSpecial.Ravenous, + cSpecial: CapeSpecial.Vainglory + ); + break; + + case "ArchPaladin": + Adv.EnhanceEquipped( + type: EnhancementType.Lucky, + hSpecial: HelmSpecial.Forge, + wSpecial: WeaponSpecial.Valiance, + cSpecial: CapeSpecial.Lament + ); + break; + + case "StoneCrusher": + case "Infinity Titan": + Adv.EnhanceEquipped( + type: EnhancementType.Wizard, + hSpecial: HelmSpecial.Pneuma, + wSpecial: WeaponSpecial.Valiance, + cSpecial: CapeSpecial.Vainglory + ); + break; + } + } + + void EquipBestDmgGear() + { + C.EquipBestItemsForMeta( + new Dictionary + { + { "Weapon", new[] { "dmgAll", "dmg", "damage" } }, + { "Armor", new[] { "dmgAll", "dmg", "damage" } }, + { "Helm", new[] { "dmgAll", "dmg", "damage" } }, + { "Cape", new[] { "dmgAll", "dmg", "damage" } }, + { "Pet", new[] { "dmgAll", "dmg", "damage" } }, + } + ); + } + + public enum NulgathComp + { + Unselected, + Fast, + F2PFast, + Common, + Balanced, + } +} diff --git a/Ultras/UltraSpeaker.cs b/Ultras/UltraSpeaker.cs index c97f5fb58..76f457aff 100644 --- a/Ultras/UltraSpeaker.cs +++ b/Ultras/UltraSpeaker.cs @@ -2,6 +2,10 @@ name: UltraSpeaker description: Ultra First Speaker helper with zoning, taunt timing, and custom rotation. tags: Ultra + +Fight notes: +- Composition order is [slot 1-4]: Fast = AP / LR / QCM / LOO, Safe = LR / AP / LOO / VDK. +- All slots are taunt-capable in this encounter flow; script equips Enrage for all roles. */ //cs_include Scripts/Ultras/CoreEngine.cs @@ -13,78 +17,10 @@ using System; using System.Collections.Generic; -using System.Dynamic; -using System.Threading.Tasks; +using System.Linq; using Skua.Core.Interfaces; -using Skua.Core.Models.Auras; using Skua.Core.Options; -#region Fast Comp - -/// -/// Fast Composition - Maximum damage output for speed -/// -// ArchPaladin -// ├─ Class: Lucky -// ├─ Helm: Forge -// ├─ Weapon: Valiance -// └─ Cape: Lament -// -// Legion Revenant -// ├─ Class: Wizard -// ├─ Helm: Pneuma -// ├─ Weapon: Arcana's Concerto -// └─ Cape: Vainglory -// -// Quantum Chronomancer -// ├─ Class: Lucky -// ├─ Helm: Forge -// ├─ Weapon: Praxis -// └─ Cape: Penitence -// -// Lord Of Order -// ├─ Class: Lucky -// ├─ Helm: Forge -// ├─ Weapon: Valiance -// └─ Cape: Penitence - -#endregion - -#region Safe Comp - -/// -/// Safe Composition - Original balanced setup -/// -// Legion Revenant -// ├─ Class: Wizard -// ├─ Helm: Wizard -// ├─ Weapon: Arcana/Valiance -// └─ Cape: Penitence -// └─ Scroll: Enrage -// -// ArchPaladin -// ├─ Class: Lucky -// ├─ Helm: Luck -// ├─ Weapon: Lacerate/Valiance -// └─ Cape: Penitence -// └─ Scroll: Enrage -// -// Lord Of Order -// ├─ Class: Lucky -// ├─ Helm: Luck -// ├─ Weapon: Valiance -// └─ Cape: Penitence -// └─ Scroll: Enrage -// -// Verus DoomKnight (or other DPS) -// ├─ Class: Lucky -// ├─ Helm: Anima -// ├─ Weapon: Valiance -// └─ Cape: Penitence -// └─ Scroll: Enrage - -#endregion - public class UltraSpeaker { private static CoreAdvanced Adv @@ -97,11 +33,12 @@ private static CoreAdvanced Adv public IScriptInterface Bot => IScriptInterface.Instance; public CoreEngine Core = new(); public CoreUltra Ultra = new(); - string? className = null; public bool DontPreconfigure = true; public string OptionsStorage = "UltraSpeaker"; - public List Options = new() + public string[] MultiOptions = { "Main", "CoreSettings", "ClassOverrides" }; + + public List Main = new() { new Option( "DoEquipClasses", @@ -109,58 +46,154 @@ private static CoreAdvanced Adv "Auto-equip classes across all 4 clients\n" + "Fast: AP / LR / QCM / LOO\n" + "Safe: LR / AP / LOO / VDK\n" - + "Unselected = off (use whatever classes you already have equipped).", - SpeakerComp.Unselected + + "Unselected = off (use current classes).", + SpeakerComp.Safe ), - new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), CoreBots.Instance.SkipOptions, }; + public List CoreSettings = new() + { + new Option("EquipBestGear", "Equip Best Gear", "Equip best gear for encounter", true), + new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), + new Option("RestoreGear", "Restore Gear", "Restore original gear after the script finishes", false), + new Option("UseLifeSteal", "Use LifeSteal", "Unused here because all slots use Enrage by design.", true), + }; + + public List ClassOverrides = new() + { + new Option("a", "Primary Class Override", "Blank = use selected comp default for slot 1.", ""), + new Option("b", "Secondary Class Override", "Blank = use selected comp default for slot 2.", ""), + new Option("c", "Tertiary Class Override", "Blank = use selected comp default for slot 3.", ""), + new Option("d", "Quaternary Class Override", "Blank = use selected comp default for slot 4.", ""), + }; + + private SpeakerComp ActiveComp = SpeakerComp.Safe; + private bool EquipBestGear; + private bool DoEnhancements; + private bool RestoreGear; + private string overrideA = string.Empty; + private string overrideB = string.Empty; + private string overrideC = string.Empty; + private string overrideD = string.Empty; public void ScriptMain(IScriptInterface bot) { - if ( - Bot.Config != null - && Bot.Config.Options.Contains(C.SkipOptions) - && !Bot.Config.Get(C.SkipOptions) - ) + if (Bot.Config != null && !Bot.Config.Get("Main", "SkipOption")) Bot.Config.Configure(); - C.Logger("This script uses the `corner spam taunt method.. and works ^_^"); - className = Bot.Player.CurrentClass?.Name?.ToLower(); + C.Logger("This script uses the corner spam taunt method."); + + ActiveComp = Bot.Config == null ? SpeakerComp.Safe : Bot.Config.Get("Main", "DoEquipClasses"); + overrideA = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty)).Trim(); + overrideB = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "b") ?? string.Empty)).Trim(); + overrideC = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "c") ?? string.Empty)).Trim(); + overrideD = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "d") ?? string.Empty)).Trim(); + EquipBestGear = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "EquipBestGear"); + DoEnhancements = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "DoEnh"); + RestoreGear = Bot.Config == null ? false : Bot.Config.Get("CoreSettings", "RestoreGear"); + + Adv.GearStore(); + try + { + Run(ActiveComp, EquipBestGear, DoEnhancements, overrideA, overrideB, overrideC, overrideD); + } + finally + { + if (RestoreGear) + Adv.GearStore(true, true); + C.SetOptions(false); + Bot.StopSync(); + } + } + + public void Run( + SpeakerComp comp = SpeakerComp.Safe, + bool equipBestGear = true, + bool doEnhancements = true, + string? classAOverride = null, + string? classBOverride = null, + string? classCOverride = null, + string? classDOverride = null + ) + { + ActiveComp = comp; + EquipBestGear = equipBestGear; + DoEnhancements = doEnhancements; + overrideA = classAOverride?.Trim() ?? string.Empty; + overrideB = classBOverride?.Trim() ?? string.Empty; + overrideC = classCOverride?.Trim() ?? string.Empty; + overrideD = classDOverride?.Trim() ?? string.Empty; + Core.Boot(); Prep(); Kill(); - C.SetOptions(false); } void Prep() { - // Sync-equip classes if a comp is selected - SpeakerComp comp = Bot.Config!.Get("DoEquipClasses"); - if (comp != SpeakerComp.Unselected) - { - string[] classes = comp switch - { - SpeakerComp.Fast => new[] { "ArchPaladin", "Legion Revenant", "Quantum Chronomancer", "Lord Of Order" }, - SpeakerComp.Safe => new[] { "Legion Revenant", "ArchPaladin", "Lord Of Order", "Verus DoomKnight" }, - _ => new[] { "ArchPaladin", "Legion Revenant", "Lord Of Order", "Verus DoomKnight" }, - }; + ApplyCompAndEquip(ActiveComp, overrideA, overrideB, overrideC, overrideD); - Ultra.EquipClassSync(classes, 4, "speaker_class.sync"); - } + if (EquipBestGear) + EquipBestDmgGear(); - if (Bot.Config!.Get("DoEnh")) + if (DoEnhancements) DoEnh(); + Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); + Ultra.BuyAlchemyPotion("Potent Honor Potion"); + Core.EquipConsumable("Potent Honor Potion"); Ultra.GetScrollOfEnrage(); } + void ApplyCompAndEquip(SpeakerComp comp, string aOverride, string bOverride, string cOverride, string dOverride) + { + if (comp == SpeakerComp.Unselected) + return; + + string[] classes = comp switch + { + SpeakerComp.Fast => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "ArchPaladin" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "Legion Revenant" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "Quantum Chronomancer" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Lord Of Order" : dOverride, + }, + SpeakerComp.Safe => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "Legion Revenant" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "ArchPaladin" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "Lord Of Order" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Verus DoomKnight" : dOverride, + }, + _ => throw new InvalidOperationException($"Unhandled SpeakerComp value: {comp}") + }; + + Ultra.EquipClassSync(classes, 4, "speaker_class.sync"); + } + + void EquipBestDmgGear() + { + C.EquipBestItemsForMeta( + new Dictionary + { + { "Weapon", new[] { "dmgAll", "dmg", "damage" } }, + { "Armor", new[] { "dmgAll", "dmg", "damage" } }, + { "Helm", new[] { "dmgAll", "dmg", "damage" } }, + { "Cape", new[] { "dmgAll", "dmg", "damage" } }, + { "Pet", new[] { "dmgAll", "dmg", "damage" } }, + } + ); + } void Kill() { + const string map = "ultraspeaker"; + const string boss = "The First Speaker"; + if (!Bot.Quests.IsUnlocked(9173)) - Bot.Log("Ultra Quest isn't unlocked, we'll fakeunlock it so you can atleast get the quest drop"); + Bot.Log("Ultra Quest is not unlocked, fake unlocking for drop support."); string syncPath = Ultra.ResolveSyncPath("UltraItemCheck.sync"); Ultra.ClearSyncFile(syncPath); @@ -170,43 +203,40 @@ void Kill() C.AddDrop("The First Speaker Silenced"); if (!Bot.Quests.IsUnlocked(9173)) Bot.Quests.UpdateQuest(9125); - Core.Join("ultraspeaker"); + + Core.Join(map); Ultra.WaitForArmy(3, "ultra_speaker.sync"); - Core.ChooseBestCell("The First Speaker"); + Core.ChooseBestCell(boss); + Bot.Player.SetSpawnPoint(); Core.EnableSkills(); while (!Bot.ShouldExit) { - // Dead → wait for respawn - if (!Bot.Player!.Alive) + if (Bot.Map?.Name != map) + { + Core.Join(map); + Core.ChooseBestCell(boss); + Bot.Player.SetSpawnPoint(); + } + + if (!Bot.Player.Alive) { Bot.Wait.ForTrue(() => Bot.Player.Alive, 20); continue; } - // Check if all players are done - bool allComplete = Ultra.CheckArmyProgressBool( - () => Bot.Inventory.Contains("The First Speaker Silenced", 1), - syncPath - ); - + bool allComplete = Ultra.CheckArmyProgressBool(() => Bot.Inventory.Contains("The First Speaker Silenced", 1), syncPath); if (allComplete) { C.Jump("Enter", "Spawn"); C.Logger("All players finished farm."); if (Bot.Quests.IsDailyComplete(9173)) - { C.Logger("Weekly already complete, try again Friday morning"); - if (Bot.Config!.Get("DoEnh")) - return; - } - else C.EnsureComplete(9173); - if (Bot.Config!.Get("DoEnh")) - Adv.GearStore(true, true); + else + C.EnsureComplete(9173); break; } - // If we're petrified, wait if (Bot.Self.Auras.Any(a => a.Name == "Stasis")) { Core.DisableSkills(); @@ -215,38 +245,24 @@ void Kill() continue; } - // Position management in Boss cell if (Bot.Player?.Cell == "Boss") { int minX = 0, maxX = 100; int minY = 485, maxY = 500; - - bool isInBox = - Bot.Player.Position.X >= minX && - Bot.Player.Position.X <= maxX && - Bot.Player.Position.Y >= minY && - Bot.Player.Position.Y <= maxY; - + bool isInBox = Bot.Player.Position.X >= minX && Bot.Player.Position.X <= maxX && Bot.Player.Position.Y >= minY && Bot.Player.Position.Y <= maxY; if (!isInBox) { Random rand = new(); - int randomX = rand.Next(minX, maxX + 1); - int randomY = rand.Next(minY, maxY + 1); - Bot.Player.WalkTo(randomX, randomY); + Bot.Player.WalkTo(rand.Next(minX, maxX + 1), rand.Next(minY, maxY + 1)); Bot.Sleep(500); } } - // Combat logic - only attack if monster exists - if (Bot.Monsters.CurrentMonsters.Any(m => m.Name == "The First Speaker" && m.Alive)) + if (Bot.Monsters.CurrentMonsters.Any(m => m.Name == boss && m.Alive)) { - Bot.Combat.Attack("The First Speaker"); - - // Use skill 5 if Focus aura is not present + Bot.Combat.Attack(boss); if (!Bot.Target.Auras.Any(a => a.Name == "Focus") && Bot.Skills.CanUseSkill(5)) - { Bot.Skills.UseSkill(5); - } } Bot.Sleep(500); } @@ -261,92 +277,34 @@ void DoEnh() switch (className.ToLower()) { case "chrono shadowslayer": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: WeaponSpecial.Valiance, - cSpecial: CapeSpecial.Penitence - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Forge, wSpecial: WeaponSpecial.Valiance, cSpecial: CapeSpecial.Penitence); break; - case "legion revenant": - Adv.EnhanceEquipped( - type: EnhancementType.Wizard, - wSpecial: WeaponSpecial.Arcanas_Concerto, - cSpecial: CapeSpecial.Vainglory - ); + Adv.EnhanceEquipped(type: EnhancementType.Wizard, wSpecial: WeaponSpecial.Arcanas_Concerto, cSpecial: CapeSpecial.Vainglory); break; - case "archpaladin": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: WeaponSpecial.Valiance, - cSpecial: CapeSpecial.Lament - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Forge, wSpecial: WeaponSpecial.Valiance, cSpecial: CapeSpecial.Lament); break; - case "lord of order": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: WeaponSpecial.Valiance, - cSpecial: CapeSpecial.Penitence - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Forge, wSpecial: WeaponSpecial.Valiance, cSpecial: CapeSpecial.Penitence); break; - case "quantum chronomancer": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: WeaponSpecial.Praxis, - cSpecial: CapeSpecial.Penitence - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Forge, wSpecial: WeaponSpecial.Praxis, cSpecial: CapeSpecial.Penitence); break; - case "verus doomknight": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Anima, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Vainglory - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Anima, wSpecial: WeaponSpecial.Ravenous, cSpecial: CapeSpecial.Vainglory); break; - case "sentinel": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Anima, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Penitence - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Anima, wSpecial: WeaponSpecial.Ravenous, cSpecial: CapeSpecial.Penitence); break; - case "archfiend": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Forge, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Penitence - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Forge, wSpecial: WeaponSpecial.Ravenous, cSpecial: CapeSpecial.Penitence); break; - case "king's echo": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Examen, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Vainglory - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Examen, wSpecial: WeaponSpecial.Ravenous, cSpecial: CapeSpecial.Vainglory); break; - case "void highlord": - Adv.EnhanceEquipped( - type: EnhancementType.Lucky, - hSpecial: HelmSpecial.Anima, - wSpecial: WeaponSpecial.Ravenous, - cSpecial: CapeSpecial.Vainglory - ); + Adv.EnhanceEquipped(type: EnhancementType.Lucky, hSpecial: HelmSpecial.Anima, wSpecial: WeaponSpecial.Ravenous, cSpecial: CapeSpecial.Vainglory); break; } } diff --git a/Ultras/UltraWarden.cs b/Ultras/UltraWarden.cs index eda1aa497..09f5ada52 100644 --- a/Ultras/UltraWarden.cs +++ b/Ultras/UltraWarden.cs @@ -1,147 +1,236 @@ -/* -name: UltraWarden -description: Ultra Warden helper with HP-band taunt trigger and army synchronization. -tags: Ultra -*/ - -//cs_include Scripts/Ultras/CoreEngine.cs -//cs_include Scripts/Ultras/CoreUltra.cs -//cs_include Scripts/CoreBots.cs -//cs_include Scripts/CoreAdvanced.cs -//cs_include Scripts/CoreFarms.cs -//cs_include Scripts/CoreStory.cs -using Skua.Core.Interfaces; -using Skua.Core.Options; - -public class UltraWarden -{ - private static CoreAdvanced Adv - { - get => _Adv ??= new CoreAdvanced(); - set => _Adv = value; - } - private CoreBots C => CoreBots.Instance; - private static CoreAdvanced _Adv; - public IScriptInterface Bot => IScriptInterface.Instance; - public CoreEngine Core = new(); - public CoreUltra Ultra = new(); - - string a, - b; - public bool DontPreconfigure = true; - public string OptionsStorage = "UltraWarden"; - public List Options = new() - { - new Option( - "DoEquipClasses", - "Automatically Equip Classes", - "Auto-equip classes across all 4 clients\n" - + "Recommended: LR / AP / LOO / VDK\n" - + "Unselected = off (use whatever classes you already have equipped).", - WardenComp.Unselected - ), - new Option("a", "Taunter Class (Primary)", "Class name that will taunt first", ""), - new Option("b", "Taunter Class (Backup)", "Backup taunter class", ""), - new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), - CoreBots.Instance.SkipOptions, - }; - - public void ScriptMain(IScriptInterface bot) - { - if ( - Bot.Config != null - && Bot.Config.Options.Contains(C.SkipOptions) - && !Bot.Config.Get(C.SkipOptions) - ) - Bot.Config.Configure(); - a = (Bot.Config!.Get("a") ?? "").Trim(); - b = (Bot.Config.Get("b") ?? "").Trim(); - - if (string.IsNullOrEmpty(a) && string.IsNullOrEmpty(b)) - { - Core.Log( - "Setup", - "Fill at least one taunter class (Primary or Backup) in Script Options." - ); - Bot.StopSync(); - return; - } - - Core.Boot(); - Prep(); - Fight(); - Bot.StopSync(); - } - - bool IsTaunter() => Core.HasClassEquipped(a) || Core.HasClassEquipped(b); - - void Prep() - { - // Sync-equip classes if a comp is selected - WardenComp comp = Bot.Config!.Get("DoEquipClasses"); - if (comp != WardenComp.Unselected) - { - string[] classes = comp switch - { - WardenComp.Recommended => new[] { "Legion Revenant", "ArchPaladin", "Lord Of Order", "Verus DoomKnight" }, - _ => throw new InvalidOperationException($"Unhandled WardenComp value: {comp}") - }; - - Ultra.EquipClassSync(classes, 4, "warden_class.sync"); - } - - Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); - if (IsTaunter()) - Ultra.GetScrollOfEnrage(); - - } - - void Fight() - { - const string map = "ultrawarden"; - const string boss = "Ultra Warden"; - - string syncPath = Ultra.ResolveSyncPath("UltraItemCheck.sync"); - Ultra.ClearSyncFile(syncPath); - Bot.Sleep(2500); - C.EnsureAccept(8153); - C.AddDrop("Warden Insignia"); - Core.Join(map); - Ultra.WaitForArmy(3, "ultra_warden.sync"); - Core.ChooseBestCell(boss); - Bot.Player.SetSpawnPoint(); - Core.EnableSkills(); - - while (!Bot.ShouldExit) - { - // Dead → wait for respawn - if (!Bot.Player.Alive) - { - Bot.Wait.ForTrue(() => Bot.Player.Alive, 20); - continue; - } - - if (Ultra.CheckArmyProgressBool(() => Bot.TempInv.Contains("Ultra Warden Defeated", 1), syncPath)) - { - C.Jump("Enter", "Spawn"); - C.Logger("All players finished farm."); - C.EnsureComplete(8153); - if (Bot.Config!.Get("DoEnh")) - Adv.GearStore(true, true); - break; - } - - if (Core.HasClassEquipped(a) || Core.HasClassEquipped(b)) - Ultra.UltraWardenTaunter(); - - Bot.Combat.Attack("*"); - Bot.Sleep(250); - } - } - - public enum WardenComp - { - Unselected, - Recommended, - } -} +/* +name: UltraWarden +description: Ultra Warden helper with HP-band taunt trigger and army synchronization. +tags: Ultra + +Fight notes: +- Composition is Recommended = [slot 1-4]: LR / AP / LOO / VDK. +- Default composition is set to Recommended/Safe behavior. +- Taunter positions are fixed by comp slots: + - Slot 2 and slot 4 are treated as taunter slots. +- Default taunters are AP (slot2) and VDK (slot4) unless overridden via class overrides. +*/ + +//cs_include Scripts/Ultras/CoreEngine.cs +//cs_include Scripts/Ultras/CoreUltra.cs +//cs_include Scripts/CoreBots.cs +//cs_include Scripts/CoreAdvanced.cs +//cs_include Scripts/CoreFarms.cs +//cs_include Scripts/CoreStory.cs +using Skua.Core.Interfaces; +using Skua.Core.Options; + +public class UltraWarden +{ + private static CoreAdvanced Adv + { + get => _Adv ??= new CoreAdvanced(); + set => _Adv = value; + } + private CoreBots C => CoreBots.Instance; + private static CoreAdvanced _Adv; + public IScriptInterface Bot => IScriptInterface.Instance; + public CoreEngine Core = new(); + public CoreUltra Ultra = new(); + + string overrideA, + overrideB, + overrideC, + overrideD; + + string tauntSlot2 = "ArchPaladin"; + string tauntSlot4 = "Verus DoomKnight"; + bool UseLifeSteal; + bool EquipBestGear; + bool RestoreGear; + WardenComp ActiveComp = WardenComp.Recommended; + public bool DontPreconfigure = true; + public string OptionsStorage = "UltraWarden"; + public string[] MultiOptions = { "Main", "CoreSettings", "ClassOverrides" }; + + public List Main = new() + { + new Option( + "DoEquipClasses", + "Automatically Equip Classes", + "Auto-equip classes across all 4 clients\n" + + "Recommended: LR / AP / LOO / VDK\n" + + "Unselected = off (use whatever classes you already have equipped).", + WardenComp.Recommended + ), + CoreBots.Instance.SkipOptions, + }; + + public List CoreSettings = new() + { + new Option("EquipBestGear", "Equip Best Gear", "Equip best gear for encounter", true), + new Option("DoEnh", "Do Enhancements", "Auto-Enhance Gear properly for the fight", true), + new Option("RestoreGear", "Restore Gear", "Restore original gear after the script finishes", false), + new Option("UseLifeSteal", "Use LifeSteal", "Non-taunters equip/restock/use Scroll of Life Steal.", true), + }; + + public List ClassOverrides = new() + { + new Option("a", "Primary Class Override", "Blank = use selected comp default for slot 1.", ""), + new Option("b", "Secondary Class Override", "Blank = use selected comp default for slot 2.", ""), + new Option("c", "Tertiary Class Override", "Blank = use selected comp default for slot 3.", ""), + new Option("d", "Quaternary Class Override", "Blank = use selected comp default for slot 4.", ""), + }; + + public void ScriptMain(IScriptInterface bot) + { + if (Bot.Config != null && !Bot.Config.Get("Main", "SkipOption")) + Bot.Config.Configure(); + + overrideA = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty)).Trim(); + overrideB = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "b") ?? string.Empty)).Trim(); + overrideC = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "c") ?? string.Empty)).Trim(); + overrideD = (Bot.Config == null ? string.Empty : (Bot.Config.Get("ClassOverrides", "d") ?? string.Empty)).Trim(); + EquipBestGear = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "EquipBestGear"); + UseLifeSteal = Bot.Config == null ? true : Bot.Config.Get("CoreSettings", "UseLifeSteal"); + RestoreGear = Bot.Config == null ? false : Bot.Config.Get("CoreSettings", "RestoreGear"); + ActiveComp = Bot.Config == null ? WardenComp.Recommended : Bot.Config.Get("Main", "DoEquipClasses"); + + Adv.GearStore(); + + try + { + Run(ActiveComp, EquipBestGear, UseLifeSteal, overrideA, overrideB, overrideC, overrideD); + } + finally + { + if (RestoreGear) + Adv.GearStore(true, true); + C.SetOptions(false); + Bot.StopSync(); + } + } + + public void Run( + WardenComp comp = WardenComp.Recommended, + bool equipBestGear = true, + bool useLifeSteal = true, + string? classAOverride = null, + string? classBOverride = null, + string? classCOverride = null, + string? classDOverride = null + ) + { + ActiveComp = comp; + EquipBestGear = equipBestGear; + UseLifeSteal = useLifeSteal; + overrideA = classAOverride?.Trim() ?? string.Empty; + overrideB = classBOverride?.Trim() ?? string.Empty; + overrideC = classCOverride?.Trim() ?? string.Empty; + overrideD = classDOverride?.Trim() ?? string.Empty; + + Core.Boot(); + Prep(); + Fight(); + } + + bool IsTaunter() => Core.HasClassEquipped(tauntSlot2) || Core.HasClassEquipped(tauntSlot4); + + void Prep() + { + ApplyCompAndEquip(ActiveComp, overrideA, overrideB, overrideC, overrideD); + + if (EquipBestGear) + EquipBestDmgGear(); + + Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); + Ultra.BuyAlchemyPotion("Potent Honor Potion"); + Core.EquipConsumable("Potent Honor Potion"); + if (IsTaunter()) + Ultra.GetScrollOfEnrage(); + else if (UseLifeSteal) + Ultra.GetScrollOfLifeSteal(); + } + + void EquipBestDmgGear() + { + C.EquipBestItemsForMeta( + new Dictionary + { + { "Weapon", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Armor", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Helm", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Cape", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + { "Pet", new[] { "dmgAll", "dmg", "gold", "cp", "rep", "xp" } }, + } + ); + } + + void ApplyCompAndEquip(WardenComp comp, string aOverride, string bOverride, string cOverride, string dOverride) + { + if (comp == WardenComp.Unselected) + return; + + string[] classes = comp switch + { + WardenComp.Recommended => new[] + { + string.IsNullOrWhiteSpace(aOverride) ? "Legion Revenant" : aOverride, + string.IsNullOrWhiteSpace(bOverride) ? "ArchPaladin" : bOverride, + string.IsNullOrWhiteSpace(cOverride) ? "Lord Of Order" : cOverride, + string.IsNullOrWhiteSpace(dOverride) ? "Verus DoomKnight" : dOverride, + }, + _ => throw new InvalidOperationException($"Unhandled WardenComp value: {comp}") + }; + + tauntSlot2 = classes[1]; + tauntSlot4 = classes[3]; + + Ultra.EquipClassSync(classes, 4, "warden_class.sync"); + } + + void Fight() + { + const string map = "ultrawarden"; + const string boss = "Ultra Warden"; + + string syncPath = Ultra.ResolveSyncPath("UltraItemCheck.sync"); + Ultra.ClearSyncFile(syncPath); + Bot.Sleep(2500); + C.EnsureAccept(8153); + C.AddDrop("Warden Insignia"); + Core.Join(map); + Ultra.WaitForArmy(3, "ultra_warden.sync"); + Core.ChooseBestCell(boss); + Bot.Player.SetSpawnPoint(); + Core.EnableSkills(); + + while (!Bot.ShouldExit) + { + // Dead → wait for respawn + if (!Bot.Player.Alive) + { + Bot.Wait.ForTrue(() => Bot.Player.Alive, 20); + continue; + } + + if (UseLifeSteal && Bot.Skills.CanUseSkill(5)) + Bot.Skills.UseSkill(5); + + if (Ultra.CheckArmyProgressBool(() => Bot.TempInv.Contains("Ultra Warden Defeated", 1), syncPath)) + { + C.Jump("Enter", "Spawn"); + C.Logger("All players finished farm."); + C.EnsureComplete(8153); + break; + } + + if (Core.HasClassEquipped(tauntSlot2) || Core.HasClassEquipped(tauntSlot4)) + Ultra.UltraWardenTaunter(); + + Bot.Combat.Attack("*"); + Bot.Sleep(250); + } + } + + public enum WardenComp + { + Unselected, + Recommended, + } +}