From eabfb3c2bf98b51ade53b6a17e8d7ab721cae77d Mon Sep 17 00:00:00 2001 From: blackarrow123 Date: Fri, 6 Mar 2026 20:26:17 +0000 Subject: [PATCH 1/6] New script util for gear handling --- CoreGearUtils.cs | 494 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 494 insertions(+) create mode 100644 CoreGearUtils.cs diff --git a/CoreGearUtils.cs b/CoreGearUtils.cs new file mode 100644 index 000000000..593111e1d --- /dev/null +++ b/CoreGearUtils.cs @@ -0,0 +1,494 @@ +/* +name: CoreGearUtils +description: Shared helper utilities for selecting and equipping best gear. +tags: core, gear, utility +*/ +//cs_include Scripts/CoreBots.cs +using System; +using System.Collections.Generic; +using System.Linq; +using Skua.Core.Interfaces; +using Skua.Core.Models.Items; + +public class CoreGearUtilsMarker { } + +/* +Usage +- Preset mode (recommended): CoreGearUtils.EquipBestGear(GearProfilePreset.Chaos); +- Custom mode: CoreGearUtils.EquipBestGear("gold,dmgAll,cp,rep,xp"); +- Store gear: var snapshot = CoreGearUtils.CaptureEquipment(); +- Restore gear: CoreGearUtils.RestoreEquipment(snapshot); + +Rationale +- This utility is profile-driven, not race-only. Any script can optimize for damage, gold, xp, cp, rep, or race tags. +- Presets provide sane defaults and consistent behavior across scripts. +- Custom mode gives full control over meta priority order when a script needs special tuning. +- After baseline equip, a stacking pass attempts to equip the best valid pair across slots: + one item for primary meta + one for secondary meta (when they can stack). + +Preset Priority Table +- Damage -> dmgAll, gold, cp, rep, xp +- Gold -> gold, dmgAll, cp, rep, xp +- Exp -> xp, dmgAll, gold, cp, rep +- ClassPoints -> cp, dmgAll, gold, rep, xp +- Reputation -> rep, dmgAll, gold, cp, xp +- Chaos -> Chaos, dmgAll, gold, cp, rep, xp +- Undead -> Undead, dmgAll, gold, cp, rep, xp +- Elemental -> Elemental, dmgAll, gold, cp, rep, xp +- Dragonkin -> Dragonkin, dmgAll, gold, cp, rep, xp +- Human -> Human, dmgAll, gold, cp, rep, xp +- Orc -> Orc, dmgAll, gold, cp, rep, xp +*/ +public enum GearProfilePreset +{ + Damage, + Gold, + Exp, + ClassPoints, + Reputation, + Chaos, + Undead, + Elemental, + Dragonkin, + Human, + Orc +} + +public static class CoreGearUtils +{ + private static readonly string[] DamageFallbackOrder = { "dmgAll", "gold", "cp", "rep", "xp" }; + private static readonly string[] RestoreSlotOrder = { "Class", "Weapon", "Armor", "Helm", "Cape", "Pet" }; + + public static void EquipBestGear(GearProfilePreset preset = GearProfilePreset.Damage) + => EquipBestGear(IScriptInterface.Instance, CoreBots.Instance, preset); + + public static void EquipBestGear(IScriptInterface bot, CoreBots core, GearProfilePreset preset = GearProfilePreset.Damage) + => EquipBestGearInternal(bot, core, BuildPlan(preset)); + + // Accepts either a preset-like token (e.g. "gold", "chaos") or a custom order + // (e.g. "gold,dmgAll,cp,rep,xp"). + public static void EquipBestGear(string profileOrMetaOrder = "damage") + => EquipBestGear(IScriptInterface.Instance, CoreBots.Instance, profileOrMetaOrder); + + public static void EquipBestGear(IScriptInterface bot, CoreBots core, string profileOrMetaOrder = "damage") + => EquipBestGearInternal(bot, core, BuildPlan(profileOrMetaOrder)); + + public static EquipmentSnapshot CaptureEquipment() + => CaptureEquipment(IScriptInterface.Instance); + + public static EquipmentSnapshot CaptureEquipment(IScriptInterface bot) + { + if (bot == null) + return new EquipmentSnapshot(new Dictionary(), Array.Empty()); + + var bySlot = new Dictionary(StringComparer.OrdinalIgnoreCase); + var extras = new List(); + + foreach (InventoryItem item in bot.Inventory.Items.Where(i => i != null && i.Equipped)) + { + string name = item.Name; + string? slot = GetEquipmentSlotKey(item.Category.ToString()); + if (string.IsNullOrWhiteSpace(slot)) + { + extras.Add(name); + continue; + } + + // Keep first seen item for a slot. + if (!bySlot.ContainsKey(slot)) + bySlot[slot] = name; + else + extras.Add(name); + } + + string[] ordered = BuildRestoreOrder(bySlot, extras); + return new EquipmentSnapshot(bySlot, ordered); + } + + public static void RestoreEquipment(EquipmentSnapshot snapshot, bool loadBank = true) + => RestoreEquipment(IScriptInterface.Instance, CoreBots.Instance, snapshot, loadBank); + + public static void RestoreEquipment(IScriptInterface bot, CoreBots core, EquipmentSnapshot snapshot, bool loadBank = true) + { + if (bot == null || core == null || snapshot == null || snapshot.OrderedItems.Length == 0) + return; + + if (loadBank) + TryLoadBank(bot); + + foreach (string itemName in snapshot.OrderedItems.Distinct(StringComparer.OrdinalIgnoreCase)) + { + if (string.IsNullOrWhiteSpace(itemName)) + continue; + + if (bot.Bank.Contains(itemName)) + core.Unbank(itemName); + + if (bot.Inventory.Contains(itemName)) + bot.Inventory.EquipItem(itemName); + } + } + + private static void EquipBestGearInternal(IScriptInterface bot, CoreBots core, GearPlan plan) + { + if (bot == null || core == null) + return; + + try + { + TryLoadBank(bot); + + core.EquipBestItemsForMeta( + new Dictionary + { + { "Weapon", plan.MetaPriority }, + { "Armor", plan.MetaPriority }, + { "Helm", plan.MetaPriority }, + { "Cape", plan.MetaPriority }, + { "Pet", plan.MetaPriority }, + } + ); + + EquipBestStackingPair(bot, core, plan.PrimaryMeta, plan.SecondaryMeta); + } + catch + { + bot.Log("Best gear equip failed. Continuing with current setup."); + } + } + + private static void TryLoadBank(IScriptInterface bot) + { + try + { + bot.Bank.Load(true); + } + catch + { + bot.Log("Bank load failed before best-gear selection; continuing."); + } + } + + private static GearPlan BuildPlan(GearProfilePreset preset) + { + return preset switch + { + GearProfilePreset.Gold => NewPlan("gold", new[] { "gold", "dmgAll", "cp", "rep", "xp" }), + GearProfilePreset.Exp => NewPlan("xp", new[] { "xp", "dmgAll", "gold", "cp", "rep" }), + GearProfilePreset.ClassPoints => NewPlan("cp", new[] { "cp", "dmgAll", "gold", "rep", "xp" }), + GearProfilePreset.Reputation => NewPlan("rep", new[] { "rep", "dmgAll", "gold", "cp", "xp" }), + GearProfilePreset.Chaos => NewPlan("Chaos", BuildMetaPriority("Chaos", DamageFallbackOrder)), + GearProfilePreset.Undead => NewPlan("Undead", BuildMetaPriority("Undead", DamageFallbackOrder)), + GearProfilePreset.Elemental => NewPlan("Elemental", BuildMetaPriority("Elemental", DamageFallbackOrder)), + GearProfilePreset.Dragonkin => NewPlan("Dragonkin", BuildMetaPriority("Dragonkin", DamageFallbackOrder)), + GearProfilePreset.Human => NewPlan("Human", BuildMetaPriority("Human", DamageFallbackOrder)), + GearProfilePreset.Orc => NewPlan("Orc", BuildMetaPriority("Orc", DamageFallbackOrder)), + _ => NewPlan("dmgAll", new[] { "dmgAll", "gold", "cp", "rep", "xp" }), + }; + } + + private static GearPlan BuildPlan(string profileOrMetaOrder) + { + string raw = (profileOrMetaOrder ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(raw)) + return BuildPlan(GearProfilePreset.Damage); + + var tokens = ParseMetaList(raw); + if (tokens.Count > 1) + { + string primary = tokens[0]; + return NewPlan(primary, BuildMetaPriority(primary, tokens.Skip(1).ToArray())); + } + + string single = tokens.Count == 1 ? tokens[0] : raw; + if (TryMapPreset(single, out GearProfilePreset preset)) + return BuildPlan(preset); + + string normalized = NormalizeMetaToken(single); + return NewPlan(normalized, BuildMetaPriority(normalized, DamageFallbackOrder)); + } + + private static GearPlan NewPlan(string primaryMeta, string[] metaPriority) + => new(primaryMeta, GetSecondaryMeta(metaPriority, primaryMeta), metaPriority); + + private static string[] BuildMetaPriority(string? primaryMeta, IEnumerable fallbackOrder) + { + var metas = new List(); + if (!string.IsNullOrWhiteSpace(primaryMeta)) + metas.Add(primaryMeta); + + foreach (string meta in fallbackOrder) + { + if (!metas.Any(x => x.Equals(meta, StringComparison.OrdinalIgnoreCase))) + metas.Add(meta); + } + + return metas.ToArray(); + } + + private static string GetSecondaryMeta(string[] metaPriority, string primaryMeta) + { + foreach (string meta in metaPriority) + { + if (!meta.Equals(primaryMeta, StringComparison.OrdinalIgnoreCase)) + return meta; + } + + return primaryMeta.Equals("dmgAll", StringComparison.OrdinalIgnoreCase) ? "gold" : "dmgAll"; + } + + private static List ParseMetaList(string raw) + { + char[] separators = { ',', ';', '|', '>' }; + return raw + .Split(separators, StringSplitOptions.RemoveEmptyEntries) + .Select(NormalizeMetaToken) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + private static string NormalizeMetaToken(string token) + { + string value = (token ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(value)) + return "dmgAll"; + + return value.ToLowerInvariant() switch + { + "dmg" => "dmgAll", + "damage" => "dmgAll", + "dmgall" => "dmgAll", + "exp" => "xp", + "experience" => "xp", + "classpoints" => "cp", + "class-points" => "cp", + "reputation" => "rep", + "undead" => "Undead", + "chaos" => "Chaos", + "elemental" => "Elemental", + "dragonkin" => "Dragonkin", + "human" => "Human", + "orc" => "Orc", + _ => value, + }; + } + + private static bool TryMapPreset(string token, out GearProfilePreset preset) + { + switch ((token ?? string.Empty).Trim().ToLowerInvariant()) + { + case "damage": + case "dmg": + case "dmgall": + preset = GearProfilePreset.Damage; + return true; + case "gold": + preset = GearProfilePreset.Gold; + return true; + case "xp": + case "exp": + case "experience": + preset = GearProfilePreset.Exp; + return true; + case "cp": + case "classpoints": + preset = GearProfilePreset.ClassPoints; + return true; + case "rep": + case "reputation": + preset = GearProfilePreset.Reputation; + return true; + case "chaos": + preset = GearProfilePreset.Chaos; + return true; + case "undead": + preset = GearProfilePreset.Undead; + return true; + case "elemental": + preset = GearProfilePreset.Elemental; + return true; + case "dragonkin": + preset = GearProfilePreset.Dragonkin; + return true; + case "human": + preset = GearProfilePreset.Human; + return true; + case "orc": + preset = GearProfilePreset.Orc; + return true; + default: + preset = GearProfilePreset.Damage; + return false; + } + } + + private static void EquipBestStackingPair(IScriptInterface bot, CoreBots core, string primaryMeta, string secondaryMeta) + { + if (string.IsNullOrWhiteSpace(primaryMeta)) + return; + + var candidates = bot.Inventory.Items + .Concat(bot.Bank.Items) + .Where(i => i != null) + .Select(i => new BoostCandidate( + i, + GetSlotKey(i.Category.ToString()), + core.GetBoostFloat(i, primaryMeta), + core.GetBoostFloat(i, secondaryMeta))) + .Where(c => c.Slot != null) + .Where(c => !c.Item.Upgrade || bot.Player.IsMember) + .Where(c => c.Slot != "Weapon" || c.Item.EnhancementLevel > 0) + .ToList(); + + var primaryPool = new List(candidates.Where(c => c.Primary > 0)) { null }; + var secondaryPool = new List(candidates.Where(c => c.Secondary > 0)) { null }; + + BoostCandidate? bestPrimary = null; + BoostCandidate? bestSecondary = null; + double bestTotal = 0; + + foreach (BoostCandidate? primary in primaryPool) + { + foreach (BoostCandidate? secondary in secondaryPool) + { + if (primary == null && secondary == null) + continue; + + if (primary != null && secondary != null && primary.Slot == secondary.Slot && primary.Item.ID != secondary.Item.ID) + continue; + + double total = (primary?.Primary ?? 0) + (secondary?.Secondary ?? 0); + if (total <= bestTotal) + continue; + + bestTotal = total; + bestPrimary = primary; + bestSecondary = secondary; + } + } + + if (bestTotal <= 0) + return; + + if (bestPrimary != null) + EquipCandidate(bot, core, bestPrimary); + + if (bestSecondary != null && (bestPrimary == null || bestSecondary.Item.ID != bestPrimary.Item.ID)) + EquipCandidate(bot, core, bestSecondary); + } + + private static void EquipCandidate(IScriptInterface bot, CoreBots core, BoostCandidate candidate) + { + string name = candidate.Item.Name; + if (bot.Inventory.IsEquipped(name)) + return; + + if (bot.Bank.Contains(name)) + core.Unbank(name); + + if (bot.Inventory.Contains(name)) + bot.Inventory.EquipItem(name); + } + + private static string[] BuildRestoreOrder(Dictionary bySlot, List extras) + { + var ordered = new List(bySlot.Count + extras.Count); + + foreach (string slot in RestoreSlotOrder) + { + if (bySlot.TryGetValue(slot, out string? item) && !string.IsNullOrWhiteSpace(item)) + ordered.Add(item); + } + + ordered.AddRange( + extras + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Distinct(StringComparer.OrdinalIgnoreCase) + ); + + return ordered.ToArray(); + } + + private static string? GetEquipmentSlotKey(string category) + { + if (string.IsNullOrWhiteSpace(category)) + return null; + + return category switch + { + "Class" => "Class", + _ => GetSlotKey(category), + }; + } + + private static string? GetSlotKey(string category) + { + if (string.IsNullOrWhiteSpace(category)) + return null; + + return category switch + { + "Armor" => "Armor", + "Helm" => "Helm", + "Cape" => "Cape", + "Pet" => "Pet", + "Sword" => "Weapon", + "Axe" => "Weapon", + "Dagger" => "Weapon", + "Gun" => "Weapon", + "HandGun" => "Weapon", + "Rifle" => "Weapon", + "Bow" => "Weapon", + "Mace" => "Weapon", + "Gauntlet" => "Weapon", + "Polearm" => "Weapon", + "Staff" => "Weapon", + "Wand" => "Weapon", + "Whip" => "Weapon", + _ => null, + }; + } + + private sealed class BoostCandidate + { + public BoostCandidate(InventoryItem item, string? slot, double primary, double secondary) + { + Item = item; + Slot = slot; + Primary = primary; + Secondary = secondary; + } + + public InventoryItem Item { get; } + public string? Slot { get; } + public double Primary { get; } + public double Secondary { get; } + } + + private sealed class GearPlan + { + public GearPlan(string primaryMeta, string secondaryMeta, string[] metaPriority) + { + PrimaryMeta = primaryMeta; + SecondaryMeta = secondaryMeta; + MetaPriority = metaPriority ?? Array.Empty(); + } + + public string PrimaryMeta { get; } + public string SecondaryMeta { get; } + public string[] MetaPriority { get; } + } +} + +public sealed class EquipmentSnapshot +{ + public EquipmentSnapshot(Dictionary bySlot, string[] orderedItems) + { + BySlot = bySlot ?? new Dictionary(StringComparer.OrdinalIgnoreCase); + OrderedItems = orderedItems ?? Array.Empty(); + } + + public Dictionary BySlot { get; } + public string[] OrderedItems { get; } +} From af99f9169d8e48a1de586a296c9adeef2c317267 Mon Sep 17 00:00:00 2001 From: blackarrow123 Date: Fri, 6 Mar 2026 20:52:37 +0000 Subject: [PATCH 2/6] Improve Drakath setup --- Ultras/ChampionDrakath.cs | 247 +++++++++++++++++++++++++++++--------- 1 file changed, 189 insertions(+), 58 deletions(-) diff --git a/Ultras/ChampionDrakath.cs b/Ultras/ChampionDrakath.cs index 88ba1c7ef..9296e9c32 100644 --- a/Ultras/ChampionDrakath.cs +++ b/Ultras/ChampionDrakath.cs @@ -4,12 +4,13 @@ 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/Ultras/CoreEngine.cs +//cs_include Scripts/Ultras/CoreUltra.cs +//cs_include Scripts/CoreGearUtils.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; @@ -130,59 +131,97 @@ private static CoreAdvanced Adv public bool DontPreconfigure = true; - public string OptionsStorage = "ChampionDrakathTauntSelect"; + 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 }; + private const string LifeStealScroll = "Scroll of Life Steal"; + private const string LifeStealShopMap = "terminatemple"; + private const int LifeStealShopId = 2328; + private const int LifeStealMinStock = 10; + private const int LifeStealRestockTo = 50; - public List Options = new() + 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("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))) + 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.", ""), + }; + + bool UseLifeSteal; + + public void ScriptMain(IScriptInterface bot) + { + if (Bot.Config != null + && !Bot.Config.Get("Main", "SkipOption")) + Bot.Config.Configure(); + + DrakathComp comp = Bot.Config!.Get("Main", "DoEquipClasses"); + overrideA = (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty).Trim(); + overrideB = (Bot.Config.Get("ClassOverrides", "b") ?? string.Empty).Trim(); + overrideC = (Bot.Config.Get("ClassOverrides", "c") ?? string.Empty).Trim(); + overrideD = (Bot.Config.Get("ClassOverrides", "d") ?? string.Empty).Trim(); + a = overrideA; + b = overrideB; + c = overrideC; + d = overrideD; + UseLifeSteal = Bot.Config.Get("CoreSettings", "UseLifeSteal"); + + bool usingComp = comp != DrakathComp.Unselected; + int taunterCount = (int)Bot.Config!.Get("CoreSettings", "HowManyTaunts"); + if (!usingComp && ( + string.IsNullOrEmpty(a) + || (taunterCount >= 2 && string.IsNullOrEmpty(b)) + || (taunterCount >= 3 && string.IsNullOrEmpty(c)) + || (taunterCount >= 4 && string.IsNullOrEmpty(d)) + )) { - Core.Log("Setup", "Primary taunter required."); + Core.Log("Setup", "Fill taunter class overrides for all enabled taunter slots."); Bot.StopSync(); return; } - if (SoloTaunt) - { - b = string.Empty; - c = string.Empty; - d = string.Empty; - } - - Core.Boot(); - Prep(); - Fight(); - C.JumpWait(); - C.SetOptions(false); - } + EquipmentSnapshot equippedBefore = CoreGearUtils.CaptureEquipment(Bot); + + try + { + Core.Boot(); + Prep(); + Fight(); + C.JumpWait(); + } + finally + { + CoreGearUtils.RestoreEquipment(Bot, C, equippedBefore); + C.SetOptions(false); + } + } bool IsTaunter() { @@ -192,7 +231,7 @@ bool IsTaunter() return false; // Check based on HowManyTaunts setting - int taunterCount = (int)Bot.Config!.Get("HowManyTaunts"); + int taunterCount = (int)Bot.Config!.Get("CoreSettings", "HowManyTaunts"); if (taunterCount >= 1 && !string.IsNullOrEmpty(a) && currentClass.Contains(a)) return true; @@ -206,10 +245,17 @@ bool IsTaunter() return false; } - void Prep() - { - if (Bot.Config!.Get("DoEnh")) - DoEnhs(); + void Prep() + { + DrakathComp comp = Bot.Config!.Get("Main", "DoEquipClasses"); + if (comp != DrakathComp.Unselected) + ApplyCompAndEquip(comp, overrideA, overrideB, overrideC, overrideD); + + if (Bot.Config!.Get("CoreSettings", "EquipBestGear")) + CoreGearUtils.EquipBestGear(Bot, C, GearProfilePreset.Chaos); + + if (Bot.Config!.Get("CoreSettings", "DoEnh")) + DoEnhs(); Ultra.UseAlchemyPotions( Ultra.GetBestTonicPotion(), @@ -218,6 +264,82 @@ void Prep() if (IsTaunter()) Ultra.GetScrollOfEnrage(); + else if (UseLifeSteal) + EnsureLifeStealScroll(); + } + + void EnsureLifeStealScroll() + { + C.Unbank(LifeStealScroll); + int qty = Bot.Inventory.GetQuantity(LifeStealScroll); + if (qty < LifeStealMinStock) + { + C.BuyItem(LifeStealShopMap, LifeStealShopId, LifeStealScroll, LifeStealRestockTo); + qty = Bot.Inventory.GetQuantity(LifeStealScroll); + } + + if (qty > 0) + Core.EquipConsumable(LifeStealScroll); + } + + void ApplyCompAndEquip(DrakathComp comp, string aOverride, string bOverride, string cOverride, string dOverride) + { + 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; + + 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(); + } + + Ultra.EquipClassSync(classes, 4, "champion_drakath_class.sync", allowDuplicates: true); } void Fight() @@ -232,11 +354,11 @@ void Fight() C.EnsureAccept(8300); C.AddDrop("Champion Drakath Insignia"); - Core.Join(map); - Ultra.WaitForArmy(3, "champion_drakath.sync"); - Core.ChooseBestCell(boss); - Bot.Player.SetSpawnPoint(); - Core.EnableSkills(); + Core.Join(map); + Ultra.WaitForArmy(3, "champion_drakath.sync"); + Core.ChooseBestCell(boss); + Bot.Player.SetSpawnPoint(); + Core.EnableSkills(); bool[] tauntFired = new bool[8]; // 18M-4M in 2M chunks previousHP = 0; // Reset at fight start @@ -257,15 +379,16 @@ 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; - } + break; + } Bot.Combat.Attack("*"); Bot.Sleep(500); + 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 @@ -510,4 +633,12 @@ enum HowManyTaunts Three = 3, Four = 4 } + + enum DrakathComp + { + Unselected, + Safe, + Fast, + Cheapest + } } From 1ff74697907e5e24c5b358d145210d1b83986f4b Mon Sep 17 00:00:00 2001 From: blackarrow123 Date: Fri, 6 Mar 2026 20:54:27 +0000 Subject: [PATCH 3/6] Nulgath Improved --- Ultras/UltraNulgath.cs | 858 +++++++++++++++++++++++------------------ 1 file changed, 483 insertions(+), 375 deletions(-) diff --git a/Ultras/UltraNulgath.cs b/Ultras/UltraNulgath.cs index e8b6cde68..4cbbc3202 100644 --- a/Ultras/UltraNulgath.cs +++ b/Ultras/UltraNulgath.cs @@ -1,375 +1,483 @@ -/* -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/CoreGearUtils.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; + + private const string LifeStealScroll = "Scroll of Life Steal"; + private const string LifeStealShopMap = "terminatemple"; + private const int LifeStealShopId = 2328; + private const int LifeStealMinStock = 10; + private const int LifeStealRestockTo = 50; + + 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("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(); + + NulgathComp comp = Bot.Config!.Get("Main", "DoEquipClasses"); + overrideA = (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty).Trim(); + overrideB = (Bot.Config.Get("ClassOverrides", "b") ?? string.Empty).Trim(); + overrideC = (Bot.Config.Get("ClassOverrides", "c") ?? string.Empty).Trim(); + overrideD = (Bot.Config.Get("ClassOverrides", "d") ?? string.Empty).Trim(); + + a = overrideA; + b = overrideB; + c = overrideC; + d = overrideD; + + UseLifeSteal = Bot.Config.Get("CoreSettings", "UseLifeSteal"); + + bool usingComp = comp != 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; + } + + EquipmentSnapshot equippedBefore = CoreGearUtils.CaptureEquipment(Bot); + + try + { + Core.Boot(); + Prep(); + Fight(); + } + finally + { + CoreGearUtils.RestoreEquipment(Bot, C, equippedBefore); + C.SetOptions(false); + } + } + + 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() + { + NulgathComp comp = Bot.Config!.Get("Main", "DoEquipClasses"); + if (comp != NulgathComp.Unselected) + ApplyCompAndEquip(comp, overrideA, overrideB, overrideC, overrideD); + + if (Bot.Config.Get("CoreSettings", "EquipBestGear")) + CoreGearUtils.EquipBestGear(Bot, C, GearProfilePreset.Damage); + + if (Bot.Config.Get("CoreSettings", "DoEnh")) + DoEnhs(); + + Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); + + if (IsTaunter()) + Ultra.GetScrollOfEnrage(); + else if (UseLifeSteal) + EnsureLifeStealScroll(); + } + + void EnsureLifeStealScroll() + { + C.Unbank(LifeStealScroll); + int qty = Bot.Inventory.GetQuantity(LifeStealScroll); + if (qty < LifeStealMinStock) + { + C.BuyItem(LifeStealShopMap, LifeStealShopId, LifeStealScroll, LifeStealRestockTo); + qty = Bot.Inventory.GetQuantity(LifeStealScroll); + } + + if (qty > 0) + Core.EquipConsumable(LifeStealScroll); + } + + 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.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); + } + + 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; + } + + 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; + } + } + + public enum NulgathComp + { + Unselected, + Fast, + F2PFast, + Common, + Balanced, + } +} From f45b91ba83bddfe30843862427281e63e46e7e47 Mon Sep 17 00:00:00 2001 From: blackarrow123 Date: Fri, 6 Mar 2026 21:02:11 +0000 Subject: [PATCH 4/6] Used shared helper for LifeSteal --- Ultras/ChampionDrakath.cs | 114 ++++++++++++++++---------------------- Ultras/CoreUltra.cs | 19 +++++++ Ultras/UltraNulgath.cs | 24 +------- 3 files changed, 71 insertions(+), 86 deletions(-) diff --git a/Ultras/ChampionDrakath.cs b/Ultras/ChampionDrakath.cs index 9296e9c32..ebb8206ec 100644 --- a/Ultras/ChampionDrakath.cs +++ b/Ultras/ChampionDrakath.cs @@ -4,13 +4,13 @@ tags: Ultra */ -//cs_include Scripts/Ultras/CoreEngine.cs -//cs_include Scripts/Ultras/CoreUltra.cs -//cs_include Scripts/CoreGearUtils.cs -//cs_include Scripts/CoreBots.cs -//cs_include Scripts/CoreFarms.cs -//cs_include Scripts/CoreAdvanced.cs -//cs_include Scripts/CoreStory.cs +//cs_include Scripts/Ultras/CoreEngine.cs +//cs_include Scripts/Ultras/CoreUltra.cs +//cs_include Scripts/CoreGearUtils.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; @@ -137,11 +137,6 @@ private static CoreAdvanced Adv string overrideA, overrideB, overrideC, overrideD; int previousHP = 0; private static int[] hpThresholds = { 18100000, 16100000, 14100000, 12100000, 10100000, 8100000, 6100000, 4100000 }; - private const string LifeStealScroll = "Scroll of Life Steal"; - private const string LifeStealShopMap = "terminatemple"; - private const int LifeStealShopId = 2328; - private const int LifeStealMinStock = 10; - private const int LifeStealRestockTo = 50; public List Main = new() { @@ -176,11 +171,11 @@ private static CoreAdvanced Adv bool UseLifeSteal; - public void ScriptMain(IScriptInterface bot) - { - if (Bot.Config != null - && !Bot.Config.Get("Main", "SkipOption")) - Bot.Config.Configure(); + public void ScriptMain(IScriptInterface bot) + { + if (Bot.Config != null + && !Bot.Config.Get("Main", "SkipOption")) + Bot.Config.Configure(); DrakathComp comp = Bot.Config!.Get("Main", "DoEquipClasses"); overrideA = (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty).Trim(); @@ -207,21 +202,21 @@ public void ScriptMain(IScriptInterface bot) return; } - EquipmentSnapshot equippedBefore = CoreGearUtils.CaptureEquipment(Bot); - - try - { - Core.Boot(); - Prep(); - Fight(); - C.JumpWait(); - } - finally - { - CoreGearUtils.RestoreEquipment(Bot, C, equippedBefore); - C.SetOptions(false); - } - } + EquipmentSnapshot equippedBefore = CoreGearUtils.CaptureEquipment(Bot); + + try + { + Core.Boot(); + Prep(); + Fight(); + C.JumpWait(); + } + finally + { + CoreGearUtils.RestoreEquipment(Bot, C, equippedBefore); + C.SetOptions(false); + } + } bool IsTaunter() { @@ -245,17 +240,17 @@ bool IsTaunter() return false; } - void Prep() - { - DrakathComp comp = Bot.Config!.Get("Main", "DoEquipClasses"); - if (comp != DrakathComp.Unselected) - ApplyCompAndEquip(comp, overrideA, overrideB, overrideC, overrideD); - - if (Bot.Config!.Get("CoreSettings", "EquipBestGear")) - CoreGearUtils.EquipBestGear(Bot, C, GearProfilePreset.Chaos); - - if (Bot.Config!.Get("CoreSettings", "DoEnh")) - DoEnhs(); + void Prep() + { + DrakathComp comp = Bot.Config!.Get("Main", "DoEquipClasses"); + if (comp != DrakathComp.Unselected) + ApplyCompAndEquip(comp, overrideA, overrideB, overrideC, overrideD); + + if (Bot.Config!.Get("CoreSettings", "EquipBestGear")) + CoreGearUtils.EquipBestGear(Bot, C, GearProfilePreset.Chaos); + + if (Bot.Config!.Get("CoreSettings", "DoEnh")) + DoEnhs(); Ultra.UseAlchemyPotions( Ultra.GetBestTonicPotion(), @@ -265,21 +260,7 @@ void Prep() if (IsTaunter()) Ultra.GetScrollOfEnrage(); else if (UseLifeSteal) - EnsureLifeStealScroll(); - } - - void EnsureLifeStealScroll() - { - C.Unbank(LifeStealScroll); - int qty = Bot.Inventory.GetQuantity(LifeStealScroll); - if (qty < LifeStealMinStock) - { - C.BuyItem(LifeStealShopMap, LifeStealShopId, LifeStealScroll, LifeStealRestockTo); - qty = Bot.Inventory.GetQuantity(LifeStealScroll); - } - - if (qty > 0) - Core.EquipConsumable(LifeStealScroll); + Ultra.GetScrollOfLifeSteal(); } void ApplyCompAndEquip(DrakathComp comp, string aOverride, string bOverride, string cOverride, string dOverride) @@ -354,11 +335,11 @@ void Fight() C.EnsureAccept(8300); C.AddDrop("Champion Drakath Insignia"); - Core.Join(map); - Ultra.WaitForArmy(3, "champion_drakath.sync"); - Core.ChooseBestCell(boss); - Bot.Player.SetSpawnPoint(); - Core.EnableSkills(); + Core.Join(map); + Ultra.WaitForArmy(3, "champion_drakath.sync"); + Core.ChooseBestCell(boss); + Bot.Player.SetSpawnPoint(); + Core.EnableSkills(); bool[] tauntFired = new bool[8]; // 18M-4M in 2M chunks previousHP = 0; // Reset at fight start @@ -379,13 +360,14 @@ void Fight() if (!Bot.Quests.IsDailyComplete(8300)) C.EnsureComplete(8300); else Bot.Log("Daily already Complete"); - break; - } + break; + } Bot.Combat.Attack("*"); 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); @@ -434,6 +416,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); @@ -459,6 +442,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); diff --git a/Ultras/CoreUltra.cs b/Ultras/CoreUltra.cs index 4e62935d5..73d2e5331 100644 --- a/Ultras/CoreUltra.cs +++ b/Ultras/CoreUltra.cs @@ -1151,6 +1151,25 @@ 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; + + Core.Unbank(scroll); + + int qty = Bot.Inventory.GetQuantity(scroll); + if (qty < minStock) + { + Core.BuyItem(shopMap, shopId, scroll, restockTo); + qty = Bot.Inventory.GetQuantity(scroll); + } + + if (qty > 0) + Core.EquipConsumable(scroll); + } + public void UseTaunt() { // Dead → wait for respawn diff --git a/Ultras/UltraNulgath.cs b/Ultras/UltraNulgath.cs index 4cbbc3202..10162c070 100644 --- a/Ultras/UltraNulgath.cs +++ b/Ultras/UltraNulgath.cs @@ -73,12 +73,6 @@ private static CoreAdvanced Adv string overrideA, overrideB, overrideC, overrideD; bool UseLifeSteal; - private const string LifeStealScroll = "Scroll of Life Steal"; - private const string LifeStealShopMap = "terminatemple"; - private const int LifeStealShopId = 2328; - private const int LifeStealMinStock = 10; - private const int LifeStealRestockTo = 50; - public List Main = new() { new Option( @@ -188,21 +182,7 @@ void Prep() if (IsTaunter()) Ultra.GetScrollOfEnrage(); else if (UseLifeSteal) - EnsureLifeStealScroll(); - } - - void EnsureLifeStealScroll() - { - C.Unbank(LifeStealScroll); - int qty = Bot.Inventory.GetQuantity(LifeStealScroll); - if (qty < LifeStealMinStock) - { - C.BuyItem(LifeStealShopMap, LifeStealShopId, LifeStealScroll, LifeStealRestockTo); - qty = Bot.Inventory.GetQuantity(LifeStealScroll); - } - - if (qty > 0) - Core.EquipConsumable(LifeStealScroll); + Ultra.GetScrollOfLifeSteal(); } void ApplyCompAndEquip(NulgathComp comp, string aOverride, string bOverride, string cOverride, string dOverride) @@ -332,6 +312,7 @@ void Fight() 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); @@ -349,6 +330,7 @@ void Fight() 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 From 317e225511f16e5a141f87ea0c6f0db3863eb22f Mon Sep 17 00:00:00 2001 From: blackarrow123 Date: Fri, 6 Mar 2026 23:43:48 +0000 Subject: [PATCH 5/6] Fix unbank ref --- Ultras/CoreUltra.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ultras/CoreUltra.cs b/Ultras/CoreUltra.cs index 73d2e5331..239303ed9 100644 --- a/Ultras/CoreUltra.cs +++ b/Ultras/CoreUltra.cs @@ -1157,7 +1157,7 @@ public void GetScrollOfLifeSteal(int minStock = 10, int restockTo = 50) const string shopMap = "terminatemple"; const int shopId = 2328; - Core.Unbank(scroll); + C.Unbank(scroll); int qty = Bot.Inventory.GetQuantity(scroll); if (qty < minStock) From c12a805b8f46490cf14a0066983fe93328639328 Mon Sep 17 00:00:00 2001 From: blackarrow123 Date: Mon, 9 Mar 2026 11:12:51 +0000 Subject: [PATCH 6/6] Cleanup + Weekly ultras --- CoreAdvanced.cs | 20 +- CoreBots.cs | 24 +- CoreGearUtils.cs | 494 -------------------- Ultras/0UltraDaily.cs | 400 ++++++++++++++++ Ultras/0UltraWeekly.cs | 444 ++++++++++++++++++ Ultras/ChampionDrakath.cs | 273 ++++++----- Ultras/CoreUltra.cs | 197 +++++--- Ultras/UltraAvatarTyndarius.cs | 350 +++++++------- Ultras/UltraDage.cs | 375 +++++++-------- Ultras/UltraDarkon.cs | 402 ++++++++++++----- Ultras/UltraDrago.cs | 803 ++++++++++++++------------------- Ultras/UltraEngineer.cs | 259 ++++++++--- Ultras/UltraEzrajal.cs | 242 ++++++++-- Ultras/UltraGramiel.cs | 600 +++++++++++------------- Ultras/UltraNulgath.cs | 97 +++- Ultras/UltraSpeaker.cs | 358 +++++++-------- Ultras/UltraWarden.cs | 383 ++++++++++------ 17 files changed, 3285 insertions(+), 2436 deletions(-) delete mode 100644 CoreGearUtils.cs create mode 100644 Ultras/0UltraDaily.cs create mode 100644 Ultras/0UltraWeekly.cs 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/CoreGearUtils.cs b/CoreGearUtils.cs deleted file mode 100644 index 593111e1d..000000000 --- a/CoreGearUtils.cs +++ /dev/null @@ -1,494 +0,0 @@ -/* -name: CoreGearUtils -description: Shared helper utilities for selecting and equipping best gear. -tags: core, gear, utility -*/ -//cs_include Scripts/CoreBots.cs -using System; -using System.Collections.Generic; -using System.Linq; -using Skua.Core.Interfaces; -using Skua.Core.Models.Items; - -public class CoreGearUtilsMarker { } - -/* -Usage -- Preset mode (recommended): CoreGearUtils.EquipBestGear(GearProfilePreset.Chaos); -- Custom mode: CoreGearUtils.EquipBestGear("gold,dmgAll,cp,rep,xp"); -- Store gear: var snapshot = CoreGearUtils.CaptureEquipment(); -- Restore gear: CoreGearUtils.RestoreEquipment(snapshot); - -Rationale -- This utility is profile-driven, not race-only. Any script can optimize for damage, gold, xp, cp, rep, or race tags. -- Presets provide sane defaults and consistent behavior across scripts. -- Custom mode gives full control over meta priority order when a script needs special tuning. -- After baseline equip, a stacking pass attempts to equip the best valid pair across slots: - one item for primary meta + one for secondary meta (when they can stack). - -Preset Priority Table -- Damage -> dmgAll, gold, cp, rep, xp -- Gold -> gold, dmgAll, cp, rep, xp -- Exp -> xp, dmgAll, gold, cp, rep -- ClassPoints -> cp, dmgAll, gold, rep, xp -- Reputation -> rep, dmgAll, gold, cp, xp -- Chaos -> Chaos, dmgAll, gold, cp, rep, xp -- Undead -> Undead, dmgAll, gold, cp, rep, xp -- Elemental -> Elemental, dmgAll, gold, cp, rep, xp -- Dragonkin -> Dragonkin, dmgAll, gold, cp, rep, xp -- Human -> Human, dmgAll, gold, cp, rep, xp -- Orc -> Orc, dmgAll, gold, cp, rep, xp -*/ -public enum GearProfilePreset -{ - Damage, - Gold, - Exp, - ClassPoints, - Reputation, - Chaos, - Undead, - Elemental, - Dragonkin, - Human, - Orc -} - -public static class CoreGearUtils -{ - private static readonly string[] DamageFallbackOrder = { "dmgAll", "gold", "cp", "rep", "xp" }; - private static readonly string[] RestoreSlotOrder = { "Class", "Weapon", "Armor", "Helm", "Cape", "Pet" }; - - public static void EquipBestGear(GearProfilePreset preset = GearProfilePreset.Damage) - => EquipBestGear(IScriptInterface.Instance, CoreBots.Instance, preset); - - public static void EquipBestGear(IScriptInterface bot, CoreBots core, GearProfilePreset preset = GearProfilePreset.Damage) - => EquipBestGearInternal(bot, core, BuildPlan(preset)); - - // Accepts either a preset-like token (e.g. "gold", "chaos") or a custom order - // (e.g. "gold,dmgAll,cp,rep,xp"). - public static void EquipBestGear(string profileOrMetaOrder = "damage") - => EquipBestGear(IScriptInterface.Instance, CoreBots.Instance, profileOrMetaOrder); - - public static void EquipBestGear(IScriptInterface bot, CoreBots core, string profileOrMetaOrder = "damage") - => EquipBestGearInternal(bot, core, BuildPlan(profileOrMetaOrder)); - - public static EquipmentSnapshot CaptureEquipment() - => CaptureEquipment(IScriptInterface.Instance); - - public static EquipmentSnapshot CaptureEquipment(IScriptInterface bot) - { - if (bot == null) - return new EquipmentSnapshot(new Dictionary(), Array.Empty()); - - var bySlot = new Dictionary(StringComparer.OrdinalIgnoreCase); - var extras = new List(); - - foreach (InventoryItem item in bot.Inventory.Items.Where(i => i != null && i.Equipped)) - { - string name = item.Name; - string? slot = GetEquipmentSlotKey(item.Category.ToString()); - if (string.IsNullOrWhiteSpace(slot)) - { - extras.Add(name); - continue; - } - - // Keep first seen item for a slot. - if (!bySlot.ContainsKey(slot)) - bySlot[slot] = name; - else - extras.Add(name); - } - - string[] ordered = BuildRestoreOrder(bySlot, extras); - return new EquipmentSnapshot(bySlot, ordered); - } - - public static void RestoreEquipment(EquipmentSnapshot snapshot, bool loadBank = true) - => RestoreEquipment(IScriptInterface.Instance, CoreBots.Instance, snapshot, loadBank); - - public static void RestoreEquipment(IScriptInterface bot, CoreBots core, EquipmentSnapshot snapshot, bool loadBank = true) - { - if (bot == null || core == null || snapshot == null || snapshot.OrderedItems.Length == 0) - return; - - if (loadBank) - TryLoadBank(bot); - - foreach (string itemName in snapshot.OrderedItems.Distinct(StringComparer.OrdinalIgnoreCase)) - { - if (string.IsNullOrWhiteSpace(itemName)) - continue; - - if (bot.Bank.Contains(itemName)) - core.Unbank(itemName); - - if (bot.Inventory.Contains(itemName)) - bot.Inventory.EquipItem(itemName); - } - } - - private static void EquipBestGearInternal(IScriptInterface bot, CoreBots core, GearPlan plan) - { - if (bot == null || core == null) - return; - - try - { - TryLoadBank(bot); - - core.EquipBestItemsForMeta( - new Dictionary - { - { "Weapon", plan.MetaPriority }, - { "Armor", plan.MetaPriority }, - { "Helm", plan.MetaPriority }, - { "Cape", plan.MetaPriority }, - { "Pet", plan.MetaPriority }, - } - ); - - EquipBestStackingPair(bot, core, plan.PrimaryMeta, plan.SecondaryMeta); - } - catch - { - bot.Log("Best gear equip failed. Continuing with current setup."); - } - } - - private static void TryLoadBank(IScriptInterface bot) - { - try - { - bot.Bank.Load(true); - } - catch - { - bot.Log("Bank load failed before best-gear selection; continuing."); - } - } - - private static GearPlan BuildPlan(GearProfilePreset preset) - { - return preset switch - { - GearProfilePreset.Gold => NewPlan("gold", new[] { "gold", "dmgAll", "cp", "rep", "xp" }), - GearProfilePreset.Exp => NewPlan("xp", new[] { "xp", "dmgAll", "gold", "cp", "rep" }), - GearProfilePreset.ClassPoints => NewPlan("cp", new[] { "cp", "dmgAll", "gold", "rep", "xp" }), - GearProfilePreset.Reputation => NewPlan("rep", new[] { "rep", "dmgAll", "gold", "cp", "xp" }), - GearProfilePreset.Chaos => NewPlan("Chaos", BuildMetaPriority("Chaos", DamageFallbackOrder)), - GearProfilePreset.Undead => NewPlan("Undead", BuildMetaPriority("Undead", DamageFallbackOrder)), - GearProfilePreset.Elemental => NewPlan("Elemental", BuildMetaPriority("Elemental", DamageFallbackOrder)), - GearProfilePreset.Dragonkin => NewPlan("Dragonkin", BuildMetaPriority("Dragonkin", DamageFallbackOrder)), - GearProfilePreset.Human => NewPlan("Human", BuildMetaPriority("Human", DamageFallbackOrder)), - GearProfilePreset.Orc => NewPlan("Orc", BuildMetaPriority("Orc", DamageFallbackOrder)), - _ => NewPlan("dmgAll", new[] { "dmgAll", "gold", "cp", "rep", "xp" }), - }; - } - - private static GearPlan BuildPlan(string profileOrMetaOrder) - { - string raw = (profileOrMetaOrder ?? string.Empty).Trim(); - if (string.IsNullOrWhiteSpace(raw)) - return BuildPlan(GearProfilePreset.Damage); - - var tokens = ParseMetaList(raw); - if (tokens.Count > 1) - { - string primary = tokens[0]; - return NewPlan(primary, BuildMetaPriority(primary, tokens.Skip(1).ToArray())); - } - - string single = tokens.Count == 1 ? tokens[0] : raw; - if (TryMapPreset(single, out GearProfilePreset preset)) - return BuildPlan(preset); - - string normalized = NormalizeMetaToken(single); - return NewPlan(normalized, BuildMetaPriority(normalized, DamageFallbackOrder)); - } - - private static GearPlan NewPlan(string primaryMeta, string[] metaPriority) - => new(primaryMeta, GetSecondaryMeta(metaPriority, primaryMeta), metaPriority); - - private static string[] BuildMetaPriority(string? primaryMeta, IEnumerable fallbackOrder) - { - var metas = new List(); - if (!string.IsNullOrWhiteSpace(primaryMeta)) - metas.Add(primaryMeta); - - foreach (string meta in fallbackOrder) - { - if (!metas.Any(x => x.Equals(meta, StringComparison.OrdinalIgnoreCase))) - metas.Add(meta); - } - - return metas.ToArray(); - } - - private static string GetSecondaryMeta(string[] metaPriority, string primaryMeta) - { - foreach (string meta in metaPriority) - { - if (!meta.Equals(primaryMeta, StringComparison.OrdinalIgnoreCase)) - return meta; - } - - return primaryMeta.Equals("dmgAll", StringComparison.OrdinalIgnoreCase) ? "gold" : "dmgAll"; - } - - private static List ParseMetaList(string raw) - { - char[] separators = { ',', ';', '|', '>' }; - return raw - .Split(separators, StringSplitOptions.RemoveEmptyEntries) - .Select(NormalizeMetaToken) - .Where(x => !string.IsNullOrWhiteSpace(x)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - } - - private static string NormalizeMetaToken(string token) - { - string value = (token ?? string.Empty).Trim(); - if (string.IsNullOrWhiteSpace(value)) - return "dmgAll"; - - return value.ToLowerInvariant() switch - { - "dmg" => "dmgAll", - "damage" => "dmgAll", - "dmgall" => "dmgAll", - "exp" => "xp", - "experience" => "xp", - "classpoints" => "cp", - "class-points" => "cp", - "reputation" => "rep", - "undead" => "Undead", - "chaos" => "Chaos", - "elemental" => "Elemental", - "dragonkin" => "Dragonkin", - "human" => "Human", - "orc" => "Orc", - _ => value, - }; - } - - private static bool TryMapPreset(string token, out GearProfilePreset preset) - { - switch ((token ?? string.Empty).Trim().ToLowerInvariant()) - { - case "damage": - case "dmg": - case "dmgall": - preset = GearProfilePreset.Damage; - return true; - case "gold": - preset = GearProfilePreset.Gold; - return true; - case "xp": - case "exp": - case "experience": - preset = GearProfilePreset.Exp; - return true; - case "cp": - case "classpoints": - preset = GearProfilePreset.ClassPoints; - return true; - case "rep": - case "reputation": - preset = GearProfilePreset.Reputation; - return true; - case "chaos": - preset = GearProfilePreset.Chaos; - return true; - case "undead": - preset = GearProfilePreset.Undead; - return true; - case "elemental": - preset = GearProfilePreset.Elemental; - return true; - case "dragonkin": - preset = GearProfilePreset.Dragonkin; - return true; - case "human": - preset = GearProfilePreset.Human; - return true; - case "orc": - preset = GearProfilePreset.Orc; - return true; - default: - preset = GearProfilePreset.Damage; - return false; - } - } - - private static void EquipBestStackingPair(IScriptInterface bot, CoreBots core, string primaryMeta, string secondaryMeta) - { - if (string.IsNullOrWhiteSpace(primaryMeta)) - return; - - var candidates = bot.Inventory.Items - .Concat(bot.Bank.Items) - .Where(i => i != null) - .Select(i => new BoostCandidate( - i, - GetSlotKey(i.Category.ToString()), - core.GetBoostFloat(i, primaryMeta), - core.GetBoostFloat(i, secondaryMeta))) - .Where(c => c.Slot != null) - .Where(c => !c.Item.Upgrade || bot.Player.IsMember) - .Where(c => c.Slot != "Weapon" || c.Item.EnhancementLevel > 0) - .ToList(); - - var primaryPool = new List(candidates.Where(c => c.Primary > 0)) { null }; - var secondaryPool = new List(candidates.Where(c => c.Secondary > 0)) { null }; - - BoostCandidate? bestPrimary = null; - BoostCandidate? bestSecondary = null; - double bestTotal = 0; - - foreach (BoostCandidate? primary in primaryPool) - { - foreach (BoostCandidate? secondary in secondaryPool) - { - if (primary == null && secondary == null) - continue; - - if (primary != null && secondary != null && primary.Slot == secondary.Slot && primary.Item.ID != secondary.Item.ID) - continue; - - double total = (primary?.Primary ?? 0) + (secondary?.Secondary ?? 0); - if (total <= bestTotal) - continue; - - bestTotal = total; - bestPrimary = primary; - bestSecondary = secondary; - } - } - - if (bestTotal <= 0) - return; - - if (bestPrimary != null) - EquipCandidate(bot, core, bestPrimary); - - if (bestSecondary != null && (bestPrimary == null || bestSecondary.Item.ID != bestPrimary.Item.ID)) - EquipCandidate(bot, core, bestSecondary); - } - - private static void EquipCandidate(IScriptInterface bot, CoreBots core, BoostCandidate candidate) - { - string name = candidate.Item.Name; - if (bot.Inventory.IsEquipped(name)) - return; - - if (bot.Bank.Contains(name)) - core.Unbank(name); - - if (bot.Inventory.Contains(name)) - bot.Inventory.EquipItem(name); - } - - private static string[] BuildRestoreOrder(Dictionary bySlot, List extras) - { - var ordered = new List(bySlot.Count + extras.Count); - - foreach (string slot in RestoreSlotOrder) - { - if (bySlot.TryGetValue(slot, out string? item) && !string.IsNullOrWhiteSpace(item)) - ordered.Add(item); - } - - ordered.AddRange( - extras - .Where(x => !string.IsNullOrWhiteSpace(x)) - .Distinct(StringComparer.OrdinalIgnoreCase) - ); - - return ordered.ToArray(); - } - - private static string? GetEquipmentSlotKey(string category) - { - if (string.IsNullOrWhiteSpace(category)) - return null; - - return category switch - { - "Class" => "Class", - _ => GetSlotKey(category), - }; - } - - private static string? GetSlotKey(string category) - { - if (string.IsNullOrWhiteSpace(category)) - return null; - - return category switch - { - "Armor" => "Armor", - "Helm" => "Helm", - "Cape" => "Cape", - "Pet" => "Pet", - "Sword" => "Weapon", - "Axe" => "Weapon", - "Dagger" => "Weapon", - "Gun" => "Weapon", - "HandGun" => "Weapon", - "Rifle" => "Weapon", - "Bow" => "Weapon", - "Mace" => "Weapon", - "Gauntlet" => "Weapon", - "Polearm" => "Weapon", - "Staff" => "Weapon", - "Wand" => "Weapon", - "Whip" => "Weapon", - _ => null, - }; - } - - private sealed class BoostCandidate - { - public BoostCandidate(InventoryItem item, string? slot, double primary, double secondary) - { - Item = item; - Slot = slot; - Primary = primary; - Secondary = secondary; - } - - public InventoryItem Item { get; } - public string? Slot { get; } - public double Primary { get; } - public double Secondary { get; } - } - - private sealed class GearPlan - { - public GearPlan(string primaryMeta, string secondaryMeta, string[] metaPriority) - { - PrimaryMeta = primaryMeta; - SecondaryMeta = secondaryMeta; - MetaPriority = metaPriority ?? Array.Empty(); - } - - public string PrimaryMeta { get; } - public string SecondaryMeta { get; } - public string[] MetaPriority { get; } - } -} - -public sealed class EquipmentSnapshot -{ - public EquipmentSnapshot(Dictionary bySlot, string[] orderedItems) - { - BySlot = bySlot ?? new Dictionary(StringComparer.OrdinalIgnoreCase); - OrderedItems = orderedItems ?? Array.Empty(); - } - - public Dictionary BySlot { get; } - public string[] OrderedItems { get; } -} 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 ebb8206ec..7b12f607f 100644 --- a/Ultras/ChampionDrakath.cs +++ b/Ultras/ChampionDrakath.cs @@ -4,16 +4,16 @@ tags: Ultra */ -//cs_include Scripts/Ultras/CoreEngine.cs -//cs_include Scripts/Ultras/CoreUltra.cs -//cs_include Scripts/CoreGearUtils.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 @@ -133,10 +133,15 @@ private static CoreAdvanced Adv public bool DontPreconfigure = true; 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 }; + 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() { @@ -153,13 +158,14 @@ private static CoreAdvanced Adv CoreBots.Instance.SkipOptions, }; - public List CoreSettings = new() - { - new Option("EquipBestGear", "Equip Best Gear", "Equip best gear for encounter", true), - new Option("DoEnh", "Do Enhancements", "", true), - new Option("UseLifeSteal", "Use LifeSteal", "Non-taunters equip/restock/use Scroll of Life Steal.", true), - new Option("HowManyTaunts", "How many taunters", "", HowManyTaunts.Two), - }; + 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 List ClassOverrides = new() { @@ -169,54 +175,88 @@ private static CoreAdvanced Adv new Option("d", "Quaternary Class Override", "Blank = use selected comp default for slot 4.", ""), }; - bool UseLifeSteal; - - public void ScriptMain(IScriptInterface bot) - { - if (Bot.Config != null - && !Bot.Config.Get("Main", "SkipOption")) - Bot.Config.Configure(); - - DrakathComp comp = Bot.Config!.Get("Main", "DoEquipClasses"); - overrideA = (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty).Trim(); - overrideB = (Bot.Config.Get("ClassOverrides", "b") ?? string.Empty).Trim(); - overrideC = (Bot.Config.Get("ClassOverrides", "c") ?? string.Empty).Trim(); - overrideD = (Bot.Config.Get("ClassOverrides", "d") ?? string.Empty).Trim(); - a = overrideA; - b = overrideB; - c = overrideC; - d = overrideD; - UseLifeSteal = Bot.Config.Get("CoreSettings", "UseLifeSteal"); - - bool usingComp = comp != DrakathComp.Unselected; - int taunterCount = (int)Bot.Config!.Get("CoreSettings", "HowManyTaunts"); - 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; + 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; } - EquipmentSnapshot equippedBefore = CoreGearUtils.CaptureEquipment(Bot); - - try - { - Core.Boot(); - Prep(); - Fight(); - C.JumpWait(); - } - finally - { - CoreGearUtils.RestoreEquipment(Bot, C, equippedBefore); - 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() { @@ -226,41 +266,40 @@ bool IsTaunter() return false; // Check based on HowManyTaunts setting - int taunterCount = (int)Bot.Config!.Get("CoreSettings", "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() - { - DrakathComp comp = Bot.Config!.Get("Main", "DoEquipClasses"); - if (comp != DrakathComp.Unselected) - ApplyCompAndEquip(comp, overrideA, overrideB, overrideC, overrideD); - - if (Bot.Config!.Get("CoreSettings", "EquipBestGear")) - CoreGearUtils.EquipBestGear(Bot, C, GearProfilePreset.Chaos); - - if (Bot.Config!.Get("CoreSettings", "DoEnh")) - DoEnhs(); - - Ultra.UseAlchemyPotions( - Ultra.GetBestTonicPotion(), - Ultra.GetBestElixirPotion() - ); - - if (IsTaunter()) - Ultra.GetScrollOfEnrage(); - else if (UseLifeSteal) - Ultra.GetScrollOfLifeSteal(); + 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) @@ -344,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) { @@ -463,7 +508,7 @@ void Fight() C.JumpWait(); } - void DoEnhs() + void DoEnhs() { string className = Bot.Player!.CurrentClass?.Name ?? string.Empty; if (string.IsNullOrEmpty(className)) @@ -606,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 @@ -618,10 +677,10 @@ enum HowManyTaunts Four = 4 } - enum DrakathComp - { - Unselected, - Safe, + public enum DrakathComp + { + Unselected, + Safe, Fast, Cheapest } diff --git a/Ultras/CoreUltra.cs b/Ultras/CoreUltra.cs index 239303ed9..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; + if (slotIndex >= classSlots.Length) + return true; - bool filled = false; - - // 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 @@ -1160,14 +1187,30 @@ public void GetScrollOfLifeSteal(int minStock = 10, int restockTo = 50) C.Unbank(scroll); int qty = Bot.Inventory.GetQuantity(scroll); + if (qty < minStock) { - Core.BuyItem(shopMap, shopId, scroll, restockTo); + 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() @@ -1707,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 10162c070..fbe324854 100644 --- a/Ultras/UltraNulgath.cs +++ b/Ultras/UltraNulgath.cs @@ -6,7 +6,6 @@ //cs_include Scripts/Ultras/CoreEngine.cs //cs_include Scripts/Ultras/CoreUltra.cs -//cs_include Scripts/CoreGearUtils.cs //cs_include Scripts/CoreBots.cs //cs_include Scripts/CoreFarms.cs //cs_include Scripts/CoreAdvanced.cs @@ -72,6 +71,10 @@ private static CoreAdvanced Adv 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() { @@ -94,6 +97,7 @@ private static CoreAdvanced Adv { 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), }; @@ -115,20 +119,22 @@ public void ScriptMain(IScriptInterface bot) if (Bot.Config != null && !Bot.Config.Get("Main", "SkipOption")) Bot.Config.Configure(); - NulgathComp comp = Bot.Config!.Get("Main", "DoEquipClasses"); - overrideA = (Bot.Config.Get("ClassOverrides", "a") ?? string.Empty).Trim(); - overrideB = (Bot.Config.Get("ClassOverrides", "b") ?? string.Empty).Trim(); - overrideC = (Bot.Config.Get("ClassOverrides", "c") ?? string.Empty).Trim(); - overrideD = (Bot.Config.Get("ClassOverrides", "d") ?? string.Empty).Trim(); + 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; - UseLifeSteal = Bot.Config.Get("CoreSettings", "UseLifeSteal"); - - bool usingComp = comp != NulgathComp.Unselected; + 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."); @@ -136,21 +142,50 @@ public void ScriptMain(IScriptInterface bot) return; } - EquipmentSnapshot equippedBefore = CoreGearUtils.CaptureEquipment(Bot); + Adv.GearStore(); try { - Core.Boot(); - Prep(); - Fight(); + Run(ActiveComp, EquipBestGear, DoEnhancements, UseLifeSteal, overrideA, overrideB, overrideC, overrideD); } finally { - CoreGearUtils.RestoreEquipment(Bot, C, equippedBefore); + 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; @@ -167,17 +202,18 @@ bool IsTaunter() void Prep() { - NulgathComp comp = Bot.Config!.Get("Main", "DoEquipClasses"); - if (comp != NulgathComp.Unselected) - ApplyCompAndEquip(comp, overrideA, overrideB, overrideC, overrideD); + if (ActiveComp != NulgathComp.Unselected) + ApplyCompAndEquip(ActiveComp, overrideA, overrideB, overrideC, overrideD); - if (Bot.Config.Get("CoreSettings", "EquipBestGear")) - CoreGearUtils.EquipBestGear(Bot, C, GearProfilePreset.Damage); + if (EquipBestGear) + EquipBestDmgGear(); - if (Bot.Config.Get("CoreSettings", "DoEnh")) + if (DoEnhancements) DoEnhs(); Ultra.UseAlchemyPotions(Ultra.GetBestTonicPotion(), Ultra.GetBestElixirPotion()); + Ultra.BuyAlchemyPotion("Potent Honor Potion"); + Core.EquipConsumable("Potent Honor Potion"); if (IsTaunter()) Ultra.GetScrollOfEnrage(); @@ -280,6 +316,13 @@ void Fight() 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); @@ -454,6 +497,20 @@ void DoEnhs() } } + 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, 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, + } +}