From 57e472b68279c523d3b940f0cc6e2fee1a321ed6 Mon Sep 17 00:00:00 2001 From: Reinen <127223416+DaruKani@users.noreply.github.com> Date: Sun, 24 May 2026 19:03:44 +0700 Subject: [PATCH 1/6] Add Archfiend Deck Playable Draft version --- Decks/AI_Archfiend.ydk | 78 ++ Game/AI/Decks/Archfiend.cs | 1765 ++++++++++++++++++++++++++++++++++++ WindBot.csproj | 1 + 3 files changed, 1844 insertions(+) create mode 100644 Decks/AI_Archfiend.ydk create mode 100644 Game/AI/Decks/Archfiend.cs diff --git a/Decks/AI_Archfiend.ydk b/Decks/AI_Archfiend.ydk new file mode 100644 index 00000000..0a1748e5 --- /dev/null +++ b/Decks/AI_Archfiend.ydk @@ -0,0 +1,78 @@ +#created by MDPro3 +#main +70781052 +70781052 +95718355 +95718355 +95718355 +22938501 +100264002 +100264002 +100264002 +58769832 +58769832 +58769832 +63198739 +63198739 +63198739 +11248645 +11248645 +11248645 +14558127 +14558127 +23434538 +49238328 +49238328 +100264003 +100264003 +100264003 +90764871 +90764871 +90764871 +82997779 +82997779 +82997779 +56506740 +56506740 +56506740 +24224830 +29095457 +94423983 +10045474 +10045474 +#extra +11901678 +11901678 +11901678 +45349196 +45349196 +45349196 +32775808 +32775808 +32775808 +64635042 +64635042 +64635042 +61248471 +61248471 +61248471 +!side +70781052 +26462013 +99161253 +#pickup +#0 +#0 +#0 +#case +#1080001 +#protector +#1070001 +#field +#1090001 +#grave +#1100001 +#stand +#1110001 +#mate +#1000001 \ No newline at end of file diff --git a/Game/AI/Decks/Archfiend.cs b/Game/AI/Decks/Archfiend.cs new file mode 100644 index 00000000..a7c7c864 --- /dev/null +++ b/Game/AI/Decks/Archfiend.cs @@ -0,0 +1,1765 @@ +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 +{ + // NOT FINISHED YET + [Deck("Archfiend", "AI_Archfiend")] + public class ArchfiendExecutor : DefaultExecutor + { + public class CardId + { + public const int SMSkull = 70781052; + public const int RegenArch = 95718355; + public const int RegenSage = 22938501; + public const int Origin = 100264002; + public const int Royal = 58769832; + public const int PMBeryl = 63198739; + public const int Highness = 11248645; + public const int Makourai = 100264003; + public const int Strategy = 90764871; + public const int Usurpation = 82997779; + public const int PMLL = 56506740; + public const int PMDR = 29095457; + public const int Simul = 94423983; + public const int Playtime = 87985506; + + + //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; + } + + //Setcode + const int SetcodeArchfiend = 0x45; + const int SetcodeMaliss = 0x1bf; + const int SetcodeTimeLord = 0x4a; + const int SetcodePhantom = 0xdb; + const int SetcodeOrcust = 0x11b; + const int SetcodeHorus = 0x19d; + const int SetcodeDarkWorld = 0x6; + const int SetcodeSkyStriker = 0x115; + List notToNegateIdList = new List { 58699500, 20343502, 19403423 }; + List notToDestroySpellTrap = new List { 50005218, 6767771 }; + + Dictionary> DeckCountTable = new Dictionary> + { + { 3, new List + { + CardId.RegenArch, + CardId.Origin, + CardId.Royal, + CardId.Highness, + CardId.PMBeryl, + CardId.Makourai, + CardId.Strategy, + CardId.Usurpation, + CardId.PMLL + } + }, + + { 2, new List + { + _CardId.AshBlossom, + _CardId.InfiniteImpermanence, + CardId.SMSkull, + _CardId.PotOfExtravagance + } + }, + + { 1, new List + { + _CardId.MaxxC, + _CardId.CalledByTheGrave, + CardId.PMDR, + CardId.Simul, + CardId.RegenSage, + CardId.Playtime + + } + }, + }; + + private static readonly int[] PreferDiscard = + { + CardId.Playtime, + CardId.Highness, + CardId.Makourai, + CardId.Usurpation, + CardId.Royal, + CardId.Strategy, + CardId.RegenArch, + CardId.Origin, + CardId.SMSkull + }; + + //Flags + int myTurnCount = 0; + int summonCount = 1; + const int hintTimingMainEnd = 0x4; + const int hintToHand = 0x200000; + bool activatingLodeSpSummonEffect = false; + + + List infiniteImpermanenceList = new List(); + List currentNegateCardList = new List(); + List currentDestroyCardList = new List(); + List enemyPlaceThisTurn = new List(); + List activatedCardIdList = new List(); + + public ArchfiendExecutor(GameAI ai, Duel duel) + : base(ai, duel) + { + AddExecutor(ExecutorType.Activate, _CardId.PotOfExtravagance, PotOfExtravaganceActivate); + AddExecutor(ExecutorType.Activate, _CardId.MaxxC, MaxxCActivate); + AddExecutor(ExecutorType.Activate, _CardId.AshBlossom, AshBlossomActivate); + AddExecutor(ExecutorType.Activate, _CardId.CalledByTheGrave, CalledbytheGraveActivate); + AddExecutor(ExecutorType.Activate, _CardId.InfiniteImpermanence, InfiniteImpermanenceActivate); + AddExecutor(ExecutorType.Activate, CardId.PMDR, PrimiteDrillbeamActivate); + AddExecutor(ExecutorType.Activate, CardId.Origin, OriginActivate); + AddExecutor(ExecutorType.Activate, CardId.RegenSage); + //Rough code + AddExecutor(ExecutorType.Activate, CardId.Usurpation, Usurpation); + AddExecutor(ExecutorType.Activate, CardId.Strategy, StrategyActivate); + AddExecutor(ExecutorType.Activate, CardId.Royal, RoyalActivate); + AddExecutor(ExecutorType.Activate, CardId.Highness, HighnessActivate); + AddExecutor(ExecutorType.Activate, CardId.RegenArch); + AddExecutor(ExecutorType.Activate, CardId.Playtime, Playtime); + AddExecutor(ExecutorType.Activate, CardId.Makourai, MakouraiActivate); + AddExecutor(ExecutorType.SpSummon, CardId.Origin, OriginSpSummon); + + AddExecutor(ExecutorType.Activate, CardId.Simul, Simul); + AddExecutor(ExecutorType.SpSummon, CardId.RegenArch); + AddExecutor(ExecutorType.SpSummon, CardId.RegenSage); + AddExecutor(ExecutorType.Activate, CardId.PMLL, PrimiteLordlyLodeActivate); + AddExecutor(ExecutorType.Summon, Level4MonsterSummon); + AddExecutor(ExecutorType.Activate, CardId.PMBeryl, PrimiteDragonEtherBerylActivate); + AddExecutor(ExecutorType.Activate, CardId.PMLL, PrimiteLordlyLodeSpSummon); + + AddExecutor(ExecutorType.SpellSet, DefaultSpellSet); + AddExecutor(ExecutorType.Repos, DefaultMonsterRepos); + } + + #region Default + public override void OnNewTurn() + { + if (Duel.Player == 0) + { + myTurnCount++; + } + // reset + summonCount = 0; + activatingLodeSpSummonEffect = false; + infiniteImpermanenceList.Clear(); + currentNegateCardList.Clear(); + currentDestroyCardList.Clear(); + enemyPlaceThisTurn.Clear(); + activatedCardIdList.Clear(); + + base.OnNewTurn(); + } + public override bool OnSelectHand() { return true; /* Go first by default.*/} + + 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, false, avoid_list); + return true; + } + } + else + { + SelectSTPlace(); + } + 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; + + return CheckCardShouldNegate(lastcard); + } + 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, bool forNegate = false) + { + 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; + } + public bool CheckCardShouldNegate(ClientCard card) + { + if (card == null) return false; + if (card.IsMonster() && card.HasSetcode(_Setcode.TimeLord) && Duel.Phase == DuelPhase.Standby) return false; + if (notToNegateIdList.Contains(card.Id)) return false; + if (card.HasSetcode(_Setcode.Danger) && card.Location == CardLocation.Hand) return false; + if (card.IsMonster() && card.Location == CardLocation.MonsterZone && card.HasPosition(CardPosition.Defence)) + { + if (Enemy.MonsterZone.Any(c => CheckNumber41(c)) || Bot.MonsterZone.Any(c => CheckNumber41(c))) return false; + } + if (DefaultCheckWhetherCardIsNegated(card)) return false; + if (card.Location == CardLocation.SpellZone) + { + int sequence = card.Sequence; + if (card.Controller == 1) sequence = 4 - sequence; + if (infiniteImpermanenceNegatedColumns.Contains(sequence)) return false; + } + if (card.IsCode(_CardId.MulcharmyPurulia, _CardId.MulcharmyFuwalos, _CardId.MulcharmyNyalus, _CardId.MaxxC)) return false; + if (card.IsDisabled()) return false; + + return true; + } + + public bool PotOfExtravaganceActivate() + { + if (CheckWhetherNegated()) return false; + SelectSTPlace(Card, true); + AI.SelectOption(1); + return true; + } + + public override IList OnSelectCard(IList cards, int min, int max, int hint, bool cancelable) + { + ChainInfo currentChain = Duel.GetCurrentSolvingChainInfo(); + if (currentChain == null) + { + // for activating target + ClientCard lastChainCard = Util.GetLastChainCard(); + if (lastChainCard != null && lastChainCard.Controller == 0) + { + switch (lastChainCard.Id) + { + case CardId.PMDR: + { + // negate cards on chain + foreach (ClientCard card in Duel.CurrentChain) + { + if (card.Controller == 1 && card.IsOnField() && card.IsFaceup() && !card.IsDisabled() && !currentNegateCardList.Contains(card) + && CheckCanBeTargeted(card, true, CardType.Spell) && CheckCardShouldNegate(card) && cards.Contains(card)) + { + currentNegateCardList.Add(card); + return Util.CheckSelectCount(new List { card }, cards, min, max); + } + } + // remove problematic enemy cards + List targetList = GetNormalEnemyTargetList(canBeTarget: true, ignoreCurrentDestroy: true, selfType: CardType.Monster, forNegate: false); + foreach (ClientCard target in targetList) + { + if (cards.Contains(target)) + { + currentNegateCardList.Add(target); + return Util.CheckSelectCount(new List { target }, cards, min, max); + } + } + // select enemy cards + List enemyCards = ShuffleList(cards.Where(c => c.Controller == 1).ToList()); + foreach (ClientCard target in enemyCards) + { + if (cards.Contains(target)) + { + currentNegateCardList.Add(target); + return Util.CheckSelectCount(new List { target }, cards, min, max); + } + } + break; + } + case _CardId.InfiniteImpermanence: + { + int sequence = lastChainCard.Sequence; + List targetList = GetMonsterListForTargetNegate(true, CardType.Trap); + foreach (ClientCard target in targetList) + { + if (cards.Contains(target)) + { + currentNegateCardList.Add(target); + if (sequence >= 0) + { + ClientCard spell = Enemy.SpellZone[sequence]; + if (spell != null && spell.IsFaceup()) + { + currentNegateCardList.Add(spell); + } + } + return Util.CheckSelectCount(new List { target }, cards, min, max); + } + } + targetList = GetProblematicEnemyCardList(canBeTarget: true, ignoreSpells: false, selfType: CardType.Trap); + foreach (ClientCard target in targetList) + { + if (cards.Contains(target)) + { + currentNegateCardList.Add(target); + if (sequence >= 0) + { + ClientCard spell = Enemy.SpellZone[sequence]; + if (spell != null && spell.IsFaceup()) + { + currentNegateCardList.Add(spell); + } + } + return Util.CheckSelectCount(new List { target }, cards, min, max); + } + } + break; + } + + } + } + } + + if (currentChain != null) + { + if (currentChain.ActivateController == 0) + { + // for solving chain + switch (currentChain.ActivateId) + { + case CardId.PMBeryl: + if (hint == HintMsg.Set) + { + int targetId = CardId.PMLL; + if (activatedCardIdList.Contains(CardId.PMLL) || !DefaultCheckWhetherBotCanSearch() || Bot.HasInSpellZone(CardId.PMLL)) + { + targetId = CardId.PMDR; + } + ClientCard target = cards.FirstOrDefault(c => c.IsCode(targetId)); + if (target != null) + { + return Util.CheckSelectCount(new List { target }, cards, min, max); + } + } + break; + + case CardId.PMLL: + { + if (hint == HintMsg.AddToHand) + { + List targetIdList = new List { CardId.PMBeryl, CardId.PMDR }; + if (summonCount == 0) + { + // whether need to search drillbeam + bool canTriggerPrimiteBeam = Bot.Hand.Any(c => c.IsCode(CardId.PMBeryl, CardId.PMLL, CardId.SMSkull)); + canTriggerPrimiteBeam |= Bot.HasInMonstersZone(new List { CardId.SMSkull }, faceUp: true); + canTriggerPrimiteBeam |= Bot.GetSpells().Any(c => (c.IsFacedown() || Duel.CurrentChain.Contains(c))); + if (canTriggerPrimiteBeam) + { + targetIdList.Insert(0, CardId.PMDR); + } + } + foreach (int targetId in targetIdList) + { + ClientCard target = cards.FirstOrDefault(c => c.IsCode(targetId)); + if (target != null) + { + return Util.CheckSelectCount(new List { target }, cards, min, max); + } + } + } + break; + } + case CardId.Strategy: + { + if (hint == HintMsg.Remove) + { + ClientCard cost = cards + .Where(c => c != null && c.Controller == 0) + .OrderBy(c => GetArchfiendCostPriority(c)) + .FirstOrDefault(); + + if (cost != null) + return Util.CheckSelectCount(new List { cost }, cards, min, max); + } + + if (hint == HintMsg.AddToHand) + { + int targetId = GetArchfiendSearchTarget(exceptId: CardId.Strategy); + ClientCard target = cards.FirstOrDefault(c => c.IsCode(targetId)); + if (target != null) + return Util.CheckSelectCount(new List { target }, cards, min, max); + } + break; + } + + case CardId.Royal: + { + if (hint == HintMsg.AddToHand) + { + int targetId = GetArchfiendSearchTarget(exceptId: CardId.Royal); + ClientCard target = cards.FirstOrDefault(c => c.IsCode(targetId)); + if (target != null) + return Util.CheckSelectCount(new List { target }, cards, min, max); + } + break; + } + + case CardId.Highness: + { + if (hint == HintMsg.Remove) + { + ClientCard cost = cards + .Where(c => c != null && c.Controller == 0 && c.HasSetcode(SetcodeArchfiend)) + .OrderBy(c => GetArchfiendCostPriority(c)) + .FirstOrDefault(); + + if (cost != null) + return Util.CheckSelectCount(new List { cost }, cards, min, max); + } + + if (hint == HintMsg.AddToHand) + { + List targetIds = GetHighnessSearchTargets(); + List selected = new List(); + + foreach (int id in targetIds) + { + ClientCard target = cards.FirstOrDefault(c => c.IsCode(id) && !selected.Contains(c)); + if (target != null) selected.Add(target); + } + + if (selected.Count > 0) + return Util.CheckSelectCount(selected, cards, min, max); + } + break; + } + case CardId.Makourai: + { + if (hint == HintMsg.AddToHand) + { + ClientCard target = cards.FirstOrDefault(c => c.IsCode(CardId.SMSkull)); + if (target != null) + return Util.CheckSelectCount(new List { target }, cards, min, max); + } + + break; + } + default: + break; + } + } + if (currentChain.ActivateController == 1) + { + switch (currentChain.ActivateId) + { + case _CardId.EvenlyMatched: + { + Logger.DebugWriteLine("=== Evenly Matched activated."); + List banishList = new List(); + List botMonsters = Bot.GetMonsters().Where(card => !card.HasType(CardType.Token)).ToList(); + // monster + List faceDownMonsters = botMonsters.Where(card => card.IsFacedown()).ToList(); + banishList.AddRange(faceDownMonsters); + List dumpMainMonsterList = botMonsters.Where(card => !banishList.Contains(card) + && CheckRemainInDeck(card.Id) > 0).ToList(); + dumpMainMonsterList.Sort(CardContainer.CompareCardAttack); + banishList.AddRange(dumpMainMonsterList); + // spells + List faceUpSpells = Bot.GetSpells().Where(c => c.IsFaceup()).ToList(); + banishList.AddRange(ShuffleList(faceUpSpells)); + // other monster + List otherMonsters = botMonsters.Where(card => !banishList.Contains(card)).ToList(); + otherMonsters.Sort(CardContainer.CompareCardAttack); + banishList.AddRange(otherMonsters); + List faceDownSpells = Bot.GetSpells().Where(c => c.IsFacedown()).ToList(); + banishList.AddRange(ShuffleList(faceDownSpells)); + return Util.CheckSelectCount(banishList, cards, min, max); + } + default: + break; + } + } + } + + return base.OnSelectCard(cards, min, max, hint, cancelable); + } + + #endregion + + #region Primite + public bool PrimiteDragonEtherBerylActivate() + { + if (Card.Location == CardLocation.Grave) + { + return !CheckWhetherNegated(); + } + // to grave + if (ActivateDescription == Util.GetStringId(CardId.PMBeryl, 1)) + { + return false; + + } + else + { + // search + return !CheckWhetherNegated(); + } + } + + public bool PrimiteDrillbeamActivate() + { + if (Card.Location == CardLocation.Grave) + { + if (CheckWhetherNegated()) return false; + + // check whether can active + bool canActivate = Bot.HasInHand(CardId.PMLL) || Bot.HasInHand(CardId.PMBeryl); + canActivate |= Bot.GetMonsters().Any(c => c.IsFaceup() && c.IsCode(CardId.SMSkull)); + canActivate |= Bot.GetSpellCountWithoutField() <= 3; + + if (canActivate) + { + activatedCardIdList.Add(Card.Id + 1); + return true; + } + + // cannot activate + return false; + } + + // negate + if (CheckWhetherNegated(true, true, CardType.Spell)) + { + return false; + } + + bool activateFlag = false; + + // negate problematic enemy card + List problematicEnemyCardList = GetProblematicEnemyCardList(true, false, CardType.Spell); + if (problematicEnemyCardList.Count(c => !c.IsDisabled()) > 0) + { + problematicEnemyCardList.RemoveAll(c => currentNegateCardList.Contains(c)); + if (problematicEnemyCardList.Count > 0) + { + activateFlag = true; + } + } + + // negate cards on chain + foreach (ClientCard card in Duel.CurrentChain) + { + if (card.Controller == 1 && card.IsOnField() && card.IsFaceup() && !card.IsDisabled() && !currentNegateCardList.Contains(card) + && CheckCanBeTargeted(card, true, CardType.Spell) && CheckCardShouldNegate(card)) + { + activateFlag = true; + } + } + + // can recycle, so activate it + if (Bot.HasInMonstersZone(CardId.PMBeryl, faceUp: true) && !activatedCardIdList.Contains(CardId.PMDR + 1) + && (CurrentTiming & hintToHand) == 0) + { + List targetList = GetNormalEnemyTargetList(true, true, CardType.Spell, true); + if (targetList.Count > 0) + { + activateFlag = true; + } + } + + // become target + if (DefaultOnBecomeTarget()) + { + activateFlag |= Enemy.GetSpells().Any(c => c.IsFaceup() && !c.IsDisabled() && CheckCanBeTargeted(c, true, CardType.Spell)); + activateFlag |= Enemy.GetMonsters().Any(c => c.IsFaceup() && !c.IsDisabled() && CheckCanBeTargeted(c, true, CardType.Spell)); + } + + if (Duel.Phase == DuelPhase.BattleStep) + { + // remove problematic enemy monster + ClientCard problematicEnemyMonster = GetProblematicEnemyMonster(0, true, true, CardType.Spell); + if (problematicEnemyMonster != null && !problematicEnemyMonster.IsDisabled()) + { + activateFlag = true; + } + } + + if (activateFlag) + { + activatedCardIdList.Add(Card.Id); + SelectSTPlace(Card, true); + return true; + } + + return false; + } + + public bool PrimiteLordlyLodeActivate() + { + if (Card.Location == CardLocation.SpellZone && Card.IsFaceup()) + { + return false; + } + + // activate + bool activateFlag = PrimiteLordlyLodeActivateCheck(); + Logger.DebugWriteLine("PrimiteLordlyLodeActivate: " + activateFlag); + if (activateFlag) + { + SelectSTPlace(Card, true); + activatedCardIdList.Add(Card.Id); + return true; + } + return false; + } + + public bool PrimiteLordlyLodeActivateCheck() + { + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + bool activateFlag = false; + if (Bot.HasInHandOrHasInMonstersZone(CardId.PMBeryl) && DefaultCheckWhetherBotCanSearch()) + { + // for search drillbeam + activateFlag |= CheckRemainInDeck(CardId.PMDR) > 0; + activateFlag |= summonCount <= 0 && Card.Location == CardLocation.SpellZone && Card.IsFacedown(); + } + if (summonCount > 0 && !Bot.HasInHand(CardId.PMBeryl) && CheckRemainInDeck(CardId.PMBeryl) > 0 && DefaultCheckWhetherBotCanSearch()) + { + // for search ether beryl + activateFlag |= Bot.HasInGraveyard(CardId.PMDR); + activateFlag |= !CheckWhetherNegated(true, true, CardType.Monster); + } + if (!Bot.HasInSpellZone(CardId.PMLL, true, true)) + { + // for activate it + activateFlag |= DefaultCheckWhetherBotCanSearch(); + + // for special summon + CardLocation loc; + if (Bot.HasInHand(CardId.SMSkull)) + { + loc = CardLocation.Hand; + } + else if (CheckRemainInDeck(CardId.SMSkull) > 0) + { + loc = CardLocation.Deck; + } + else if (Bot.HasInGraveyard(CardId.SMSkull)) + { + loc = CardLocation.Grave; + } + else + { + return false; + } + } + if (Card.Location == CardLocation.SpellZone && Card.IsFacedown()) + { + activateFlag |= DefaultCheckWhetherBotCanSearch(); + } + return activateFlag; + } + + public bool PrimiteLordlyLodeSpSummon() + { + if (Card.Location == CardLocation.SpellZone && Card.IsFaceup()) + { + // add activating flag + if (CheckWhetherNegated()) return false; + if (!PrimiteLordlyLodeSpSummonCheck()) return false; + activatingLodeSpSummonEffect = true; + activatedCardIdList.Add(Card.Id + 1); + return true; + } + + return false; + } + + public bool PrimiteLordlyLodeSpSummonCheck() + { + // special summon + CardLocation loc; + if (Bot.HasInHand(CardId.SMSkull)) + { + loc = CardLocation.Hand; + } + else if (CheckRemainInDeck(CardId.SMSkull) > 0) + { + loc = CardLocation.Deck; + } + else if (Bot.HasInGraveyard(CardId.SMSkull)) + { + loc = CardLocation.Grave; + } + else + { + return false; + } + int drawCount = GetSpecialSummonDrawCount(loc); + return drawCount < 2; + } + + public int GetSpecialSummonDrawCount(CardLocation loc) + { + int res = 0; + if (!DefaultCheckWhetherEnemyCanDraw()) + { + return 0; + } + if (enemyResolvedEffectIdList.Contains(_CardId.MaxxC)) + { + res++; + } + + if ((loc & CardLocation.Hand) != 0) + { + res += enemyResolvedEffectIdList.Count(id => id == _CardId.MulcharmyPurulia); + } + if ((loc & (CardLocation.Deck | CardLocation.Extra)) != 0) + { + res += enemyResolvedEffectIdList.Count(id => id == _CardId.MulcharmyFuwalos); + } + if ((loc & (CardLocation.Grave | CardLocation.Removed)) != 0) + { + res += enemyResolvedEffectIdList.Count(id => id == _CardId.MulcharmyNyalus); + } + + return res; + } + + public bool Level4MonsterSummon() + { + if (!Card.IsCode(CardId.PMBeryl, CardId.Highness)) + { + return false; + } + + bool canSummonDragon = Bot.HasInHand(CardId.PMBeryl); + if (!activatedCardIdList.Contains(CardId.PMLL) && DefaultCheckWhetherBotCanSearch()) + { + canSummonDragon |= Bot.HasInHand(CardId.PMLL) && Bot.GetSpellCountWithoutField() < 5; + canSummonDragon |= Bot.GetSpells().Any(c => c.IsCode(CardId.PMLL) && c.IsFacedown()); + } + if (canSummonDragon) + { + bool summonFlag = false; + // summon to search? + if (!CheckWhetherNegated(true, true)) + { + summonFlag |= !activatedCardIdList.Contains(CardId.PMLL) && !Bot.HasInHandOrInSpellZone(CardId.PMLL) && CheckRemainInDeck(CardId.PMLL) > 0; + summonFlag |= CheckRemainInDeck(CardId.PMDR) > 0; + } + + // summon to recycle beam + if (!Bot.HasInMonstersZone(CardId.PMBeryl, faceUp: true) && !activatedCardIdList.Contains(CardId.PMDR + 1) + && Bot.HasInGraveyard(CardId.PMDR) && Bot.GetSpellCountWithoutField() < 5) + { + summonFlag = true; + } + + if (summonFlag && Card.IsCode(CardId.PMBeryl)) + { + summonCount--; + return true; + } + } + bool canSummonHighness = Bot.HasInHand(CardId.Highness) && Bot.Graveyard.Any(card => card != null && card.HasSetcode(SetcodeArchfiend)) + && DefaultCheckWhetherBotCanSearch(); + + if (canSummonHighness && Card.IsCode(CardId.Highness)) + { + summonCount--; + return true; + } + + return false; + } + + #endregion + + #region original code + + private bool Playtime() + { + if (CheckWhetherNegated(true, true, CardType.Trap)) return false; + + int target = GetPlaytimeTarget(); + if (target == 0) return false; + + AI.SelectCard(target); + return true; + } + private int GetPlaytimeTarget() + { + if (Duel.Player == 0) + { + if (CanPlaytimeSummon(CardId.Royal)) return CardId.Royal; + if (!Bot.HasInMonstersZone(CardId.Origin, faceUp: true) && CanPlaytimeSummon(CardId.Origin)) return CardId.Origin; + if (CanPlaytimeSummon(CardId.SMSkull)) return CardId.SMSkull; + return 0; + } + + if (Bot.HasInMonstersZone(CardId.Origin, faceUp: true) && Bot.HasInMonstersZone(CardId.SMSkull, faceUp: true)) + return 0; + + if (!Bot.HasInMonstersZone(CardId.Origin, faceUp: true) && CanPlaytimeSummon(CardId.Origin)) return CardId.Origin; + if (!Bot.HasInMonstersZone(CardId.SMSkull, faceUp: true) && CanPlaytimeSummon(CardId.SMSkull)) return CardId.SMSkull; + if (CanPlaytimeSummon(CardId.Royal)) return CardId.Royal; + + return 0; + } + private bool CanPlaytimeSummon(int id) + { + return Bot.HasInHand(id) || CheckRemainInDeck(id) > 0; + } + private bool Usurpation() + { + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + if (Bot.GetSpellCountWithoutField() >= 5) return false; + int trapId = GetUsurpationTrapToSet(); + if (trapId == 0) return false; + + SelectSTPlace(Card, true); + AI.SelectCard(trapId); + return true; + } + private int GetUsurpationTrapToSet() + { + if (Duel.Player == 1 && CanSetArchfiendTrap(CardId.Simul) && ShouldUseSimul()) + return CardId.Simul; + + if (CanSetArchfiendTrap(CardId.Playtime)) + return CardId.Playtime; + + if (CanSetArchfiendTrap(CardId.Simul)) + return CardId.Simul; + + return 0; + } + private bool Simul() + { + if (CheckWhetherNegated(true, true, CardType.Trap)) return false; + + return ShouldUseSimul(); + } + + private bool ShouldUseSimul() + { + if (Duel.Player == 1) + { + if (Duel.Phase != DuelPhase.Main1 && Duel.Phase != DuelPhase.Main2) return false; + return Enemy.GetMonsters().Any(IsRitualFusionSynchroXyzLink); + } + return false; + } + + private bool IsRitualFusionSynchroXyzLink(ClientCard card) + { + return card != null && card.IsFaceup() && card.IsMonster() + && (card.HasType(CardType.Ritual) + || card.HasType(CardType.Fusion) + || card.HasType(CardType.Synchro) + || card.HasType(CardType.Xyz) + || card.HasType(CardType.Link)); + } + + private bool CanSetArchfiendTrap(int id) + { + return Bot.HasInGraveyard(id) || CheckRemainInDeck(id) > 0; + } + + private bool StrategyActivate() + { + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + + // ถ้าอยู่ในมือ/เซ็ตอยู่ และยังไม่มี Strategy face-up ก็ activate ได้ + if (!Bot.HasInSpellZone(CardId.Strategy, true, true)) + { + SelectSTPlace(Card, true); + activatedCardIdList.Add(Card.Id); + return true; + } + + // ถ้า face-up อยู่แล้ว ใช้ effect search + if (Card.Location == CardLocation.SpellZone && Card.IsFaceup()) + { + if (!DefaultCheckWhetherBotCanSearch()) return false; + if (!HasArchfiendCostForStrategy()) return false; + + int target = GetArchfiendSearchTarget(exceptId: CardId.Strategy); + if (target == 0) return false; + + activatedCardIdList.Add(Card.Id + 1); + + // option 0 = banish cost เพื่อ search + AI.SelectOption(0); + AI.SelectCard(target); + return true; + } + + return false; + } + + private bool RoyalActivate() + { + // Royal เป็น monster effect จาก hand/face-up field + if (CheckWhetherNegated()) return false; + if (!DefaultCheckWhetherBotCanSearch()) return false; + + int target = GetArchfiendSearchTarget(exceptId: CardId.Royal); + if (target == 0) return false; + + // ใช้เพื่อหา starter/ต่อ play และได้ normal summon เพิ่ม + activatedCardIdList.Add(Card.Id); + AI.SelectCard(target); + return true; + } + + private bool HighnessActivate() + { + if (CheckWhetherNegated()) return false; + + // กรณี GY effect: ถ้า engine ถามได้ แปลว่า trigger ถูกแล้ว + if (Card.Location == CardLocation.Grave) + { + return true; + } + + // Normal Summon effect + if (Card.Location == CardLocation.MonsterZone) + { + if (!DefaultCheckWhetherBotCanSearch()) return false; + if (!HasArchfiendCostInGrave()) return false; + + List targets = GetHighnessSearchTargets(); + if (targets.Count < 2) return false; + + activatedCardIdList.Add(Card.Id); + AI.SelectCard(targets[0], targets[1]); + return true; + } + + return false; + } + private bool HasArchfiendCostForStrategy() + { + return Bot.Hand.Any(c => c != null && (c.IsMonster() && c.HasRace(CardRace.Fiend) || c.HasSetcode(SetcodeArchfiend))) + || Bot.Graveyard.Any(c => c != null && (c.IsMonster() && c.HasRace(CardRace.Fiend) || c.HasSetcode(SetcodeArchfiend))); + } + + private bool HasArchfiendCostInGrave() + { + return Bot.Graveyard.Any(c => c != null && c.HasSetcode(SetcodeArchfiend)); + } + + private int GetArchfiendSearchTarget(int exceptId = 0) + { + List priority = new List + { + CardId.Royal, // starter: search + extra normal + CardId.Highness, // add 2 ถ้ามี grave cost + CardId.Usurpation, // access trap / ritual line + CardId.Playtime, + CardId.Simul, + CardId.Origin, + CardId.SMSkull, + CardId.Makourai + }; + + foreach (int id in priority) + { + if (id == exceptId) continue; + if (CheckRemainInDeck(id) > 0) return id; + } + + return 0; + } + + private List GetHighnessSearchTargets() + { + List result = new List(); + + List priority = new List + { + CardId.Royal, + CardId.Usurpation, + CardId.Playtime, + CardId.Simul, + CardId.Origin, + CardId.SMSkull, + CardId.Makourai, + CardId.Strategy + }; + + foreach (int id in priority) + { + if (id == CardId.Highness) continue; + if (CheckRemainInDeck(id) <= 0) continue; + if (result.Contains(id)) continue; + + result.Add(id); + if (result.Count >= 2) break; + } + + return result; + } + private int GetArchfiendCostPriority(ClientCard card) + { + if (card == null) return 999; + + // อยาก banish ของที่ใช้หมด/ไม่สำคัญก่อน + if (card.IsCode(CardId.Playtime)) return 1; + if (card.IsCode(CardId.Simul)) return 2; + if (card.IsCode(CardId.Usurpation)) return 3; + if (card.IsCode(CardId.Makourai)) return 4; + if (card.IsCode(CardId.Strategy)) return 5; + if (card.IsCode(CardId.Highness)) return 6; + if (card.IsCode(CardId.Royal)) return 7; + if (card.IsCode(CardId.Origin)) return 8; + if (card.IsCode(CardId.SMSkull)) return 9; + + return 100; + } + private bool OriginActivate() + { + if (CheckWhetherNegated()) return false; + + // Effect 3: negate monster effect ฝั่งตรงข้าม ถ้าเราคุม Summoned Skull + if (Duel.LastChainPlayer == 1 && Util.GetLastChainCard() != null) + { + ClientCard last = Util.GetLastChainCard(); + + if (last.IsMonster() + && Bot.HasInMonstersZone(CardId.SMSkull, faceUp: true) + && CheckLastChainShouldNegated()) + { + currentNegateCardList.Add(last); + return true; + } + + return false; + } + + // Effect 1: จากมือ tribute Archfiend อื่นเพื่อ SS ตัวเอง + if (Card.Location == CardLocation.Hand) + { + ClientCard tribute = GetOriginTributeCost(); + if (tribute == null) return false; + + AI.SelectCard(tribute); + return true; + } + + // Effect 2: ถ้า summon สำเร็จ เรียก Summoned Skull / Level 6 Fiend 2500 ATK + if (Card.Location == CardLocation.MonsterZone) + { + if (Bot.HasInHand(CardId.SMSkull) || CheckRemainInDeck(CardId.SMSkull) > 0 || Bot.HasInGraveyard(CardId.SMSkull)) + { + AI.SelectCard(CardId.SMSkull); + return true; + } + } + + return false; + } + + private bool OriginSpSummon() + { + if (Card.Location != CardLocation.Hand) return false; + if (CheckWhetherNegated()) return false; + + ClientCard tribute = GetOriginTributeCost(); + if (tribute == null) return false; + + AI.SelectCard(tribute); + return true; + } + + private ClientCard GetOriginTributeCost() + { + List candidates = new List(); + + candidates.AddRange(Bot.Hand.Where(c => + c != null + && c != Card + && c.HasSetcode(SetcodeArchfiend))); + + candidates.AddRange(Bot.GetMonsters().Where(c => + c != null + && c != Card + && c.HasSetcode(SetcodeArchfiend))); + + // อย่า tribute Summoned Skull ถ้าใช้เป็นเงื่อนไข negate ของ Origin + candidates.RemoveAll(c => c.IsCode(CardId.SMSkull) && !Bot.HasInMonstersZone(CardId.SMSkull, faceUp: true)); + + return candidates + .OrderBy(c => GetOriginTributePriority(c)) + .FirstOrDefault(); + } + + private int GetOriginTributePriority(ClientCard card) + { + if (card == null) return 999; + + // ของที่อยากจ่ายก่อน + if (card.IsCode(CardId.Playtime)) return 1; + if (card.IsCode(CardId.Simul)) return 2; + if (card.IsCode(CardId.Makourai)) return 3; + if (card.IsCode(CardId.Usurpation)) return 4; + if (card.IsCode(CardId.Strategy)) return 5; + + // monster ที่พอจ่ายได้ + if (card.IsCode(CardId.RegenArch)) return 10; + if (card.IsCode(CardId.Highness)) return 20; + if (card.IsCode(CardId.Royal)) return 30; + + // ไม่ค่อยอยากจ่าย + if (card.IsCode(CardId.Origin)) return 80; + if (card.IsCode(CardId.SMSkull)) return 90; + + return 100; + } + private bool MakouraiActivate() + { + if (CheckWhetherNegated(true, true, CardType.Spell)) return false; + + // Effect 2: GY effect เก็บ Summoned Skull กลับมือ + if (Card.Location == CardLocation.Grave) + { + if (Duel.Player != 0) return false; + if (!Bot.HasInGraveyard(CardId.SMSkull)) return false; + + AI.SelectCard(CardId.SMSkull); + activatedCardIdList.Add(Card.Id + 1); + return true; + } + + // Effect 1: Quick-Play จากมือ/สนาม เลือก Archfiend เราเพื่อบัฟ + ล้างมอน + ClientCard target = GetBestMakouraiTarget(); + if (target == null) return false; + + bool canDestroySomething = Enemy.GetMonsters().Any(e => + e != null + && e.IsFaceup() + && e.Attack >= 0 + && e.Attack < target.Attack + 600); + + bool battlePush = Duel.Phase == DuelPhase.BattleStep || Duel.Phase == DuelPhase.Damage; + + // กดถ้าล้างมอนได้ หรือกำลัง Battle เพื่อดัน damage + if (!canDestroySomething && !battlePush) return false; + + if (Card.Location == CardLocation.Hand) + { + SelectSTPlace(Card, true); + } + + AI.SelectCard(target); + activatedCardIdList.Add(Card.Id); + return true; + } + private ClientCard GetBestMakouraiTarget() + { + List archfiends = Bot.GetMonsters() + .Where(c => c != null + && c.IsFaceup() + && c.HasSetcode(SetcodeArchfiend) + && !c.IsDisabled()) + .OrderByDescending(c => CountMakouraiDestroyTargets(c)) + .ThenByDescending(c => c.Attack) + .ToList(); + + if (archfiends.Count <= 0) return null; + + return archfiends[0]; + } + + private int CountMakouraiDestroyTargets(ClientCard target) + { + if (target == null) return 0; + + int afterBoostAtk = target.Attack + 600; + + return Enemy.GetMonsters().Count(e => + e != null + && e.IsFaceup() + && e.Attack >= 0 + && e.Attack < afterBoostAtk); + } + #endregion + } +} \ No newline at end of file diff --git a/WindBot.csproj b/WindBot.csproj index 6fb39ee9..6f161fc6 100644 --- a/WindBot.csproj +++ b/WindBot.csproj @@ -69,6 +69,7 @@ + From 42130b170b4beb9330de4b8a83561965b9bc4c31 Mon Sep 17 00:00:00 2001 From: Reinen <127223416+DaruKani@users.noreply.github.com> Date: Sun, 24 May 2026 19:05:05 +0700 Subject: [PATCH 2/6] Update Deck List --- Decks/AI_Archfiend.ydk | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/Decks/AI_Archfiend.ydk b/Decks/AI_Archfiend.ydk index 0a1748e5..640cdc9c 100644 --- a/Decks/AI_Archfiend.ydk +++ b/Decks/AI_Archfiend.ydk @@ -40,6 +40,7 @@ 94423983 10045474 10045474 +87985506 #extra 11901678 11901678 @@ -55,24 +56,4 @@ 64635042 61248471 61248471 -61248471 -!side -70781052 -26462013 -99161253 -#pickup -#0 -#0 -#0 -#case -#1080001 -#protector -#1070001 -#field -#1090001 -#grave -#1100001 -#stand -#1110001 -#mate -#1000001 \ No newline at end of file +61248471 \ No newline at end of file From 0167d2dc6faed77faa6005fcd481a813235b683b Mon Sep 17 00:00:00 2001 From: Reinen <127223416+DaruKani@users.noreply.github.com> Date: Sun, 24 May 2026 22:08:59 +0700 Subject: [PATCH 3/6] Finish ? Wait for Makourai and Origin CardID --- Game/AI/Decks/Archfiend.cs | 370 +++++++++++++++++++++++++++---------- 1 file changed, 269 insertions(+), 101 deletions(-) diff --git a/Game/AI/Decks/Archfiend.cs b/Game/AI/Decks/Archfiend.cs index a7c7c864..bb3c16e8 100644 --- a/Game/AI/Decks/Archfiend.cs +++ b/Game/AI/Decks/Archfiend.cs @@ -137,15 +137,18 @@ public ArchfiendExecutor(GameAI ai, Duel duel) AddExecutor(ExecutorType.Activate, _CardId.CalledByTheGrave, CalledbytheGraveActivate); AddExecutor(ExecutorType.Activate, _CardId.InfiniteImpermanence, InfiniteImpermanenceActivate); AddExecutor(ExecutorType.Activate, CardId.PMDR, PrimiteDrillbeamActivate); - AddExecutor(ExecutorType.Activate, CardId.Origin, OriginActivate); AddExecutor(ExecutorType.Activate, CardId.RegenSage); //Rough code AddExecutor(ExecutorType.Activate, CardId.Usurpation, Usurpation); AddExecutor(ExecutorType.Activate, CardId.Strategy, StrategyActivate); + AddExecutor(ExecutorType.Activate, CardId.Playtime, Playtime); AddExecutor(ExecutorType.Activate, CardId.Royal, RoyalActivate); AddExecutor(ExecutorType.Activate, CardId.Highness, HighnessActivate); + AddExecutor(ExecutorType.Summon, Level4MonsterSummon); + AddExecutor(ExecutorType.SpSummon, CardId.RegenArch); AddExecutor(ExecutorType.Activate, CardId.RegenArch); - AddExecutor(ExecutorType.Activate, CardId.Playtime, Playtime); + AddExecutor(ExecutorType.SpSummon, CardId.RegenSage); + AddExecutor(ExecutorType.Activate, CardId.Origin, OriginActivate); AddExecutor(ExecutorType.Activate, CardId.Makourai, MakouraiActivate); AddExecutor(ExecutorType.SpSummon, CardId.Origin, OriginSpSummon); @@ -153,7 +156,6 @@ public ArchfiendExecutor(GameAI ai, Duel duel) AddExecutor(ExecutorType.SpSummon, CardId.RegenArch); AddExecutor(ExecutorType.SpSummon, CardId.RegenSage); AddExecutor(ExecutorType.Activate, CardId.PMLL, PrimiteLordlyLodeActivate); - AddExecutor(ExecutorType.Summon, Level4MonsterSummon); AddExecutor(ExecutorType.Activate, CardId.PMBeryl, PrimiteDragonEtherBerylActivate); AddExecutor(ExecutorType.Activate, CardId.PMLL, PrimiteLordlyLodeSpSummon); @@ -169,7 +171,7 @@ public override void OnNewTurn() myTurnCount++; } // reset - summonCount = 0; + summonCount = 1; activatingLodeSpSummonEffect = false; infiniteImpermanenceList.Clear(); currentNegateCardList.Clear(); @@ -920,20 +922,22 @@ public override IList OnSelectCard(IList cards, int min, { if (hint == HintMsg.AddToHand) { - List targetIdList = new List { CardId.PMBeryl, CardId.PMDR }; - if (summonCount == 0) + List targetIdList = new List(); + if (ShouldPMLLSearchDrillbeam()) { - // whether need to search drillbeam - bool canTriggerPrimiteBeam = Bot.Hand.Any(c => c.IsCode(CardId.PMBeryl, CardId.PMLL, CardId.SMSkull)); - canTriggerPrimiteBeam |= Bot.HasInMonstersZone(new List { CardId.SMSkull }, faceUp: true); - canTriggerPrimiteBeam |= Bot.GetSpells().Any(c => (c.IsFacedown() || Duel.CurrentChain.Contains(c))); - if (canTriggerPrimiteBeam) - { - targetIdList.Insert(0, CardId.PMDR); - } + targetIdList.Add(CardId.PMDR); + targetIdList.Add(CardId.PMBeryl); } + else + { + targetIdList.Add(CardId.PMBeryl); + targetIdList.Add(CardId.PMDR); + } + foreach (int targetId in targetIdList) { + if (CheckRemainInDeck(targetId) <= 0) continue; + ClientCard target = cards.FirstOrDefault(c => c.IsCode(targetId)); if (target != null) { @@ -1203,26 +1207,23 @@ public bool PrimiteLordlyLodeActivateCheck() activateFlag |= Bot.HasInGraveyard(CardId.PMDR); activateFlag |= !CheckWhetherNegated(true, true, CardType.Monster); } + if (Bot.HasInHandOrHasInMonstersZone(CardId.SMSkull) && DefaultCheckWhetherBotCanSearch()) + { + // for search drillbeam + activateFlag |= CheckRemainInDeck(CardId.PMDR) > 0; + activateFlag |= summonCount <= 0 && Card.Location == CardLocation.SpellZone && Card.IsFacedown(); + } if (!Bot.HasInSpellZone(CardId.PMLL, true, true)) { // for activate it activateFlag |= DefaultCheckWhetherBotCanSearch(); // for special summon - CardLocation loc; - if (Bot.HasInHand(CardId.SMSkull)) - { - loc = CardLocation.Hand; - } - else if (CheckRemainInDeck(CardId.SMSkull) > 0) - { - loc = CardLocation.Deck; - } - else if (Bot.HasInGraveyard(CardId.SMSkull)) - { - loc = CardLocation.Grave; - } - else + bool hasSMSkull = Bot.HasInHand(CardId.SMSkull) + || CheckRemainInDeck(CardId.SMSkull) > 0 + || Bot.HasInGraveyard(CardId.SMSkull); + + if (!hasSMSkull) { return false; } @@ -1251,6 +1252,12 @@ public bool PrimiteLordlyLodeSpSummon() public bool PrimiteLordlyLodeSpSummonCheck() { + if (Bot.HasInMonstersZone(CardId.SMSkull, faceUp: true)) + { + return false; + } + if (!IsBadHandForPrimiteLodeSpSummon()) + return false; // special summon CardLocation loc; if (Bot.HasInHand(CardId.SMSkull)) @@ -1272,7 +1279,83 @@ public bool PrimiteLordlyLodeSpSummonCheck() int drawCount = GetSpecialSummonDrawCount(loc); return drawCount < 2; } + private bool IsBadHandForPrimiteLodeSpSummon() + { + // ถ้ามี play หลักอื่นอยู่ ยังไม่ถือว่ามือเน่า + if (HasArchfiendPlayableStarter()) + return false; + + // ถ้ายัง Normal Beryl/Highness ได้ ยังไม่ต้องใช้ LL SS + if (summonCount >= 0 && Bot.HasInHand(CardId.PMBeryl)) + return false; + if (summonCount >= 0 && Bot.HasInHand(CardId.Highness) && HasArchfiendCostInGrave()) + return false; + + // ถ้ามี Origin + มี cost ให้เล่นเองได้ ก็ยังไม่เน่า + if (Bot.HasInHand(CardId.Origin) && GetOriginTributeCost() != null) + return false; + + // ถ้ามี Royal แล้วยังไม่ได้ใช้ ก็ยังมี play + if (Bot.HasInHand(CardId.Royal) && !IsArchfiendCardUsedThisTurn(CardId.Royal)) + return false; + + // ถ้ามี Strategy/Usurpation แล้วยังไม่ได้ใช้ ก็ยังมีทางต่อ + if (Bot.HasInHand(CardId.Strategy) && !IsArchfiendCardUsedThisTurn(CardId.Strategy)) + return false; + + if (Bot.HasInHand(CardId.Usurpation) && !IsArchfiendCardUsedThisTurn(CardId.Usurpation)) + return false; + + // ถ้า PMDR อยู่ในมือ/เซ็ตไว้แล้ว ก็ยังมี interaction ไม่จำเป็นต้องฝืน SS + if (Bot.HasInHand(CardId.PMDR) || Bot.HasInSpellZone(CardId.PMDR)) + return false; + + return true; + } + private bool HasArchfiendPlayableStarter() + { + if (Bot.HasInHand(CardId.Royal) && !IsArchfiendCardUsedThisTurn(CardId.Royal)) + return true; + + if (Bot.HasInHand(CardId.Strategy) + && !IsArchfiendCardUsedThisTurn(CardId.Strategy) + && HasArchfiendCostForStrategy()) + return true; + + if (Bot.HasInHand(CardId.Usurpation) + && !IsArchfiendCardUsedThisTurn(CardId.Usurpation) + && !IsArchfiendCardUsedThisTurn(CardId.Playtime) + && GetUsurpationTrapToSet() != 0) + return true; + + if (Bot.HasInHand(CardId.Origin) && GetOriginTributeCost() != null) + return true; + + if (HasRegenArchPlayableWithoutOther2500()) + return true; + + + return false; + } + private bool HasRegenArchPlayableWithoutOther2500() + { + bool hasRegenArch = + Bot.HasInHand(CardId.RegenArch) + || Bot.HasInMonstersZone(CardId.RegenArch, faceUp: true); + + if (!hasRegenArch) + return false; + + bool hasOtherAtk2500Monster = + Bot.GetMonsters().Any(c => + c != null + && c.IsFaceup() + && !c.IsCode(CardId.RegenArch) + && c.Attack == 2500); + + return !hasOtherAtk2500Monster; + } public int GetSpecialSummonDrawCount(CardLocation loc) { int res = 0; @@ -1337,8 +1420,7 @@ public bool Level4MonsterSummon() return true; } } - bool canSummonHighness = Bot.HasInHand(CardId.Highness) && Bot.Graveyard.Any(card => card != null && card.HasSetcode(SetcodeArchfiend)) - && DefaultCheckWhetherBotCanSearch(); + bool canSummonHighness = ShouldNormalSummonHighness(); if (canSummonHighness && Card.IsCode(CardId.Highness)) { @@ -1348,7 +1430,43 @@ public bool Level4MonsterSummon() return false; } + private bool ShouldNormalSummonHighness() + { + if (!Bot.HasInHand(CardId.Highness)) return false; + + if (IsArchfiendCardUsedThisTurn(CardId.Highness)) return false; + + if (Bot.HasInMonstersZone(CardId.Highness, faceUp: true)) return false; + + if (IsArchfiendBoardReady()) return false; + + if (!DefaultCheckWhetherBotCanSearch()) return false; + + if (!Bot.Graveyard.Any(card => card != null && card.HasSetcode(SetcodeArchfiend))) return false; + + return true; + } + private bool ShouldPMLLSearchDrillbeam() + { + if (CheckRemainInDeck(CardId.PMDR) <= 0) return false; + + if (Bot.HasInHand(CardId.PMDR) || Bot.HasInSpellZone(CardId.PMDR)) + return false; + + if (Bot.HasInHandOrHasInMonstersZone(CardId.PMBeryl)) + return true; + + if (Bot.HasInHandOrHasInMonstersZone(CardId.SMSkull)) + return true; + if (Bot.GetSpells().Any(c => c.IsFacedown() || Duel.CurrentChain.Contains(c))) + return true; + + if (summonCount <= 0) + return true; + + return false; + } #endregion #region original code @@ -1356,11 +1474,14 @@ public bool Level4MonsterSummon() private bool Playtime() { if (CheckWhetherNegated(true, true, CardType.Trap)) return false; + if (IsArchfiendBoardReady()) return false; + if (Duel.Player == 0 && Bot.HasInHand(CardId.Royal)) return false; int target = GetPlaytimeTarget(); if (target == 0) return false; AI.SelectCard(target); + activatedCardIdList.Add(Card.Id); return true; } private int GetPlaytimeTarget() @@ -1395,10 +1516,20 @@ private bool Usurpation() SelectSTPlace(Card, true); AI.SelectCard(trapId); + activatedCardIdList.Add(Card.Id); return true; } private int GetUsurpationTrapToSet() { + if(IsArchfiendBoardReady() && CanSetArchfiendTrap(CardId.Simul)) + return CardId.Simul; + + if(Bot.HasInHand(CardId.Royal) && CanSetArchfiendTrap(CardId.Simul)) + return CardId.Simul; + + if (IsArchfiendCardUsedThisTurn(CardId.Royal) && CanSetArchfiendTrap(CardId.Simul)) + return CardId.Simul; + if (Duel.Player == 1 && CanSetArchfiendTrap(CardId.Simul) && ShouldUseSimul()) return CardId.Simul; @@ -1413,7 +1544,7 @@ private int GetUsurpationTrapToSet() private bool Simul() { if (CheckWhetherNegated(true, true, CardType.Trap)) return false; - + activatedCardIdList.Add(Card.Id); return ShouldUseSimul(); } @@ -1446,7 +1577,6 @@ private bool StrategyActivate() { if (CheckWhetherNegated(true, true, CardType.Spell)) return false; - // ถ้าอยู่ในมือ/เซ็ตอยู่ และยังไม่มี Strategy face-up ก็ activate ได้ if (!Bot.HasInSpellZone(CardId.Strategy, true, true)) { SelectSTPlace(Card, true); @@ -1454,7 +1584,6 @@ private bool StrategyActivate() return true; } - // ถ้า face-up อยู่แล้ว ใช้ effect search if (Card.Location == CardLocation.SpellZone && Card.IsFaceup()) { if (!DefaultCheckWhetherBotCanSearch()) return false; @@ -1465,8 +1594,6 @@ private bool StrategyActivate() activatedCardIdList.Add(Card.Id + 1); - // option 0 = banish cost เพื่อ search - AI.SelectOption(0); AI.SelectCard(target); return true; } @@ -1476,14 +1603,12 @@ private bool StrategyActivate() private bool RoyalActivate() { - // Royal เป็น monster effect จาก hand/face-up field if (CheckWhetherNegated()) return false; if (!DefaultCheckWhetherBotCanSearch()) return false; int target = GetArchfiendSearchTarget(exceptId: CardId.Royal); if (target == 0) return false; - // ใช้เพื่อหา starter/ต่อ play และได้ normal summon เพิ่ม activatedCardIdList.Add(Card.Id); AI.SelectCard(target); return true; @@ -1493,7 +1618,6 @@ private bool HighnessActivate() { if (CheckWhetherNegated()) return false; - // กรณี GY effect: ถ้า engine ถามได้ แปลว่า trigger ถูกแล้ว if (Card.Location == CardLocation.Grave) { return true; @@ -1530,75 +1654,114 @@ private int GetArchfiendSearchTarget(int exceptId = 0) { List priority = new List { - CardId.Royal, // starter: search + extra normal - CardId.Highness, // add 2 ถ้ามี grave cost - CardId.Usurpation, // access trap / ritual line - CardId.Playtime, - CardId.Simul, + CardId.Royal, + CardId.Highness, CardId.Origin, - CardId.SMSkull, + CardId.RegenArch, + CardId.Usurpation, CardId.Makourai }; foreach (int id in priority) { if (id == exceptId) continue; - if (CheckRemainInDeck(id) > 0) return id; + if (CheckRemainInDeck(id) <= 0) continue; + if (HasUnusedInHand(id)) continue; + + return id; } return 0; } + private bool HasUnusedInHand(int id) + { + return Bot.HasInHand(id) && !IsArchfiendCardUsedThisTurn(id); + } private List GetHighnessSearchTargets() { - List result = new List(); - - List priority = new List + return new List { - CardId.Royal, - CardId.Usurpation, - CardId.Playtime, - CardId.Simul, CardId.Origin, - CardId.SMSkull, + CardId.RegenArch, CardId.Makourai, - CardId.Strategy - }; - - foreach (int id in priority) - { - if (id == CardId.Highness) continue; - if (CheckRemainInDeck(id) <= 0) continue; - if (result.Contains(id)) continue; + CardId.Usurpation, + CardId.Strategy, + CardId.Royal + } + .Where(id => id != CardId.Highness) + .Where(id => CheckRemainInDeck(id) > 0) + .OrderBy(id => GetHighnessSearchPriority(id)) + .Take(2) + .ToList(); + } + private int GetHighnessSearchPriority(int id) + { + int score = 0; + + // ใบที่ใช้แล้วในเทิร์นนี้ ลด priority หนัก ๆ + if (IsArchfiendCardUsedThisTurn(id)) + score += 1000; + + // มีอยู่แล้วในมือก็ไม่ค่อยอยากหยิบซ้ำ + if (Bot.HasInHand(id)) + score += 200; + + // มี face-up อยู่แล้วก็ลด priority + if (Bot.HasInMonstersZone(id, faceUp: true) || Bot.HasInSpellZone(id, true)) + score += 300; + + // base priority + if (id == CardId.Royal) score += 1; + else if (id == CardId.Usurpation) score += 2; + else if (id == CardId.Origin) score += 3; + else if (id == CardId.Makourai) score += 4; + else if (id == CardId.Strategy) score += 5; + else if (id == CardId.Playtime) score += 6; + else if (id == CardId.Simul) score += 7; + else if (id == CardId.SMSkull) score += 8; + else score += 100; + + return score; + } - result.Add(id); - if (result.Count >= 2) break; - } + private bool IsArchfiendCardUsedThisTurn(int id) + { + if (activatedCardIdList.Contains(id)) return true; + if (activatedCardIdList.Contains(id + 1)) return true; - return result; + return false; } private int GetArchfiendCostPriority(ClientCard card) { if (card == null) return 999; - // อยาก banish ของที่ใช้หมด/ไม่สำคัญก่อน - if (card.IsCode(CardId.Playtime)) return 1; - if (card.IsCode(CardId.Simul)) return 2; - if (card.IsCode(CardId.Usurpation)) return 3; - if (card.IsCode(CardId.Makourai)) return 4; - if (card.IsCode(CardId.Strategy)) return 5; - if (card.IsCode(CardId.Highness)) return 6; - if (card.IsCode(CardId.Royal)) return 7; - if (card.IsCode(CardId.Origin)) return 8; - if (card.IsCode(CardId.SMSkull)) return 9; + int locationScore = 0; + + if (card.Location == CardLocation.Grave) locationScore = 0; + else if (card.Location == CardLocation.Hand) locationScore = 100; + else locationScore = 500; - return 100; + int cardScore = 100; + + + if (card.IsCode(CardId.Playtime) && (card.Location == CardLocation.Hand)) cardScore = 1; + else if (card.IsCode(CardId.Usurpation)) cardScore = 2; + else if (card.IsCode(CardId.Strategy)) cardScore = 3; + else if (card.IsCode(CardId.Highness)) cardScore = 4; + else if (card.IsCode(CardId.Makourai)) cardScore = 6; + else if (card.IsCode(CardId.Royal)) cardScore = 7; + else if (card.IsCode(CardId.Origin)) cardScore = 8; + else if (card.IsCode(CardId.SMSkull)) cardScore = 9; + else if (card.IsCode(CardId.Playtime) && (card.Location == CardLocation.Grave)) cardScore = 501; + else if (card.IsCode(CardId.Simul) && (card.Location == CardLocation.Grave)) cardScore = 502; + + return locationScore + cardScore; } private bool OriginActivate() { if (CheckWhetherNegated()) return false; - // Effect 3: negate monster effect ฝั่งตรงข้าม ถ้าเราคุม Summoned Skull if (Duel.LastChainPlayer == 1 && Util.GetLastChainCard() != null) { ClientCard last = Util.GetLastChainCard(); @@ -1614,7 +1777,6 @@ private bool OriginActivate() return false; } - // Effect 1: จากมือ tribute Archfiend อื่นเพื่อ SS ตัวเอง if (Card.Location == CardLocation.Hand) { ClientCard tribute = GetOriginTributeCost(); @@ -1624,7 +1786,6 @@ private bool OriginActivate() return true; } - // Effect 2: ถ้า summon สำเร็จ เรียก Summoned Skull / Level 6 Fiend 2500 ATK if (Card.Location == CardLocation.MonsterZone) { if (Bot.HasInHand(CardId.SMSkull) || CheckRemainInDeck(CardId.SMSkull) > 0 || Bot.HasInGraveyard(CardId.SMSkull)) @@ -1663,41 +1824,39 @@ private ClientCard GetOriginTributeCost() && c != Card && c.HasSetcode(SetcodeArchfiend))); - // อย่า tribute Summoned Skull ถ้าใช้เป็นเงื่อนไข negate ของ Origin - candidates.RemoveAll(c => c.IsCode(CardId.SMSkull) && !Bot.HasInMonstersZone(CardId.SMSkull, faceUp: true)); - return candidates .OrderBy(c => GetOriginTributePriority(c)) - .FirstOrDefault(); + .FirstOrDefault(c => GetOriginTributePriority(c) < 9999); } private int GetOriginTributePriority(ClientCard card) { - if (card == null) return 999; + if (card == null) return 9999; - // ของที่อยากจ่ายก่อน - if (card.IsCode(CardId.Playtime)) return 1; - if (card.IsCode(CardId.Simul)) return 2; - if (card.IsCode(CardId.Makourai)) return 3; - if (card.IsCode(CardId.Usurpation)) return 4; - if (card.IsCode(CardId.Strategy)) return 5; + if (card.IsCode(CardId.RegenArch) && card.Location == CardLocation.MonsterZone) + return 9999; - // monster ที่พอจ่ายได้ - if (card.IsCode(CardId.RegenArch)) return 10; - if (card.IsCode(CardId.Highness)) return 20; - if (card.IsCode(CardId.Royal)) return 30; + int locationScore = 0; - // ไม่ค่อยอยากจ่าย - if (card.IsCode(CardId.Origin)) return 80; - if (card.IsCode(CardId.SMSkull)) return 90; + if (card.Location == CardLocation.Hand) locationScore = 0; + else if (card.Location == CardLocation.MonsterZone) locationScore = 100; + else return 9999; - return 100; + int cardScore = 100; + + if (card.IsCode(CardId.Highness)&&(card.Location == CardLocation.MonsterZone)) cardScore = -99; + else if (card.IsCode(CardId.SMSkull)) cardScore = 2; + else if (card.IsCode(CardId.Origin)) cardScore = 3; + else if (card.IsCode(CardId.Highness)) cardScore = 4; + else if (card.IsCode(CardId.RegenArch)) cardScore = 5; + else if (card.IsCode(CardId.Royal)) cardScore = 6; + + return locationScore + cardScore; } private bool MakouraiActivate() { if (CheckWhetherNegated(true, true, CardType.Spell)) return false; - // Effect 2: GY effect เก็บ Summoned Skull กลับมือ if (Card.Location == CardLocation.Grave) { if (Duel.Player != 0) return false; @@ -1708,7 +1867,6 @@ private bool MakouraiActivate() return true; } - // Effect 1: Quick-Play จากมือ/สนาม เลือก Archfiend เราเพื่อบัฟ + ล้างมอน ClientCard target = GetBestMakouraiTarget(); if (target == null) return false; @@ -1720,7 +1878,6 @@ private bool MakouraiActivate() bool battlePush = Duel.Phase == DuelPhase.BattleStep || Duel.Phase == DuelPhase.Damage; - // กดถ้าล้างมอนได้ หรือกำลัง Battle เพื่อดัน damage if (!canDestroySomething && !battlePush) return false; if (Card.Location == CardLocation.Hand) @@ -1760,6 +1917,17 @@ private int CountMakouraiDestroyTargets(ClientCard target) && e.Attack >= 0 && e.Attack < afterBoostAtk); } + private bool IsArchfiendBoardReady() + { + bool hasOrigin = Bot.HasInMonstersZone(CardId.Origin, faceUp: true); + bool hasSMSkull = Bot.HasInMonstersZone(CardId.SMSkull, faceUp: true); + bool hasRegen = Bot.HasInHandOrHasInMonstersZone(CardId.RegenArch) && Bot.HasInMonstersZone(CardId.RegenArch, faceUp: true); + + if (hasOrigin && hasSMSkull && hasRegen) + return true; + + return false; + } #endregion } } \ No newline at end of file From 03d789a414e02a644389a43b54c52677471adc04 Mon Sep 17 00:00:00 2001 From: Reinen <127223416+DaruKani@users.noreply.github.com> Date: Tue, 2 Jun 2026 10:54:00 +0700 Subject: [PATCH 4/6] Update Card ID --- Decks/AI_Archfiend.ydk | 12 ++++++------ Game/AI/Decks/Archfiend.cs | 7 +++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Decks/AI_Archfiend.ydk b/Decks/AI_Archfiend.ydk index 640cdc9c..181ddf28 100644 --- a/Decks/AI_Archfiend.ydk +++ b/Decks/AI_Archfiend.ydk @@ -6,9 +6,9 @@ 95718355 95718355 22938501 -100264002 -100264002 -100264002 +79621896 +79621896 +79621896 58769832 58769832 58769832 @@ -23,9 +23,9 @@ 23434538 49238328 49238328 -100264003 -100264003 -100264003 +15725501 +15725501 +15725501 90764871 90764871 90764871 diff --git a/Game/AI/Decks/Archfiend.cs b/Game/AI/Decks/Archfiend.cs index bb3c16e8..a1e1d380 100644 --- a/Game/AI/Decks/Archfiend.cs +++ b/Game/AI/Decks/Archfiend.cs @@ -8,7 +8,6 @@ namespace WindBot.Game.AI.Decks { - // NOT FINISHED YET [Deck("Archfiend", "AI_Archfiend")] public class ArchfiendExecutor : DefaultExecutor { @@ -17,11 +16,11 @@ public class CardId public const int SMSkull = 70781052; public const int RegenArch = 95718355; public const int RegenSage = 22938501; - public const int Origin = 100264002; + public const int Origin = 79621896; public const int Royal = 58769832; public const int PMBeryl = 63198739; public const int Highness = 11248645; - public const int Makourai = 100264003; + public const int Makourai = 15725501; public const int Strategy = 90764871; public const int Usurpation = 82997779; public const int PMLL = 56506740; @@ -138,7 +137,7 @@ public ArchfiendExecutor(GameAI ai, Duel duel) AddExecutor(ExecutorType.Activate, _CardId.InfiniteImpermanence, InfiniteImpermanenceActivate); AddExecutor(ExecutorType.Activate, CardId.PMDR, PrimiteDrillbeamActivate); AddExecutor(ExecutorType.Activate, CardId.RegenSage); - //Rough code + AddExecutor(ExecutorType.Activate, CardId.Usurpation, Usurpation); AddExecutor(ExecutorType.Activate, CardId.Strategy, StrategyActivate); AddExecutor(ExecutorType.Activate, CardId.Playtime, Playtime); From ce2810404b6380871e98ad93e9c5654959182af7 Mon Sep 17 00:00:00 2001 From: Reinen <127223416+DaruKani@users.noreply.github.com> Date: Wed, 15 Jul 2026 10:38:23 +0700 Subject: [PATCH 5/6] Update Deck list Apply lastest Banlist --- Decks/AI_Archfiend.ydk | 1 - Game/AI/Decks/Archfiend.cs | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Decks/AI_Archfiend.ydk b/Decks/AI_Archfiend.ydk index 181ddf28..7d3e7294 100644 --- a/Decks/AI_Archfiend.ydk +++ b/Decks/AI_Archfiend.ydk @@ -35,7 +35,6 @@ 56506740 56506740 56506740 -24224830 29095457 94423983 10045474 diff --git a/Game/AI/Decks/Archfiend.cs b/Game/AI/Decks/Archfiend.cs index a1e1d380..732b63ce 100644 --- a/Game/AI/Decks/Archfiend.cs +++ b/Game/AI/Decks/Archfiend.cs @@ -74,23 +74,23 @@ public class CardId CardId.Makourai, CardId.Strategy, CardId.Usurpation, - CardId.PMLL + CardId.PMLL, } }, { 2, new List { _CardId.AshBlossom, - _CardId.InfiniteImpermanence, CardId.SMSkull, - _CardId.PotOfExtravagance + _CardId.PotOfExtravagance, + _CardId.InfiniteImpermanence } }, { 1, new List { _CardId.MaxxC, - _CardId.CalledByTheGrave, + //_CardId.CalledByTheGrave, CardId.PMDR, CardId.Simul, CardId.RegenSage, @@ -133,7 +133,7 @@ public ArchfiendExecutor(GameAI ai, Duel duel) AddExecutor(ExecutorType.Activate, _CardId.PotOfExtravagance, PotOfExtravaganceActivate); AddExecutor(ExecutorType.Activate, _CardId.MaxxC, MaxxCActivate); AddExecutor(ExecutorType.Activate, _CardId.AshBlossom, AshBlossomActivate); - AddExecutor(ExecutorType.Activate, _CardId.CalledByTheGrave, CalledbytheGraveActivate); + //AddExecutor(ExecutorType.Activate, _CardId.CalledByTheGrave, CalledbytheGraveActivate); AddExecutor(ExecutorType.Activate, _CardId.InfiniteImpermanence, InfiniteImpermanenceActivate); AddExecutor(ExecutorType.Activate, CardId.PMDR, PrimiteDrillbeamActivate); AddExecutor(ExecutorType.Activate, CardId.RegenSage); From 2b204f88605454fc87272b8f208d64d4b769f707 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Mon, 27 Jul 2026 10:04:45 +0800 Subject: [PATCH 6/6] rename --- Game/AI/Decks/{Archfiend.cs => ArchfiendExecutor.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Game/AI/Decks/{Archfiend.cs => ArchfiendExecutor.cs} (100%) diff --git a/Game/AI/Decks/Archfiend.cs b/Game/AI/Decks/ArchfiendExecutor.cs similarity index 100% rename from Game/AI/Decks/Archfiend.cs rename to Game/AI/Decks/ArchfiendExecutor.cs