From 26d57c2b2d85485d257cfe1487fca439f9f1c5ee Mon Sep 17 00:00:00 2001 From: Reinen <127223416+DaruKani@users.noreply.github.com> Date: Mon, 25 May 2026 23:48:30 +0700 Subject: [PATCH 1/3] Rough one playable with stupid logic need many fix --- Decks/AI_SacredBot.ydk | 59 + Game/AI/Decks/SacredExecutor.cs | 1892 +++++++++++++++++++++++++++++++ WindBot.csproj | 1 + 3 files changed, 1952 insertions(+) create mode 100644 Decks/AI_SacredBot.ydk create mode 100644 Game/AI/Decks/SacredExecutor.cs diff --git a/Decks/AI_SacredBot.ydk b/Decks/AI_SacredBot.ydk new file mode 100644 index 00000000..1f6ce67b --- /dev/null +++ b/Decks/AI_SacredBot.ydk @@ -0,0 +1,59 @@ +#created by MDPro3 +#main +38776201 +38776201 +38776201 +50251045 +50251045 +50251045 +96345184 +96345184 +96345184 +23856331 +48770333 +22734799 +42141493 +42141493 +42141493 +14558127 +14558127 +23434538 +59138498 +59138498 +59138498 +1259915 +1259915 +1259915 +89753095 +89753095 +89753095 +65861210 +65861210 +65861210 +58570206 +58570206 +58570206 +24224830 +86132414 +86132414 +86132414 +50147815 +7044562 +7044562 +7044562 +#extra +7894706 +7894706 +7894706 +17745969 +17745969 +17745969 +85065943 +85065943 +85065943 +15291624 +92359409 +56910167 +70636044 +29301450 +41999284 \ No newline at end of file diff --git a/Game/AI/Decks/SacredExecutor.cs b/Game/AI/Decks/SacredExecutor.cs new file mode 100644 index 00000000..4bbed1c6 --- /dev/null +++ b/Game/AI/Decks/SacredExecutor.cs @@ -0,0 +1,1892 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using WindBot; +using WindBot.Game; +using WindBot.Game.AI; +using YGOSharp.OCGWrapper.Enums; + +namespace WindBot.Game.AI.Decks +{ + // CORI Sacred Beast Executor + // Routed from user-corrected combo notes: + // 1) Starter = Lightning Crash / Card of the Soul -> Hamon. + // 2) Hamon searches Unleashing first. If Unleashing is already available, search Skyfire. If both are available, search Paradise as spare cost. + // 3) Unleashing is the real core: search 3 different pieces, never discard Uria, and intentionally discard Hamon/Orchestrator when following the Hamon route. + // 4) If no starter/searcher, Normal Martyr first; Martyr on-summon must search Skyfire only. + // 5) Skyfire places Skyfire x2 + Fallen Paradise; never activate Fallen Paradise from hand as opener. Fallen Paradise pays Skyfire x3 first and summons Orchestrator. + // 6) Opponent turn: Destruction Chant / Divine Abyss / Raviel wipe are held as interrupts, with Raviel wipe only if Martyr x2 can be tributed. + [Deck("SacredBot", "AI_SacredBot")] + class SacredExecutor : DefaultExecutor + { + public class CardId + { + // ===== Main Deck: CORI Sacred Beast core ===== + public const int UnleashingTheSacredBeasts = 38776201; + public const int HamonSacredBeastOfSinfulCatastrophe = 50251045; + public const int RavielSacredBeastOfEndlessEternity = 96345184; + public const int UriaSacredBeastOfCataclysmicFire = 23856331; + public const int TheOrchestratorOfTheSacredBeasts = 22734799; + public const int MartyrOfTheSacredBeasts = 59138498; + public const int SkyfireOfTheSacredBeast = 1259915; + public const int FallenParadiseOfTheSacredBeasts = 65861210; + public const int DivineAbyssOfTheSacredBeast = 86132414; + public const int DestructionChantOfTheSacredBeast = 50147815; + + // ===== Main Deck: utility / hand traps ===== + public const int ThunderKingTheLightningstrikeKaiju = 48770333; + public const int MulcharmyFuwalos = 42141493; + public const int AshBlossom = 14558127; + public const int MaxxC = 23434538; + public const int LightningCrash = 89753095; // user เรียก Thunder Clap: ใช้เปิดหา Hamon ได้ + public const int Thunderclap = LightningCrash; // alias กันชื่อเก่าในโค้ด + public const int HeavyPolymerization = 58570206; + public const int CalledByTheGrave = 24224830; + public const int CardOfTheSoul = 7044562; // user พิมพ์ Call of the soul; ID นี้คือ Card of the Soul + + // ===== Extra Deck ===== + public const int PhantasmalSacredBeastsOfChaos = 7894706; + public const int SuperVehicroidMobileBase = 17745969; + public const int SaintAzamina = 85065943; + public const int ThunderDragonColossus = 15291624; + public const int SuperdreadnoughtRailCannonGustavRocket = 92359409; + public const int SuperdreadnoughtRailCannonGustavMax = 56910167; + public const int VarudrasTheFinalBringer = 70636044; + public const int SPLittleKnight = 29301450; + public const int Linkuriboh = 41999284; + } + + private readonly HashSet SacredBeastMonsterIds = new HashSet + { + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.RavielSacredBeastOfEndlessEternity, + CardId.UriaSacredBeastOfCataclysmicFire + }; + + private readonly HashSet SacredBeastSpellIds = new HashSet + { + CardId.UnleashingTheSacredBeasts, + CardId.SkyfireOfTheSacredBeast, + CardId.FallenParadiseOfTheSacredBeasts + }; + + private readonly HashSet SacredBeastTrapIds = new HashSet + { + CardId.DivineAbyssOfTheSacredBeast, + CardId.DestructionChantOfTheSacredBeast + }; + + private readonly HashSet SacredBeastSupportMonsterIds = new HashSet + { + CardId.TheOrchestratorOfTheSacredBeasts, + CardId.MartyrOfTheSacredBeasts + }; + + private readonly Dictionary DeckCountTable = new Dictionary + { + { CardId.UnleashingTheSacredBeasts, 3 }, + { CardId.HamonSacredBeastOfSinfulCatastrophe, 3 }, + { CardId.RavielSacredBeastOfEndlessEternity, 3 }, + { CardId.UriaSacredBeastOfCataclysmicFire, 1 }, + { CardId.ThunderKingTheLightningstrikeKaiju, 1 }, + { CardId.TheOrchestratorOfTheSacredBeasts, 1 }, + { CardId.MulcharmyFuwalos, 3 }, + { CardId.AshBlossom, 2 }, + { CardId.MaxxC, 1 }, + { CardId.MartyrOfTheSacredBeasts, 3 }, + { CardId.SkyfireOfTheSacredBeast, 3 }, + { CardId.LightningCrash, 3 }, + { CardId.FallenParadiseOfTheSacredBeasts, 3 }, + { CardId.HeavyPolymerization, 3 }, + { CardId.CalledByTheGrave, 1 }, + { CardId.DivineAbyssOfTheSacredBeast, 3 }, + { CardId.DestructionChantOfTheSacredBeast, 1 }, + { CardId.CardOfTheSoul, 3 } + }; + + private readonly List CurrentNegateCardList = new List(); + private int SPLittleKnightRemoveStep = 0; + + // Lightweight route memory. This makes Unleashing know whether Hamon was actually used first, + // instead of guessing from whether Unleashing was already in the opening hand. + private bool HamonSearchQueuedForRoute = false; + + public SacredExecutor(GameAI ai, Duel duel) + : base(ai, duel) + { + // ===== Defensive chain / interruption first ===== + AddExecutor(ExecutorType.Activate, CardId.AshBlossom, AshBlossomActivate); + AddExecutor(ExecutorType.Activate, CardId.CalledByTheGrave, CalledByTheGraveActivate); + AddExecutor(ExecutorType.Activate, CardId.MaxxC, MaxxCActivate); + AddExecutor(ExecutorType.Activate, CardId.MulcharmyFuwalos, FuwalosActivate); + AddExecutor(ExecutorType.Activate, CardId.VarudrasTheFinalBringer, VarudrasActivate); + AddExecutor(ExecutorType.Activate, CardId.PhantasmalSacredBeastsOfChaos, PhantasmalSacredBeastsOfChaosActivate); + AddExecutor(ExecutorType.Activate, CardId.SPLittleKnight, SPLittleKnightActivate); + AddExecutor(ExecutorType.Activate, CardId.DivineAbyssOfTheSacredBeast, DivineAbyssActivate); + AddExecutor(ExecutorType.Activate, CardId.DestructionChantOfTheSacredBeast, DestructionChantActivate); + + // ===== Going second / board breaker ===== + AddExecutor(ExecutorType.Activate, CardId.HeavyPolymerization, HeavyPolymerizationActivate); + AddExecutor(ExecutorType.SpSummon, CardId.ThunderKingTheLightningstrikeKaiju, ThunderKingKaijuSummon); + + // ===== Main starters / searchers ===== + // IMPORTANT: split by actual effect/location. Do not use one giant Activate per card. + // Route should decide “what should be next”; each executor should answer only one prompt/effect. + AddExecutor(ExecutorType.Activate, CardId.LightningCrash, LightningCrash_Starter_SearchHamon); + AddExecutor(ExecutorType.Activate, CardId.CardOfTheSoul, CardOfTheSoul_Starter_SearchHamonOrRaviel); + + AddExecutor(ExecutorType.Activate, CardId.HamonSacredBeastOfSinfulCatastrophe, Hamon_Hand_SearchSpell); + AddExecutor(ExecutorType.Activate, CardId.RavielSacredBeastOfEndlessEternity, Raviel_Hand_SearchUria); + AddExecutor(ExecutorType.Activate, CardId.RavielSacredBeastOfEndlessEternity, Raviel_Field_BoardWipeOnlyWithMartyr2); + AddExecutor(ExecutorType.Activate, CardId.UriaSacredBeastOfCataclysmicFire, Uria_Hand_SearchDestructionChant); + AddExecutor(ExecutorType.Activate, CardId.UriaSacredBeastOfCataclysmicFire, Uria_Field_DestroyFaceupST); + + AddExecutor(ExecutorType.Activate, CardId.UnleashingTheSacredBeasts, Unleashing_Main_Search3Discard2); + AddExecutor(ExecutorType.Activate, CardId.UnleashingTheSacredBeasts, Unleashing_GY_RecoverySearch); + + AddExecutor(ExecutorType.Activate, CardId.MartyrOfTheSacredBeasts, Martyr_OnSummon_PlaceSkyfireOnly); + AddExecutor(ExecutorType.Activate, CardId.MartyrOfTheSacredBeasts, Martyr_Field_SummonTwoMartyr); + AddExecutor(ExecutorType.Activate, CardId.MartyrOfTheSacredBeasts, Martyr_GY_EndPhaseRecovery); + + AddExecutor(ExecutorType.Activate, CardId.SkyfireOfTheSacredBeast, Skyfire_Hand_ActivateCardOnly); + AddExecutor(ExecutorType.Activate, CardId.SkyfireOfTheSacredBeast, Skyfire_Field_Place2RevealPlaceParadise); + AddExecutor(ExecutorType.Activate, CardId.SkyfireOfTheSacredBeast, Skyfire_GY_EndPhaseRecovery); + + AddExecutor(ExecutorType.Activate, CardId.FallenParadiseOfTheSacredBeasts, FallenParadise_Field_SummonByCost3); + AddExecutor(ExecutorType.Activate, CardId.FallenParadiseOfTheSacredBeasts, FallenParadise_Field_Draw2AfterSetup); + + AddExecutor(ExecutorType.Activate, CardId.TheOrchestratorOfTheSacredBeasts, Orchestrator_Hand_SummonSacredBeast); + AddExecutor(ExecutorType.Activate, CardId.TheOrchestratorOfTheSacredBeasts, Orchestrator_Field_ReviveRouteTarget); + AddExecutor(ExecutorType.Activate, CardId.TheOrchestratorOfTheSacredBeasts, Orchestrator_GY_ReviveLevel10); + + // ===== Normal Summon plan ===== + AddExecutor(ExecutorType.Summon, CardId.MartyrOfTheSacredBeasts, MartyrSummon); + AddExecutor(ExecutorType.Summon, CardId.TheOrchestratorOfTheSacredBeasts, OrchestratorSummon); + + // ===== Extra Deck plan ===== + AddExecutor(ExecutorType.SpSummon, CardId.PhantasmalSacredBeastsOfChaos, PhantasmalSacredBeastsOfChaosSummon); + AddExecutor(ExecutorType.SpSummon, CardId.VarudrasTheFinalBringer, Rank10NegateSummon); + AddExecutor(ExecutorType.SpSummon, CardId.SuperdreadnoughtRailCannonGustavRocket, GustavRocketSummon); + AddExecutor(ExecutorType.SpSummon, CardId.SuperdreadnoughtRailCannonGustavMax, GustavMaxSummon); + AddExecutor(ExecutorType.SpSummon, CardId.ThunderDragonColossus, ThunderDragonColossusSummon); + AddExecutor(ExecutorType.SpSummon, CardId.SPLittleKnight, SPLittleKnightSummon); + AddExecutor(ExecutorType.SpSummon, CardId.Linkuriboh, LinkuribohSummon); + + // ===== Generic fallback ===== + AddExecutor(ExecutorType.Repos, MonsterRepos); + AddExecutor(ExecutorType.SpellSet, SpellSet); + } + + #region Helper + + private int CheckRemainInDeck(int id) + { + if (!DeckCountTable.ContainsKey(id)) return 0; + return Bot.GetRemainingCount(id, DeckCountTable[id]); + } + + private int CheckRemainInDeck(params int[] ids) + { + int result = 0; + foreach (int id in ids) result += CheckRemainInDeck(id); + return result; + } + + private bool CheckWhetherNegated(bool disablecheck = true, bool toFieldCheck = false, CardType type = 0) + { + if (DefaultCheckWhetherCardIsNegated(Card)) return true; + if (disablecheck + && (Card.Location == CardLocation.MonsterZone || Card.Location == CardLocation.SpellZone) + && Card.IsFaceup() + && Card.IsDisabled()) + { + return true; + } + return false; + } + + private int GetBotLifePointsSafe() + { + // WindBot หลาย fork ใช้ชื่อ property ไม่เหมือนกัน เลยใช้ reflection เพื่อไม่ผูกกับ API ชื่อเดียว + string[] names = { "LifePoints", "LP", "Lp", "lifePoints" }; + foreach (string name in names) + { + var prop = Bot.GetType().GetProperty(name); + if (prop != null) + { + object value = prop.GetValue(Bot, null); + if (value != null) return Convert.ToInt32(value); + } + + var field = Bot.GetType().GetField(name); + if (field != null) + { + object value = field.GetValue(Bot); + if (value != null) return Convert.ToInt32(value); + } + } + + // ถ้าอ่าน LP ไม่ได้ ให้ถือว่า 8000 เพื่อให้ Card of the Soul ยังทำงานตอนเปิดเกมได้ + return 8000; + } + + private bool IsSacredBeastMonster(ClientCard card) + { + return card != null && SacredBeastMonsterIds.Contains(card.Id); + } + + private bool IsSacredBeastSupport(ClientCard card) + { + return card != null && (SacredBeastMonsterIds.Contains(card.Id) || SacredBeastSupportMonsterIds.Contains(card.Id)); + } + + private bool HasSacredBeastOnField() + { + return Bot.GetMonsters().Any(IsSacredBeastMonster); + } + + private bool HasSacredBeastInHand() + { + return Bot.Hand.Any(IsSacredBeastMonster); + } + + private bool HasSacredBeastInGrave() + { + return Bot.Graveyard.Any(IsSacredBeastMonster); + } + + private bool HasFreeMonsterZone() + { + return Bot.GetMonsterCount() < 5; + } + + private bool HasTwoFreeMonsterZones() + { + return Bot.GetMonsterCount() <= 3; + } + + private bool HasCoreFieldReady() + { + return HasSacredBeastOnField() && Bot.HasInSpellZone(CardId.FallenParadiseOfTheSacredBeasts, true); + } + + private bool HasPhantasmalOnField() + { + return Bot.HasInMonstersZone(CardId.PhantasmalSacredBeastsOfChaos, true); + } + + private int CountSacredBeastsOnField() + { + return Bot.GetMonsters().Count(IsSacredBeastMonster); + } + + private int CountLevel10MonstersOnField() + { + return Bot.GetMonsters().Count(c => c != null && c.IsFaceup() && c.Level == 10); + } + + private int CountFaceupSpellTrap(int id) + { + return Bot.GetSpells().Count(c => c != null && c.IsFaceup() && c.IsCode(id)); + } + + private int CountDifferentSacredBeastsRemainInDeck() + { + int count = 0; + foreach (int id in SacredBeastMonsterIds) + { + if (CheckRemainInDeck(id) > 0) count++; + } + return count; + } + + private int CountCardsByTypeForFallenParadise(CardType type) + { + int hand = Bot.Hand.Count(c => c != null && c.HasType(type)); + int field = Bot.GetMonsters().Count(c => c != null && c.IsFaceup() && c.HasType(type)); + field += Bot.GetSpells().Count(c => c != null && c.IsFaceup() && c.HasType(type)); + return hand + field; + } + + private bool CanPayFallenParadiseCost() + { + return CountCardsByTypeForFallenParadise(CardType.Monster) >= 3 + || CountCardsByTypeForFallenParadise(CardType.Spell) >= 3 + || CountCardsByTypeForFallenParadise(CardType.Trap) >= 3; + } + + private bool IsBoardWipedRecoveryState() + { + // หลังโดน board wipe: สนามโล่งหรือเกือบโล่ง แต่ GY ยังมี engine ให้กู้เกม + if (Bot.GetMonsterCount() > 0) return false; + return HasSacredBeastInGrave() + || Bot.Graveyard.Any(c => c != null && c.IsCode( + CardId.TheOrchestratorOfTheSacredBeasts, + CardId.MartyrOfTheSacredBeasts, + CardId.UnleashingTheSacredBeasts, + CardId.SkyfireOfTheSacredBeast, + CardId.DivineAbyssOfTheSacredBeast, + CardId.DestructionChantOfTheSacredBeast)); + } + + private bool IsSacredEndBoardReady() + { + // กันปัญหา search/extend หลังบอร์ดจบ เช่น Hamon/Uria/Raviel ยังพยายามกดบนมือหลังได้บอสแล้ว + if (HasPhantasmalOnField()) return true; + if (HasCoreFieldReady() && CountSacredBeastsOnField() >= 2 && Bot.HasInSpellZone(CardId.DivineAbyssOfTheSacredBeast, true)) return true; + return false; + } + + private bool ShouldOpenOrExtendCombo() + { + if (IsSacredEndBoardReady()) return false; + return HasFreeMonsterZone() || IsBoardWipedRecoveryState(); + } + + private int[] SacredBeastMonsterPriority() + { + return new int[] + { + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.RavielSacredBeastOfEndlessEternity, + CardId.UriaSacredBeastOfCataclysmicFire + }; + } + + private int[] SacredBeastMonsterSearchPriority() + { + // Combo 1 ต้องเข้า Hamon ก่อนเพื่อหา Skyfire/Fallen + // Combo 2 / recovery ต้องมี Raviel เพื่อหา monster piece ต่อ + if (!Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe) && CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) > 0) + { + return new int[] + { + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.RavielSacredBeastOfEndlessEternity, + CardId.UriaSacredBeastOfCataclysmicFire + }; + } + + if (!Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity) && CheckRemainInDeck(CardId.RavielSacredBeastOfEndlessEternity) > 0) + { + return new int[] + { + CardId.RavielSacredBeastOfEndlessEternity, + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.UriaSacredBeastOfCataclysmicFire + }; + } + + return SacredBeastMonsterPriority(); + } + + private int[] RavielSearchPriority() + { + // Raviel หา Sacred Beast monster ยกเว้นตัวเอง: เอา Hamon ก่อน ถ้ามีแล้วค่อย Orchestrator/Martyr/Uria + return new int[] + { + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.TheOrchestratorOfTheSacredBeasts, + CardId.MartyrOfTheSacredBeasts, + CardId.UriaSacredBeastOfCataclysmicFire + }; + } + + private int[] SacredBeastSpellPriority() + { + int hamonTarget = PickHamonSpellSearchTarget(); + if (hamonTarget != 0) + { + return new int[] { hamonTarget }; + } + + return new int[] + { + CardId.UnleashingTheSacredBeasts, + CardId.SkyfireOfTheSacredBeast, + CardId.FallenParadiseOfTheSacredBeasts + }; + } + + private int[] SacredBeastTrapPriority() + { + return new int[] + { + CardId.DivineAbyssOfTheSacredBeast, + CardId.DestructionChantOfTheSacredBeast + }; + } + + private int[] MartyrPlacePriority() + { + // User route: Martyr summon must search/place Skyfire only. + // Do not let Martyr fetch Fallen/Divine/Chant during the main combo, because Skyfire is the bridge into Fallen Paradise. + return new int[] { CardId.SkyfireOfTheSacredBeast }; + } + + private ClientCard GetBestEnemyMonster(bool faceupOnly = true) + { + IEnumerable monsters = Enemy.GetMonsters().Where(c => c != null); + if (faceupOnly) monsters = monsters.Where(c => c.IsFaceup()); + return monsters + .OrderByDescending(c => c.IsFloodgate() ? 100000 : 0) + .ThenByDescending(c => c.IsMonsterDangerous() ? 50000 : 0) + .ThenByDescending(c => c.Attack) + .FirstOrDefault(); + } + + private ClientCard GetBestEnemyCard(bool faceupOnly = false) + { + ClientCard monster = GetBestEnemyMonster(faceupOnly); + if (monster != null) return monster; + + IEnumerable spells = Enemy.GetSpells().Where(c => c != null); + if (faceupOnly) spells = spells.Where(c => c.IsFaceup()); + return spells.FirstOrDefault(); + } + + private bool CheckLastChainShouldNegated() + { + ClientCard last = Util.GetLastChainCard(); + if (last == null || last.Controller != 1) return false; + if (DefaultCheckWhetherCardIsNegated(last) || last.IsDisabled()) return false; + if (last.IsCode(CardId.MulcharmyFuwalos, CardId.MaxxC)) return false; + return true; + } + + private int GetTurnSafe() + { + string[] names = { "Turn", "TurnCount", "CurrentTurn" }; + foreach (string name in names) + { + var prop = Duel.GetType().GetProperty(name); + if (prop != null) + { + object value = prop.GetValue(Duel, null); + if (value != null) return Convert.ToInt32(value); + } + + var field = Duel.GetType().GetField(name); + if (field != null) + { + object value = field.GetValue(Duel); + if (value != null) return Convert.ToInt32(value); + } + } + return 1; + } + + private bool IsLikelyGoingFirst() + { + return GetTurnSafe() <= 1 && Enemy.GetMonsterCount() == 0; + } + + private bool HasCardAccessible(int id) + { + return Bot.HasInHand(id) + || Bot.HasInSpellZone(id, true) + || Bot.HasInMonstersZone(id, true) + || Bot.Graveyard.Any(c => c != null && c.IsCode(id)); + } + + private bool HasPrimaryStarterOrSearcher() + { + // Main route starters/searchers that can naturally reach Hamon -> Unleashing. + // Raviel is intentionally not counted here, because the corrected route uses Raviel after Skyfire/Fallen is online. + if (Bot.HasInHand(CardId.LightningCrash)) return true; + if (Bot.HasInHand(CardId.CardOfTheSoul) && GetBotLifePointsSafe() == 8000) return true; + if (Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe)) return true; + if (Bot.HasInHand(CardId.UnleashingTheSacredBeasts)) return true; + return false; + } + + private bool ShouldStartFromMartyrFallback() + { + // Bad hand fallback from user note: + // no starter/searcher -> Normal Martyr first, search/place Skyfire, and never open with Paradise from hand. + if (HasPrimaryStarterOrSearcher()) return false; + if (!Bot.HasInHand(CardId.MartyrOfTheSacredBeasts)) return false; + if (Bot.HasInMonstersZone(CardId.MartyrOfTheSacredBeasts, true)) return false; + if (Bot.HasInSpellZone(CardId.SkyfireOfTheSacredBeast, true)) return false; + return HasFreeMonsterZone(); + } + + private bool IsCoreStartedByMartyr() + { + return Bot.HasInMonstersZone(CardId.MartyrOfTheSacredBeasts, true) + || Bot.HasInSpellZone(CardId.SkyfireOfTheSacredBeast, true) + || Bot.HasInSpellZone(CardId.FallenParadiseOfTheSacredBeasts, true); + } + + private int PickHamonSpellSearchTarget() + { + // Corrected route: Hamon -> Unleashing first. If Unleashing exists, get Skyfire. + // If both Unleashing and Skyfire exist, Paradise becomes a spare cost/resource. + if (!HasCardAccessible(CardId.UnleashingTheSacredBeasts) + && CheckRemainInDeck(CardId.UnleashingTheSacredBeasts) > 0) + return CardId.UnleashingTheSacredBeasts; + + if (!HasCardAccessible(CardId.SkyfireOfTheSacredBeast) + && CheckRemainInDeck(CardId.SkyfireOfTheSacredBeast) > 0) + return CardId.SkyfireOfTheSacredBeast; + + if (!HasCardAccessible(CardId.FallenParadiseOfTheSacredBeasts) + && CheckRemainInDeck(CardId.FallenParadiseOfTheSacredBeasts) > 0) + return CardId.FallenParadiseOfTheSacredBeasts; + + if (CheckRemainInDeck(CardId.UnleashingTheSacredBeasts) > 0) + return CardId.UnleashingTheSacredBeasts; + + if (CheckRemainInDeck(CardId.SkyfireOfTheSacredBeast) > 0) + return CardId.SkyfireOfTheSacredBeast; + + if (CheckRemainInDeck(CardId.FallenParadiseOfTheSacredBeasts) > 0) + return CardId.FallenParadiseOfTheSacredBeasts; + + return 0; + } + + private bool HasUsedHamonLine() + { + // Do not infer this from "Unleashing is accessible" because Unleashing can be in the opening hand. + // It counts only if Hamon activation was queued or Hamon has already reached GY. + return HamonSearchQueuedForRoute + || Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe)); + } + + private bool IsMartyrNegatedOrInterrupted() + { + ClientCard martyr = Bot.GetMonsters() + .FirstOrDefault(c => c != null && c.IsFaceup() && c.IsCode(CardId.MartyrOfTheSacredBeasts)); + if (martyr == null) return false; + if (DefaultCheckWhetherCardIsNegated(martyr) || martyr.IsDisabled()) return true; + + // Practical checkpoint from the route: Martyr is on field, but no face-up Skyfire was placed. + return CountFaceupSpellTrap(CardId.SkyfireOfTheSacredBeast) == 0 + && !Bot.HasInSpellZone(CardId.SkyfireOfTheSacredBeast, true) + && HasCardAccessible(CardId.UnleashingTheSacredBeasts); + } + + private int CountFaceupMartyrOnField() + { + return Bot.GetMonsters().Count(c => c != null && c.IsFaceup() && c.IsCode(CardId.MartyrOfTheSacredBeasts)); + } + + private bool IsNeverDiscard(int id) + { + // Uria must stay in hand for the Destruction Chant line. + if (id == CardId.UriaSacredBeastOfCataclysmicFire) return true; + + // Never pay important Extra Deck monsters as Fallen/other generic cost if a fork can place them in hand/field somehow. + return id == CardId.PhantasmalSacredBeastsOfChaos + || id == CardId.ThunderDragonColossus + || id == CardId.VarudrasTheFinalBringer + || id == CardId.SPLittleKnight + || id == CardId.SuperdreadnoughtRailCannonGustavRocket + || id == CardId.SuperdreadnoughtRailCannonGustavMax; + } + + private int DiscardScore(ClientCard card, HashSet protectedIds, bool preferHamon = false, bool preferOrchestrator = false) + { + if (card == null) return 9999; + if (IsNeverDiscard(card.Id)) return 9999; + if (protectedIds != null && protectedIds.Contains(card.Id)) return 9999; + + if (preferHamon && card.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe)) return -100; + if (preferOrchestrator && card.IsCode(CardId.TheOrchestratorOfTheSacredBeasts)) return -90; + + bool duplicate = Bot.Hand.Count(c => c != null && c.Id == card.Id) >= 2; + if (duplicate) return 0; + + if (card.IsCode(CardId.DivineAbyssOfTheSacredBeast)) return 1; // brick in hand + if (card.IsCode(CardId.CardOfTheSoul) && GetBotLifePointsSafe() != 8000) return 2; + if (card.IsCode(CardId.HeavyPolymerization) && IsLikelyGoingFirst()) return 3; + if (card.IsCode(CardId.ThunderKingTheLightningstrikeKaiju) && IsLikelyGoingFirst()) return 4; + if (card.IsCode(CardId.MulcharmyFuwalos)) return 5; + if (card.IsCode(CardId.SkyfireOfTheSacredBeast) && CountFaceupSpellTrap(CardId.SkyfireOfTheSacredBeast) >= 2) return 6; + if (card.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe, CardId.RavielSacredBeastOfEndlessEternity)) return 50; + if (card.IsCode(CardId.AshBlossom, CardId.MaxxC, CardId.CalledByTheGrave)) return 70; + return 20; + } + + private ClientCard GetBestDiscardCost(IEnumerable protectedIds = null, bool preferHamon = false, bool preferOrchestrator = false) + { + HashSet protectedSet = protectedIds == null + ? new HashSet() + : new HashSet(protectedIds); + + return Bot.Hand + .Where(c => c != null) + .OrderBy(c => DiscardScore(c, protectedSet, preferHamon, preferOrchestrator)) + .FirstOrDefault(c => DiscardScore(c, protectedSet, preferHamon, preferOrchestrator) < 9999); + } + + private List GetBestDiscardCosts(int count, IEnumerable protectedIds = null) + { + HashSet protectedSet = protectedIds == null + ? new HashSet() + : new HashSet(protectedIds); + + return Bot.Hand + .Where(c => c != null) + .OrderBy(c => DiscardScore(c, protectedSet)) + .Where(c => DiscardScore(c, protectedSet) < 9999) + .Take(count) + .ToList(); + } + + private void SelectDiscardCost(params int[] protectedIds) + { + ClientCard discard = GetBestDiscardCost(protectedIds); + if (discard != null) AI.SelectCard(discard); + } + + private bool QueueSearchThenDiscard(int searchTarget, params int[] protectedIds) + { + if (searchTarget == 0) return false; + + ClientCard discard = GetBestDiscardCost(protectedIds.Concat(new int[] { searchTarget })); + if (discard == null) return false; + + AI.SelectCard(searchTarget); + AI.SelectNextCard(discard); + return true; + } + + private void QueueUnleashingSearchAndCost() + { + bool hamonAlreadyUsed = HasUsedHamonLine() + || Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe)); + + if (hamonAlreadyUsed) + { + // Case 1: Hamon was used first. + // Search Raviel + Martyr + Orchestrator, then discard Hamon + Orchestrator. + AI.SelectCard(new[] + { + CardId.RavielSacredBeastOfEndlessEternity, + CardId.MartyrOfTheSacredBeasts, + CardId.TheOrchestratorOfTheSacredBeasts + }); + AI.SelectNextCard(new[] + { + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.TheOrchestratorOfTheSacredBeasts + }); + return; + } + + // Case 2: Hamon has not been used. + // Search Raviel + Martyr + Hamon. Cost is flexible, but never Uria and never the searched/core cards. + AI.SelectCard(new[] + { + CardId.RavielSacredBeastOfEndlessEternity, + CardId.MartyrOfTheSacredBeasts, + CardId.HamonSacredBeastOfSinfulCatastrophe + }); + + List costs = GetBestDiscardCosts(2, new int[] + { + CardId.RavielSacredBeastOfEndlessEternity, + CardId.MartyrOfTheSacredBeasts, + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.UriaSacredBeastOfCataclysmicFire + }); + + if (costs.Count >= 2) + AI.SelectNextCard(costs.Take(2).ToList()); + } + + private List GetFallenParadiseCostCards() + { + // Priority 1: Skyfire x3. + List skyfires = Bot.GetSpells() + .Where(c => c != null && c.IsFaceup() && c.IsCode(CardId.SkyfireOfTheSacredBeast)) + .Take(3) + .ToList(); + if (skyfires.Count >= 3) return skyfires; + + // Priority 2: Martyr x3 if the Martyr line was not interrupted. + List martyrs = Bot.GetMonsters() + .Where(c => c != null && c.IsFaceup() && c.IsCode(CardId.MartyrOfTheSacredBeasts) + && !DefaultCheckWhetherCardIsNegated(c) && !c.IsDisabled()) + .Take(3) + .ToList(); + if (martyrs.Count >= 3) return martyrs; + + // Priority 3: if Martyr was stopped, use expendable monsters from hand/field, especially Fuwalos or duplicates. + List monsters = Bot.GetMonsters() + .Where(c => c != null && c.IsFaceup() && c.HasType(CardType.Monster) && !IsNeverDiscard(c.Id)) + .Concat(Bot.Hand.Where(c => c != null && c.HasType(CardType.Monster) && !IsNeverDiscard(c.Id))) + .OrderBy(c => + { + if (c.IsCode(CardId.MulcharmyFuwalos)) return 0; + if (Bot.Hand.Count(h => h != null && h.Id == c.Id) >= 2) return 1; + if (c.IsCode(CardId.ThunderKingTheLightningstrikeKaiju) && IsLikelyGoingFirst()) return 2; + if (c.IsCode(CardId.AshBlossom, CardId.MaxxC)) return 8; + if (c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe, CardId.RavielSacredBeastOfEndlessEternity)) return 20; + return 5; + }) + .Take(3) + .ToList(); + if (monsters.Count >= 3) return monsters; + + // Last resort: spare spells, but never consume Unleashing before it starts the combo if it is still in hand. + List spells = Bot.GetSpells() + .Where(c => c != null && c.IsFaceup() && c.HasType(CardType.Spell)) + .Concat(Bot.Hand.Where(c => c != null && c.HasType(CardType.Spell) + && !c.IsCode(CardId.UnleashingTheSacredBeasts))) + .Where(c => c != null && !IsNeverDiscard(c.Id)) + .OrderBy(c => c.IsCode(CardId.HeavyPolymerization) && IsLikelyGoingFirst() ? 0 : 5) + .Take(3) + .ToList(); + if (spells.Count >= 3) return spells; + + return new List(); + } + + private int CountAccessibleSkyfireCopiesForEffect() + { + // Skyfire can place 2 copies from hand, Deck, and/or GY. + // Do not count the current face-up Skyfire that is activating its effect. + int count = CheckRemainInDeck(CardId.SkyfireOfTheSacredBeast); + count += Bot.Hand.Count(c => c != null && c.IsCode(CardId.SkyfireOfTheSacredBeast)); + count += Bot.Graveyard.Count(c => c != null && c.IsCode(CardId.SkyfireOfTheSacredBeast)); + return count; + } + + private int PickSkyfireRevealTarget() + { + // Route checkpoint wants Raviel revealed here when possible, because Raviel then searches Uria. + if (Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity)) + return CardId.RavielSacredBeastOfEndlessEternity; + + if (Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe)) + return CardId.HamonSacredBeastOfSinfulCatastrophe; + + if (Bot.HasInHand(CardId.UriaSacredBeastOfCataclysmicFire)) + return CardId.UriaSacredBeastOfCataclysmicFire; + + return 0; + } + + private int PickFallenParadiseSummonTarget() + { + // Correct route target: first Fallen Paradise summon should produce Orchestrator. + if (!Bot.HasInMonstersZone(CardId.TheOrchestratorOfTheSacredBeasts, true) + && !Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.TheOrchestratorOfTheSacredBeasts)) + && CheckRemainInDeck(CardId.TheOrchestratorOfTheSacredBeasts) > 0) + return CardId.TheOrchestratorOfTheSacredBeasts; + + if (!Bot.HasInMonstersZone(CardId.RavielSacredBeastOfEndlessEternity, true) + && (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.RavielSacredBeastOfEndlessEternity)) + || CheckRemainInDeck(CardId.RavielSacredBeastOfEndlessEternity) > 0)) + return CardId.RavielSacredBeastOfEndlessEternity; + + if (!Bot.HasInMonstersZone(CardId.HamonSacredBeastOfSinfulCatastrophe, true) + && (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe)) + || CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) > 0)) + return CardId.HamonSacredBeastOfSinfulCatastrophe; + + return SacredBeastMonsterSearchPriority().FirstOrDefault(); + } + + private bool CanMakePhantasmalFusion() + { + int fieldSacred = Bot.GetMonsters().Count(IsSacredBeastMonster); + int fieldLevel10 = CountLevel10MonstersOnField(); + int handLevel10 = Bot.Hand.Count(c => c != null && c.Level == 10); + return fieldSacred >= 3 || fieldLevel10 + handLevel10 >= 3; + } + + private bool HasOpponentRelevantChain() + { + return Duel.LastChainPlayer == 1 && CheckLastChainShouldNegated(); + } + + #endregion + + #region Hand traps / generic defense + + private bool AshBlossomActivate() + { + return HasOpponentRelevantChain(); + } + + private bool MaxxCActivate() + { + return Duel.Player == 1; + } + + private bool FuwalosActivate() + { + return Duel.Player == 1 && Bot.GetMonsterCount() == 0 && Bot.GetSpellCount() == 0; + } + + private bool CalledByTheGraveActivate() + { + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + + ClientCard last = Util.GetLastChainCard(); + if (last != null && last.Controller == 1 && last.Location == CardLocation.Grave) + { + AI.SelectCard(last); + return true; + } + + ClientCard target = Enemy.Graveyard.FirstOrDefault(c => c != null && c.IsCode(CardId.AshBlossom, CardId.MaxxC, CardId.MulcharmyFuwalos)); + if (target != null) + { + AI.SelectCard(target); + return true; + } + + return false; + } + + #endregion + + #region Main Deck core - effect split executors + + // ===================================================================== + // Effect-split executors + // --------------------------------------------------------------------- + // Each method below answers exactly ONE card effect / prompt family. + // Do not merge hand / field / GY effects into one method again. + // If a card still refuses to activate, log ActivateDescription for only + // that specific method and fix the string id, instead of changing route. + // ===================================================================== + + private bool LightningCrash_Starter_SearchHamon() + { + if (Card.Location != CardLocation.Hand && Card.Location != CardLocation.SpellZone) return false; + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (!ShouldOpenOrExtendCombo()) return false; + if (Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe)) return false; + if (CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) <= 0) return false; + + AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); + return true; + } + + private bool CardOfTheSoul_Starter_SearchHamonOrRaviel() + { + if (Card.Location != CardLocation.Hand && Card.Location != CardLocation.SpellZone) return false; + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (!ShouldOpenOrExtendCombo()) return false; + if (GetBotLifePointsSafe() != 8000) return false; + + if (!Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe) + && CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) > 0) + { + AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); + return true; + } + + if (!Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity) + && CheckRemainInDeck(CardId.RavielSacredBeastOfEndlessEternity) > 0) + { + AI.SelectCard(CardId.RavielSacredBeastOfEndlessEternity); + return true; + } + + return false; + } + + private bool Hamon_Hand_SearchSpell() + { + if (Card.Location != CardLocation.Hand) return false; + if (!ShouldOpenOrExtendCombo()) return false; + + int searchTarget = PickHamonSpellSearchTarget(); + if (searchTarget == 0) return false; + + HamonSearchQueuedForRoute = true; + + // Normal route: never discard Hamon or searched card. + // If Martyr already got stopped and Hamon is still usable, the route explicitly wants Hamon as cost. + if (IsMartyrNegatedOrInterrupted()) + { + AI.SelectCard(searchTarget); + AI.SelectNextCard(CardId.HamonSacredBeastOfSinfulCatastrophe); + return true; + } + + return QueueSearchThenDiscard( + searchTarget, + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.UriaSacredBeastOfCataclysmicFire); + } + + private bool Raviel_Hand_SearchUria() + { + if (Card.Location != CardLocation.Hand) return false; + if (!ShouldOpenOrExtendCombo()) return false; + if (ShouldStartFromMartyrFallback()) return false; + if (Bot.HasInHand(CardId.UriaSacredBeastOfCataclysmicFire)) return false; + if (CheckRemainInDeck(CardId.UriaSacredBeastOfCataclysmicFire) <= 0) return false; + + AI.SelectCard(CardId.UriaSacredBeastOfCataclysmicFire); + AI.SelectNextCard(CardId.RavielSacredBeastOfEndlessEternity); + return true; + } + + private bool Raviel_Field_BoardWipeOnlyWithMartyr2() + { + if (Card.Location != CardLocation.MonsterZone) return false; + if (Duel.Player != 1) return false; + if (Enemy.GetMonsterCount() <= 0) return false; + + // User rule: use Raviel wipe ONLY when Martyr x2 can be tributed. + return CountFaceupMartyrOnField() >= 2; + } + + private bool Uria_Hand_SearchDestructionChant() + { + if (Card.Location != CardLocation.Hand) return false; + if (!ShouldOpenOrExtendCombo()) return false; + if (CheckRemainInDeck(CardId.DestructionChantOfTheSacredBeast) <= 0) return false; + + ClientCard discard = GetBestDiscardCost(new int[] + { + CardId.UriaSacredBeastOfCataclysmicFire, + CardId.DestructionChantOfTheSacredBeast + }); + if (discard == null) return false; + + AI.SelectCard(CardId.DestructionChantOfTheSacredBeast); + AI.SelectNextCard(discard); + return true; + } + + private bool Uria_Field_DestroyFaceupST() + { + if (Card.Location != CardLocation.MonsterZone) return false; + ClientCard target = Enemy.GetSpells().FirstOrDefault(c => c != null && c.IsFaceup()); + if (target == null) return false; + + AI.SelectCard(target); + return true; + } + + private bool Unleashing_Main_Search3Discard2() + { + if (Card.Location != CardLocation.Hand && Card.Location != CardLocation.SpellZone) return false; + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (!ShouldOpenOrExtendCombo()) return false; + + QueueUnleashingSearchAndCost(); + return true; + } + + private bool Unleashing_GY_RecoverySearch() + { + if (Card.Location != CardLocation.Grave) return false; + if (!IsBoardWipedRecoveryState()) return false; + + int target = SacredBeastMonsterSearchPriority().FirstOrDefault(id => CheckRemainInDeck(id) > 0); + if (target == 0) return false; + + AI.SelectCard(target); + return true; + } + + private bool Martyr_OnSummon_PlaceSkyfireOnly() + { + if (Card.Location != CardLocation.MonsterZone) return false; + if (CheckWhetherNegated()) return false; + + // Effect 0: on-summon place/search S/T. Correct route says Skyfire ONLY. + if (!(ActivateDescription == -1 || ActivateDescription == Util.GetStringId(CardId.MartyrOfTheSacredBeasts, 0))) + return false; + + if (Bot.GetSpellCount() >= 5) return false; + if (CheckRemainInDeck(CardId.SkyfireOfTheSacredBeast) <= 0) return false; + + AI.SelectCard(CardId.SkyfireOfTheSacredBeast); + return true; + } + + private bool Martyr_Field_SummonTwoMartyr() + { + if (Card.Location != CardLocation.MonsterZone) return false; + if (CheckWhetherNegated()) return false; + + // Effect 1: summon two Martyrs. + if (ActivateDescription != Util.GetStringId(CardId.MartyrOfTheSacredBeasts, 1)) return false; + if (!HasSacredBeastOnField()) return false; + if (!HasTwoFreeMonsterZones()) return false; + if (CheckRemainInDeck(CardId.MartyrOfTheSacredBeasts) + Bot.Graveyard.Count(c => c != null && c.IsCode(CardId.MartyrOfTheSacredBeasts)) < 2) return false; + + // Let system pick the two Martyrs if script only asks yes/no; otherwise preselect Martyr ID twice. + AI.SelectCard(new[] { CardId.MartyrOfTheSacredBeasts, CardId.MartyrOfTheSacredBeasts }); + return true; + } + + private bool Martyr_GY_EndPhaseRecovery() + { + if (Card.Location != CardLocation.Grave) return false; + if (Duel.Player != 1 || Duel.Phase != DuelPhase.End) return false; + return Bot.Graveyard.Any(IsSacredBeastMonster); + } + + private bool Skyfire_Hand_ActivateCardOnly() + { + if (Card.Location != CardLocation.Hand) return false; + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (ShouldStartFromMartyrFallback()) return false; + if (Bot.HasInSpellZone(CardId.SkyfireOfTheSacredBeast, true)) return false; + if (PickSkyfireRevealTarget() == 0) return false; + + // Only activate/set the Continuous Spell. The field effect is a different executor. + SelectSTPlace(null, true); + return true; + } + + private bool Skyfire_Field_Place2RevealPlaceParadise() + { + if (Card.Location != CardLocation.SpellZone) return false; + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (IsSacredEndBoardReady()) return false; + if (Duel.Player != 0) return false; + if (Duel.Phase != DuelPhase.Main1 && Duel.Phase != DuelPhase.Main2) return false; + + int revealTarget = PickSkyfireRevealTarget(); + if (revealTarget == 0) return false; + if (CountAccessibleSkyfireCopiesForEffect() < 2) return false; + + // Prompt 1: choose/place 2 Skyfire. + AI.SelectCard(new[] + { + CardId.SkyfireOfTheSacredBeast, + CardId.SkyfireOfTheSacredBeast + }); + + // Prompt 2: reveal LV10 Sacred Beast in hand. + AI.SelectNextCard(revealTarget); + + // Prompt 3: place Fallen Paradise. + if (CheckRemainInDeck(CardId.FallenParadiseOfTheSacredBeasts) > 0) + AI.SelectNextCard(CardId.FallenParadiseOfTheSacredBeasts); + + return true; + } + + private bool Skyfire_GY_EndPhaseRecovery() + { + if (Card.Location != CardLocation.Grave) return false; + if (Duel.Player != 1 || Duel.Phase != DuelPhase.End) return false; + return true; + } + + private bool FallenParadise_Field_SummonByCost3() + { + if (Card.Location != CardLocation.SpellZone) return false; + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (ShouldStartFromMartyrFallback()) return false; + if (!HasFreeMonsterZone()) return false; + + List costCards = GetFallenParadiseCostCards(); + int target = PickFallenParadiseSummonTarget(); + if (costCards.Count < 3 || target == 0) return false; + + AI.SelectCard(costCards.Take(3).ToList()); + AI.SelectNextCard(target); + return true; + } + + private bool FallenParadise_Field_Draw2AfterSetup() + { + if (Card.Location != CardLocation.SpellZone) return false; + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (!HasSacredBeastOnField()) return false; + if (CountLevel10MonstersOnField() < 1) return false; + + // Draw should happen after setup, not before summon route. + if (!(Bot.HasInMonstersZone(CardId.ThunderDragonColossus, true) + || Bot.HasInMonstersZone(CardId.SPLittleKnight, true) + || CountSacredBeastsOnField() >= 2)) return false; + + return true; + } + + private bool Orchestrator_Hand_SummonSacredBeast() + { + if (Card.Location != CardLocation.Hand) return false; + if (CheckWhetherNegated()) return false; + if (!HasFreeMonsterZone()) return false; + if (IsSacredEndBoardReady()) return false; + if (!HasSacredBeastInHand()) return false; + + int target = Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity) + ? CardId.RavielSacredBeastOfEndlessEternity + : SacredBeastMonsterPriority().FirstOrDefault(id => Bot.HasInHand(id)); + if (target == 0) return false; + + ClientCard discard = GetBestDiscardCost(new int[] { CardId.UriaSacredBeastOfCataclysmicFire, target }); + if (discard == null) return false; + + AI.SelectCard(target); + AI.SelectNextCard(discard); + return true; + } + + private bool Orchestrator_Field_ReviveRouteTarget() + { + if (Card.Location != CardLocation.MonsterZone) return false; + if (CheckWhetherNegated()) return false; + if (!HasFreeMonsterZone()) return false; + if (IsSacredEndBoardReady()) return false; + + int target = 0; + if (IsMartyrNegatedOrInterrupted() + && Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.MartyrOfTheSacredBeasts))) + target = CardId.MartyrOfTheSacredBeasts; + else if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.RavielSacredBeastOfEndlessEternity))) + target = CardId.RavielSacredBeastOfEndlessEternity; + else if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe))) + target = CardId.HamonSacredBeastOfSinfulCatastrophe; + else if (HasSacredBeastInHand()) + target = SacredBeastMonsterPriority().FirstOrDefault(id => Bot.HasInHand(id)); + + if (target == 0) return false; + + ClientCard discard = GetBestDiscardCost(new int[] { CardId.UriaSacredBeastOfCataclysmicFire, target }); + if (discard == null) return false; + + AI.SelectCard(target); + AI.SelectNextCard(discard); + return true; + } + + private bool Orchestrator_GY_ReviveLevel10() + { + if (Card.Location != CardLocation.Grave) return false; + if (!HasFreeMonsterZone()) return false; + if (IsSacredEndBoardReady()) return false; + + if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.RavielSacredBeastOfEndlessEternity))) + { + AI.SelectCard(CardId.RavielSacredBeastOfEndlessEternity); + return true; + } + + if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe))) + { + AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); + return true; + } + + return false; + } + + #endregion + + #region Main Deck core + + private bool UnleashingTheSacredBeastsActivate() + { + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (!ShouldOpenOrExtendCombo()) return false; + + if (Card.Location == CardLocation.Hand || Card.Location == CardLocation.SpellZone) + { + // Core combo starts here. Search 3 non-duplicate pieces, with route-specific cost. + QueueUnleashingSearchAndCost(); + return true; + } + + if (Card.Location == CardLocation.Grave) + { + // Recovery: only after board wipe / resource loss. + if (!IsBoardWipedRecoveryState()) return false; + int target = SacredBeastMonsterSearchPriority().FirstOrDefault(id => CheckRemainInDeck(id) > 0); + if (target == 0) return false; + AI.SelectCard(target); + return true; + } + + return false; + } + + + private bool LightningCrashActivate() + { + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (!ShouldOpenOrExtendCombo()) return false; + + // Starter only: Thunder Clap / Lightning Crash opens by finding Hamon. + if (!Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe) + && CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) > 0) + { + AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); + return true; + } + + return false; + } + + + private bool ThunderclapActivate() + { + return LightningCrashActivate(); + } + + private bool CardOfTheSoulActivate() + { + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (!ShouldOpenOrExtendCombo()) return false; + + // Card of the Soul is starter/extender only when LP is exactly 8000. + // After LP changes, treat it as discard/cost fodder instead. + if (GetBotLifePointsSafe() != 8000) return false; + + if (!Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe) + && CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) > 0) + { + AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); + return true; + } + + if (!Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity) + && CheckRemainInDeck(CardId.RavielSacredBeastOfEndlessEternity) > 0) + { + AI.SelectCard(CardId.RavielSacredBeastOfEndlessEternity); + return true; + } + + return false; + } + + + private bool HamonActivate() + { + if (Card.Location == CardLocation.Hand) + { + if (!ShouldOpenOrExtendCombo()) return false; + + int searchTarget = PickHamonSpellSearchTarget(); + if (searchTarget == 0) return false; + + // Normal Hamon line: do not discard Hamon or the searched card. + // Exception handled naturally later: if Martyr gets stopped and Hamon has not been used, Hamon may search Skyfire and discard Hamon. + bool martyrStoppedFallback = IsMartyrNegatedOrInterrupted() + && !Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe)); + + if (martyrStoppedFallback) + { + HamonSearchQueuedForRoute = true; + AI.SelectCard(searchTarget); + AI.SelectNextCard(CardId.HamonSacredBeastOfSinfulCatastrophe); + return true; + } + + HamonSearchQueuedForRoute = true; + return QueueSearchThenDiscard( + searchTarget, + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.UriaSacredBeastOfCataclysmicFire); + } + + if (Card.Location == CardLocation.MonsterZone) + { + return false; + } + + return false; + } + + + private bool RavielActivate() + { + if (Card.Location == CardLocation.Hand) + { + if (!ShouldOpenOrExtendCombo()) return false; + if (ShouldStartFromMartyrFallback()) return false; + + // Correct checkpoint: after Skyfire/Fallen setup, Raviel searches Uria and discards Raviel. + if (!Bot.HasInHand(CardId.UriaSacredBeastOfCataclysmicFire) + && CheckRemainInDeck(CardId.UriaSacredBeastOfCataclysmicFire) > 0) + { + AI.SelectCard(CardId.UriaSacredBeastOfCataclysmicFire); + AI.SelectNextCard(CardId.RavielSacredBeastOfEndlessEternity); + return true; + } + + // Fallback only if Uria is already available: find Orchestrator/Martyr/Hamon, but still never discard Uria. + int target = new int[] + { + CardId.TheOrchestratorOfTheSacredBeasts, + CardId.MartyrOfTheSacredBeasts, + CardId.HamonSacredBeastOfSinfulCatastrophe + }.FirstOrDefault(id => !Bot.HasInHand(id) && CheckRemainInDeck(id) > 0); + + if (target == 0) return false; + return QueueSearchThenDiscard(target, CardId.UriaSacredBeastOfCataclysmicFire, target); + } + + if (Card.Location == CardLocation.MonsterZone) + { + // Important: Raviel board wipe only if Martyr x2 can be tributed. Otherwise never fire it. + if (Duel.Player != 1) return false; + if (Enemy.GetMonsterCount() <= 0) return false; + return CountFaceupMartyrOnField() >= 2; + } + + return false; + } + + + private bool UriaActivate() + { + if (Card.Location == CardLocation.Hand) + { + if (!ShouldOpenOrExtendCombo()) return false; + if (CheckRemainInDeck(CardId.DestructionChantOfTheSacredBeast) <= 0) return false; + + ClientCard discard = GetBestDiscardCost(new int[] + { + CardId.UriaSacredBeastOfCataclysmicFire, + CardId.DestructionChantOfTheSacredBeast + }); + if (discard == null) return false; + + AI.SelectCard(CardId.DestructionChantOfTheSacredBeast); + AI.SelectNextCard(discard); + return true; + } + + if (Card.Location == CardLocation.MonsterZone) + { + ClientCard target = Enemy.GetSpells().Where(c => c != null && c.IsFaceup()).FirstOrDefault(); + if (target != null) + { + AI.SelectCard(target); + return true; + } + } + + return false; + } + + + private bool OrchestratorActivate() + { + if (CheckWhetherNegated()) return false; + if (!HasFreeMonsterZone()) return false; + if (IsSacredEndBoardReady()) return false; + + if (Card.Location == CardLocation.Hand) + { + if (!HasSacredBeastInHand()) return false; + + int target = Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity) + ? CardId.RavielSacredBeastOfEndlessEternity + : SacredBeastMonsterPriority().FirstOrDefault(id => Bot.HasInHand(id)); + if (target == 0) return false; + + ClientCard discard = GetBestDiscardCost(new int[] { CardId.UriaSacredBeastOfCataclysmicFire, target }); + if (discard == null) return false; + + AI.SelectCard(target); + AI.SelectNextCard(discard); + return true; + } + + if (Card.Location == CardLocation.MonsterZone) + { + // If Martyr was not stopped, revive Raviel first. + // If Martyr was stopped, revive Martyr to continue the line. + int target = 0; + if (IsMartyrNegatedOrInterrupted() + && Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.MartyrOfTheSacredBeasts))) + target = CardId.MartyrOfTheSacredBeasts; + else if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.RavielSacredBeastOfEndlessEternity))) + target = CardId.RavielSacredBeastOfEndlessEternity; + else if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe))) + target = CardId.HamonSacredBeastOfSinfulCatastrophe; + else if (HasSacredBeastInHand()) + target = SacredBeastMonsterPriority().FirstOrDefault(id => Bot.HasInHand(id)); + + if (target == 0) return false; + + ClientCard discard = GetBestDiscardCost(new int[] { CardId.UriaSacredBeastOfCataclysmicFire, target }); + if (discard == null) return false; + + AI.SelectCard(target); + AI.SelectNextCard(discard); + return true; + } + + if (Card.Location == CardLocation.Grave) + { + // After Colossus / Link line: revive Raviel first, then Hamon. + if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.RavielSacredBeastOfEndlessEternity))) + { + AI.SelectCard(CardId.RavielSacredBeastOfEndlessEternity); + return true; + } + + if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe))) + { + AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); + return true; + } + } + + return false; + } + + + private bool MartyrActivate() + { + if (CheckWhetherNegated()) return false; + + if (Card.Location == CardLocation.MonsterZone) + { + // On summon: Skyfire only. Do not place Fallen/Divine/Chant here. + if (ActivateDescription == -1 || ActivateDescription == Util.GetStringId(CardId.MartyrOfTheSacredBeasts, 0)) + { + if (Bot.GetSpellCount() >= 5) return false; + if (CheckRemainInDeck(CardId.SkyfireOfTheSacredBeast) <= 0) return false; + AI.SelectCard(CardId.SkyfireOfTheSacredBeast); + return true; + } + + // Combo step 8: if Martyr remains on field, summon two more Martyrs. + if (ActivateDescription == Util.GetStringId(CardId.MartyrOfTheSacredBeasts, 1)) + { + return HasSacredBeastOnField() && HasTwoFreeMonsterZones(); + } + } + + if (Card.Location == CardLocation.Grave) + { + return Duel.Player == 1 && Duel.Phase == DuelPhase.End && Bot.Graveyard.Any(IsSacredBeastMonster); + } + + return false; + } + + + private bool SkyfireActivate() + { + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (IsSacredEndBoardReady()) return false; + + if (Card.Location == CardLocation.SpellZone) + { + // Skyfire effect is not one flat selection. + // Prompt 1: place 2 Skyfire. + // Prompt 2: reveal 1 Level 10 Sacred Beast in hand. + // Prompt 3: place Fallen Paradise from Deck. + if (Duel.Player != 0) return false; + if (Duel.Phase != DuelPhase.Main1 && Duel.Phase != DuelPhase.Main2) return false; + + int revealTarget = PickSkyfireRevealTarget(); + if (revealTarget == 0) return false; + + if (CountAccessibleSkyfireCopiesForEffect() < 2) return false; + + AI.SelectCard(new[] + { + CardId.SkyfireOfTheSacredBeast, + CardId.SkyfireOfTheSacredBeast + }); + AI.SelectNextCard(revealTarget); + + if (CheckRemainInDeck(CardId.FallenParadiseOfTheSacredBeasts) > 0) + AI.SelectNextCard(CardId.FallenParadiseOfTheSacredBeasts); + + return true; + } + + if (Card.Location == CardLocation.Hand) + { + if (ShouldStartFromMartyrFallback()) return false; + + // Activating Skyfire from hand only places the Continuous Spell on the field. + // The ignition effect will be handled by the SpellZone branch on the next action window. + if (Bot.HasInSpellZone(CardId.SkyfireOfTheSacredBeast, true)) return false; + if (PickSkyfireRevealTarget() == 0) return false; + + SelectSTPlace(null, true); + return true; + } + + if (Card.Location == CardLocation.Grave && Duel.Player == 1 && Duel.Phase == DuelPhase.End) + { + return true; + } + + return false; + } + + + private bool FallenParadiseActivate() + { + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + + // Important bad-hand rule from user note: + // Do not activate Fallen Paradise from hand as an opener. It must be placed by Skyfire/Martyr line first. + if (Card.Location == CardLocation.Hand) return false; + if (Card.Location != CardLocation.SpellZone) return false; + if (ShouldStartFromMartyrFallback()) return false; + + // Summon effect comes before draw in the corrected route. + if (HasFreeMonsterZone()) + { + List costCards = GetFallenParadiseCostCards(); + int target = PickFallenParadiseSummonTarget(); + + if (costCards.Count >= 3 && target != 0) + { + // Cost priority: Skyfire x3 -> Martyr x3 -> expendable monsters. Never pay Uria. + AI.SelectCard(costCards.Take(3).ToList()); + AI.SelectNextCard(target); + return true; + } + } + + // Draw 2 only after the combo has put a Level 10 / revived body on board. + if (HasSacredBeastOnField() + && CountLevel10MonstersOnField() >= 1 + && (Bot.HasInMonstersZone(CardId.ThunderDragonColossus, true) + || Bot.HasInMonstersZone(CardId.SPLittleKnight, true) + || CountSacredBeastsOnField() >= 2)) + { + return true; + } + + return false; + } + + + private bool DivineAbyssActivate() + { + if (CheckWhetherNegated(true, true, CardType.Trap)) return false; + + if (Card.Location == CardLocation.SpellZone) + { + // Opponent turn interrupt line. First place copies, then flip enemy monster. + if (Duel.Player == 1 && CountFaceupSpellTrap(CardId.DivineAbyssOfTheSacredBeast) < 3 && Bot.GetSpellCount() < 5) + { + AI.SelectCard(CardId.DivineAbyssOfTheSacredBeast, CardId.DivineAbyssOfTheSacredBeast); + return true; + } + + ClientCard target = GetBestEnemyMonster(true); + if (Duel.Player == 1 && target != null) + { + AI.SelectCard(target); + return true; + } + } + + if (Card.Location == CardLocation.Grave && Duel.Player == 1 && Duel.Phase == DuelPhase.End) + { + return true; + } + + return false; + } + + + private bool DestructionChantActivate() + { + if (CheckWhetherNegated(true, true, CardType.Trap)) return false; + + if (Card.Location == CardLocation.Hand || Card.Location == CardLocation.SpellZone) + { + // Main use is opponent turn. + if (Duel.Player != 1 && !HasOpponentRelevantChain()) return false; + + if (HasFreeMonsterZone() && (HasSacredBeastInHand() || HasSacredBeastInGrave())) + { + // If only 1 Level 10, revive another Level 10 with a different name. + // If already 2 Level 10, revive Martyr for the follow-up Divine Abyss line. + if (CountLevel10MonstersOnField() >= 2 + && Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.MartyrOfTheSacredBeasts))) + { + AI.SelectCard(CardId.MartyrOfTheSacredBeasts); + return true; + } + + int target = new int[] + { + CardId.RavielSacredBeastOfEndlessEternity, + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.UriaSacredBeastOfCataclysmicFire + }.FirstOrDefault(id => !Bot.HasInMonstersZone(id, true) + && (Bot.Graveyard.Any(c => c != null && c.IsCode(id)) || Bot.HasInHand(id))); + + if (target == 0) return false; + AI.SelectCard(target); + return true; + } + + if (CountSacredBeastsOnField() >= 2 && GetBestEnemyCard(true) != null) + { + AI.SelectCard(GetBestEnemyCard(true)); + return true; + } + } + + if (Card.Location == CardLocation.Grave) + { + // If Raviel can tribute Martyr x2 for board wipe, wait. Otherwise use GY fusion line. + if (Bot.HasInMonstersZone(CardId.RavielSacredBeastOfEndlessEternity, true) + && CountFaceupMartyrOnField() >= 2 + && Enemy.GetMonsterCount() > 0) + return false; + + if (CanMakePhantasmalFusion()) + { + AI.SelectCard(CardId.PhantasmalSacredBeastsOfChaos); + return true; + } + } + + return false; + } + + + private bool HeavyPolymerizationActivate() + { + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + + // ตาม note: ใช้ตอนฝ่ายตรงข้ามมี monster 3 ใบ เพื่อ surprise เป็นตัวใหญ่ + // ไม่ใช้เป็น extender ฝั่งเราเอง เพราะจะเสีย LP และอาจตัด resource โดยไม่จำเป็น + if (Enemy.GetMonsterCount() < 3) return false; + + AI.SelectCard( + CardId.PhantasmalSacredBeastsOfChaos, + CardId.SaintAzamina, + CardId.SaintAzamina, + CardId.SaintAzamina); + return true; + } + + #endregion + + #region Normal Summon / Special Summon + + private bool MartyrSummon() + { + // Bad hand fallback: if there is no starter/searcher, Martyr is the first play. + // Its on-summon effect is locked to Skyfire in MartyrActivate(). + if (IsSacredEndBoardReady()) return false; + if (Bot.HasInMonstersZone(CardId.MartyrOfTheSacredBeasts, true) && HasCoreFieldReady()) return false; + if (ShouldStartFromMartyrFallback()) return true; + return true; + } + + private bool OrchestratorSummon() + { + if (IsSacredEndBoardReady()) return false; + if (HasCoreFieldReady() && CountSacredBeastsOnField() >= 2) return false; + return HasSacredBeastInHand() || HasSacredBeastInGrave(); + } + + private bool ThunderKingKaijuSummon() + { + // ใช้เฉพาะมีตัวน่ากลัวจริง ไม่เอาไปตัด resource opponent โดยไม่จำเป็นตอนเรารำได้อยู่แล้ว + ClientCard target = GetBestEnemyMonster(true); + if (target == null) return false; + if (!target.IsMonsterDangerous() && !target.IsFloodgate() && Enemy.GetMonsterCount() < 2) return false; + + AI.SelectCard(target); + return true; + } + + private bool PhantasmalSacredBeastsOfChaosSummon() + { + if (HasPhantasmalOnField()) return false; + return CountSacredBeastsOnField() >= 3 || CountLevel10MonstersOnField() >= 3; + } + + private bool Rank10NegateSummon() + { + if (HasPhantasmalOnField()) return false; + if (Bot.HasInMonstersZone(CardId.VarudrasTheFinalBringer, true)) return false; + return CountLevel10MonstersOnField() >= 2; + } + + private bool GustavMaxSummon() + { + if (HasPhantasmalOnField()) return false; + if (CountLevel10MonstersOnField() < 2) return false; + + // ปิดเกม / หลังมี negate rank10 แล้วเท่านั้น ไม่แย่ง material ก่อน combo จบ + return Bot.HasInMonstersZone(CardId.VarudrasTheFinalBringer, true) || Enemy.GetMonsterCount() == 0; + } + + private bool GustavRocketSummon() + { + if (HasPhantasmalOnField()) return false; + return Bot.HasInMonstersZone(CardId.SuperdreadnoughtRailCannonGustavMax, true) + || CountLevel10MonstersOnField() >= 3; + } + + private bool ThunderDragonColossusSummon() + { + if (HasPhantasmalOnField()) return false; + if (Bot.HasInMonstersZone(CardId.ThunderDragonColossus, true)) return false; + + // Corrected route step 7: fuse/convert with Orchestrator after its field effect has been used. + // Approximation: Orchestrator is face-up on field and we already have a Sacred Beast in GY/field from the route. + return Bot.HasInMonstersZone(CardId.TheOrchestratorOfTheSacredBeasts, true) + && (HasSacredBeastInGrave() || HasSacredBeastOnField()); + } + + + private bool SPLittleKnightSummon() + { + if (HasPhantasmalOnField()) return false; + if (GetBestEnemyCard() == null) return false; + + int martyrCount = CountFaceupMartyrOnField(); + bool hasLinkuriboh = Bot.HasInMonstersZone(CardId.Linkuriboh, true); + + // Corrected route step 9: use Martyr x2, or Linkuriboh + Martyr only. + return martyrCount >= 2 || (hasLinkuriboh && martyrCount >= 1); + } + + + private bool LinkuribohSummon() + { + // Only link the Martyr that got stopped, as in the corrected checkpoint. + if (!IsMartyrNegatedOrInterrupted()) return false; + return Bot.GetMonsters().Any(c => c != null && c.IsFaceup() + && c.Level == 1 + && c.IsCode(CardId.MartyrOfTheSacredBeasts)); + } + + + #endregion + + #region Extra Deck effects + + private bool PhantasmalSacredBeastsOfChaosActivate() + { + if (CheckWhetherNegated()) return false; + + // negate monster ฝั่งตรงข้ามเท่านั้น ไม่กดใส่ตัวเอง + ClientCard target = GetBestEnemyMonster(true); + if (target == null) return false; + if (Duel.Player == 0 && !target.IsMonsterDangerous() && !HasOpponentRelevantChain()) return false; + + AI.SelectCard(target); + return true; + } + + private bool VarudrasActivate() + { + if (CheckWhetherNegated()) return false; + return HasOpponentRelevantChain(); + } + + private bool SPLittleKnightActivate() + { + if (CheckWhetherNegated()) return false; + + if (ActivateDescription == -1 || ActivateDescription == Util.GetStringId(CardId.SPLittleKnight, 0)) + { + ClientCard target = GetBestEnemyCard(); + if (target == null) return false; + + AI.SelectCard(target); + SPLittleKnightRemoveStep = 1; + return true; + } + + if (ActivateDescription == Util.GetStringId(CardId.SPLittleKnight, 1)) + { + ClientCard self = Bot.GetMonsters().Where(c => c != null && Duel.ChainTargets.Contains(c)).FirstOrDefault(); + ClientCard enemy = GetBestEnemyMonster(true); + if (self != null && enemy != null) + { + AI.SelectCard(self); + AI.SelectNextCard(enemy); + return true; + } + } + + return false; + } + + #endregion + + #region Fallback + + private bool MonsterRepos() + { + if (Card == null || Card.IsFacedown()) return false; + if (Card.HasType(CardType.Link)) return false; + + bool enemyBetter = Enemy.GetMonsters().Any(c => c != null && c.IsAttack() && c.Attack >= Card.GetDefensePower()); + if (Card.IsAttack() && enemyBetter && Card.Defense > Card.Attack) return true; + if (Card.IsDefense() && !enemyBetter && Card.Attack > Card.Defense) return true; + return false; + } + + private bool SpellSet() + { + if (Card == null) return false; + + if (Card.IsTrap() || Card.HasType(CardType.QuickPlay)) + { + SelectSTPlace(); + return true; + } + + return false; + } + public void SelectSTPlace(ClientCard card = null, bool avoidImpermanence = false, List avoidList = null) + { + if (card == null) card = Card; + if (card.Location == CardLocation.SpellZone) + { + return; + } + List list = new List(); + for (int seq = 0; seq < 5; ++seq) + { + if (Bot.SpellZone[seq] == null) + { + if (card != null && card.Location == CardLocation.Hand && avoidImpermanence && infiniteImpermanenceNegatedColumns.Contains(seq)) continue; + if (avoidList != null && avoidList.Contains(seq)) continue; + list.Add(seq); + } + } + int n = list.Count; + while (n-- > 1) + { + int index = Program.Rand.Next(list.Count); + int nextIndex = (index + Program.Rand.Next(list.Count - 1)) % list.Count; + int tempInt = list[index]; + list[index] = list[nextIndex]; + list[nextIndex] = tempInt; + } + if (avoidImpermanence && Bot.GetMonsters().Any(c => c.IsFaceup() && !c.IsDisabled())) + { + foreach (int seq in list) + { + ClientCard enemySpell = Enemy.SpellZone[4 - seq]; + if (enemySpell != null && enemySpell.IsFacedown()) continue; + int zone = (int)System.Math.Pow(2, seq); + AI.SelectPlace(zone); + return; + } + } + foreach (int seq in list) + { + int zone = (int)System.Math.Pow(2, seq); + AI.SelectPlace(zone); + return; + } + AI.SelectPlace(0); + } + #endregion + } +} diff --git a/WindBot.csproj b/WindBot.csproj index 6fb39ee9..bcf7a7ed 100644 --- a/WindBot.csproj +++ b/WindBot.csproj @@ -84,6 +84,7 @@ + From 9d9bfe32ad666356df3c188c06dc7c691c5c441d Mon Sep 17 00:00:00 2001 From: Reinen <127223416+DaruKani@users.noreply.github.com> Date: Tue, 2 Jun 2026 00:33:54 +0700 Subject: [PATCH 2/3] Update SacredBot Script all card Should be work fine --- Game/AI/Decks/SacredExecutor.cs | 3643 +++++++++++++++++++------------ 1 file changed, 2256 insertions(+), 1387 deletions(-) diff --git a/Game/AI/Decks/SacredExecutor.cs b/Game/AI/Decks/SacredExecutor.cs index 4bbed1c6..b74f0052 100644 --- a/Game/AI/Decks/SacredExecutor.cs +++ b/Game/AI/Decks/SacredExecutor.cs @@ -5,23 +5,15 @@ using WindBot.Game; using WindBot.Game.AI; using YGOSharp.OCGWrapper.Enums; +using static WindBot.Game.AI.Decks.TimeThiefExecutor; namespace WindBot.Game.AI.Decks { - // CORI Sacred Beast Executor - // Routed from user-corrected combo notes: - // 1) Starter = Lightning Crash / Card of the Soul -> Hamon. - // 2) Hamon searches Unleashing first. If Unleashing is already available, search Skyfire. If both are available, search Paradise as spare cost. - // 3) Unleashing is the real core: search 3 different pieces, never discard Uria, and intentionally discard Hamon/Orchestrator when following the Hamon route. - // 4) If no starter/searcher, Normal Martyr first; Martyr on-summon must search Skyfire only. - // 5) Skyfire places Skyfire x2 + Fallen Paradise; never activate Fallen Paradise from hand as opener. Fallen Paradise pays Skyfire x3 first and summons Orchestrator. - // 6) Opponent turn: Destruction Chant / Divine Abyss / Raviel wipe are held as interrupts, with Raviel wipe only if Martyr x2 can be tributed. [Deck("SacredBot", "AI_SacredBot")] class SacredExecutor : DefaultExecutor { public class CardId { - // ===== Main Deck: CORI Sacred Beast core ===== public const int UnleashingTheSacredBeasts = 38776201; public const int HamonSacredBeastOfSinfulCatastrophe = 50251045; public const int RavielSacredBeastOfEndlessEternity = 96345184; @@ -33,18 +25,15 @@ public class CardId public const int DivineAbyssOfTheSacredBeast = 86132414; public const int DestructionChantOfTheSacredBeast = 50147815; - // ===== Main Deck: utility / hand traps ===== public const int ThunderKingTheLightningstrikeKaiju = 48770333; public const int MulcharmyFuwalos = 42141493; public const int AshBlossom = 14558127; public const int MaxxC = 23434538; - public const int LightningCrash = 89753095; // user เรียก Thunder Clap: ใช้เปิดหา Hamon ได้ - public const int Thunderclap = LightningCrash; // alias กันชื่อเก่าในโค้ด + public const int LightningCrash = 89753095; public const int HeavyPolymerization = 58570206; public const int CalledByTheGrave = 24224830; - public const int CardOfTheSoul = 7044562; // user พิมพ์ Call of the soul; ID นี้คือ Card of the Soul + public const int CardOfTheSoul = 7044562; - // ===== Extra Deck ===== public const int PhantasmalSacredBeastsOfChaos = 7894706; public const int SuperVehicroidMobileBase = 17745969; public const int SaintAzamina = 85065943; @@ -54,1098 +43,2194 @@ public class CardId public const int VarudrasTheFinalBringer = 70636044; public const int SPLittleKnight = 29301450; public const int Linkuriboh = 41999284; - } - - private readonly HashSet SacredBeastMonsterIds = new HashSet - { - CardId.HamonSacredBeastOfSinfulCatastrophe, - CardId.RavielSacredBeastOfEndlessEternity, - CardId.UriaSacredBeastOfCataclysmicFire - }; - private readonly HashSet SacredBeastSpellIds = new HashSet - { - CardId.UnleashingTheSacredBeasts, - CardId.SkyfireOfTheSacredBeast, - CardId.FallenParadiseOfTheSacredBeasts - }; - - private readonly HashSet SacredBeastTrapIds = new HashSet - { - CardId.DivineAbyssOfTheSacredBeast, - CardId.DestructionChantOfTheSacredBeast - }; + //Black list + public const int Lancea = 34267821; + public const int NaturalExterio = 99916754; + public const int NaturalBeast = 33198837; + public const int ImperialOrder = 61740673; + public const int SwordsmanLV7 = 37267041; + public const int RoyalDecree = 51452091; + public const int Number41BagooskatheTerriblyTiredTapir = 90590303; + public const int InspectorBoarder = 15397015; + public const int SkillDrain = 82732705; + public const int DivineArsenalAAZEUS_SkyThunder = 90448279; + public const int DimensionShifter = 91800273; + public const int MacroCosmos = 30241314; + public const int DimensionalFissure = 81674782; + public const int BanisheroftheRadiance = 94853057; + public const int BanisheroftheLight = 61528025; + public const int KashtiraAriseHeart = 48626373; + public const int GhostMournerMoonlitChill = 52038441; + public const int NibiruThePrimalBeing = 27204311; + } + private readonly Dictionary> DeckCountTable = new Dictionary> + { + { 3, new List + { + CardId.UnleashingTheSacredBeasts, + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.RavielSacredBeastOfEndlessEternity, + CardId.MulcharmyFuwalos, + CardId.MartyrOfTheSacredBeasts, + CardId.SkyfireOfTheSacredBeast, + CardId.LightningCrash, + CardId.FallenParadiseOfTheSacredBeasts, + CardId.HeavyPolymerization, + CardId.DivineAbyssOfTheSacredBeast, + CardId.CardOfTheSoul + } + }, - private readonly HashSet SacredBeastSupportMonsterIds = new HashSet - { - CardId.TheOrchestratorOfTheSacredBeasts, - CardId.MartyrOfTheSacredBeasts - }; + { 2, new List + { + CardId.AshBlossom + } + }, - private readonly Dictionary DeckCountTable = new Dictionary - { - { CardId.UnleashingTheSacredBeasts, 3 }, - { CardId.HamonSacredBeastOfSinfulCatastrophe, 3 }, - { CardId.RavielSacredBeastOfEndlessEternity, 3 }, - { CardId.UriaSacredBeastOfCataclysmicFire, 1 }, - { CardId.ThunderKingTheLightningstrikeKaiju, 1 }, - { CardId.TheOrchestratorOfTheSacredBeasts, 1 }, - { CardId.MulcharmyFuwalos, 3 }, - { CardId.AshBlossom, 2 }, - { CardId.MaxxC, 1 }, - { CardId.MartyrOfTheSacredBeasts, 3 }, - { CardId.SkyfireOfTheSacredBeast, 3 }, - { CardId.LightningCrash, 3 }, - { CardId.FallenParadiseOfTheSacredBeasts, 3 }, - { CardId.HeavyPolymerization, 3 }, - { CardId.CalledByTheGrave, 1 }, - { CardId.DivineAbyssOfTheSacredBeast, 3 }, - { CardId.DestructionChantOfTheSacredBeast, 1 }, - { CardId.CardOfTheSoul, 3 } + { 1, new List + { + CardId.UriaSacredBeastOfCataclysmicFire, + CardId.ThunderKingTheLightningstrikeKaiju, + CardId.TheOrchestratorOfTheSacredBeasts, + CardId.MaxxC, + CardId.CalledByTheGrave, + CardId.DestructionChantOfTheSacredBeast + } + }, }; - private readonly List CurrentNegateCardList = new List(); - private int SPLittleKnightRemoveStep = 0; - - // Lightweight route memory. This makes Unleashing know whether Hamon was actually used first, - // instead of guessing from whether Unleashing was already in the opening hand. - private bool HamonSearchQueuedForRoute = false; + const int SetcodeTimeLord = 0x4a; + const int SetcodePhantom = 0xdb; + const int SetcodeOrcust = 0x11b; + const int SetcodeHorus = 0x19d; + const int SetcodeDarkWorld = 0x6; + const int SetcodeSkyStriker = 0x115; + const int SetcodeSacredBeast = 0x1144; + + List notToNegateIdList = new List { 58699500, 20343502, 19403423 }; + List notToDestroySpellTrap = new List { 50005218, 6767771 }; + + List infiniteImpermanenceList = new List(); + List currentNegateCardList = new List(); + List currentDestroyCardList = new List(); + List enemyPlaceThisTurn = new List(); + int SPLittleKnightRemoveStep = 0; + + int myTurnCount = 0; + bool useHamonSearchEffectAlready = false; + bool useLightningCrash = false; + int paradise = 3; + bool normalSummon = false; + bool useRaviel = false; + bool useOchestFromField = false; + bool useOchestFromGY = false; + bool Martyrx3 = false; + bool resolvingUnleashing = false; + bool resolvingUnleashingHamonLine = false; + int fallenParadiseCostCode = 0; + bool resolvingFallenParadise = false; + int fallenParadiseTarget = 0; + bool resolvingColossusSummon = false; + bool resolvingRavielBoardWipe = false; + bool resolvingSPLittleKnightSummon = false; + List spLittleKnightMaterialPlan = new List(); + bool resolvingHeavyPolymerization = false; + int heavyPolyMaterialPicked = 0; + int heavyPolyMaterialNeed = 0; + bool resolvingRank10Summon = false; + List rank10MaterialPlan = new List(); + bool resolvingGustavRocketSummon = false; + ClientCard gustavRocketDiscardPlan = null; + bool gustavRocketDiscardSelected = false; + bool gustavRocketMaxSelected = false; public SacredExecutor(GameAI ai, Duel duel) : base(ai, duel) { - // ===== Defensive chain / interruption first ===== AddExecutor(ExecutorType.Activate, CardId.AshBlossom, AshBlossomActivate); - AddExecutor(ExecutorType.Activate, CardId.CalledByTheGrave, CalledByTheGraveActivate); + AddExecutor(ExecutorType.Activate, CardId.CalledByTheGrave, CalledbytheGraveActivate); AddExecutor(ExecutorType.Activate, CardId.MaxxC, MaxxCActivate); - AddExecutor(ExecutorType.Activate, CardId.MulcharmyFuwalos, FuwalosActivate); + AddExecutor(ExecutorType.Activate, CardId.MulcharmyFuwalos, MaxxCActivate); AddExecutor(ExecutorType.Activate, CardId.VarudrasTheFinalBringer, VarudrasActivate); AddExecutor(ExecutorType.Activate, CardId.PhantasmalSacredBeastsOfChaos, PhantasmalSacredBeastsOfChaosActivate); AddExecutor(ExecutorType.Activate, CardId.SPLittleKnight, SPLittleKnightActivate); AddExecutor(ExecutorType.Activate, CardId.DivineAbyssOfTheSacredBeast, DivineAbyssActivate); AddExecutor(ExecutorType.Activate, CardId.DestructionChantOfTheSacredBeast, DestructionChantActivate); + AddExecutor(ExecutorType.Activate, _CardId.CrossoutDesignator, CrossoutDesignatorActivate); + AddExecutor(ExecutorType.Activate, _CardId.InfiniteImpermanence, InfiniteImpermanenceActivate); + AddExecutor(ExecutorType.Activate, CardId.UriaSacredBeastOfCataclysmicFire, Uria_Field_DestroyST); + AddExecutor(ExecutorType.Activate, CardId.RavielSacredBeastOfEndlessEternity, Raviel_Field_BoardWipeOnlyWithMartyr2); + AddExecutor(ExecutorType.Activate, CardId.Linkuriboh, LinkuribohActivate); + AddExecutor(ExecutorType.Activate, CardId.SuperdreadnoughtRailCannonGustavMax); + AddExecutor(ExecutorType.Activate, CardId.SuperdreadnoughtRailCannonGustavRocket, GustavRocketActivate); - // ===== Going second / board breaker ===== + AddExecutor(ExecutorType.Activate, CardId.UnleashingTheSacredBeasts, Unleashing_GY_Recovery); + AddExecutor(ExecutorType.Activate, CardId.MartyrOfTheSacredBeasts, Martyr_GY_EndPhaseRecovery); + AddExecutor(ExecutorType.Activate, CardId.SkyfireOfTheSacredBeast, Skyfire_GY_EndPhaseRecovery); + AddExecutor(ExecutorType.Activate, CardId.FallenParadiseOfTheSacredBeasts, FallenParadise_Field_Draw2AfterSetup); AddExecutor(ExecutorType.Activate, CardId.HeavyPolymerization, HeavyPolymerizationActivate); - AddExecutor(ExecutorType.SpSummon, CardId.ThunderKingTheLightningstrikeKaiju, ThunderKingKaijuSummon); - - // ===== Main starters / searchers ===== - // IMPORTANT: split by actual effect/location. Do not use one giant Activate per card. - // Route should decide “what should be next”; each executor should answer only one prompt/effect. - AddExecutor(ExecutorType.Activate, CardId.LightningCrash, LightningCrash_Starter_SearchHamon); + AddExecutor(ExecutorType.SpSummon, CardId.ThunderKingTheLightningstrikeKaiju, DefaultKaijuSpsummon); AddExecutor(ExecutorType.Activate, CardId.CardOfTheSoul, CardOfTheSoul_Starter_SearchHamonOrRaviel); - + AddExecutor(ExecutorType.Activate, CardId.LightningCrash, LightningCrash_Starter_SearchHamon); AddExecutor(ExecutorType.Activate, CardId.HamonSacredBeastOfSinfulCatastrophe, Hamon_Hand_SearchSpell); + AddExecutor(ExecutorType.Activate, CardId.UnleashingTheSacredBeasts, Unleashing_Main_Search3Discard2); + AddExecutor(ExecutorType.Summon, CardId.MartyrOfTheSacredBeasts, MartyrSummon); + AddExecutor(ExecutorType.Activate, CardId.MartyrOfTheSacredBeasts, Martyr_OnSummon_Place); + AddExecutor(ExecutorType.Activate, CardId.SkyfireOfTheSacredBeast, Skyfire_Field_Place2RevealPlaceParadise); AddExecutor(ExecutorType.Activate, CardId.RavielSacredBeastOfEndlessEternity, Raviel_Hand_SearchUria); - AddExecutor(ExecutorType.Activate, CardId.RavielSacredBeastOfEndlessEternity, Raviel_Field_BoardWipeOnlyWithMartyr2); AddExecutor(ExecutorType.Activate, CardId.UriaSacredBeastOfCataclysmicFire, Uria_Hand_SearchDestructionChant); - AddExecutor(ExecutorType.Activate, CardId.UriaSacredBeastOfCataclysmicFire, Uria_Field_DestroyFaceupST); - - AddExecutor(ExecutorType.Activate, CardId.UnleashingTheSacredBeasts, Unleashing_Main_Search3Discard2); - AddExecutor(ExecutorType.Activate, CardId.UnleashingTheSacredBeasts, Unleashing_GY_RecoverySearch); - - AddExecutor(ExecutorType.Activate, CardId.MartyrOfTheSacredBeasts, Martyr_OnSummon_PlaceSkyfireOnly); AddExecutor(ExecutorType.Activate, CardId.MartyrOfTheSacredBeasts, Martyr_Field_SummonTwoMartyr); - AddExecutor(ExecutorType.Activate, CardId.MartyrOfTheSacredBeasts, Martyr_GY_EndPhaseRecovery); - - AddExecutor(ExecutorType.Activate, CardId.SkyfireOfTheSacredBeast, Skyfire_Hand_ActivateCardOnly); - AddExecutor(ExecutorType.Activate, CardId.SkyfireOfTheSacredBeast, Skyfire_Field_Place2RevealPlaceParadise); - AddExecutor(ExecutorType.Activate, CardId.SkyfireOfTheSacredBeast, Skyfire_GY_EndPhaseRecovery); - - AddExecutor(ExecutorType.Activate, CardId.FallenParadiseOfTheSacredBeasts, FallenParadise_Field_SummonByCost3); - AddExecutor(ExecutorType.Activate, CardId.FallenParadiseOfTheSacredBeasts, FallenParadise_Field_Draw2AfterSetup); - - AddExecutor(ExecutorType.Activate, CardId.TheOrchestratorOfTheSacredBeasts, Orchestrator_Hand_SummonSacredBeast); AddExecutor(ExecutorType.Activate, CardId.TheOrchestratorOfTheSacredBeasts, Orchestrator_Field_ReviveRouteTarget); - AddExecutor(ExecutorType.Activate, CardId.TheOrchestratorOfTheSacredBeasts, Orchestrator_GY_ReviveLevel10); - - // ===== Normal Summon plan ===== - AddExecutor(ExecutorType.Summon, CardId.MartyrOfTheSacredBeasts, MartyrSummon); - AddExecutor(ExecutorType.Summon, CardId.TheOrchestratorOfTheSacredBeasts, OrchestratorSummon); - - // ===== Extra Deck plan ===== - AddExecutor(ExecutorType.SpSummon, CardId.PhantasmalSacredBeastsOfChaos, PhantasmalSacredBeastsOfChaosSummon); - AddExecutor(ExecutorType.SpSummon, CardId.VarudrasTheFinalBringer, Rank10NegateSummon); - AddExecutor(ExecutorType.SpSummon, CardId.SuperdreadnoughtRailCannonGustavRocket, GustavRocketSummon); - AddExecutor(ExecutorType.SpSummon, CardId.SuperdreadnoughtRailCannonGustavMax, GustavMaxSummon); AddExecutor(ExecutorType.SpSummon, CardId.ThunderDragonColossus, ThunderDragonColossusSummon); + AddExecutor(ExecutorType.Activate, CardId.TheOrchestratorOfTheSacredBeasts, Orchestrator_GY_ReviveLevel10); AddExecutor(ExecutorType.SpSummon, CardId.SPLittleKnight, SPLittleKnightSummon); AddExecutor(ExecutorType.SpSummon, CardId.Linkuriboh, LinkuribohSummon); + AddExecutor(ExecutorType.Activate, CardId.FallenParadiseOfTheSacredBeasts, FallenParadise_Field_SummonByCost3); + AddExecutor(ExecutorType.Activate, CardId.SkyfireOfTheSacredBeast, Skyfire_Hand_ActivateCardOnly); + AddExecutor(ExecutorType.SpSummon, CardId.PhantasmalSacredBeastsOfChaos, PhantasmalSacredBeastsOfChaosSummon); - // ===== Generic fallback ===== - AddExecutor(ExecutorType.Repos, MonsterRepos); - AddExecutor(ExecutorType.SpellSet, SpellSet); - } - - #region Helper + AddExecutor(ExecutorType.SpSummon, CardId.VarudrasTheFinalBringer, VarudrasSummon); + AddExecutor(ExecutorType.SpSummon, CardId.SuperdreadnoughtRailCannonGustavMax, GustavMaxSummon); + AddExecutor(ExecutorType.SpSummon, CardId.SuperdreadnoughtRailCannonGustavRocket, GustavRocketSummonOnMax); - private int CheckRemainInDeck(int id) - { - if (!DeckCountTable.ContainsKey(id)) return 0; - return Bot.GetRemainingCount(id, DeckCountTable[id]); - } + - private int CheckRemainInDeck(params int[] ids) - { - int result = 0; - foreach (int id in ids) result += CheckRemainInDeck(id); - return result; + AddExecutor(ExecutorType.Repos, Repos); + AddExecutor(ExecutorType.SpellSet, SpellSetCheck); } - private bool CheckWhetherNegated(bool disablecheck = true, bool toFieldCheck = false, CardType type = 0) + #region Default + public override void OnNewTurn() { - if (DefaultCheckWhetherCardIsNegated(Card)) return true; - if (disablecheck - && (Card.Location == CardLocation.MonsterZone || Card.Location == CardLocation.SpellZone) - && Card.IsFaceup() - && Card.IsDisabled()) + if (Duel.Player == 0) { - return true; + myTurnCount++; } - return false; - } - - private int GetBotLifePointsSafe() - { - // WindBot หลาย fork ใช้ชื่อ property ไม่เหมือนกัน เลยใช้ reflection เพื่อไม่ผูกกับ API ชื่อเดียว - string[] names = { "LifePoints", "LP", "Lp", "lifePoints" }; - foreach (string name in names) + // reset + SPLittleKnightRemoveStep = 0; + useLightningCrash = false; + useHamonSearchEffectAlready = false; + infiniteImpermanenceList.Clear(); + currentNegateCardList.Clear(); + currentDestroyCardList.Clear(); + enemyPlaceThisTurn.Clear(); + paradise = 3; + normalSummon = false; + useRaviel = false; + useOchestFromField = false; + useOchestFromGY = false; + resolvingUnleashing = false; + resolvingUnleashingHamonLine = false; + resolvingFallenParadise = false; + fallenParadiseTarget = 0; + fallenParadiseCostCode = 0; + Martyrx3 = false; + resolvingColossusSummon = false; + resolvingRavielBoardWipe = false; + resolvingSPLittleKnightSummon = false; + spLittleKnightMaterialPlan.Clear(); + resolvingHeavyPolymerization = false; + heavyPolyMaterialPicked = 0; + heavyPolyMaterialNeed = 0; + resolvingRank10Summon = false; + rank10MaterialPlan.Clear(); + resolvingGustavRocketSummon = false; + gustavRocketDiscardPlan = null; + gustavRocketDiscardSelected = false; + gustavRocketMaxSelected = false; + + base.OnNewTurn(); + } + public override bool OnSelectHand() { return true; /* Go first by default.*/} + public override int OnSelectOption(IList options) + { + ChainInfo currentSolvingChain = Duel.GetCurrentSolvingChainInfo(); + Logger.DebugWriteLine($"OnSelectOption: CurrentSolving={currentSolvingChain} count={options.Count} options=[{string.Join(", ", options.Select((v, i) => $"{i}:{v}"))}]"); + if (Duel.Phase == DuelPhase.End && Duel.Player == 0 && Bot.HasInMonstersZone(CardId.SuperdreadnoughtRailCannonGustavRocket, true)) { - var prop = Bot.GetType().GetProperty(name); - if (prop != null) - { - object value = prop.GetValue(Bot, null); - if (value != null) return Convert.ToInt32(value); - } + ClientCard rocket = Bot.GetMonsters() + .FirstOrDefault(c => c != null + && c.IsFaceup() + && c.IsCode(CardId.SuperdreadnoughtRailCannonGustavRocket)); - var field = Bot.GetType().GetField(name); - if (field != null) + if (rocket != null && rocket.Overlays != null && rocket.Overlays.Count > 0) { - object value = field.GetValue(Bot); - if (value != null) return Convert.ToInt32(value); + Logger.DebugWriteLine("Gustav Rocket End Phase: detach overlay YES"); + return 0; } } - - // ถ้าอ่าน LP ไม่ได้ ให้ถือว่า 8000 เพื่อให้ Card of the Soul ยังทำงานตอนเปิดเกมได้ - return 8000; - } - - private bool IsSacredBeastMonster(ClientCard card) - { - return card != null && SacredBeastMonsterIds.Contains(card.Id); - } - - private bool IsSacredBeastSupport(ClientCard card) - { - return card != null && (SacredBeastMonsterIds.Contains(card.Id) || SacredBeastSupportMonsterIds.Contains(card.Id)); + if (resolvingFallenParadise) + { + Logger.DebugWriteLine("Fallen Paradise SelectYesNo: YES"); + return 0; + } + Logger.DebugWriteLine("OnSelectOption Default"); + return base.OnSelectOption(options); } - - private bool HasSacredBeastOnField() + public override IList OnSelectCard(IList cards, int min, int max, int hint, bool cancelable) { - return Bot.GetMonsters().Any(IsSacredBeastMonster); - } + ChainInfo currentSolvingChain = Duel.GetCurrentSolvingChainInfo(); + Logger.DebugWriteLine( "OnSelectCard " + cards.Count + " " + min + " " + max + " hint=" + hint + " cancelable=" + cancelable + " cards=["+string.Join(", ", cards.Select(c => c == null ? "null" : $"{c.Name}({c.Id}) C{c.Controller} L{c.Location}")) + "]"); - private bool HasSacredBeastInHand() - { - return Bot.Hand.Any(IsSacredBeastMonster); - } + ClientCard trigger = Util.GetLastChainCard(); + if (resolvingGustavRocketSummon) + { + // discard cost จากมือ + ClientCard discard = null; - private bool HasSacredBeastInGrave() - { - return Bot.Graveyard.Any(IsSacredBeastMonster); - } + if (!gustavRocketDiscardSelected) + { + if (gustavRocketDiscardPlan != null && cards.Contains(gustavRocketDiscardPlan)) + discard = gustavRocketDiscardPlan; - private bool HasFreeMonsterZone() - { - return Bot.GetMonsterCount() < 5; - } + if (discard == null) + { + discard = cards + .Where(c => c != null && c.Location == CardLocation.Hand) + .OrderBy(c => DiscardScore(c, new HashSet + { + CardId.UriaSacredBeastOfCataclysmicFire, + CardId.RavielSacredBeastOfEndlessEternity, + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.MartyrOfTheSacredBeasts, + CardId.DestructionChantOfTheSacredBeast, + CardId.UnleashingTheSacredBeasts + })) + .FirstOrDefault(); + } - private bool HasTwoFreeMonsterZones() - { - return Bot.GetMonsterCount() <= 3; - } + if (discard != null && discard.Location == CardLocation.Hand) + { + Logger.DebugWriteLine("Gustav Rocket discard cost pick: " + discard.Id); + gustavRocketDiscardSelected = true; - private bool HasCoreFieldReady() - { - return HasSacredBeastOnField() && Bot.HasInSpellZone(CardId.FallenParadiseOfTheSacredBeasts, true); - } + if (gustavRocketMaxSelected) + { + resolvingGustavRocketSummon = false; + gustavRocketDiscardPlan = null; + } - private bool HasPhantasmalOnField() - { - return Bot.HasInMonstersZone(CardId.PhantasmalSacredBeastsOfChaos, true); - } + return new List { discard }; + } + } - private int CountSacredBeastsOnField() - { - return Bot.GetMonsters().Count(IsSacredBeastMonster); - } + // เลือก Gustav Max เป็นตัวให้ Rocket ทับ + ClientCard gmax = cards.FirstOrDefault(c => + c != null + && c.Controller == 0 + && c.Location == CardLocation.MonsterZone + && c.IsFaceup() + && c.IsCode(CardId.SuperdreadnoughtRailCannonGustavMax)); - private int CountLevel10MonstersOnField() - { - return Bot.GetMonsters().Count(c => c != null && c.IsFaceup() && c.Level == 10); - } + if (gmax != null) + { + Logger.DebugWriteLine("Gustav Rocket overlay pick: Gustav Max"); + gustavRocketMaxSelected = true; - private int CountFaceupSpellTrap(int id) - { - return Bot.GetSpells().Count(c => c != null && c.IsFaceup() && c.IsCode(id)); - } + if (gustavRocketDiscardSelected) + { + resolvingGustavRocketSummon = false; + gustavRocketDiscardPlan = null; + } - private int CountDifferentSacredBeastsRemainInDeck() - { - int count = 0; - foreach (int id in SacredBeastMonsterIds) - { - if (CheckRemainInDeck(id) > 0) count++; + return new List { gmax }; + } } - return count; - } + if (resolvingRank10Summon && hint == 500) + { + List picked = rank10MaterialPlan + .Where(c => c != null && cards.Contains(c)) + .Take(max) + .ToList(); - private int CountCardsByTypeForFallenParadise(CardType type) - { - int hand = Bot.Hand.Count(c => c != null && c.HasType(type)); - int field = Bot.GetMonsters().Count(c => c != null && c.IsFaceup() && c.HasType(type)); - field += Bot.GetSpells().Count(c => c != null && c.IsFaceup() && c.HasType(type)); - return hand + field; - } + if (picked.Count < min) + { + picked = cards + .Where(c => c != null + && c.Controller == 0 + && c.Location == CardLocation.MonsterZone + && c.IsFaceup() + && c.Level == 10) + .OrderBy(c => Rank10MaterialScore(c)) + .Where(c => Rank10MaterialScore(c) < 9999) + .Take(max) + .ToList(); + } - private bool CanPayFallenParadiseCost() - { - return CountCardsByTypeForFallenParadise(CardType.Monster) >= 3 - || CountCardsByTypeForFallenParadise(CardType.Spell) >= 3 - || CountCardsByTypeForFallenParadise(CardType.Trap) >= 3; - } + if (picked.Count >= min) + { + Logger.DebugWriteLine("Rank10 material pick: " + + string.Join(", ", picked.Select(c => c.Id))); - private bool IsBoardWipedRecoveryState() - { - // หลังโดน board wipe: สนามโล่งหรือเกือบโล่ง แต่ GY ยังมี engine ให้กู้เกม - if (Bot.GetMonsterCount() > 0) return false; - return HasSacredBeastInGrave() - || Bot.Graveyard.Any(c => c != null && c.IsCode( - CardId.TheOrchestratorOfTheSacredBeasts, - CardId.MartyrOfTheSacredBeasts, - CardId.UnleashingTheSacredBeasts, - CardId.SkyfireOfTheSacredBeast, - CardId.DivineAbyssOfTheSacredBeast, - CardId.DestructionChantOfTheSacredBeast)); - } + foreach (ClientCard c in picked) + rank10MaterialPlan.Remove(c); - private bool IsSacredEndBoardReady() - { - // กันปัญหา search/extend หลังบอร์ดจบ เช่น Hamon/Uria/Raviel ยังพยายามกดบนมือหลังได้บอสแล้ว - if (HasPhantasmalOnField()) return true; - if (HasCoreFieldReady() && CountSacredBeastsOnField() >= 2 && Bot.HasInSpellZone(CardId.DivineAbyssOfTheSacredBeast, true)) return true; - return false; - } + if (rank10MaterialPlan.Count == 0 || picked.Count >= 2) + { + resolvingRank10Summon = false; + rank10MaterialPlan.Clear(); + } - private bool ShouldOpenOrExtendCombo() - { - if (IsSacredEndBoardReady()) return false; - return HasFreeMonsterZone() || IsBoardWipedRecoveryState(); - } + return picked; + } - private int[] SacredBeastMonsterPriority() - { - return new int[] + Logger.DebugWriteLine("Rank10 material no safe pick."); + resolvingRank10Summon = false; + rank10MaterialPlan.Clear(); + } + if (hint == 527 && cards.Any(c => c != null && c.Location == CardLocation.Deck && (c.IsCode(CardId.DivineAbyssOfTheSacredBeast) || c.IsCode(CardId.FallenParadiseOfTheSacredBeasts) || c.IsCode(CardId.SkyfireOfTheSacredBeast)))) { - CardId.HamonSacredBeastOfSinfulCatastrophe, - CardId.RavielSacredBeastOfEndlessEternity, - CardId.UriaSacredBeastOfCataclysmicFire - }; - } + int target = 0; - private int[] SacredBeastMonsterSearchPriority() - { - // Combo 1 ต้องเข้า Hamon ก่อนเพื่อหา Skyfire/Fallen - // Combo 2 / recovery ต้องมี Raviel เพื่อหา monster piece ต่อ - if (!Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe) && CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) > 0) - { - return new int[] + if (Duel.Player == 1) { - CardId.HamonSacredBeastOfSinfulCatastrophe, - CardId.RavielSacredBeastOfEndlessEternity, - CardId.UriaSacredBeastOfCataclysmicFire - }; + if (!Bot.HasInSpellZone(CardId.DivineAbyssOfTheSacredBeast) + && CheckRemainInDeck(CardId.DivineAbyssOfTheSacredBeast) > 0) + { + target = CardId.DivineAbyssOfTheSacredBeast; + } + } + + if (target == 0 && Duel.Player == 0) + { + if (CheckRemainInDeck(CardId.SkyfireOfTheSacredBeast) > 0) + target = CardId.SkyfireOfTheSacredBeast; + } + + ClientCard pick = cards.FirstOrDefault(c => + c != null + && c.Location == CardLocation.Deck + && c.IsCode(target)); + + if (pick != null) + { + Logger.DebugWriteLine("Martyr place pick: " + pick.Id); + return new List { pick }; + } } - if (!Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity) && CheckRemainInDeck(CardId.RavielSacredBeastOfEndlessEternity) > 0) + if (resolvingHeavyPolymerization) { - return new int[] + if (hint == 509) { - CardId.RavielSacredBeastOfEndlessEternity, - CardId.HamonSacredBeastOfSinfulCatastrophe, - CardId.UriaSacredBeastOfCataclysmicFire - }; - } + ClientCard fusion = cards.FirstOrDefault(c => + c != null + && c.Location == CardLocation.Extra + && c.IsCode(CardId.PhantasmalSacredBeastsOfChaos)); - return SacredBeastMonsterPriority(); - } + if (fusion != null) + { + Logger.DebugWriteLine("Heavy Poly fusion target: " + fusion.Id); + return new List { fusion }; + } + } - private int[] RavielSearchPriority() - { - // Raviel หา Sacred Beast monster ยกเว้นตัวเอง: เอา Hamon ก่อน ถ้ามีแล้วค่อย Orchestrator/Martyr/Uria - return new int[] - { - CardId.HamonSacredBeastOfSinfulCatastrophe, - CardId.TheOrchestratorOfTheSacredBeasts, - CardId.MartyrOfTheSacredBeasts, - CardId.UriaSacredBeastOfCataclysmicFire - }; - } + // เลือก material + if (hint == 511) + { + ClientCard zeroExtra = cards.FirstOrDefault(c => + c != null + && c.Location == CardLocation.Extra + && ( + c.IsCode(CardId.SuperVehicroidMobileBase) + || c.IsCode(CardId.SaintAzamina) + )); + + if (zeroExtra != null) + { + heavyPolyMaterialPicked++; - private int[] SacredBeastSpellPriority() - { - int hamonTarget = PickHamonSpellSearchTarget(); - if (hamonTarget != 0) - { - return new int[] { hamonTarget }; - } + Logger.DebugWriteLine("Heavy Poly material pick extra 0: " + zeroExtra.Id); - return new int[] - { - CardId.UnleashingTheSacredBeasts, - CardId.SkyfireOfTheSacredBeast, - CardId.FallenParadiseOfTheSacredBeasts - }; - } + if (heavyPolyMaterialPicked >= heavyPolyMaterialNeed) + { + resolvingHeavyPolymerization = false; + heavyPolyMaterialPicked = 0; + heavyPolyMaterialNeed = 0; + } - private int[] SacredBeastTrapPriority() - { - return new int[] - { - CardId.DivineAbyssOfTheSacredBeast, - CardId.DestructionChantOfTheSacredBeast - }; - } + return new List { zeroExtra }; + } - private int[] MartyrPlacePriority() - { - // User route: Martyr summon must search/place Skyfire only. - // Do not let Martyr fetch Fallen/Divine/Chant during the main combo, because Skyfire is the bridge into Fallen Paradise. - return new int[] { CardId.SkyfireOfTheSacredBeast }; - } + ClientCard ownSafe = cards + .Where(c => c != null + && !c.IsCode(CardId.PhantasmalSacredBeastsOfChaos) + && IsPhantasmalChaosMaterial(c)) + .OrderBy(c => HeavyPolyOwnMaterialScore(c)) + .FirstOrDefault(); - private ClientCard GetBestEnemyMonster(bool faceupOnly = true) - { - IEnumerable monsters = Enemy.GetMonsters().Where(c => c != null); - if (faceupOnly) monsters = monsters.Where(c => c.IsFaceup()); - return monsters - .OrderByDescending(c => c.IsFloodgate() ? 100000 : 0) - .ThenByDescending(c => c.IsMonsterDangerous() ? 50000 : 0) - .ThenByDescending(c => c.Attack) - .FirstOrDefault(); - } + if (ownSafe != null) + { + heavyPolyMaterialPicked++; - private ClientCard GetBestEnemyCard(bool faceupOnly = false) - { - ClientCard monster = GetBestEnemyMonster(faceupOnly); - if (monster != null) return monster; + Logger.DebugWriteLine("Heavy Poly material pick own: " + ownSafe.Id); - IEnumerable spells = Enemy.GetSpells().Where(c => c != null); - if (faceupOnly) spells = spells.Where(c => c.IsFaceup()); - return spells.FirstOrDefault(); - } + if (heavyPolyMaterialPicked >= heavyPolyMaterialNeed) + { + resolvingHeavyPolymerization = false; + heavyPolyMaterialPicked = 0; + heavyPolyMaterialNeed = 0; + } - private bool CheckLastChainShouldNegated() - { - ClientCard last = Util.GetLastChainCard(); - if (last == null || last.Controller != 1) return false; - if (DefaultCheckWhetherCardIsNegated(last) || last.IsDisabled()) return false; - if (last.IsCode(CardId.MulcharmyFuwalos, CardId.MaxxC)) return false; - return true; - } + return new List { ownSafe }; + } - private int GetTurnSafe() - { - string[] names = { "Turn", "TurnCount", "CurrentTurn" }; - foreach (string name in names) + Logger.DebugWriteLine("Heavy Poly no safe material."); + resolvingHeavyPolymerization = false; + heavyPolyMaterialPicked = 0; + heavyPolyMaterialNeed = 0; + } + } + if (resolvingSPLittleKnightSummon && hint == 533) { - var prop = Duel.GetType().GetProperty(name); - if (prop != null) + ClientCard pick = spLittleKnightMaterialPlan + .FirstOrDefault(c => c != null && cards.Contains(c)); + + if (pick == null) { - object value = prop.GetValue(Duel, null); - if (value != null) return Convert.ToInt32(value); + pick = cards + .Where(c => c != null + && c.Controller == 0 + && c.Location == CardLocation.MonsterZone) + .OrderBy(c => SPLittleKnightMaterialScore(c)) + .FirstOrDefault(c => SPLittleKnightMaterialScore(c) < 9999); } - var field = Duel.GetType().GetField(name); - if (field != null) + if (pick != null) { - object value = field.GetValue(Duel); - if (value != null) return Convert.ToInt32(value); - } - } - return 1; - } + Logger.DebugWriteLine("S:P material pick: " + pick.Id); - private bool IsLikelyGoingFirst() - { - return GetTurnSafe() <= 1 && Enemy.GetMonsterCount() == 0; - } + spLittleKnightMaterialPlan.Remove(pick); - private bool HasCardAccessible(int id) - { - return Bot.HasInHand(id) - || Bot.HasInSpellZone(id, true) - || Bot.HasInMonstersZone(id, true) - || Bot.Graveyard.Any(c => c != null && c.IsCode(id)); - } + if (spLittleKnightMaterialPlan.Count == 0) + resolvingSPLittleKnightSummon = false; - private bool HasPrimaryStarterOrSearcher() - { - // Main route starters/searchers that can naturally reach Hamon -> Unleashing. - // Raviel is intentionally not counted here, because the corrected route uses Raviel after Skyfire/Fallen is online. - if (Bot.HasInHand(CardId.LightningCrash)) return true; - if (Bot.HasInHand(CardId.CardOfTheSoul) && GetBotLifePointsSafe() == 8000) return true; - if (Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe)) return true; - if (Bot.HasInHand(CardId.UnleashingTheSacredBeasts)) return true; - return false; - } + return new List { pick }; + } - private bool ShouldStartFromMartyrFallback() - { - // Bad hand fallback from user note: - // no starter/searcher -> Normal Martyr first, search/place Skyfire, and never open with Paradise from hand. - if (HasPrimaryStarterOrSearcher()) return false; - if (!Bot.HasInHand(CardId.MartyrOfTheSacredBeasts)) return false; - if (Bot.HasInMonstersZone(CardId.MartyrOfTheSacredBeasts, true)) return false; - if (Bot.HasInSpellZone(CardId.SkyfireOfTheSacredBeast, true)) return false; - return HasFreeMonsterZone(); - } + Logger.DebugWriteLine("S:P material no safe pick."); + resolvingSPLittleKnightSummon = false; + spLittleKnightMaterialPlan.Clear(); + } + if (resolvingRavielBoardWipe) + { + List martyrs = cards + .Where(c => c != null + && c.Controller == 0 + && c.Location == CardLocation.MonsterZone + && c.IsFaceup() + && c.IsCode(CardId.MartyrOfTheSacredBeasts)) + .Take(max) + .ToList(); + + if (martyrs.Count >= min) + { + Logger.DebugWriteLine("Raviel board wipe cost pick: " + + string.Join(", ", martyrs.Select(c => c.Id))); - private bool IsCoreStartedByMartyr() - { - return Bot.HasInMonstersZone(CardId.MartyrOfTheSacredBeasts, true) - || Bot.HasInSpellZone(CardId.SkyfireOfTheSacredBeast, true) - || Bot.HasInSpellZone(CardId.FallenParadiseOfTheSacredBeasts, true); - } + if (martyrs.Count >= 2 || max == 1) + resolvingRavielBoardWipe = false; - private int PickHamonSpellSearchTarget() - { - // Corrected route: Hamon -> Unleashing first. If Unleashing exists, get Skyfire. - // If both Unleashing and Skyfire exist, Paradise becomes a spare cost/resource. - if (!HasCardAccessible(CardId.UnleashingTheSacredBeasts) - && CheckRemainInDeck(CardId.UnleashingTheSacredBeasts) > 0) - return CardId.UnleashingTheSacredBeasts; + return martyrs.Take(max).ToList(); + } + + resolvingRavielBoardWipe = false; + } + if (resolvingColossusSummon) + { + ClientCard orchest = cards.FirstOrDefault(c => + c != null + && c.IsFaceup() + && c.IsCode(CardId.TheOrchestratorOfTheSacredBeasts) + && c.Location == CardLocation.MonsterZone); + + if (orchest != null) + { + Logger.DebugWriteLine("Colossus material pick: Orchestrator"); + resolvingColossusSummon = false; + return new List { orchest }; + } + resolvingColossusSummon = false; + } + // ===== Unleashing: prompt search / prompt discard ===== + if (resolvingUnleashing) + { + Logger.DebugWriteLine( + "Resolving Unleashing. HamonLine=" + resolvingUnleashingHamonLine + + " min=" + min + + " max=" + max + + " cards=[" + string.Join(", ", cards.Select(c => + c == null ? "null" : $"{c.Id} L{c.Location}" + )) + "]" + ); + + int[] searchIds = resolvingUnleashingHamonLine + ? new[] + { + CardId.RavielSacredBeastOfEndlessEternity, + CardId.MartyrOfTheSacredBeasts, + CardId.TheOrchestratorOfTheSacredBeasts + } + : new[] + { + CardId.RavielSacredBeastOfEndlessEternity, + CardId.MartyrOfTheSacredBeasts, + CardId.HamonSacredBeastOfSinfulCatastrophe + }; + + bool looksLikeSearchPrompt = cards.Any(c => + c != null + && c.Location == CardLocation.Deck + && searchIds.Any(id => c.IsCode(id))); + + if (looksLikeSearchPrompt) + { + List picked = PickCardsByIdPriority(cards, searchIds, Math.Min(3, max)); + + if (picked.Count >= min) + { + Logger.DebugWriteLine( + "Unleashing search pick: " + + string.Join(", ", picked.Select(c => c.Id)) + ); + + return picked; + } + } + bool looksLikeDiscardPrompt = cards.Any(c => + c != null + && c.Location == CardLocation.Hand); + + if (looksLikeDiscardPrompt && min <= 2 && max >= 2) + { + List discard = new List(); + + if (resolvingUnleashingHamonLine) + { + discard = PickCardsByIdPriority(cards, new[] + { + CardId.TheOrchestratorOfTheSacredBeasts, + CardId.HamonSacredBeastOfSinfulCatastrophe + }, 2); + + if (discard.Count < 2) + { + HashSet protect = new HashSet + { + CardId.RavielSacredBeastOfEndlessEternity, + CardId.MartyrOfTheSacredBeasts, + CardId.UriaSacredBeastOfCataclysmicFire + }; + + discard.AddRange(cards + .Where(c => c != null && c.Location == CardLocation.Hand && !discard.Contains(c)) + .OrderBy(c => DiscardScore( + c, + protect, + preferHamon: true, + preferOrchestrator: true)) + .Where(c => DiscardScore( + c, + protect, + preferHamon: true, + preferOrchestrator: true) < 9999) + .Take(2 - discard.Count)); + } + } + else + { + HashSet protect = new HashSet + { + CardId.RavielSacredBeastOfEndlessEternity, + CardId.MartyrOfTheSacredBeasts, + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.UriaSacredBeastOfCataclysmicFire + }; + + discard = cards + .Where(c => c != null && c.Location == CardLocation.Hand) + .OrderBy(c => DiscardScore(c, protect)) + .Where(c => DiscardScore(c, protect) < 9999) + .Take(2) + .ToList(); + } + + if (discard.Count >= 2) + { + Logger.DebugWriteLine( + "Unleashing discard pick: " + + string.Join(", ", discard.Take(2).Select(c => c.Id)) + ); + + resolvingUnleashing = false; + resolvingUnleashingHamonLine = false; + + return discard.Take(2).ToList(); + } + } + Logger.DebugWriteLine("Unleashing prompt not handled, keep state."); + } + // ===== Fallen Paradise: cost 3 1 by 1 / summon target ===== + if (resolvingFallenParadise) + { + // cost prompt: Lua select cost hint=504 + if (hint == 504 && fallenParadiseCostCode != 0) + { + ClientCard cost = cards.FirstOrDefault(c => + c != null + && c.IsFaceup() + && c.IsCode(fallenParadiseCostCode) + && ( + c.Location == CardLocation.SpellZone + || c.Location == CardLocation.MonsterZone + )); + + if (cost != null) + { + Logger.DebugWriteLine("Fallen Paradise cost pick: " + cost.Id); + return new List { cost }; + } + } + + // summon target prompt: hint=509 + if (hint == 509 && fallenParadiseTarget != 0) + { + ClientCard target = cards.FirstOrDefault(c => + c != null && c.IsCode(fallenParadiseTarget)); + + if (target != null) + { + Logger.DebugWriteLine("Fallen Paradise summon pick: " + target.Id); + + resolvingFallenParadise = false; + fallenParadiseTarget = 0; + fallenParadiseCostCode = 0; + + return new List { target }; + } + } + } + if (trigger != null && trigger.IsCode(CardId.DestructionChantOfTheSacredBeast)) + { + List enemyTargets = cards + .Where(c => c != null && c.Controller == 1 && c.IsOnField()) + .ToList(); + + if (enemyTargets.Count > 0) + { + ClientCard target = enemyTargets + .OrderByDescending(c => c.IsMonsterDangerous() ? 100 : 0) + .ThenByDescending(c => c.IsFloodgate() ? 80 : 0) + .ThenByDescending(c => c.Attack) + .FirstOrDefault(); + + if (target != null) + return new List { target }; + } + } + + if (trigger != null && trigger.IsCode(CardId.DivineAbyssOfTheSacredBeast)) + { + List enemyMonsterTargets = cards + .Where(c => c != null + && c.Controller == 1 + && c.Location == CardLocation.MonsterZone + && c.IsFaceup()) + .ToList(); + + if (enemyMonsterTargets.Count > 0) + { + ClientCard target = enemyMonsterTargets + .OrderByDescending(c => c.IsMonsterDangerous() ? 100 : 0) + .ThenByDescending(c => c.Attack) + .FirstOrDefault(); + + if (target != null) + return new List { target }; + } + + List abyssCopies = cards + .Where(c => c != null && c.IsCode(CardId.DivineAbyssOfTheSacredBeast)) + .Take(max) + .ToList(); + + if (abyssCopies.Count >= min) + return abyssCopies; + } + + if (trigger != null && trigger.IsCode(CardId.SPLittleKnight)) + { + List targetList = cards + .Where(c => c != null && c.Controller == 1) + .OrderByDescending(c => c.IsMonsterDangerous() ? 100 : 0) + .ThenByDescending(c => c.IsFloodgate() ? 80 : 0) + .ThenByDescending(c => c.Attack) + .Take(max) + .ToList(); + + if (targetList.Count >= min) + return targetList; + } + + + Logger.DebugWriteLine("Use default."); + return base.OnSelectCard(cards, min, max, hint, cancelable); + } + public int CheckRemainInDeck(int id) + { + for (int count = 1; count < 4; ++count) + { + if (DeckCountTable[count].Contains(id)) + { + return Bot.GetRemainingCount(id, count); + } + } + return 0; + } + public bool CheckAtAdvantage() + { + if (GetProblematicEnemyMonster() == null && Bot.GetMonsters().Any(card => card.IsFaceup())) + { + return true; + } + return false; + } + public bool AshBlossomActivate() + { + if (CheckWhetherNegated(true) || !CheckLastChainShouldNegated()) return false; + if (Duel.LastChainPlayer == 1 && Util.GetLastChainCard().IsCode(_CardId.MaxxC)) + { + if (CheckAtAdvantage() && Duel.Turn > 1) + { + return false; + } + } + return DefaultAshBlossomAndJoyousSpring(); + } + public bool MaxxCActivate() + { + if (CheckWhetherNegated(true) || Duel.LastChainPlayer == 0) return false; + return DefaultMaxxC(); + } + public bool CrossoutDesignatorActivate() + { + if (CheckWhetherNegated() || !CheckLastChainShouldNegated()) return false; + if (Duel.LastChainPlayer == 1 && Util.GetLastChainCard() != null) + { + int code = Util.GetLastChainCard().Id; + int alias = Util.GetLastChainCard().Alias; + ClientCard last = Util.GetLastChainCard(); + if (last.IsMonster() && (last.HasType(CardType.Fusion) || last.HasType(CardType.Synchro) || last.HasType(CardType.Xyz) || last.HasType(CardType.Link))) + { + return false; + } + if (alias != 0 && alias - code < 10) code = alias; + if (code == 0) return false; + if (DefaultCheckWhetherCardIdIsNegated(code)) return false; + if (CheckRemainInDeck(code) > 0) + { + if (!(Card.Location == CardLocation.SpellZone)) + { + SelectSTPlace(null, true); + } + AI.SelectAnnounceID(code); + currentNegateCardList.AddRange(Enemy.MonsterZone.Where(c => c != null && c.IsFaceup() && c.IsCode(code))); + return true; + } + } + return false; + } + public bool InfiniteImpermanenceActivate() + { + if (CheckWhetherNegated()) return false; + foreach (ClientCard m in Enemy.GetMonsters()) + { + if (m.IsMonsterShouldBeDisabledBeforeItUseEffect() && !m.IsDisabled() && Duel.LastChainPlayer != 0) + { + if (Card.Location == CardLocation.SpellZone) + { + for (int i = 0; i < 5; ++i) + { + if (Bot.SpellZone[i] == Card) + { + infiniteImpermanenceList.Add(i); + break; + } + } + } + if (Card.Location == CardLocation.Hand) + { + SelectSTPlace(Card, true); + } + AI.SelectCard(m); + return true; + } + } + ClientCard LastChainCard = Util.GetLastChainCard(); + if (Card.Location == CardLocation.SpellZone) + { + int this_seq = -1; + int that_seq = -1; + for (int i = 0; i < 5; ++i) + { + if (Bot.SpellZone[i] == Card) this_seq = i; + if (LastChainCard != null + && LastChainCard.Controller == 1 && LastChainCard.Location == CardLocation.SpellZone && Enemy.SpellZone[i] == LastChainCard) that_seq = i; + else if (Duel.Player == 0 && Util.GetProblematicEnemySpell() != null + && Enemy.SpellZone[i] != null && Enemy.SpellZone[i].IsFloodgate()) that_seq = i; + } + if ((this_seq * that_seq >= 0 && this_seq + that_seq == 4) + || (Util.IsChainTarget(Card)) + || (LastChainCard != null && LastChainCard.Controller == 1 && LastChainCard.IsCode(_CardId.HarpiesFeatherDuster))) + { + ClientCard target = GetProblematicEnemyMonster(canBeTarget: true); + List enemyMonsters = Enemy.GetMonsters(); + AI.SelectCard(target); + infiniteImpermanenceList.Add(this_seq); + return true; + } + } + if ((LastChainCard == null || LastChainCard.Controller != 1 || LastChainCard.Location != CardLocation.MonsterZone + || LastChainCard.IsDisabled() || LastChainCard.IsShouldNotBeTarget() || LastChainCard.IsShouldNotBeSpellTrapTarget())) + return false; + + if (Card.Location == CardLocation.SpellZone) + { + for (int i = 0; i < 5; ++i) + { + if (Bot.SpellZone[i] == Card) + { + infiniteImpermanenceList.Add(i); + break; + } + } + } + if (Card.Location == CardLocation.Hand) + { + SelectSTPlace(Card, true); + } + if (LastChainCard != null) AI.SelectCard(LastChainCard); + else + { + List enemyMonsters = Enemy.GetMonsters(); + enemyMonsters.Sort(CardContainer.CompareCardAttack); + enemyMonsters.Reverse(); + foreach (ClientCard card in enemyMonsters) + { + if (card.IsFaceup() && !card.IsShouldNotBeTarget() && !card.IsShouldNotBeSpellTrapTarget()) + { + AI.SelectCard(card); + return true; + } + } + } + return true; + } + public bool CalledbytheGraveActivate() + { + if (CheckWhetherNegated() || !CheckLastChainShouldNegated()) + { + return false; + } + if (Duel.LastChainPlayer == 1) + { + if (Util.GetLastChainCard().IsMonster()) + { + int code = Util.GetLastChainCard().GetOriginCode(); + if (code == 0) return false; + if (DefaultCheckWhetherCardIdIsNegated(code)) return false; + if (Util.GetLastChainCard().IsCode(_CardId.MaxxC) && CheckAtAdvantage() && Duel.Turn > 1) + { + return false; + } + ClientCard graveTarget = Enemy.Graveyard.GetFirstMatchingCard(card => card.IsMonster() && card.GetOriginCode() == code); + if (graveTarget != null) + { + if (!(Card.Location == CardLocation.SpellZone)) + { + SelectSTPlace(null, true); + } + AI.SelectCard(graveTarget); + currentDestroyCardList.Add(graveTarget); + return true; + } + } + foreach (ClientCard graveCard in Enemy.Graveyard) + { + if (Duel.ChainTargets.Contains(graveCard) && graveCard.IsMonster()) + { + if (!(Card.Location == CardLocation.SpellZone)) + { + SelectSTPlace(null, true); + } + int code = graveCard.Id; + AI.SelectCard(graveCard); + currentDestroyCardList.Add(graveCard); + return true; + } + } + if (Duel.ChainTargets.Contains(Card)) + { + List enemyMonsters = Enemy.Graveyard.GetMatchingCards(card => card.IsMonster()).ToList(); + if (enemyMonsters.Count > 0) + { + enemyMonsters.Sort(CardContainer.CompareCardAttack); + enemyMonsters.Reverse(); + int code = enemyMonsters[0].Id; + AI.SelectCard(code); + currentDestroyCardList.Add(enemyMonsters[0]); + return true; + } + } + } + if (Duel.LastChainPlayer == 1) return false; + List targets = GetDangerousCardinEnemyGrave(true); + if (targets.Count > 0) + { + int code = targets[0].Id; + if (!(Card.Location == CardLocation.SpellZone)) + { + SelectSTPlace(null, true); + } + AI.SelectCard(code); + currentDestroyCardList.Add(targets[0]); + return true; + } + return false; + } + public bool SpellSetCheck() + { + if (Duel.Phase == DuelPhase.Main1 && Bot.HasAttackingMonster() && Duel.Turn > 1) return false; + List onlyOneSetList = new List { }; + if (onlyOneSetList.Contains(Card.Id) && Bot.HasInSpellZone(Card.Id)) + { + return false; + } + if ((Card.IsTrap() || Card.HasType(CardType.QuickPlay))) + { + + List avoid_list = new List(); + int setFornfiniteImpermanence = 0; + for (int i = 0; i < 5; ++i) + { + if (Enemy.SpellZone[i] != null && Enemy.SpellZone[i].IsFaceup() && Bot.SpellZone[4 - i] == null) + { + avoid_list.Add(4 - i); + setFornfiniteImpermanence += (int)System.Math.Pow(2, 4 - i); + } + } + if (Bot.HasInHand(_CardId.InfiniteImpermanence)) + { + if (Card.IsCode(_CardId.InfiniteImpermanence)) + { + AI.SelectPlace(setFornfiniteImpermanence); + return true; + } + else + { + SelectSTPlace(Card, true, avoid_list); + return true; + } + } + else + { + SelectSTPlace(Card, true); + } + return true; + } + return false; + } + public List GetDangerousCardinEnemyGrave(bool onlyMonster = false) + { + List result = Enemy.Graveyard.GetMatchingCards(card => + (!onlyMonster || card.IsMonster()) && (card.HasSetcode(SetcodeOrcust) || card.HasSetcode(SetcodePhantom) || card.HasSetcode(SetcodeHorus) || card.HasSetcode(SetcodeDarkWorld) || card.HasSetcode(SetcodeSkyStriker))).ToList(); + List dangerMonsterIdList = new List { 99937011, 63542003, 9411399, 28954097, 30680659, 32731036 }; + result.AddRange(Enemy.Graveyard.GetMatchingCards(card => dangerMonsterIdList.Contains(card.Id))); + return result; + } + public bool CheckWhetherNegated(bool disablecheck = true, bool toFieldCheck = false, CardType type = 0) + { + bool isMonster = type == 0 && Card.IsMonster(); + isMonster |= ((int)type & (int)CardType.Monster) != 0; + bool isSpellOrTrap = type == 0 && (Card.IsSpell() || Card.IsTrap()); + isSpellOrTrap |= (((int)type & (int)CardType.Spell) != 0) || (((int)type & (int)CardType.Trap) != 0); + bool isCounter = ((int)type & (int)CardType.Counter) != 0; + if (isSpellOrTrap && toFieldCheck && CheckSpellWillBeNegate(isCounter)) + return true; + if (DefaultCheckWhetherCardIsNegated(Card)) return true; + if (isMonster && (toFieldCheck || Card.Location == CardLocation.MonsterZone)) + { + if ((toFieldCheck && (((int)type & (int)CardType.Link) != 0)) || Card.IsDefense()) + { + if (Enemy.MonsterZone.Any(card => CheckNumber41(card)) || Bot.MonsterZone.Any(card => CheckNumber41(card))) return true; + } + if (Enemy.HasInSpellZone(CardId.SkillDrain, true)) return true; + } + if (disablecheck) return (Card.Location == CardLocation.MonsterZone || Card.Location == CardLocation.SpellZone) && Card.IsDisabled() && Card.IsFaceup(); + return false; + } + public bool CheckNumber41(ClientCard card) + { + return card != null && card.IsFaceup() && card.IsCode(CardId.Number41BagooskatheTerriblyTiredTapir) && card.IsDefense() && !card.IsDisabled(); + } + public void SelectSTPlace(ClientCard card = null, bool avoidImpermanence = false, List avoidList = null) + { + if (card == null) card = Card; + List list = new List(); + for (int seq = 0; seq < 5; ++seq) + { + if (Bot.SpellZone[seq] == null) + { + if (avoidImpermanence && infiniteImpermanenceList.Contains(seq)) continue; + //if (card != null && card.Location == CardLocation.Hand && avoidImpermanence && infiniteImpermanenceList.Contains(seq)) continue; + if (avoidList != null && avoidList.Contains(seq)) continue; + list.Add(seq); + } + } + int n = list.Count; + while (n-- > 1) + { + int index = Program.Rand.Next(list.Count); + int nextIndex = (index + Program.Rand.Next(list.Count - 1)) % list.Count; + int tempInt = list[index]; + list[index] = list[nextIndex]; + list[nextIndex] = tempInt; + } + if (avoidImpermanence && Bot.GetMonsters().Any(c => c.IsFaceup() && !c.IsDisabled())) + { + foreach (int seq in list) + { + ClientCard enemySpell = Enemy.SpellZone[4 - seq]; + if (enemySpell != null && enemySpell.IsFacedown()) continue; + int zone = (int)System.Math.Pow(2, seq); + AI.SelectPlace(zone); + return; + } + } + foreach (int seq in list) + { + int zone = (int)System.Math.Pow(2, seq); + AI.SelectPlace(zone); + return; + } + AI.SelectPlace(0); + } + public bool CheckSpellWillBeNegate(bool isCounter = false, ClientCard target = null) + { + if (target == null) target = Card; + if (target.Location != CardLocation.SpellZone && target.Location != CardLocation.Hand) return false; + + if (Enemy.HasInMonstersZone(CardId.NaturalExterio, true) && !isCounter) return true; + if (target.IsSpell()) + { + if (Enemy.HasInMonstersZone(CardId.NaturalBeast, true)) return true; + if (Enemy.HasInSpellZone(CardId.ImperialOrder, true) || Bot.HasInSpellZone(CardId.ImperialOrder, true)) return true; + if (Enemy.HasInMonstersZone(CardId.SwordsmanLV7, true) || Bot.HasInMonstersZone(CardId.SwordsmanLV7, true)) return true; + } + if (target.IsTrap() && (Enemy.HasInSpellZone(CardId.RoyalDecree, true) || Bot.HasInSpellZone(CardId.RoyalDecree, true))) return true; + if (target.Location == CardLocation.SpellZone && (target.IsSpell() || target.IsTrap())) + { + int selfSeq = -1; + for (int i = 0; i < 5; ++i) + { + if (Bot.SpellZone[i] == Card) selfSeq = i; + } + if (infiniteImpermanenceList.Contains(selfSeq)) return true; + } + return false; + } + public bool CheckLastChainShouldNegated() + { + ClientCard lastcard = Util.GetLastChainCard(); + if (lastcard == null || lastcard.Controller != 1) return false; + if (lastcard.IsMonster() && lastcard.HasSetcode(SetcodeTimeLord) && Duel.Phase == DuelPhase.Standby) return false; + if (notToNegateIdList.Contains(lastcard.Id)) return false; + if (DefaultCheckWhetherCardIsNegated(lastcard)) return false; + if (Duel.Turn == 1 && lastcard.IsCode(_CardId.MaxxC)) return false; + + return true; + } + public ClientCard GetProblematicEnemyMonster(int attack = 0, bool canBeTarget = false, bool ignoreCurrentDestroy = false, CardType selfType = 0) + { + ClientCard floodagateCard = Enemy.GetMonsters().Where(c => c?.Data != null && (ignoreCurrentDestroy || !currentDestroyCardList.Contains(c)) + && c.IsFloodgate() && c.IsFaceup() + && CheckCanBeTargeted(c, canBeTarget, selfType) + && CheckShouldNotIgnore(c)).OrderByDescending(card => card.Attack).FirstOrDefault(); + if (floodagateCard != null) return floodagateCard; + + ClientCard dangerCard = Enemy.MonsterZone.Where(c => c?.Data != null && (ignoreCurrentDestroy || !currentDestroyCardList.Contains(c)) + && c.IsMonsterDangerous() && c.IsFaceup() && CheckCanBeTargeted(c, canBeTarget, selfType) + && CheckShouldNotIgnore(c)).OrderByDescending(card => card.Attack).FirstOrDefault(); + if (dangerCard != null) return dangerCard; + + ClientCard invincibleCard = Enemy.MonsterZone.Where(c => c?.Data != null && (ignoreCurrentDestroy || !currentDestroyCardList.Contains(c)) + && c.IsMonsterInvincible() && c.IsFaceup() && CheckCanBeTargeted(c, canBeTarget, selfType) + && CheckShouldNotIgnore(c)).OrderByDescending(card => card.Attack).FirstOrDefault(); + if (invincibleCard != null) return invincibleCard; + + ClientCard equippedCard = Enemy.MonsterZone.Where(c => c?.Data != null && (ignoreCurrentDestroy || !currentDestroyCardList.Contains(c)) + && c.EquipCards.Count > 0 && CheckCanBeTargeted(c, canBeTarget, selfType) + && CheckShouldNotIgnore(c)).OrderByDescending(card => card.Attack).FirstOrDefault(); + if (equippedCard != null) return equippedCard; + + ClientCard enemyExtraMonster = Enemy.MonsterZone.Where(c => c != null && (ignoreCurrentDestroy || !currentDestroyCardList.Contains(c)) + && (c.HasType(CardType.Fusion | CardType.Ritual | CardType.Synchro | CardType.Xyz) || (c.HasType(CardType.Link) && c.LinkCount >= 2)) + && CheckCanBeTargeted(c, canBeTarget, selfType) && CheckShouldNotIgnore(c)).OrderByDescending(card => card.Attack).FirstOrDefault(); + if (enemyExtraMonster != null) return enemyExtraMonster; + + if (attack >= 0) + { + if (attack == 0) + attack = Util.GetBestAttack(Bot); + ClientCard betterCard = Enemy.MonsterZone.Where(card => card != null + && card.GetDefensePower() >= attack && card.GetDefensePower() > 0 && card.IsAttack() && CheckCanBeTargeted(card, canBeTarget, selfType) + && (ignoreCurrentDestroy || !currentDestroyCardList.Contains(card))).OrderByDescending(card => card.Attack).FirstOrDefault(); + if (betterCard != null) return betterCard; + } + return null; + } + public bool CheckCanBeTargeted(ClientCard card, bool canBeTarget, CardType selfType) + { + if (card == null) return true; + if (canBeTarget) + { + if (card.IsShouldNotBeTarget()) return false; + if (((int)selfType & (int)CardType.Monster) > 0 && card.IsShouldNotBeMonsterTarget()) return false; + if (((int)selfType & (int)CardType.Spell) > 0 && card.IsShouldNotBeSpellTrapTarget()) return false; + if (((int)selfType & (int)CardType.Trap) > 0 && (card.IsShouldNotBeSpellTrapTarget() && !card.IsDisabled())) return false; + } + return true; + } + public bool CheckShouldNotIgnore(ClientCard cards) + { + return !currentDestroyCardList.Contains(cards) && !currentNegateCardList.Contains(cards); + } + public List ShuffleList(List list) + { + List result = list; + int n = result.Count; + while (n-- > 1) + { + int index = Program.Rand.Next(result.Count); + int nextIndex = (index + Program.Rand.Next(result.Count - 1)) % result.Count; + T tempCard = result[index]; + result[index] = result[nextIndex]; + result[nextIndex] = tempCard; + } + return result; + } + public List GetProblematicEnemyCardList(bool canBeTarget = false, bool ignoreSpells = false, CardType selfType = 0) + { + List resultList = new List(); + + List floodagateList = Enemy.MonsterZone.Where(c => c?.Data != null && !currentDestroyCardList.Contains(c) + && c.IsFloodgate() && c.IsFaceup() && CheckCanBeTargeted(c, canBeTarget, selfType)).OrderByDescending(card => card.Attack).ToList(); + if (floodagateList.Count > 0) resultList.AddRange(floodagateList); + + List problemEnemySpellList = Enemy.SpellZone.Where(c => c?.Data != null && !resultList.Contains(c) && !currentDestroyCardList.Contains(c) + && c.IsFloodgate() && c.IsFaceup() && CheckCanBeTargeted(c, canBeTarget, selfType)).ToList(); + if (problemEnemySpellList.Count > 0) resultList.AddRange(ShuffleList(problemEnemySpellList)); + + List dangerList = Enemy.MonsterZone.Where(c => c?.Data != null && !resultList.Contains(c) && !currentDestroyCardList.Contains(c) + && c.IsMonsterDangerous() && c.IsFaceup() && CheckCanBeTargeted(c, canBeTarget, selfType)).OrderByDescending(card => card.Attack).ToList(); + if (dangerList.Count > 0 && (Duel.Player == 0 || (Duel.Phase > DuelPhase.Main1 && Duel.Phase < DuelPhase.Main2))) resultList.AddRange(dangerList); + + List invincibleList = Enemy.MonsterZone.Where(c => c?.Data != null && !resultList.Contains(c) && !currentDestroyCardList.Contains(c) + && c.IsMonsterInvincible() && c.IsFaceup() && CheckCanBeTargeted(c, canBeTarget, selfType)).OrderByDescending(card => card.Attack).ToList(); + if (invincibleList.Count > 0) resultList.AddRange(invincibleList); + + List enemyMonsters = Enemy.GetMonsters().Where(c => !currentDestroyCardList.Contains(c)).OrderByDescending(card => card.Attack).ToList(); + if (enemyMonsters.Count > 0) + { + foreach (ClientCard target in enemyMonsters) + { + if ((target.HasType(CardType.Fusion | CardType.Ritual | CardType.Synchro | CardType.Xyz) + || (target.HasType(CardType.Link) && target.LinkCount >= 2)) + && !resultList.Contains(target) && CheckCanBeTargeted(target, canBeTarget, selfType)) + { + resultList.Add(target); + } + } + } + + List spells = Enemy.GetSpells().Where(c => c.IsFaceup() && !currentDestroyCardList.Contains(c) + && c.HasType(CardType.Equip | CardType.Pendulum | CardType.Field | CardType.Continuous) && CheckCanBeTargeted(c, canBeTarget, selfType) + && !notToDestroySpellTrap.Contains(c.Id)).ToList(); + if (spells.Count > 0 && !ignoreSpells) resultList.AddRange(ShuffleList(spells)); + + return resultList; + } + public List GetNormalEnemyTargetList(bool canBeTarget = true, bool ignoreCurrentDestroy = false, CardType selfType = 0) + { + List targetList = GetProblematicEnemyCardList(canBeTarget, selfType: selfType); + List enemyMonster = Enemy.GetMonsters().Where(card => card.IsFaceup() && !targetList.Contains(card) + && (!ignoreCurrentDestroy || !currentDestroyCardList.Contains(card))).ToList(); + enemyMonster.Sort(CardContainer.CompareCardAttack); + enemyMonster.Reverse(); + targetList.AddRange(enemyMonster); + targetList.AddRange(ShuffleList(Enemy.GetSpells().Where(card => (!ignoreCurrentDestroy || !currentDestroyCardList.Contains(card)) && enemyPlaceThisTurn.Contains(card)).ToList())); + targetList.AddRange(ShuffleList(Enemy.GetSpells().Where(card => (!ignoreCurrentDestroy || !currentDestroyCardList.Contains(card)) && !enemyPlaceThisTurn.Contains(card)).ToList())); + targetList.AddRange(ShuffleList(Enemy.GetMonsters().Where(card => card.IsFacedown() && (!ignoreCurrentDestroy || !currentDestroyCardList.Contains(card))).ToList())); + + return targetList; + } + public List GetMonsterListForTargetNegate(bool canBeTarget = false, CardType selfType = 0) + { + List resultList = new List(); + if (CheckWhetherNegated()) + { + return resultList; + } + + ClientCard target = Enemy.MonsterZone.FirstOrDefault(card => card?.Data != null + && card.IsMonsterShouldBeDisabledBeforeItUseEffect() && card.IsFaceup() && !card.IsShouldNotBeTarget() + && CheckCanBeTargeted(card, canBeTarget, selfType) + && !currentNegateCardList.Contains(card)); + if (target != null) + { + resultList.Add(target); + } + + foreach (ClientCard chainingCard in Duel.CurrentChain) + { + if (chainingCard.Location == CardLocation.MonsterZone && chainingCard.Controller == 1 && !chainingCard.IsDisabled() + && CheckCanBeTargeted(chainingCard, canBeTarget, selfType) && !currentNegateCardList.Contains(chainingCard)) + { + resultList.Add(chainingCard); + } + } + + return resultList; + } + public ClientCard GetBestEnemyMonster(bool onlyFaceup = false, bool canBeTarget = false) + { + ClientCard card = GetProblematicEnemyMonster(0, canBeTarget); + if (card != null) return card; + card = Enemy.MonsterZone.GetHighestAttackMonster(canBeTarget); + if (card != null) return card; + List monsters = Enemy.GetMonsters(); + if (monsters.Count > 0 && !onlyFaceup) return ShuffleCardList(monsters)[0]; + return null; + } + public ClientCard GetBestEnemySpell(bool onlyFaceup = false, bool canBeTarget = false) + { + List problemEnemySpellList = Enemy.SpellZone.Where(c => c?.Data != null + && c.IsFloodgate() && c.IsFaceup() && (!canBeTarget || !c.IsShouldNotBeTarget())).ToList(); + if (problemEnemySpellList.Count > 0) + { + return ShuffleCardList(problemEnemySpellList)[0]; + } + + List spells = Enemy.GetSpells().Where(card => !(card.IsFaceup() && card.IsCode(_CardId.EvenlyMatched))).ToList(); + + List faceUpList = spells.Where(ecard => ecard.IsFaceup() && (ecard.HasType(CardType.Continuous) || ecard.HasType(CardType.Field) || ecard.HasType(CardType.Pendulum))).ToList(); + if (faceUpList.Count > 0) + { + return ShuffleCardList(faceUpList)[0]; + } + + if (spells.Count > 0 && !onlyFaceup) + { + return ShuffleCardList(spells)[0]; + } + + return null; + } + public List ShuffleCardList(List list) + { + List result = list; + int n = result.Count; + while (n-- > 1) + { + int index = Program.Rand.Next(n + 1); + ClientCard temp = result[index]; + result[index] = result[n]; + result[n] = temp; + } + return result; + } + public ClientCard GetBestEnemyCard(bool onlyFaceup = false, bool canBeTarget = false, bool checkGrave = false) + { + ClientCard card = GetBestEnemyMonster(onlyFaceup, canBeTarget); + if (card != null) return card; + + card = GetBestEnemySpell(onlyFaceup, canBeTarget); + if (card != null) return card; + + if (checkGrave && Enemy.Graveyard.Count > 0) + { + List graveMonsterList = Enemy.Graveyard.GetMatchingCards(c => c.IsMonster()).ToList(); + if (graveMonsterList.Count > 0) + { + graveMonsterList.Sort(CardContainer.CompareCardAttack); + graveMonsterList.Reverse(); + return graveMonsterList[0]; + } + return ShuffleCardList(Enemy.Graveyard.ToList())[0]; + } + return null; + } + private bool Repos() + { + bool enemyBetter = Util.IsAllEnemyBetter(true); + + if (Card.IsAttack() && enemyBetter) + return true; + if (Card.IsFacedown()) + return true; + if (Card.IsDefense() && !enemyBetter && Card.Attack >= Card.Defense) + return true; + if (Card.IsAttack() && Card.IsCode(CardId.MartyrOfTheSacredBeasts)) + return true; + if (Card == null || Card.IsFacedown()) return false; + if (Card.HasType(CardType.Link)) return false; - if (!HasCardAccessible(CardId.SkyfireOfTheSacredBeast) - && CheckRemainInDeck(CardId.SkyfireOfTheSacredBeast) > 0) - return CardId.SkyfireOfTheSacredBeast; + return false; + } + #endregion + #region Work Space + private bool VarudrasActivate() + { + if (CheckWhetherNegated()) return false; - if (!HasCardAccessible(CardId.FallenParadiseOfTheSacredBeasts) - && CheckRemainInDeck(CardId.FallenParadiseOfTheSacredBeasts) > 0) - return CardId.FallenParadiseOfTheSacredBeasts; + List targetList = GetNormalEnemyTargetList(true, true); + int desc = ActivateDescription; + int d1 = Util.GetStringId(CardId.VarudrasTheFinalBringer, 1); + int d2 = Util.GetStringId(CardId.VarudrasTheFinalBringer, 2); - if (CheckRemainInDeck(CardId.UnleashingTheSacredBeasts) > 0) - return CardId.UnleashingTheSacredBeasts; + var enemyPick = targetList.FirstOrDefault(c => c != null && c.Controller == 1); - if (CheckRemainInDeck(CardId.SkyfireOfTheSacredBeast) > 0) - return CardId.SkyfireOfTheSacredBeast; + if (desc == d1 && Duel.LastChainPlayer == 1 && Duel.CurrentChain.Count > 0) + { + if (!CheckLastChainShouldNegated()) return false; + return true; + } - if (CheckRemainInDeck(CardId.FallenParadiseOfTheSacredBeasts) > 0) - return CardId.FallenParadiseOfTheSacredBeasts; + if (desc == d1 || desc == d2 || desc == -1) + { + List enemyTargets = GetNormalEnemyTargetList(true, true) + .Where(c => c != null && c.Controller == 1) + .ToList(); - return 0; + if (enemyTargets.Count == 0) + return false; + + AI.SelectCard(enemyTargets); + return true; + } + + return false; } + public bool SPLittleKnightActivate() + { + if (ActivateDescription == -1 || ActivateDescription == Util.GetStringId(CardId.SPLittleKnight, 0)) + { + // banish card + List problemCardList = GetProblematicEnemyCardList(true, selfType: CardType.Monster); + problemCardList.AddRange(GetNormalEnemyTargetList(true, true, CardType.Monster)); + problemCardList.AddRange(Enemy.Graveyard.Where(card => card.HasType(CardType.Monster)).OrderByDescending(card => card.Attack)); + problemCardList.AddRange(Enemy.Graveyard.Where(card => !card.HasType(CardType.Monster))); + return true; + } + else if (ActivateDescription == Util.GetStringId(CardId.SPLittleKnight, 1)) + { + ClientCard selfMonster = null; + foreach (ClientCard target in Bot.GetMonsters()) + { + if (Duel.ChainTargets.Contains(target)) + { + selfMonster = target; + break; + } + } + if (selfMonster == null) + { + if (Duel.Player == 1) + { + selfMonster = Bot.GetMonsters().Where(card => card.IsAttack()).OrderBy(card => card.Attack).FirstOrDefault(); + if (!Util.IsOneEnemyBetterThanValue(selfMonster.Attack, true)) selfMonster = null; + } + } + if (selfMonster != null) + { + ClientCard nextMonster = null; + List selfTargetList = Bot.GetMonsters().Where(card => card != selfMonster).ToList(); + if (Enemy.GetMonsterCount() == 0 && selfTargetList.Count() > 0) + { + selfTargetList.Sort(CompareUsableAttack); + nextMonster = selfTargetList[0]; + } + if (Enemy.GetMonsterCount() > 0) + { + nextMonster = GetProblematicEnemyMonster(0, true, false, CardType.Monster); + } + if (nextMonster != null) + { + SPLittleKnightRemoveStep = 1; + return true; + } + } + } + + return false; + } + public bool SPLittleKnightSummon() + { + if (CheckWhetherNegated(true, true, CardType.Monster)) return false; + + bool forceForZone = ShouldForceSPLittleKnightForZone(); + + if (!forceForZone && !HasSPLittleKnightTargetNow()) + return false; + + List materials = PickSPLittleKnightMaterials(); - private bool HasUsedHamonLine() + if (materials.Count < 2) return false; + + spLittleKnightMaterialPlan = materials.Take(2).ToList(); + resolvingSPLittleKnightSummon = true; + + AI.SelectMaterials(spLittleKnightMaterialPlan); + return true; + } + private List PickSPLittleKnightMaterials() { - // Do not infer this from "Unleashing is accessible" because Unleashing can be in the opening hand. - // It counts only if Hamon activation was queued or Hamon has already reached GY. - return HamonSearchQueuedForRoute - || Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe)); + return Bot.GetMonsters() + .Where(c => c != null + && c.IsFaceup() + && c.HasType(CardType.Effect)) + .Select(c => new + { + Card = c, + Score = SPLittleKnightMaterialScore(c) + }) + .Where(x => x.Score < 9999) + .OrderBy(x => x.Score) + .Select(x => x.Card) + .Take(2) + .ToList(); } + private int SPLittleKnightMaterialScore(ClientCard card) + { + if (card == null) return 9999; + if (card.Level == 10 && card.HasSetcode(SetcodeSacredBeast)) + return 9999; + if (card.IsCode( + CardId.PhantasmalSacredBeastsOfChaos, + CardId.VarudrasTheFinalBringer, + CardId.ThunderDragonColossus, + CardId.SuperdreadnoughtRailCannonGustavRocket, + CardId.SuperdreadnoughtRailCannonGustavMax)) + return 9999; + + if (card.IsCode(CardId.TheOrchestratorOfTheSacredBeasts) && Bot.HasInExtra(CardId.ThunderDragonColossus)) + return 9999; + + if (card.IsCode(CardId.MartyrOfTheSacredBeasts)) + return 1; + + if (card.IsCode(CardId.Linkuriboh)) + return 2; + + if (card.IsCode(CardId.TheOrchestratorOfTheSacredBeasts)) + return 30; + + return 50; + } + public int CompareUsableAttack(ClientCard cardA, ClientCard cardB) + { + if (cardA == null && cardB == null) + return 0; + if (cardA == null) + return -1; + if (cardB == null) + return 1; + int powerA = (cardA.IsDefense()) ? 0 : cardA.Attack; + int powerB = (cardB.IsDefense()) ? 0 : cardB.Attack; + if (powerA < powerB) + return -1; + if (powerA == powerB) + return CardContainer.CompareCardLevel(cardA, cardB); + return 1; + } private bool IsMartyrNegatedOrInterrupted() { ClientCard martyr = Bot.GetMonsters() .FirstOrDefault(c => c != null && c.IsFaceup() && c.IsCode(CardId.MartyrOfTheSacredBeasts)); if (martyr == null) return false; if (DefaultCheckWhetherCardIsNegated(martyr) || martyr.IsDisabled()) return true; - - // Practical checkpoint from the route: Martyr is on field, but no face-up Skyfire was placed. - return CountFaceupSpellTrap(CardId.SkyfireOfTheSacredBeast) == 0 + + /*return CountFaceupSpellTrap(CardId.SkyfireOfTheSacredBeast) == 0 && !Bot.HasInSpellZone(CardId.SkyfireOfTheSacredBeast, true) - && HasCardAccessible(CardId.UnleashingTheSacredBeasts); + && HasCardAccessible(CardId.UnleashingTheSacredBeasts);*/ + return false; } - - private int CountFaceupMartyrOnField() + private bool HasCardAccessible(int id) { - return Bot.GetMonsters().Count(c => c != null && c.IsFaceup() && c.IsCode(CardId.MartyrOfTheSacredBeasts)); + return Bot.HasInHand(id) + || Bot.HasInSpellZone(id, true) + || Bot.HasInMonstersZone(id, true) + || Bot.Graveyard.Any(c => c != null && c.IsCode(id)); } - - private bool IsNeverDiscard(int id) + private int CountFaceupSpellTrap(int id) { - // Uria must stay in hand for the Destruction Chant line. - if (id == CardId.UriaSacredBeastOfCataclysmicFire) return true; - - // Never pay important Extra Deck monsters as Fallen/other generic cost if a fork can place them in hand/field somehow. - return id == CardId.PhantasmalSacredBeastsOfChaos - || id == CardId.ThunderDragonColossus - || id == CardId.VarudrasTheFinalBringer - || id == CardId.SPLittleKnight - || id == CardId.SuperdreadnoughtRailCannonGustavRocket - || id == CardId.SuperdreadnoughtRailCannonGustavMax; + return Bot.GetSpells().Count(c => c != null && c.IsFaceup() && c.IsCode(id)); } - - private int DiscardScore(ClientCard card, HashSet protectedIds, bool preferHamon = false, bool preferOrchestrator = false) + private bool LinkuribohSummon() { - if (card == null) return 9999; - if (IsNeverDiscard(card.Id)) return 9999; - if (protectedIds != null && protectedIds.Contains(card.Id)) return 9999; - - if (preferHamon && card.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe)) return -100; - if (preferOrchestrator && card.IsCode(CardId.TheOrchestratorOfTheSacredBeasts)) return -90; - - bool duplicate = Bot.Hand.Count(c => c != null && c.Id == card.Id) >= 2; - if (duplicate) return 0; + if (!IsMartyrNegatedOrInterrupted()) return false; - if (card.IsCode(CardId.DivineAbyssOfTheSacredBeast)) return 1; // brick in hand - if (card.IsCode(CardId.CardOfTheSoul) && GetBotLifePointsSafe() != 8000) return 2; - if (card.IsCode(CardId.HeavyPolymerization) && IsLikelyGoingFirst()) return 3; - if (card.IsCode(CardId.ThunderKingTheLightningstrikeKaiju) && IsLikelyGoingFirst()) return 4; - if (card.IsCode(CardId.MulcharmyFuwalos)) return 5; - if (card.IsCode(CardId.SkyfireOfTheSacredBeast) && CountFaceupSpellTrap(CardId.SkyfireOfTheSacredBeast) >= 2) return 6; - if (card.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe, CardId.RavielSacredBeastOfEndlessEternity)) return 50; - if (card.IsCode(CardId.AshBlossom, CardId.MaxxC, CardId.CalledByTheGrave)) return 70; - return 20; - } + ClientCard martyr = Bot.GetMonsters() + .FirstOrDefault(c => c != null + && c.IsFaceup() + && c.Level == 1 + && c.IsCode(CardId.MartyrOfTheSacredBeasts)); - private ClientCard GetBestDiscardCost(IEnumerable protectedIds = null, bool preferHamon = false, bool preferOrchestrator = false) - { - HashSet protectedSet = protectedIds == null - ? new HashSet() - : new HashSet(protectedIds); + if (martyr == null) return false; - return Bot.Hand - .Where(c => c != null) - .OrderBy(c => DiscardScore(c, protectedSet, preferHamon, preferOrchestrator)) - .FirstOrDefault(c => DiscardScore(c, protectedSet, preferHamon, preferOrchestrator) < 9999); + AI.SelectMaterials(new List { martyr }); + return true; } - - private List GetBestDiscardCosts(int count, IEnumerable protectedIds = null) + private bool PhantasmalSacredBeastsOfChaosActivate() { - HashSet protectedSet = protectedIds == null - ? new HashSet() - : new HashSet(protectedIds); + if (CheckWhetherNegated(true, false, CardType.Monster)) return false; - return Bot.Hand - .Where(c => c != null) - .OrderBy(c => DiscardScore(c, protectedSet)) - .Where(c => DiscardScore(c, protectedSet) < 9999) - .Take(count) - .ToList(); - } + List targetList = GetMonsterListForTargetNegate(true, CardType.Monster); + if (targetList.Count == 0) return false; - private void SelectDiscardCost(params int[] protectedIds) - { - ClientCard discard = GetBestDiscardCost(protectedIds); - if (discard != null) AI.SelectCard(discard); - } + ClientCard target = targetList.FirstOrDefault(c => + c != null + && c.Controller == 1 + && c.IsFaceup() + && !c.IsDisabled() + && c.HasType(CardType.Effect) + ); - private bool QueueSearchThenDiscard(int searchTarget, params int[] protectedIds) - { - if (searchTarget == 0) return false; + if (target == null) return false; - ClientCard discard = GetBestDiscardCost(protectedIds.Concat(new int[] { searchTarget })); - if (discard == null) return false; + if (Duel.LastChainPlayer != 1 + && !target.IsMonsterShouldBeDisabledBeforeItUseEffect() + && !target.IsMonsterDangerous()) + { + return false; + } - AI.SelectCard(searchTarget); - AI.SelectNextCard(discard); + AI.SelectCard(target); + currentNegateCardList.Add(target); return true; } - - private void QueueUnleashingSearchAndCost() + private bool DivineAbyssActivate() { - bool hamonAlreadyUsed = HasUsedHamonLine() - || Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe)); + if (CheckWhetherNegated(true, true, CardType.Trap)) return false; - if (hamonAlreadyUsed) + if (Card.Location == CardLocation.SpellZone) { - // Case 1: Hamon was used first. - // Search Raviel + Martyr + Orchestrator, then discard Hamon + Orchestrator. - AI.SelectCard(new[] - { - CardId.RavielSacredBeastOfEndlessEternity, - CardId.MartyrOfTheSacredBeasts, - CardId.TheOrchestratorOfTheSacredBeasts - }); - AI.SelectNextCard(new[] + if (Duel.Player != 1) return false; + if (Duel.CurrentChain.Count > 0 && Duel.LastChainPlayer != 1) + return false; + if (CountFaceupSpellTrap(CardId.DivineAbyssOfTheSacredBeast) < 3 + && Bot.GetSpellCount() <= 3 + && CheckRemainInDeck(CardId.DivineAbyssOfTheSacredBeast) > 0) { - CardId.HamonSacredBeastOfSinfulCatastrophe, - CardId.TheOrchestratorOfTheSacredBeasts - }); - return; + AI.SelectCard(new List{ CardId.DivineAbyssOfTheSacredBeast, + CardId.DivineAbyssOfTheSacredBeast }); + return true; + } + List targetList = GetNormalEnemyTargetList( canBeTarget: true, ignoreCurrentDestroy: true, selfType: CardType.Trap ) + .Where(c => c != null && c.Controller == 1 && c.Location == CardLocation.MonsterZone && c.IsFaceup()).ToList(); + + if (targetList.Count == 0) return false; + + AI.SelectCard(targetList); + return true; } - // Case 2: Hamon has not been used. - // Search Raviel + Martyr + Hamon. Cost is flexible, but never Uria and never the searched/core cards. - AI.SelectCard(new[] + if (Card.Location == CardLocation.Grave + && Duel.Player == 1 + && Duel.Phase == DuelPhase.End) { - CardId.RavielSacredBeastOfEndlessEternity, - CardId.MartyrOfTheSacredBeasts, - CardId.HamonSacredBeastOfSinfulCatastrophe - }); + return true; + } + + return false; + } + private bool DestructionChantActivate() + { + if (CheckWhetherNegated(true, true, CardType.Trap)) return false; - List costs = GetBestDiscardCosts(2, new int[] + if (Card.Location == CardLocation.SpellZone) { - CardId.RavielSacredBeastOfEndlessEternity, - CardId.MartyrOfTheSacredBeasts, - CardId.HamonSacredBeastOfSinfulCatastrophe, - CardId.UriaSacredBeastOfCataclysmicFire - }); + if (Duel.LastChainPlayer != 1) return false; + if (!CheckLastChainShouldNegated()) return false; + if (!HasSacredBeastInGYForDestructionChant()) return false; - if (costs.Count >= 2) - AI.SelectNextCard(costs.Take(2).ToList()); - } + if (!HasFreeMonsterZone()) return false; - private List GetFallenParadiseCostCards() - { - // Priority 1: Skyfire x3. - List skyfires = Bot.GetSpells() - .Where(c => c != null && c.IsFaceup() && c.IsCode(CardId.SkyfireOfTheSacredBeast)) - .Take(3) - .ToList(); - if (skyfires.Count >= 3) return skyfires; + int summonTarget = PickDestructionChantSummonTarget(); + if (summonTarget == 0) return false; - // Priority 2: Martyr x3 if the Martyr line was not interrupted. - List martyrs = Bot.GetMonsters() - .Where(c => c != null && c.IsFaceup() && c.IsCode(CardId.MartyrOfTheSacredBeasts) - && !DefaultCheckWhetherCardIsNegated(c) && !c.IsDisabled()) - .Take(3) - .ToList(); - if (martyrs.Count >= 3) return martyrs; + AI.SelectCard(summonTarget); + return true; + } - // Priority 3: if Martyr was stopped, use expendable monsters from hand/field, especially Fuwalos or duplicates. - List monsters = Bot.GetMonsters() - .Where(c => c != null && c.IsFaceup() && c.HasType(CardType.Monster) && !IsNeverDiscard(c.Id)) - .Concat(Bot.Hand.Where(c => c != null && c.HasType(CardType.Monster) && !IsNeverDiscard(c.Id))) - .OrderBy(c => - { - if (c.IsCode(CardId.MulcharmyFuwalos)) return 0; - if (Bot.Hand.Count(h => h != null && h.Id == c.Id) >= 2) return 1; - if (c.IsCode(CardId.ThunderKingTheLightningstrikeKaiju) && IsLikelyGoingFirst()) return 2; - if (c.IsCode(CardId.AshBlossom, CardId.MaxxC)) return 8; - if (c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe, CardId.RavielSacredBeastOfEndlessEternity)) return 20; - return 5; - }) - .Take(3) - .ToList(); - if (monsters.Count >= 3) return monsters; - - // Last resort: spare spells, but never consume Unleashing before it starts the combo if it is still in hand. - List spells = Bot.GetSpells() - .Where(c => c != null && c.IsFaceup() && c.HasType(CardType.Spell)) - .Concat(Bot.Hand.Where(c => c != null && c.HasType(CardType.Spell) - && !c.IsCode(CardId.UnleashingTheSacredBeasts))) - .Where(c => c != null && !IsNeverDiscard(c.Id)) - .OrderBy(c => c.IsCode(CardId.HeavyPolymerization) && IsLikelyGoingFirst() ? 0 : 5) - .Take(3) - .ToList(); - if (spells.Count >= 3) return spells; + if (Card.Location == CardLocation.Grave) + { + if (ShouldWaitRavielBoardWipe()) + return false; + if (Duel.Player == 1 && Duel.Phase != DuelPhase.End) + return false; - return new List(); - } + if (!CanMakePhantasmalFusion()) return false; - private int CountAccessibleSkyfireCopiesForEffect() + AI.SelectCard(CardId.PhantasmalSacredBeastsOfChaos); + return true; + } + + return false; + } + private bool HasSacredBeastInGYForDestructionChant() { - // Skyfire can place 2 copies from hand, Deck, and/or GY. - // Do not count the current face-up Skyfire that is activating its effect. - int count = CheckRemainInDeck(CardId.SkyfireOfTheSacredBeast); - count += Bot.Hand.Count(c => c != null && c.IsCode(CardId.SkyfireOfTheSacredBeast)); - count += Bot.Graveyard.Count(c => c != null && c.IsCode(CardId.SkyfireOfTheSacredBeast)); - return count; + return Bot.Graveyard.Any(c => c != null && c.HasSetcode(SetcodeSacredBeast)); } - - private int PickSkyfireRevealTarget() + private int PickDestructionChantSummonTarget() { - // Route checkpoint wants Raviel revealed here when possible, because Raviel then searches Uria. - if (Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity)) - return CardId.RavielSacredBeastOfEndlessEternity; - - if (Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe)) - return CardId.HamonSacredBeastOfSinfulCatastrophe; + if (CountLevel10MonstersOnField() >= 2 + && Bot.HasInGraveyard(CardId.MartyrOfTheSacredBeasts)) + { + return CardId.MartyrOfTheSacredBeasts; + } - if (Bot.HasInHand(CardId.UriaSacredBeastOfCataclysmicFire)) - return CardId.UriaSacredBeastOfCataclysmicFire; + int[] priority = + { + CardId.RavielSacredBeastOfEndlessEternity, + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.UriaSacredBeastOfCataclysmicFire + }; + foreach (int id in priority) + { + if (Bot.HasInMonstersZone(id, true)) continue; + if (Bot.HasInGraveyard(id)) return id; + } return 0; } - - private int PickFallenParadiseSummonTarget() + private bool CanMakePhantasmalFusion() { - // Correct route target: first Fallen Paradise summon should produce Orchestrator. - if (!Bot.HasInMonstersZone(CardId.TheOrchestratorOfTheSacredBeasts, true) - && !Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.TheOrchestratorOfTheSacredBeasts)) - && CheckRemainInDeck(CardId.TheOrchestratorOfTheSacredBeasts) > 0) - return CardId.TheOrchestratorOfTheSacredBeasts; - - if (!Bot.HasInMonstersZone(CardId.RavielSacredBeastOfEndlessEternity, true) - && (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.RavielSacredBeastOfEndlessEternity)) - || CheckRemainInDeck(CardId.RavielSacredBeastOfEndlessEternity) > 0)) - return CardId.RavielSacredBeastOfEndlessEternity; - - if (!Bot.HasInMonstersZone(CardId.HamonSacredBeastOfSinfulCatastrophe, true) - && (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe)) - || CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) > 0)) - return CardId.HamonSacredBeastOfSinfulCatastrophe; + int materialCount = Bot.GetMonsters().Count(c => c != null && c.IsFaceup() && IsPhantasmalChaosMaterial(c)) + + Bot.Hand.Count(c => c != null && IsPhantasmalChaosMaterial(c)); - return SacredBeastMonsterSearchPriority().FirstOrDefault(); + return materialCount >= 3; } - - private bool CanMakePhantasmalFusion() + private bool PhantasmalSacredBeastsOfChaosSummon() { - int fieldSacred = Bot.GetMonsters().Count(IsSacredBeastMonster); - int fieldLevel10 = CountLevel10MonstersOnField(); - int handLevel10 = Bot.Hand.Count(c => c != null && c.Level == 10); - return fieldSacred >= 3 || fieldLevel10 + handLevel10 >= 3; + return Bot.GetMonsters() + .Count(c => c != null && c.IsFaceup() && IsPhantasmalChaosMaterial(c)) >= 3; } - - private bool HasOpponentRelevantChain() + private bool IsPhantasmalChaosMaterial(ClientCard card) { - return Duel.LastChainPlayer == 1 && CheckLastChainShouldNegated(); + return card != null + && card.IsMonster() + && card.Level == 10 + && !card.IsCode(CardId.PhantasmalSacredBeastsOfChaos) + && (card.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe) + || card.IsCode(CardId.RavielSacredBeastOfEndlessEternity) + || card.IsCode(CardId.UriaSacredBeastOfCataclysmicFire) + ); } - - #endregion - - #region Hand traps / generic defense - - private bool AshBlossomActivate() + private int CountLevel10MonstersOnField() { - return HasOpponentRelevantChain(); + return Bot.GetMonsters().Count(c => c != null && c.IsFaceup() && c.Level == 10 && c.HasSetcode(SetcodeSacredBeast)); } - - private bool MaxxCActivate() + private bool HasFreeMonsterZone() { - return Duel.Player == 1; + return Bot.GetMonstersInMainZone().Count(c => c != null) < 5; } - - private bool FuwalosActivate() + private int CountFaceupMartyrOnField() { - return Duel.Player == 1 && Bot.GetMonsterCount() == 0 && Bot.GetSpellCount() == 0; + return Bot.GetMonsters().Count(c => c != null && c.IsFaceup() && c.IsCode(CardId.MartyrOfTheSacredBeasts)); } - - private bool CalledByTheGraveActivate() + private bool CardOfTheSoul_Starter_SearchHamonOrRaviel() { + if (Card.Location != CardLocation.Hand && Card.Location != CardLocation.SpellZone) return false; if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (Bot.LifePoints != 8000) return false; + + if (Card.Location == CardLocation.Hand) + { + SelectSTPlace(null, true); + } + bool hasHamon = Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe); + bool hasRaviel = Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity); - ClientCard last = Util.GetLastChainCard(); - if (last != null && last.Controller == 1 && last.Location == CardLocation.Grave) + if (!hasHamon && !useHamonSearchEffectAlready && CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) > 0) { - AI.SelectCard(last); + AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); return true; } - ClientCard target = Enemy.Graveyard.FirstOrDefault(c => c != null && c.IsCode(CardId.AshBlossom, CardId.MaxxC, CardId.MulcharmyFuwalos)); - if (target != null) + if ((hasHamon || useHamonSearchEffectAlready) && !hasRaviel && CheckRemainInDeck(CardId.RavielSacredBeastOfEndlessEternity) > 0) { - AI.SelectCard(target); + AI.SelectCard(CardId.RavielSacredBeastOfEndlessEternity); return true; } return false; } - - #endregion - - #region Main Deck core - effect split executors - - // ===================================================================== - // Effect-split executors - // --------------------------------------------------------------------- - // Each method below answers exactly ONE card effect / prompt family. - // Do not merge hand / field / GY effects into one method again. - // If a card still refuses to activate, log ActivateDescription for only - // that specific method and fix the string id, instead of changing route. - // ===================================================================== - private bool LightningCrash_Starter_SearchHamon() { if (Card.Location != CardLocation.Hand && Card.Location != CardLocation.SpellZone) return false; if (CheckWhetherNegated(true, true, CardType.Spell)) return false; - if (!ShouldOpenOrExtendCombo()) return false; - if (Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe)) return false; - if (CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) <= 0) return false; - - AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); - return true; - } - private bool CardOfTheSoul_Starter_SearchHamonOrRaviel() - { - if (Card.Location != CardLocation.Hand && Card.Location != CardLocation.SpellZone) return false; - if (CheckWhetherNegated(true, true, CardType.Spell)) return false; - if (!ShouldOpenOrExtendCombo()) return false; - if (GetBotLifePointsSafe() != 8000) return false; + bool hasHamon = Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe); + bool hasKaiju = Bot.HasInHand(CardId.ThunderKingTheLightningstrikeKaiju); - if (!Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe) - && CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) > 0) + if (!hasHamon && !useHamonSearchEffectAlready && CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) > 0) { AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); + SelectSTPlace(null, true); + useLightningCrash = true; return true; } - if (!Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity) - && CheckRemainInDeck(CardId.RavielSacredBeastOfEndlessEternity) > 0) + if (Enemy.GetMonsterCount() > 0 && !hasKaiju && CheckRemainInDeck(CardId.ThunderKingTheLightningstrikeKaiju) > 0) { - AI.SelectCard(CardId.RavielSacredBeastOfEndlessEternity); + AI.SelectCard(CardId.ThunderKingTheLightningstrikeKaiju); + SelectSTPlace(null, true); + useLightningCrash = true; return true; } - return false; } - private bool Hamon_Hand_SearchSpell() { if (Card.Location != CardLocation.Hand) return false; - if (!ShouldOpenOrExtendCombo()) return false; + if (useHamonSearchEffectAlready) return false; int searchTarget = PickHamonSpellSearchTarget(); if (searchTarget == 0) return false; - HamonSearchQueuedForRoute = true; - // Normal route: never discard Hamon or searched card. - // If Martyr already got stopped and Hamon is still usable, the route explicitly wants Hamon as cost. if (IsMartyrNegatedOrInterrupted()) { AI.SelectCard(searchTarget); AI.SelectNextCard(CardId.HamonSacredBeastOfSinfulCatastrophe); + useHamonSearchEffectAlready = true; return true; } - return QueueSearchThenDiscard( - searchTarget, - CardId.HamonSacredBeastOfSinfulCatastrophe, - CardId.UriaSacredBeastOfCataclysmicFire); + bool result = QueueSearchThenDiscard( searchTarget, CardId.HamonSacredBeastOfSinfulCatastrophe, CardId.UriaSacredBeastOfCataclysmicFire); + if (result) useHamonSearchEffectAlready = true; + return result; } - - private bool Raviel_Hand_SearchUria() + private int PickHamonSpellSearchTarget() { - if (Card.Location != CardLocation.Hand) return false; - if (!ShouldOpenOrExtendCombo()) return false; - if (ShouldStartFromMartyrFallback()) return false; - if (Bot.HasInHand(CardId.UriaSacredBeastOfCataclysmicFire)) return false; - if (CheckRemainInDeck(CardId.UriaSacredBeastOfCataclysmicFire) <= 0) return false; + if (!HasCardAccessible(CardId.UnleashingTheSacredBeasts) + && CheckRemainInDeck(CardId.UnleashingTheSacredBeasts) > 0) + return CardId.UnleashingTheSacredBeasts; - AI.SelectCard(CardId.UriaSacredBeastOfCataclysmicFire); - AI.SelectNextCard(CardId.RavielSacredBeastOfEndlessEternity); - return true; - } + if (!HasCardAccessible(CardId.SkyfireOfTheSacredBeast) + && CheckRemainInDeck(CardId.SkyfireOfTheSacredBeast) > 0) + return CardId.SkyfireOfTheSacredBeast; - private bool Raviel_Field_BoardWipeOnlyWithMartyr2() - { - if (Card.Location != CardLocation.MonsterZone) return false; - if (Duel.Player != 1) return false; - if (Enemy.GetMonsterCount() <= 0) return false; + if (!HasCardAccessible(CardId.FallenParadiseOfTheSacredBeasts) + && CheckRemainInDeck(CardId.FallenParadiseOfTheSacredBeasts) > 0) + return CardId.FallenParadiseOfTheSacredBeasts; - // User rule: use Raviel wipe ONLY when Martyr x2 can be tributed. - return CountFaceupMartyrOnField() >= 2; - } + if (CheckRemainInDeck(CardId.UnleashingTheSacredBeasts) > 0) + return CardId.UnleashingTheSacredBeasts; - private bool Uria_Hand_SearchDestructionChant() + if (CheckRemainInDeck(CardId.SkyfireOfTheSacredBeast) > 0) + return CardId.SkyfireOfTheSacredBeast; + + if (CheckRemainInDeck(CardId.FallenParadiseOfTheSacredBeasts) > 0) + return CardId.FallenParadiseOfTheSacredBeasts; + + return 0; + } + private bool QueueSearchThenDiscard(int searchTarget, params int[] protectedIds) { - if (Card.Location != CardLocation.Hand) return false; - if (!ShouldOpenOrExtendCombo()) return false; - if (CheckRemainInDeck(CardId.DestructionChantOfTheSacredBeast) <= 0) return false; + if (searchTarget == 0) return false; - ClientCard discard = GetBestDiscardCost(new int[] - { - CardId.UriaSacredBeastOfCataclysmicFire, - CardId.DestructionChantOfTheSacredBeast - }); + ClientCard discard = GetBestDiscardCost(protectedIds.Concat(new int[] { searchTarget })); if (discard == null) return false; - AI.SelectCard(CardId.DestructionChantOfTheSacredBeast); + AI.SelectCard(searchTarget); AI.SelectNextCard(discard); return true; } + private ClientCard GetBestDiscardCost(IEnumerable protectedIds = null, bool preferHamon = false, bool preferOrchestrator = false) + { + HashSet protectedSet = protectedIds == null + ? new HashSet() + : new HashSet(protectedIds); - private bool Uria_Field_DestroyFaceupST() + return Bot.Hand + .Where(c => c != null) + .OrderBy(c => DiscardScore(c, protectedSet, preferHamon, preferOrchestrator)) + .FirstOrDefault(c => DiscardScore(c, protectedSet, preferHamon, preferOrchestrator) < 9999); + } + private int DiscardScore(ClientCard card, HashSet protectedIds, bool preferHamon = false, bool preferOrchestrator = false) { - if (Card.Location != CardLocation.MonsterZone) return false; - ClientCard target = Enemy.GetSpells().FirstOrDefault(c => c != null && c.IsFaceup()); - if (target == null) return false; + if (card == null) return 9999; + if (IsNeverDiscard(card.Id)) return 9999; + if (protectedIds != null && protectedIds.Contains(card.Id)) return 9999; - AI.SelectCard(target); - return true; + if (preferHamon && card.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe)) return -90; + if (preferOrchestrator && card.IsCode(CardId.TheOrchestratorOfTheSacredBeasts)) return -100; + + bool duplicate = Bot.Hand.Count(c => c != null && c.Id == card.Id) >= 2; + if (duplicate) return 0; + + if (card.IsCode(CardId.DivineAbyssOfTheSacredBeast)) return 1; // brick in hand + if (card.IsCode(CardId.CardOfTheSoul) && Bot.LifePoints != 8000) return 2; + if (card.IsCode(CardId.HeavyPolymerization) && Enemy.GetMonsterCount() == 0) return 3; + if (card.IsCode(CardId.ThunderKingTheLightningstrikeKaiju) && Enemy.GetMonsterCount() == 0) return 4; + if (card.IsCode(CardId.MulcharmyFuwalos) && Duel.Player == 0) return 5; + if (card.IsCode(CardId.SkyfireOfTheSacredBeast) && CountFaceupSpellTrap(CardId.SkyfireOfTheSacredBeast) >= 2) return 6; + if (card.IsCode(CardId.LightningCrash) && useLightningCrash) return 7; + if (card.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe, CardId.RavielSacredBeastOfEndlessEternity)) return 50; + if (card.IsCode(CardId.AshBlossom, CardId.MaxxC, CardId.CalledByTheGrave)) return 70; + return 20; } + private bool IsNeverDiscard(int id) + { + if (id == CardId.UriaSacredBeastOfCataclysmicFire) return true; + return id == CardId.PhantasmalSacredBeastsOfChaos + || id == CardId.ThunderDragonColossus + || id == CardId.VarudrasTheFinalBringer + || id == CardId.SPLittleKnight + || id == CardId.SuperdreadnoughtRailCannonGustavRocket + || id == CardId.SuperdreadnoughtRailCannonGustavMax; + } private bool Unleashing_Main_Search3Discard2() { if (Card.Location != CardLocation.Hand && Card.Location != CardLocation.SpellZone) return false; if (CheckWhetherNegated(true, true, CardType.Spell)) return false; - if (!ShouldOpenOrExtendCombo()) return false; - QueueUnleashingSearchAndCost(); + resolvingUnleashing = true; + resolvingUnleashingHamonLine = useHamonSearchEffectAlready; + return true; } - - private bool Unleashing_GY_RecoverySearch() + private bool Unleashing_GY_Recovery() { if (Card.Location != CardLocation.Grave) return false; - if (!IsBoardWipedRecoveryState()) return false; - int target = SacredBeastMonsterSearchPriority().FirstOrDefault(id => CheckRemainInDeck(id) > 0); - if (target == 0) return false; + if (!Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe) + && CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) > 0 + && CheckRemainInDeck(CardId.UnleashingTheSacredBeasts) > 0) + { + AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); + return true; + } - AI.SelectCard(target); - return true; - } + foreach (int id in SacredBeastMonsterSearchPriority()) + { + if (Bot.HasInHand(id)) continue; + + AI.SelectCard(id); + return true; + } - private bool Martyr_OnSummon_PlaceSkyfireOnly() + return false; + } + private int[] SacredBeastMonsterSearchPriority() + { + return new int[] + { + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.RavielSacredBeastOfEndlessEternity, + CardId.UriaSacredBeastOfCataclysmicFire + }; + } + + private bool MartyrSummon() + { + if ((Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe) || + Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity) || + Bot.HasInHand(CardId.UriaSacredBeastOfCataclysmicFire)) + && !Bot.HasInMonstersZone(CardId.MartyrOfTheSacredBeasts) + && Bot.GetMonstersInMainZone().Count <= 2) + { + normalSummon = true; + return true; + } + return false; + } + private bool Uria_Field_DestroyST() { if (Card.Location != CardLocation.MonsterZone) return false; - if (CheckWhetherNegated()) return false; + if (CheckWhetherNegated(true, false, CardType.Monster)) return false; + if (Duel.CurrentChain.Count > 0 && Duel.LastChainPlayer != 1) return false; + + List targets = Enemy.GetSpells() + .Where(c => c != null + && CheckCanBeTargeted(c, true, CardType.Monster) + && !currentDestroyCardList.Contains(c) + && !notToDestroySpellTrap.Contains(c.Id)) + .ToList(); - // Effect 0: on-summon place/search S/T. Correct route says Skyfire ONLY. - if (!(ActivateDescription == -1 || ActivateDescription == Util.GetStringId(CardId.MartyrOfTheSacredBeasts, 0))) - return false; + if (targets.Count == 0) return false; - if (Bot.GetSpellCount() >= 5) return false; - if (CheckRemainInDeck(CardId.SkyfireOfTheSacredBeast) <= 0) return false; + ClientCard target = + targets.FirstOrDefault(c => c.IsFaceup() && c.IsFloodgate()) + ?? targets.FirstOrDefault(c => c.IsFaceup() + && c.HasType(CardType.Continuous | CardType.Field | CardType.Pendulum | CardType.Equip)) + ?? targets.FirstOrDefault(c => enemyPlaceThisTurn.Contains(c)) + ?? targets.FirstOrDefault(c => c.IsFacedown()) + ?? targets.FirstOrDefault(); + + if (target == null) return false; - AI.SelectCard(CardId.SkyfireOfTheSacredBeast); + AI.SelectCard(target); + currentDestroyCardList.Add(target); return true; } - - private bool Martyr_Field_SummonTwoMartyr() + private bool Martyr_OnSummon_Place() { if (Card.Location != CardLocation.MonsterZone) return false; if (CheckWhetherNegated()) return false; - // Effect 1: summon two Martyrs. - if (ActivateDescription != Util.GetStringId(CardId.MartyrOfTheSacredBeasts, 1)) return false; - if (!HasSacredBeastOnField()) return false; - if (!HasTwoFreeMonsterZones()) return false; - if (CheckRemainInDeck(CardId.MartyrOfTheSacredBeasts) + Bot.Graveyard.Count(c => c != null && c.IsCode(CardId.MartyrOfTheSacredBeasts)) < 2) return false; + if (!(ActivateDescription == -1 || ActivateDescription == Util.GetStringId(CardId.MartyrOfTheSacredBeasts, 0))) + return false; - // Let system pick the two Martyrs if script only asks yes/no; otherwise preselect Martyr ID twice. - AI.SelectCard(new[] { CardId.MartyrOfTheSacredBeasts, CardId.MartyrOfTheSacredBeasts }); - return true; - } + if (Bot.GetSpellCount() >= 3) return false; - private bool Martyr_GY_EndPhaseRecovery() - { - if (Card.Location != CardLocation.Grave) return false; - if (Duel.Player != 1 || Duel.Phase != DuelPhase.End) return false; - return Bot.Graveyard.Any(IsSacredBeastMonster); - } + int target = 0; - private bool Skyfire_Hand_ActivateCardOnly() - { - if (Card.Location != CardLocation.Hand) return false; - if (CheckWhetherNegated(true, true, CardType.Spell)) return false; - if (ShouldStartFromMartyrFallback()) return false; - if (Bot.HasInSpellZone(CardId.SkyfireOfTheSacredBeast, true)) return false; - if (PickSkyfireRevealTarget() == 0) return false; + if (Duel.Player == 1) + { + if (!Bot.HasInSpellZone(CardId.DivineAbyssOfTheSacredBeast) + && CheckRemainInDeck(CardId.DivineAbyssOfTheSacredBeast) > 0) + { + target = CardId.DivineAbyssOfTheSacredBeast; + } + } + if (target == 0 && Duel.Player == 0) + { + if (CheckRemainInDeck(CardId.SkyfireOfTheSacredBeast) > 0) + target = CardId.SkyfireOfTheSacredBeast; + } - // Only activate/set the Continuous Spell. The field effect is a different executor. - SelectSTPlace(null, true); + if (target == 0) return false; + + AI.SelectCard(target); return true; } - private bool Skyfire_Field_Place2RevealPlaceParadise() { + if (!Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe) && + !Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity) && + !Bot.HasInHand(CardId.UriaSacredBeastOfCataclysmicFire)) return false; if (Card.Location != CardLocation.SpellZone) return false; if (CheckWhetherNegated(true, true, CardType.Spell)) return false; - if (IsSacredEndBoardReady()) return false; if (Duel.Player != 0) return false; if (Duel.Phase != DuelPhase.Main1 && Duel.Phase != DuelPhase.Main2) return false; - int revealTarget = PickSkyfireRevealTarget(); if (revealTarget == 0) return false; if (CountAccessibleSkyfireCopiesForEffect() < 2) return false; - // Prompt 1: choose/place 2 Skyfire. AI.SelectCard(new[] { CardId.SkyfireOfTheSacredBeast, CardId.SkyfireOfTheSacredBeast }); + AI.SelectNextCard(revealTarget); + + if (CheckRemainInDeck(CardId.FallenParadiseOfTheSacredBeasts) > 0) + AI.SelectNextCard(CardId.FallenParadiseOfTheSacredBeasts); + + return true; + } + private int CountAccessibleSkyfireCopiesForEffect() + { + int count = CheckRemainInDeck(CardId.SkyfireOfTheSacredBeast); + count += Bot.Hand.Count(c => c != null && c.IsCode(CardId.SkyfireOfTheSacredBeast)); + count += Bot.Graveyard.Count(c => c != null && c.IsCode(CardId.SkyfireOfTheSacredBeast)); + return count; + } + private int PickSkyfireRevealTarget() + { + if (Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity)) + return CardId.RavielSacredBeastOfEndlessEternity; + + if (Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe)) + return CardId.HamonSacredBeastOfSinfulCatastrophe; + + if (Bot.HasInHand(CardId.UriaSacredBeastOfCataclysmicFire)) + return CardId.UriaSacredBeastOfCataclysmicFire; + + return 0; + } + private bool Skyfire_GY_EndPhaseRecovery() + { + if (Card.Location != CardLocation.Grave) return false; + if (Duel.Player != 1 || Duel.Phase != DuelPhase.End) return false; + SelectSTPlace(null, true); + return true; + } + private bool FallenParadise_Field_Draw2AfterSetup() + { + if (Card.Location != CardLocation.SpellZone) return false; + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + + int drawDesc = Util.GetStringId(CardId.FallenParadiseOfTheSacredBeasts, 1); + if (ActivateDescription != drawDesc) return false; + + if (!Bot.HasInMonstersZone(CardId.HamonSacredBeastOfSinfulCatastrophe) + && !Bot.HasInMonstersZone(CardId.RavielSacredBeastOfEndlessEternity) + && !Bot.HasInMonstersZone(CardId.UriaSacredBeastOfCataclysmicFire)) + { + return false; + } + + return true; + } + private bool ThunderDragonColossusSummon() + { + if (!useHamonSearchEffectAlready) return false; + if (!useOchestFromField) return false; + + ClientCard orchest = Bot.GetMonsters() + .FirstOrDefault(c => c != null + && c.IsFaceup() + && c.IsCode(CardId.TheOrchestratorOfTheSacredBeasts)); + + if (orchest == null) return false; + + resolvingColossusSummon = true; + + AI.SelectMaterials(new List { orchest }); + return true; + } + private bool Raviel_Hand_SearchUria() + { + if (useRaviel) return false; + if (Card.Location != CardLocation.Hand) return false; + if (!Bot.HasInHand(CardId.UriaSacredBeastOfCataclysmicFire) && + CheckRemainInDeck(CardId.UriaSacredBeastOfCataclysmicFire) > 0 && + useHamonSearchEffectAlready + ) + { + AI.SelectCard(CardId.UriaSacredBeastOfCataclysmicFire); + AI.SelectNextCard(CardId.RavielSacredBeastOfEndlessEternity); + useRaviel = true; + return true; + } + else if (!Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe) && + CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) > 0 && + !useHamonSearchEffectAlready + ) + { + AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); + AI.SelectNextCard(CardId.RavielSacredBeastOfEndlessEternity); + useRaviel = true; + return true; + } + else if ( useHamonSearchEffectAlready && + !normalSummon && + !Bot.HasInHand(CardId.MartyrOfTheSacredBeasts) && + (CheckRemainInDeck(CardId.MartyrOfTheSacredBeasts) > 0) && + HasOtherSacredBeastInHandForRavielCost()) + { + AI.SelectCard(CardId.MartyrOfTheSacredBeasts); + AI.SelectNextCard(CardId.RavielSacredBeastOfEndlessEternity); + useRaviel = true; + return true; + } + else + { + return false; + } + } + private bool HasOtherSacredBeastInHandForRavielCost() + { + return Bot.Hand.Any(c => c != null + && !ReferenceEquals(c, Card) + && ( + c.IsCode(CardId.RavielSacredBeastOfEndlessEternity) + || c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe) + || c.IsCode(CardId.UriaSacredBeastOfCataclysmicFire) + )); + } + private bool Raviel_Field_BoardWipeOnlyWithMartyr2() + { + if (Card.Location != CardLocation.MonsterZone) return false; + if (CheckWhetherNegated(true, false, CardType.Monster)) return false; - // Prompt 2: reveal LV10 Sacred Beast in hand. - AI.SelectNextCard(revealTarget); + int wipeDesc = Util.GetStringId(CardId.RavielSacredBeastOfEndlessEternity, 1); + if (ActivateDescription != wipeDesc && ActivateDescription != -1) return false; - // Prompt 3: place Fallen Paradise. - if (CheckRemainInDeck(CardId.FallenParadiseOfTheSacredBeasts) > 0) - AI.SelectNextCard(CardId.FallenParadiseOfTheSacredBeasts); + if (Enemy.GetMonsterCount() <= 0) return false; + if (CountFaceupMartyrOnField() < 2) return false; + + resolvingRavielBoardWipe = true; return true; } - - private bool Skyfire_GY_EndPhaseRecovery() + private bool Martyr_GY_EndPhaseRecovery() { if (Card.Location != CardLocation.Grave) return false; if (Duel.Player != 1 || Duel.Phase != DuelPhase.End) return false; return true; } - - private bool FallenParadise_Field_SummonByCost3() + private bool Uria_Hand_SearchDestructionChant() { - if (Card.Location != CardLocation.SpellZone) return false; - if (CheckWhetherNegated(true, true, CardType.Spell)) return false; - if (ShouldStartFromMartyrFallback()) return false; - if (!HasFreeMonsterZone()) return false; + if (Card.Location != CardLocation.Hand) return false; + if (CheckRemainInDeck(CardId.DestructionChantOfTheSacredBeast) <= 0) return false; - List costCards = GetFallenParadiseCostCards(); - int target = PickFallenParadiseSummonTarget(); - if (costCards.Count < 3 || target == 0) return false; + ClientCard discard = GetBestDiscardCost(new int[] + { + CardId.UriaSacredBeastOfCataclysmicFire, + CardId.DestructionChantOfTheSacredBeast + }); + if (discard == null) return false; - AI.SelectCard(costCards.Take(3).ToList()); - AI.SelectNextCard(target); + AI.SelectCard(CardId.DestructionChantOfTheSacredBeast); + AI.SelectNextCard(discard); return true; } - - private bool FallenParadise_Field_Draw2AfterSetup() + private bool Martyr_Field_SummonTwoMartyr() { - if (Card.Location != CardLocation.SpellZone) return false; - if (CheckWhetherNegated(true, true, CardType.Spell)) return false; - if (!HasSacredBeastOnField()) return false; - if (CountLevel10MonstersOnField() < 1) return false; + if (Card.Location != CardLocation.MonsterZone) return false; + if (CheckWhetherNegated()) return false; - // Draw should happen after setup, not before summon route. - if (!(Bot.HasInMonstersZone(CardId.ThunderDragonColossus, true) - || Bot.HasInMonstersZone(CardId.SPLittleKnight, true) - || CountSacredBeastsOnField() >= 2)) return false; + if (ActivateDescription != Util.GetStringId(CardId.MartyrOfTheSacredBeasts, 1)) return false; + if (Bot.GetMonstersInMainZone().Count(c => c != null) >= 3) return false; + if (CheckRemainInDeck(CardId.MartyrOfTheSacredBeasts) + Bot.Graveyard.Count(c => c != null && c.IsCode(CardId.MartyrOfTheSacredBeasts)) < 2) return false; + AI.SelectCard(new[] { CardId.MartyrOfTheSacredBeasts, CardId.MartyrOfTheSacredBeasts }); + Martyrx3 = true; return true; } - - private bool Orchestrator_Hand_SummonSacredBeast() + private bool Skyfire_Hand_ActivateCardOnly() { if (Card.Location != CardLocation.Hand) return false; - if (CheckWhetherNegated()) return false; - if (!HasFreeMonsterZone()) return false; - if (IsSacredEndBoardReady()) return false; - if (!HasSacredBeastInHand()) return false; - - int target = Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity) - ? CardId.RavielSacredBeastOfEndlessEternity - : SacredBeastMonsterPriority().FirstOrDefault(id => Bot.HasInHand(id)); - if (target == 0) return false; - - ClientCard discard = GetBestDiscardCost(new int[] { CardId.UriaSacredBeastOfCataclysmicFire, target }); - if (discard == null) return false; + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (Bot.HasInSpellZone(CardId.SkyfireOfTheSacredBeast, true)) return false; + if (PickSkyfireRevealTarget() == 0) return false; - AI.SelectCard(target); - AI.SelectNextCard(discard); + SelectSTPlace(null, true); return true; } - private bool Orchestrator_Field_ReviveRouteTarget() { if (Card.Location != CardLocation.MonsterZone) return false; if (CheckWhetherNegated()) return false; if (!HasFreeMonsterZone()) return false; - if (IsSacredEndBoardReady()) return false; int target = 0; - if (IsMartyrNegatedOrInterrupted() - && Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.MartyrOfTheSacredBeasts))) + if (IsMartyrNegatedOrInterrupted() && Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.MartyrOfTheSacredBeasts))) target = CardId.MartyrOfTheSacredBeasts; else if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.RavielSacredBeastOfEndlessEternity))) target = CardId.RavielSacredBeastOfEndlessEternity; else if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe))) target = CardId.HamonSacredBeastOfSinfulCatastrophe; + else if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.UriaSacredBeastOfCataclysmicFire))) + target = CardId.UriaSacredBeastOfCataclysmicFire; else if (HasSacredBeastInHand()) target = SacredBeastMonsterPriority().FirstOrDefault(id => Bot.HasInHand(id)); @@ -1154,738 +2239,522 @@ private bool Orchestrator_Field_ReviveRouteTarget() ClientCard discard = GetBestDiscardCost(new int[] { CardId.UriaSacredBeastOfCataclysmicFire, target }); if (discard == null) return false; - AI.SelectCard(target); - AI.SelectNextCard(discard); + AI.SelectCard(discard); + AI.SelectNextCard(target); + useOchestFromField = true; return true; } - - private bool Orchestrator_GY_ReviveLevel10() + private bool HasSacredBeastInHand() { - if (Card.Location != CardLocation.Grave) return false; - if (!HasFreeMonsterZone()) return false; - if (IsSacredEndBoardReady()) return false; - - if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.RavielSacredBeastOfEndlessEternity))) - { - AI.SelectCard(CardId.RavielSacredBeastOfEndlessEternity); - return true; - } - - if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe))) - { - AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); - return true; - } - - return false; + return Bot.Hand.Any(IsSacredBeastMonster); } - - #endregion - - #region Main Deck core - - private bool UnleashingTheSacredBeastsActivate() + private bool IsSacredBeastMonster(ClientCard card) { - if (CheckWhetherNegated(true, true, CardType.Spell)) return false; - if (!ShouldOpenOrExtendCombo()) return false; - - if (Card.Location == CardLocation.Hand || Card.Location == CardLocation.SpellZone) - { - // Core combo starts here. Search 3 non-duplicate pieces, with route-specific cost. - QueueUnleashingSearchAndCost(); - return true; - } - - if (Card.Location == CardLocation.Grave) - { - // Recovery: only after board wipe / resource loss. - if (!IsBoardWipedRecoveryState()) return false; - int target = SacredBeastMonsterSearchPriority().FirstOrDefault(id => CheckRemainInDeck(id) > 0); - if (target == 0) return false; - AI.SelectCard(target); - return true; - } - - return false; + return card != null && card.HasSetcode(SetcodeSacredBeast); } - - - private bool LightningCrashActivate() + private int[] SacredBeastMonsterPriority() { - if (CheckWhetherNegated(true, true, CardType.Spell)) return false; - if (!ShouldOpenOrExtendCombo()) return false; - - // Starter only: Thunder Clap / Lightning Crash opens by finding Hamon. - if (!Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe) - && CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) > 0) + return new int[] { - AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); - return true; - } - - return false; - } - - - private bool ThunderclapActivate() - { - return LightningCrashActivate(); + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.RavielSacredBeastOfEndlessEternity, + CardId.UriaSacredBeastOfCataclysmicFire, + CardId.MartyrOfTheSacredBeasts + }; } - - private bool CardOfTheSoulActivate() + private bool Orchestrator_GY_ReviveLevel10() { - if (CheckWhetherNegated(true, true, CardType.Spell)) return false; - if (!ShouldOpenOrExtendCombo()) return false; - - // Card of the Soul is starter/extender only when LP is exactly 8000. - // After LP changes, treat it as discard/cost fodder instead. - if (GetBotLifePointsSafe() != 8000) return false; - - if (!Bot.HasInHand(CardId.HamonSacredBeastOfSinfulCatastrophe) - && CheckRemainInDeck(CardId.HamonSacredBeastOfSinfulCatastrophe) > 0) - { - AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); - return true; - } - - if (!Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity) - && CheckRemainInDeck(CardId.RavielSacredBeastOfEndlessEternity) > 0) - { - AI.SelectCard(CardId.RavielSacredBeastOfEndlessEternity); - return true; - } - - return false; - } - + if (Card.Location != CardLocation.Grave) return false; + if (!HasFreeMonsterZone()) return false; - private bool HamonActivate() - { - if (Card.Location == CardLocation.Hand) + if (useHamonSearchEffectAlready) { - if (!ShouldOpenOrExtendCombo()) return false; - - int searchTarget = PickHamonSpellSearchTarget(); - if (searchTarget == 0) return false; - // Normal Hamon line: do not discard Hamon or the searched card. - // Exception handled naturally later: if Martyr gets stopped and Hamon has not been used, Hamon may search Skyfire and discard Hamon. - bool martyrStoppedFallback = IsMartyrNegatedOrInterrupted() - && !Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe)); - - if (martyrStoppedFallback) + if (!Bot.HasInMonstersZone(CardId.RavielSacredBeastOfEndlessEternity) && Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.RavielSacredBeastOfEndlessEternity))) { - HamonSearchQueuedForRoute = true; - AI.SelectCard(searchTarget); - AI.SelectNextCard(CardId.HamonSacredBeastOfSinfulCatastrophe); + AI.SelectCard(CardId.RavielSacredBeastOfEndlessEternity); + useOchestFromGY = true; return true; } - HamonSearchQueuedForRoute = true; - return QueueSearchThenDiscard( - searchTarget, - CardId.HamonSacredBeastOfSinfulCatastrophe, - CardId.UriaSacredBeastOfCataclysmicFire); - } - - if (Card.Location == CardLocation.MonsterZone) - { - return false; - } - - return false; - } - - - private bool RavielActivate() - { - if (Card.Location == CardLocation.Hand) - { - if (!ShouldOpenOrExtendCombo()) return false; - if (ShouldStartFromMartyrFallback()) return false; - - // Correct checkpoint: after Skyfire/Fallen setup, Raviel searches Uria and discards Raviel. - if (!Bot.HasInHand(CardId.UriaSacredBeastOfCataclysmicFire) - && CheckRemainInDeck(CardId.UriaSacredBeastOfCataclysmicFire) > 0) + if (!Bot.HasInMonstersZone(CardId.HamonSacredBeastOfSinfulCatastrophe) && Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe))) { - AI.SelectCard(CardId.UriaSacredBeastOfCataclysmicFire); - AI.SelectNextCard(CardId.RavielSacredBeastOfEndlessEternity); + AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); + useOchestFromGY = true; return true; } - - // Fallback only if Uria is already available: find Orchestrator/Martyr/Hamon, but still never discard Uria. - int target = new int[] - { - CardId.TheOrchestratorOfTheSacredBeasts, - CardId.MartyrOfTheSacredBeasts, - CardId.HamonSacredBeastOfSinfulCatastrophe - }.FirstOrDefault(id => !Bot.HasInHand(id) && CheckRemainInDeck(id) > 0); - - if (target == 0) return false; - return QueueSearchThenDiscard(target, CardId.UriaSacredBeastOfCataclysmicFire, target); - } - - if (Card.Location == CardLocation.MonsterZone) - { - // Important: Raviel board wipe only if Martyr x2 can be tributed. Otherwise never fire it. - if (Duel.Player != 1) return false; - if (Enemy.GetMonsterCount() <= 0) return false; - return CountFaceupMartyrOnField() >= 2; } return false; } - - - private bool UriaActivate() + private bool HeavyPolymerizationActivate() { - if (Card.Location == CardLocation.Hand) - { - if (!ShouldOpenOrExtendCombo()) return false; - if (CheckRemainInDeck(CardId.DestructionChantOfTheSacredBeast) <= 0) return false; - - ClientCard discard = GetBestDiscardCost(new int[] - { - CardId.UriaSacredBeastOfCataclysmicFire, - CardId.DestructionChantOfTheSacredBeast - }); - if (discard == null) return false; - - AI.SelectCard(CardId.DestructionChantOfTheSacredBeast); - AI.SelectNextCard(discard); - return true; - } - - if (Card.Location == CardLocation.MonsterZone) - { - ClientCard target = Enemy.GetSpells().Where(c => c != null && c.IsFaceup()).FirstOrDefault(); - if (target != null) - { - AI.SelectCard(target); - return true; - } - } - - return false; - } + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + int enemyMonsterCount = Enemy.GetMonsterCount(); - private bool OrchestratorActivate() - { - if (CheckWhetherNegated()) return false; - if (!HasFreeMonsterZone()) return false; - if (IsSacredEndBoardReady()) return false; + if (enemyMonsterCount < 2) return false; if (Card.Location == CardLocation.Hand) { - if (!HasSacredBeastInHand()) return false; - - int target = Bot.HasInHand(CardId.RavielSacredBeastOfEndlessEternity) - ? CardId.RavielSacredBeastOfEndlessEternity - : SacredBeastMonsterPriority().FirstOrDefault(id => Bot.HasInHand(id)); - if (target == 0) return false; - - ClientCard discard = GetBestDiscardCost(new int[] { CardId.UriaSacredBeastOfCataclysmicFire, target }); - if (discard == null) return false; - - AI.SelectCard(target); - AI.SelectNextCard(discard); - return true; - } - - if (Card.Location == CardLocation.MonsterZone) - { - // If Martyr was not stopped, revive Raviel first. - // If Martyr was stopped, revive Martyr to continue the line. - int target = 0; - if (IsMartyrNegatedOrInterrupted() - && Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.MartyrOfTheSacredBeasts))) - target = CardId.MartyrOfTheSacredBeasts; - else if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.RavielSacredBeastOfEndlessEternity))) - target = CardId.RavielSacredBeastOfEndlessEternity; - else if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe))) - target = CardId.HamonSacredBeastOfSinfulCatastrophe; - else if (HasSacredBeastInHand()) - target = SacredBeastMonsterPriority().FirstOrDefault(id => Bot.HasInHand(id)); - - if (target == 0) return false; - - ClientCard discard = GetBestDiscardCost(new int[] { CardId.UriaSacredBeastOfCataclysmicFire, target }); - if (discard == null) return false; - - AI.SelectCard(target); - AI.SelectNextCard(discard); - return true; + SelectSTPlace(null, true); } - if (Card.Location == CardLocation.Grave) - { - // After Colossus / Link line: revive Raviel first, then Hamon. - if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.RavielSacredBeastOfEndlessEternity))) - { - AI.SelectCard(CardId.RavielSacredBeastOfEndlessEternity); - return true; - } + int zeroExtraCount = CountInExtraDeck(CardId.SuperVehicroidMobileBase) + CountInExtraDeck(CardId.SaintAzamina); - if (Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe))) - { - AI.SelectCard(CardId.HamonSacredBeastOfSinfulCatastrophe); - return true; - } - } + if (zeroExtraCount < 2) return false; - return false; - } + if (CountInExtraDeck(CardId.PhantasmalSacredBeastsOfChaos) <= 0) + return false; + resolvingHeavyPolymerization = true; + heavyPolyMaterialPicked = 0; + heavyPolyMaterialNeed = 3; - private bool MartyrActivate() + AI.SelectCard(CardId.PhantasmalSacredBeastsOfChaos); + return true; + } + private int CountInExtraDeck(int id) { - if (CheckWhetherNegated()) return false; - - if (Card.Location == CardLocation.MonsterZone) - { - // On summon: Skyfire only. Do not place Fallen/Divine/Chant here. - if (ActivateDescription == -1 || ActivateDescription == Util.GetStringId(CardId.MartyrOfTheSacredBeasts, 0)) - { - if (Bot.GetSpellCount() >= 5) return false; - if (CheckRemainInDeck(CardId.SkyfireOfTheSacredBeast) <= 0) return false; - AI.SelectCard(CardId.SkyfireOfTheSacredBeast); - return true; - } - - // Combo step 8: if Martyr remains on field, summon two more Martyrs. - if (ActivateDescription == Util.GetStringId(CardId.MartyrOfTheSacredBeasts, 1)) - { - return HasSacredBeastOnField() && HasTwoFreeMonsterZones(); - } - } - - if (Card.Location == CardLocation.Grave) - { - return Duel.Player == 1 && Duel.Phase == DuelPhase.End && Bot.Graveyard.Any(IsSacredBeastMonster); - } - - return false; + return Bot.ExtraDeck.Count(c => c != null && c.IsCode(id)); } + private int HeavyPolyOwnMaterialScore(ClientCard card) + { + if (card == null) return 9999; + if (card.IsCode(CardId.UriaSacredBeastOfCataclysmicFire)) return 100; + if (card.IsCode(CardId.RavielSacredBeastOfEndlessEternity)) return 10; + if (card.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe)) return 20; - private bool SkyfireActivate() + return 50; + } + private bool FallenParadise_Field_SummonByCost3() { + if (paradise <= 0) return false; + if (Card.Location != CardLocation.SpellZone) return false; if (CheckWhetherNegated(true, true, CardType.Spell)) return false; - if (IsSacredEndBoardReady()) return false; - - if (Card.Location == CardLocation.SpellZone) - { - // Skyfire effect is not one flat selection. - // Prompt 1: place 2 Skyfire. - // Prompt 2: reveal 1 Level 10 Sacred Beast in hand. - // Prompt 3: place Fallen Paradise from Deck. - if (Duel.Player != 0) return false; - if (Duel.Phase != DuelPhase.Main1 && Duel.Phase != DuelPhase.Main2) return false; - - int revealTarget = PickSkyfireRevealTarget(); - if (revealTarget == 0) return false; - - if (CountAccessibleSkyfireCopiesForEffect() < 2) return false; - AI.SelectCard(new[] - { - CardId.SkyfireOfTheSacredBeast, - CardId.SkyfireOfTheSacredBeast - }); - AI.SelectNextCard(revealTarget); - - if (CheckRemainInDeck(CardId.FallenParadiseOfTheSacredBeasts) > 0) - AI.SelectNextCard(CardId.FallenParadiseOfTheSacredBeasts); - - return true; - } + int summonDesc = Util.GetStringId(CardId.FallenParadiseOfTheSacredBeasts, 0); + if (ActivateDescription != summonDesc) return false; - if (Card.Location == CardLocation.Hand) - { - if (ShouldStartFromMartyrFallback()) return false; + if (!HasFreeMonsterZone()) return false; - // Activating Skyfire from hand only places the Continuous Spell on the field. - // The ignition effect will be handled by the SpellZone branch on the next action window. - if (Bot.HasInSpellZone(CardId.SkyfireOfTheSacredBeast, true)) return false; - if (PickSkyfireRevealTarget() == 0) return false; + int costCode = PickFallenParadiseCostCode(); + if (costCode == 0) return false; - SelectSTPlace(null, true); - return true; - } + int target = PickFallenParadiseSummonTarget(); + if (target == 0) return false; - if (Card.Location == CardLocation.Grave && Duel.Player == 1 && Duel.Phase == DuelPhase.End) - { - return true; - } + resolvingFallenParadise = true; + fallenParadiseTarget = target; + fallenParadiseCostCode = costCode; - return false; + paradise--; + return true; } + private int PickFallenParadiseSummonTarget() + { + if (!Bot.HasInMonstersZone(CardId.TheOrchestratorOfTheSacredBeasts)) + return CardId.TheOrchestratorOfTheSacredBeasts; + if (!Bot.HasInMonstersZone(CardId.RavielSacredBeastOfEndlessEternity)) + return CardId.RavielSacredBeastOfEndlessEternity; - private bool FallenParadiseActivate() - { - if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (!Bot.HasInMonstersZone(CardId.HamonSacredBeastOfSinfulCatastrophe)) + return CardId.HamonSacredBeastOfSinfulCatastrophe; - // Important bad-hand rule from user note: - // Do not activate Fallen Paradise from hand as an opener. It must be placed by Skyfire/Martyr line first. - if (Card.Location == CardLocation.Hand) return false; - if (Card.Location != CardLocation.SpellZone) return false; - if (ShouldStartFromMartyrFallback()) return false; + return SacredBeastMonsterSearchPriority().FirstOrDefault(); + } + private List PickCardsByIdPriority(IList cards, IEnumerable ids, int count) + { + List result = new List(); - // Summon effect comes before draw in the corrected route. - if (HasFreeMonsterZone()) + foreach (int id in ids) { - List costCards = GetFallenParadiseCostCards(); - int target = PickFallenParadiseSummonTarget(); - - if (costCards.Count >= 3 && target != 0) + foreach (ClientCard card in cards.Where(c => c != null && c.IsCode(id))) { - // Cost priority: Skyfire x3 -> Martyr x3 -> expendable monsters. Never pay Uria. - AI.SelectCard(costCards.Take(3).ToList()); - AI.SelectNextCard(target); - return true; + if (result.Contains(card)) continue; + + result.Add(card); + if (result.Count >= count) + return result; } } - // Draw 2 only after the combo has put a Level 10 / revived body on board. - if (HasSacredBeastOnField() - && CountLevel10MonstersOnField() >= 1 - && (Bot.HasInMonstersZone(CardId.ThunderDragonColossus, true) - || Bot.HasInMonstersZone(CardId.SPLittleKnight, true) - || CountSacredBeastsOnField() >= 2)) + return result; + } + private int PickFallenParadiseCostCode() + { + if (Bot.GetSpells().Count(c => + c != null + && c.IsFaceup() + && c.IsCode(CardId.SkyfireOfTheSacredBeast)) >= 3) { - return true; + return CardId.SkyfireOfTheSacredBeast; + } + if (Bot.GetMonsters().Count(c => + c != null + && c.IsFaceup() + && c.IsCode(CardId.MartyrOfTheSacredBeasts) + && !DefaultCheckWhetherCardIsNegated(c) + && !c.IsDisabled()) >= 3) + { + return CardId.MartyrOfTheSacredBeasts; + } + if (Bot.GetSpells().Count(c => + c != null + && c.IsFaceup() + && c.IsCode(CardId.DivineAbyssOfTheSacredBeast) + && !DefaultCheckWhetherCardIsNegated(c) + && !c.IsDisabled()) >= 3) + { + return CardId.DivineAbyssOfTheSacredBeast; } - return false; + return 0; } + private bool HasSPLittleKnightTargetNow() + { + if (GetProblematicEnemyCardList(true, selfType: CardType.Monster).Count > 0) + return true; + if (GetProblematicEnemyMonster(0, true, false, CardType.Monster) != null) + return true; - private bool DivineAbyssActivate() + return Enemy.GetMonsterCount() > 0 + || Enemy.GetSpellCount() > 0 + || Enemy.Graveyard.Count > 0; + } + private bool ShouldForceSPLittleKnightForZone() { - if (CheckWhetherNegated(true, true, CardType.Trap)) return false; + if (Duel.Turn != 1) return false; + if (Duel.Player != 0) return false; + if (!Martyrx3) return false; - if (Card.Location == CardLocation.SpellZone) - { - // Opponent turn interrupt line. First place copies, then flip enemy monster. - if (Duel.Player == 1 && CountFaceupSpellTrap(CardId.DivineAbyssOfTheSacredBeast) < 3 && Bot.GetSpellCount() < 5) - { - AI.SelectCard(CardId.DivineAbyssOfTheSacredBeast, CardId.DivineAbyssOfTheSacredBeast); - return true; - } + if (Bot.GetMonstersInMainZone().Count(c => c != null) < 5) + return false; - ClientCard target = GetBestEnemyMonster(true); - if (Duel.Player == 1 && target != null) - { - AI.SelectCard(target); - return true; - } - } + int martyrCount = Bot.GetMonstersInMainZone().Count(c => + c != null + && c.IsFaceup() + && c.IsCode(CardId.MartyrOfTheSacredBeasts)); - if (Card.Location == CardLocation.Grave && Duel.Player == 1 && Duel.Phase == DuelPhase.End) - { - return true; - } + bool hasOrchestrator = Bot.GetMonstersInMainZone().Any(c => + c != null + && c.IsFaceup() + && c.IsCode(CardId.TheOrchestratorOfTheSacredBeasts)); - return false; + int level10Count = Bot.GetMonstersInMainZone().Count(c => + c != null + && c.IsFaceup() + && c.Level == 10 + && c.HasSetcode(SetcodeSacredBeast)); + + return martyrCount >= 3 + && hasOrchestrator + && level10Count >= 1; } + private bool ShouldWaitRavielBoardWipe() + { + if (Duel.Player != 1) return false; + if (Enemy.GetMonsterCount() <= 0) return false; + ClientCard raviel = Bot.GetMonsters() + .FirstOrDefault(c => c != null + && c.IsFaceup() + && c.IsCode(CardId.RavielSacredBeastOfEndlessEternity)); - private bool DestructionChantActivate() + if (raviel == null) return false; + if (DefaultCheckWhetherCardIsNegated(raviel) || raviel.IsDisabled()) return false; + + return CountFaceupMartyrOnField() >= 2; + } + private bool LinkuribohActivate() { - if (CheckWhetherNegated(true, true, CardType.Trap)) return false; + if (CheckWhetherNegated(true, false, CardType.Monster)) return false; - if (Card.Location == CardLocation.Hand || Card.Location == CardLocation.SpellZone) + if (Card.Location == CardLocation.MonsterZone) { - // Main use is opponent turn. - if (Duel.Player != 1 && !HasOpponentRelevantChain()) return false; + if (Duel.Player != 1) return false; + if (Duel.Phase != DuelPhase.Battle) return false; - if (HasFreeMonsterZone() && (HasSacredBeastInHand() || HasSacredBeastInGrave())) - { - // If only 1 Level 10, revive another Level 10 with a different name. - // If already 2 Level 10, revive Martyr for the follow-up Divine Abyss line. - if (CountLevel10MonstersOnField() >= 2 - && Bot.Graveyard.Any(c => c != null && c.IsCode(CardId.MartyrOfTheSacredBeasts))) - { - AI.SelectCard(CardId.MartyrOfTheSacredBeasts); - return true; - } + ClientCard biggestEnemy = Enemy.GetMonsters() + .Where(c => c != null && c.IsFaceup()) + .OrderByDescending(c => c.Attack) + .FirstOrDefault(); - int target = new int[] - { - CardId.RavielSacredBeastOfEndlessEternity, - CardId.HamonSacredBeastOfSinfulCatastrophe, - CardId.UriaSacredBeastOfCataclysmicFire - }.FirstOrDefault(id => !Bot.HasInMonstersZone(id, true) - && (Bot.Graveyard.Any(c => c != null && c.IsCode(id)) || Bot.HasInHand(id))); + if (biggestEnemy == null) return false; - if (target == 0) return false; - AI.SelectCard(target); + if (biggestEnemy.Attack >= Bot.LifePoints) return true; - } - if (CountSacredBeastsOnField() >= 2 && GetBestEnemyCard(true) != null) - { - AI.SelectCard(GetBestEnemyCard(true)); + if (biggestEnemy.Attack >= 2500) return true; - } + + ClientCard bestBot = Bot.GetMonsters() + .Where(c => c != null && c.IsFaceup() && !c.HasType(CardType.Link)) + .OrderByDescending(c => c.GetDefensePower()) + .FirstOrDefault(); + + if (bestBot != null && biggestEnemy.Attack > bestBot.GetDefensePower()) + return true; + + return false; } if (Card.Location == CardLocation.Grave) { - // If Raviel can tribute Martyr x2 for board wipe, wait. Otherwise use GY fusion line. - if (Bot.HasInMonstersZone(CardId.RavielSacredBeastOfEndlessEternity, true) - && CountFaceupMartyrOnField() >= 2 - && Enemy.GetMonsterCount() > 0) - return false; + if (Duel.Player != 0) return false; + if (Duel.Phase != DuelPhase.Main1 && Duel.Phase != DuelPhase.Main2) return false; + if (!ShouldUseLinkuribohGYFallback()) return false; - if (CanMakePhantasmalFusion()) - { - AI.SelectCard(CardId.PhantasmalSacredBeastsOfChaos); - return true; - } + ClientCard martyr = Bot.GetMonsters() + .FirstOrDefault(c => c != null + && c.IsFaceup() + && c.Level == 1 + && c.IsCode(CardId.MartyrOfTheSacredBeasts)); + + if (martyr == null) return false; + + AI.SelectCard(martyr); + return true; } return false; } + private bool ShouldUseLinkuribohGYFallback() + { + ClientCard martyr = Bot.GetMonsters() + .FirstOrDefault(c => c != null + && c.IsFaceup() + && c.IsCode(CardId.MartyrOfTheSacredBeasts)); + if (martyr == null) return false; - private bool HeavyPolymerizationActivate() - { - if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (DefaultCheckWhetherCardIsNegated(martyr) || martyr.IsDisabled()) + return true; - // ตาม note: ใช้ตอนฝ่ายตรงข้ามมี monster 3 ใบ เพื่อ surprise เป็นตัวใหญ่ - // ไม่ใช้เป็น extender ฝั่งเราเอง เพราะจะเสีย LP และอาจตัด resource โดยไม่จำเป็น - if (Enemy.GetMonsterCount() < 3) return false; + if (HasSPLittleKnightTargetNow() + && Bot.HasInExtra(CardId.SPLittleKnight) + && PickSPLittleKnightMaterials().Count < 2) + { + return true; + } - AI.SelectCard( - CardId.PhantasmalSacredBeastsOfChaos, - CardId.SaintAzamina, - CardId.SaintAzamina, - CardId.SaintAzamina); - return true; + return false; } + private bool VarudrasSummon() + { + if (Bot.HasInMonstersZone(CardId.VarudrasTheFinalBringer, true)) + return false; - #endregion + if (!ShouldSummonVarudras()) + return false; - #region Normal Summon / Special Summon + List materials = PickRank10Materials(); + if (materials.Count < 2) return false; - private bool MartyrSummon() - { - // Bad hand fallback: if there is no starter/searcher, Martyr is the first play. - // Its on-summon effect is locked to Skyfire in MartyrActivate(). - if (IsSacredEndBoardReady()) return false; - if (Bot.HasInMonstersZone(CardId.MartyrOfTheSacredBeasts, true) && HasCoreFieldReady()) return false; - if (ShouldStartFromMartyrFallback()) return true; + resolvingRank10Summon = true; + rank10MaterialPlan = materials.Take(2).ToList(); + + AI.SelectMaterials(rank10MaterialPlan); return true; } - - private bool OrchestratorSummon() + private bool ShouldSummonVarudras() { - if (IsSacredEndBoardReady()) return false; - if (HasCoreFieldReady() && CountSacredBeastsOnField() >= 2) return false; - return HasSacredBeastInHand() || HasSacredBeastInGrave(); - } + if (CountLevel10MonstersOnField() < 2) + return false; - private bool ThunderKingKaijuSummon() - { - // ใช้เฉพาะมีตัวน่ากลัวจริง ไม่เอาไปตัด resource opponent โดยไม่จำเป็นตอนเรารำได้อยู่แล้ว - ClientCard target = GetBestEnemyMonster(true); - if (target == null) return false; - if (!target.IsMonsterDangerous() && !target.IsFloodgate() && Enemy.GetMonsterCount() < 2) return false; + if (Bot.HasInMonstersZone(CardId.RavielSacredBeastOfEndlessEternity, true) + && CountFaceupMartyrOnField() >= 2 + && Enemy.GetMonsterCount() > 0) + return false; - AI.SelectCard(target); - return true; - } + if (Duel.Player == 0 && (Enemy.GetMonsterCount() > 0 || Enemy.GetSpellCount() > 0)) + return true; - private bool PhantasmalSacredBeastsOfChaosSummon() + if (IsGoingFirstRank10Situation() && !HasComfortableRocketCost()) + return true; + + if (CountLevel10MonstersOnField() >= 3) + return true; + + return false; + } + private bool HasComfortableRocketCost() { - if (HasPhantasmalOnField()) return false; - return CountSacredBeastsOnField() >= 3 || CountLevel10MonstersOnField() >= 3; + ClientCard cost = GetBestRocketDiscardCost(); + if (cost == null) return false; + + return Bot.Hand.Count(c => c != null) >= 2; } - private bool Rank10NegateSummon() + private bool IsGoingFirstRank10Situation() { - if (HasPhantasmalOnField()) return false; - if (Bot.HasInMonstersZone(CardId.VarudrasTheFinalBringer, true)) return false; - return CountLevel10MonstersOnField() >= 2; + return Duel.Player == 0 + && Enemy.GetMonsterCount() == 0 + && Enemy.GetSpellCount() == 0; } - private bool GustavMaxSummon() { - if (HasPhantasmalOnField()) return false; - if (CountLevel10MonstersOnField() < 2) return false; + if (Bot.HasInMonstersZone(CardId.SuperdreadnoughtRailCannonGustavMax, true)) + return false; + + if (CountLevel10MonstersOnField() < 2) + return false; - // ปิดเกม / หลังมี negate rank10 แล้วเท่านั้น ไม่แย่ง material ก่อน combo จบ - return Bot.HasInMonstersZone(CardId.VarudrasTheFinalBringer, true) || Enemy.GetMonsterCount() == 0; + bool wantRocket = ShouldMakeGustavRocket(); + if (!wantRocket && Enemy.LifePoints > 2000) + return false; + + if (ShouldSummonVarudras() && !wantRocket) + return false; + + List materials = PickRank10Materials(); + if (materials.Count < 2) return false; + + resolvingRank10Summon = true; + rank10MaterialPlan = materials.Take(2).ToList(); + + AI.SelectMaterials(rank10MaterialPlan); + return true; } - private bool GustavRocketSummon() + private List PickRank10Materials() { - if (HasPhantasmalOnField()) return false; - return Bot.HasInMonstersZone(CardId.SuperdreadnoughtRailCannonGustavMax, true) - || CountLevel10MonstersOnField() >= 3; + return Bot.GetMonsters() + .Where(c => c != null && c.IsFaceup() && c.Level == 10) + .Select(c => new + { + Card = c, + Score = Rank10MaterialScore(c) + }) + .Where(x => x.Score < 9999) + .OrderBy(x => x.Score) + .Select(x => x.Card) + .Take(2) + .ToList(); } - private bool ThunderDragonColossusSummon() + private int Rank10MaterialScore(ClientCard card) { - if (HasPhantasmalOnField()) return false; - if (Bot.HasInMonstersZone(CardId.ThunderDragonColossus, true)) return false; - - // Corrected route step 7: fuse/convert with Orchestrator after its field effect has been used. - // Approximation: Orchestrator is face-up on field and we already have a Sacred Beast in GY/field from the route. - return Bot.HasInMonstersZone(CardId.TheOrchestratorOfTheSacredBeasts, true) - && (HasSacredBeastInGrave() || HasSacredBeastOnField()); - } + if (card == null) return 9999; + if (!card.IsFaceup()) return 9999; + if (card.Level != 10) return 9999; + if (card.IsCode( + CardId.PhantasmalSacredBeastsOfChaos, + CardId.VarudrasTheFinalBringer, + CardId.ThunderDragonColossus, + CardId.SuperdreadnoughtRailCannonGustavRocket, + CardId.SuperdreadnoughtRailCannonGustavMax)) + return 9999; - private bool SPLittleKnightSummon() - { - if (HasPhantasmalOnField()) return false; - if (GetBestEnemyCard() == null) return false; + if (card.IsCode(CardId.RavielSacredBeastOfEndlessEternity) + && CountFaceupMartyrOnField() >= 2 + && Enemy.GetMonsterCount() > 0) + return 9999; - int martyrCount = CountFaceupMartyrOnField(); - bool hasLinkuriboh = Bot.HasInMonstersZone(CardId.Linkuriboh, true); + bool duplicate = Bot.GetMonsters() + .Count(c => c != null && c.IsFaceup() && c.Id == card.Id) >= 2; - // Corrected route step 9: use Martyr x2, or Linkuriboh + Martyr only. - return martyrCount >= 2 || (hasLinkuriboh && martyrCount >= 1); - } + if (duplicate) return 1; + if (card.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe)) return 10; + if (card.IsCode(CardId.RavielSacredBeastOfEndlessEternity)) return 20; + if (card.IsCode(CardId.UriaSacredBeastOfCataclysmicFire)) return 30; - private bool LinkuribohSummon() - { - // Only link the Martyr that got stopped, as in the corrected checkpoint. - if (!IsMartyrNegatedOrInterrupted()) return false; - return Bot.GetMonsters().Any(c => c != null && c.IsFaceup() - && c.Level == 1 - && c.IsCode(CardId.MartyrOfTheSacredBeasts)); + return 50; } + private bool GustavRocketSummonOnMax() + { + if (!Bot.HasInExtra(CardId.SuperdreadnoughtRailCannonGustavRocket)) + return false; + ClientCard max = Bot.GetMonsters() + .FirstOrDefault(c => c != null + && c.IsFaceup() + && c.IsCode(CardId.SuperdreadnoughtRailCannonGustavMax)); - #endregion + if (max == null) return false; - #region Extra Deck effects + ClientCard discard = GetBestRocketDiscardCost(); + if (discard == null) return false; - private bool PhantasmalSacredBeastsOfChaosActivate() - { - if (CheckWhetherNegated()) return false; + if (!ShouldMakeGustavRocket()) + return false; - // negate monster ฝั่งตรงข้ามเท่านั้น ไม่กดใส่ตัวเอง - ClientCard target = GetBestEnemyMonster(true); - if (target == null) return false; - if (Duel.Player == 0 && !target.IsMonsterDangerous() && !HasOpponentRelevantChain()) return false; + resolvingGustavRocketSummon = true; + gustavRocketDiscardPlan = discard; + gustavRocketDiscardSelected = false; + gustavRocketMaxSelected = false; - AI.SelectCard(target); + AI.SelectCard(discard); + AI.SelectMaterials(new List { max }); return true; } - - private bool VarudrasActivate() + private bool ShouldMakeGustavRocket() { - if (CheckWhetherNegated()) return false; - return HasOpponentRelevantChain(); - } + if (!Bot.HasInExtra(CardId.SuperdreadnoughtRailCannonGustavRocket)) + return false; - private bool SPLittleKnightActivate() - { - if (CheckWhetherNegated()) return false; + if (!HasComfortableRocketCost()) + return false; - if (ActivateDescription == -1 || ActivateDescription == Util.GetStringId(CardId.SPLittleKnight, 0)) - { - ClientCard target = GetBestEnemyCard(); - if (target == null) return false; + if (IsGoingFirstRank10Situation()) + return true; - AI.SelectCard(target); - SPLittleKnightRemoveStep = 1; + if (Enemy.GetMonsterCount() > 0) return true; - } - if (ActivateDescription == Util.GetStringId(CardId.SPLittleKnight, 1)) - { - ClientCard self = Bot.GetMonsters().Where(c => c != null && Duel.ChainTargets.Contains(c)).FirstOrDefault(); - ClientCard enemy = GetBestEnemyMonster(true); - if (self != null && enemy != null) - { - AI.SelectCard(self); - AI.SelectNextCard(enemy); - return true; - } - } + if (Enemy.Graveyard.Any(c => c != null && c.IsMonster())) + return true; return false; } + private ClientCard GetBestRocketDiscardCost() + { + HashSet protect = new HashSet + { + CardId.UriaSacredBeastOfCataclysmicFire, + CardId.RavielSacredBeastOfEndlessEternity, + CardId.HamonSacredBeastOfSinfulCatastrophe, + CardId.MartyrOfTheSacredBeasts, + CardId.DestructionChantOfTheSacredBeast, + CardId.UnleashingTheSacredBeasts + }; - #endregion - - #region Fallback + if (!Bot.HasInSpellZone(CardId.DivineAbyssOfTheSacredBeast, true)) + protect.Add(CardId.DivineAbyssOfTheSacredBeast); - private bool MonsterRepos() + return Bot.Hand + .Where(c => c != null) + .OrderBy(c => DiscardScore(c, protect)) + .FirstOrDefault(c => DiscardScore(c, protect) < 9999); + } + private bool GustavRocketActivate() { - if (Card == null || Card.IsFacedown()) return false; - if (Card.HasType(CardType.Link)) return false; + if (CheckWhetherNegated(true, false, CardType.Monster)) return false; + if (Card.Location != CardLocation.MonsterZone) return false; - bool enemyBetter = Enemy.GetMonsters().Any(c => c != null && c.IsAttack() && c.Attack >= Card.GetDefensePower()); - if (Card.IsAttack() && enemyBetter && Card.Defense > Card.Attack) return true; - if (Card.IsDefense() && !enemyBetter && Card.Attack > Card.Defense) return true; - return false; - } + int negateDesc = Util.GetStringId(CardId.SuperdreadnoughtRailCannonGustavRocket, 1); + if (ActivateDescription != negateDesc && ActivateDescription != -1) + return false; - private bool SpellSet() - { - if (Card == null) return false; + if (Card.Overlays == null || Card.Overlays.Count == 0) + return false; - if (Card.IsTrap() || Card.HasType(CardType.QuickPlay)) - { - SelectSTPlace(); - return true; - } + if (Duel.LastChainPlayer != 1) return false; - return false; - } - public void SelectSTPlace(ClientCard card = null, bool avoidImpermanence = false, List avoidList = null) - { - if (card == null) card = Card; - if (card.Location == CardLocation.SpellZone) - { - return; - } - List list = new List(); - for (int seq = 0; seq < 5; ++seq) - { - if (Bot.SpellZone[seq] == null) - { - if (card != null && card.Location == CardLocation.Hand && avoidImpermanence && infiniteImpermanenceNegatedColumns.Contains(seq)) continue; - if (avoidList != null && avoidList.Contains(seq)) continue; - list.Add(seq); - } - } - int n = list.Count; - while (n-- > 1) - { - int index = Program.Rand.Next(list.Count); - int nextIndex = (index + Program.Rand.Next(list.Count - 1)) % list.Count; - int tempInt = list[index]; - list[index] = list[nextIndex]; - list[nextIndex] = tempInt; - } - if (avoidImpermanence && Bot.GetMonsters().Any(c => c.IsFaceup() && !c.IsDisabled())) - { - foreach (int seq in list) - { - ClientCard enemySpell = Enemy.SpellZone[4 - seq]; - if (enemySpell != null && enemySpell.IsFacedown()) continue; - int zone = (int)System.Math.Pow(2, seq); - AI.SelectPlace(zone); - return; - } - } - foreach (int seq in list) - { - int zone = (int)System.Math.Pow(2, seq); - AI.SelectPlace(zone); - return; - } - AI.SelectPlace(0); + ClientCard last = Util.GetLastChainCard(); + if (last == null) return false; + if (!last.IsMonster()) return false; + + if (!CheckLastChainShouldNegated()) return false; + + currentNegateCardList.Add(last); + currentDestroyCardList.Add(last); + return true; } #endregion } From cd5e861153d48397849fa2cf4c317e1652ae898c Mon Sep 17 00:00:00 2001 From: Reinen <127223416+DaruKani@users.noreply.github.com> Date: Tue, 2 Jun 2026 01:16:40 +0700 Subject: [PATCH 3/3] =?UTF-8?q?=E0=B9=8AUpdate=20SacredBot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Done --- Game/AI/Decks/SacredExecutor.cs | 101 +++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 27 deletions(-) diff --git a/Game/AI/Decks/SacredExecutor.cs b/Game/AI/Decks/SacredExecutor.cs index b74f0052..accb1af7 100644 --- a/Game/AI/Decks/SacredExecutor.cs +++ b/Game/AI/Decks/SacredExecutor.cs @@ -144,6 +144,7 @@ public class CardId ClientCard gustavRocketDiscardPlan = null; bool gustavRocketDiscardSelected = false; bool gustavRocketMaxSelected = false; + bool resolvingChantFusion = false; public SacredExecutor(GameAI ai, Duel duel) : base(ai, duel) @@ -239,6 +240,7 @@ public override void OnNewTurn() gustavRocketDiscardPlan = null; gustavRocketDiscardSelected = false; gustavRocketMaxSelected = false; + resolvingChantFusion = false; base.OnNewTurn(); } @@ -274,6 +276,40 @@ public override IList OnSelectCard(IList cards, int min, Logger.DebugWriteLine( "OnSelectCard " + cards.Count + " " + min + " " + max + " hint=" + hint + " cancelable=" + cancelable + " cards=["+string.Join(", ", cards.Select(c => c == null ? "null" : $"{c.Name}({c.Id}) C{c.Controller} L{c.Location}")) + "]"); ClientCard trigger = Util.GetLastChainCard(); + if (resolvingChantFusion) + { + if (hint == 509) + { + ClientCard fusion = cards.FirstOrDefault(c => + c != null + && c.Location == CardLocation.Extra + && c.IsCode(CardId.PhantasmalSacredBeastsOfChaos)); + + if (fusion != null) + { + Logger.DebugWriteLine("Chant GY fusion target: " + fusion.Id); + return new List { fusion }; + } + } + + if (hint == 511) + { + ClientCard material = cards + .Where(c => c != null && IsPhantasmalChaosMaterial(c)) + // เอาจากมือก่อน เพื่อรักษาบอร์ด ถ้า core อนุญาต + .OrderBy(c => c.Location == CardLocation.Hand ? 0 : 10) + .ThenBy(c => ChantFusionMaterialScore(c)) + .FirstOrDefault(); + + if (material != null) + { + Logger.DebugWriteLine("Chant GY fusion material pick: " + material.Id); + return new List { material }; + } + + resolvingChantFusion = false; + } + } if (resolvingGustavRocketSummon) { // discard cost จากมือ @@ -1679,11 +1715,9 @@ private bool DestructionChantActivate() { if (ShouldWaitRavielBoardWipe()) return false; - if (Duel.Player == 1 && Duel.Phase != DuelPhase.End) - return false; - + if (!CanMakePhantasmalFusion()) return false; - + resolvingChantFusion = true; AI.SelectCard(CardId.PhantasmalSacredBeastsOfChaos); return true; } @@ -2469,37 +2503,20 @@ private bool ShouldWaitRavielBoardWipe() return CountFaceupMartyrOnField() >= 2; } + private bool LinkuribohActivate() { + Logger.DebugWriteLine( + $"LinkuribohActivate loc={Card.Location} player={Duel.Player} phase={Duel.Phase} desc={ActivateDescription}" +); if (CheckWhetherNegated(true, false, CardType.Monster)) return false; if (Card.Location == CardLocation.MonsterZone) { if (Duel.Player != 1) return false; - if (Duel.Phase != DuelPhase.Battle) return false; - ClientCard biggestEnemy = Enemy.GetMonsters() - .Where(c => c != null && c.IsFaceup()) - .OrderByDescending(c => c.Attack) - .FirstOrDefault(); - - if (biggestEnemy == null) return false; - - if (biggestEnemy.Attack >= Bot.LifePoints) - return true; - - if (biggestEnemy.Attack >= 2500) - return true; - - ClientCard bestBot = Bot.GetMonsters() - .Where(c => c != null && c.IsFaceup() && !c.HasType(CardType.Link)) - .OrderByDescending(c => c.GetDefensePower()) - .FirstOrDefault(); - - if (bestBot != null && biggestEnemy.Attack > bestBot.GetDefensePower()) - return true; - - return false; + Logger.DebugWriteLine("Linkuriboh field effect: use on enemy attack."); + return true; } if (Card.Location == CardLocation.Grave) @@ -2548,6 +2565,9 @@ private bool VarudrasSummon() if (Bot.HasInMonstersZone(CardId.VarudrasTheFinalBringer, true)) return false; + if (ShouldKeepCurrentBigBoard()) + return false; + if (!ShouldSummonVarudras()) return false; @@ -2600,6 +2620,9 @@ private bool GustavMaxSummon() if (Bot.HasInMonstersZone(CardId.SuperdreadnoughtRailCannonGustavMax, true)) return false; + if (ShouldKeepCurrentBigBoard()) + return false; + if (CountLevel10MonstersOnField() < 2) return false; @@ -2756,6 +2779,30 @@ private bool GustavRocketActivate() currentDestroyCardList.Add(last); return true; } + private int ChantFusionMaterialScore(ClientCard card) + { + if (card == null) return 9999; + + if (card.IsCode(CardId.UriaSacredBeastOfCataclysmicFire)) return 1; + + if (card.IsCode(CardId.RavielSacredBeastOfEndlessEternity)) return 2; + + if (card.IsCode(CardId.HamonSacredBeastOfSinfulCatastrophe)) return 3; + + return 50; + } + private bool ShouldKeepCurrentBigBoard() + { + bool hasRaviel = Bot.HasInMonstersZone(CardId.RavielSacredBeastOfEndlessEternity); + bool hasHamon = Bot.HasInMonstersZone(CardId.HamonSacredBeastOfSinfulCatastrophe); + bool hasColossus = Bot.HasInMonstersZone(CardId.ThunderDragonColossus); + bool hasChant = Bot.HasInHand(CardId.DestructionChantOfTheSacredBeast) + || Bot.HasInSpellZone(CardId.DestructionChantOfTheSacredBeast); + bool hasMartyr = Bot.HasInGraveyard(CardId.MartyrOfTheSacredBeasts); + bool hasUria = Bot.HasInHand(CardId.UriaSacredBeastOfCataclysmicFire); + + return (hasRaviel && hasHamon && hasColossus && hasChant && hasMartyr && hasUria); + } #endregion } }