From dbb7232b28eaa9f5a3b2b75a44aaace6a8a44b7c Mon Sep 17 00:00:00 2001 From: AlphaKretin Date: Mon, 27 Apr 2020 17:53:45 +0930 Subject: [PATCH 01/13] Add first draft --- Decks/AI_Dragma.ydk | 59 +++ Game/AI/Decks/DragmaExecutor.cs | 808 ++++++++++++++++++++++++++++++++ WindBot.csproj | 1 + bots.json | 6 + 4 files changed, 874 insertions(+) create mode 100644 Decks/AI_Dragma.ydk create mode 100644 Game/AI/Decks/DragmaExecutor.cs diff --git a/Decks/AI_Dragma.ydk b/Decks/AI_Dragma.ydk new file mode 100644 index 00000000..91e18312 --- /dev/null +++ b/Decks/AI_Dragma.ydk @@ -0,0 +1,59 @@ +#created by AlphaKretin +#main +69680031 +95679145 +3717252 +60303688 +60303688 +60303688 +86120751 +86120751 +86120751 +14558127 +14558127 +14558127 +1984618 +1984618 +1984618 +25311006 +25311006 +25311006 +73628505 +74063034 +74063034 +24224830 +24224830 +24224830 +48130397 +48130397 +48130397 +47679935 +47679935 +47679935 +10045474 +10045474 +10045474 +82956214 +82956214 +82956214 +21011044 +41420027 +41420027 +41420027 +#extra +75286621 +20366274 +41209827 +69946549 +41373230 +97300502 +50907446 +94977269 +80532587 +80532587 +13529466 +74586817 +98506199 +2220237 +60303245 +!side diff --git a/Game/AI/Decks/DragmaExecutor.cs b/Game/AI/Decks/DragmaExecutor.cs new file mode 100644 index 00000000..92d74b15 --- /dev/null +++ b/Game/AI/Decks/DragmaExecutor.cs @@ -0,0 +1,808 @@ +using System; +using YGOSharp.OCGWrapper.Enums; +using System.Collections.Generic; +using WindBot; +using WindBot.Game; +using WindBot.Game.AI; +using System.Linq; +using YGOSharp.Network.Enums; +using YGOSharp.OCGWrapper; + +namespace WindBot.Game.AI.Decks +{ + [Deck("Dragma", "AI_Dragma")] + class DragmaExecutor : DefaultExecutor + { + public class CardId + { + public const int InvokedAleister = 86120751; + public const int InvokedInvocation = 74063034; + public const int InvokedMeltdown = 47679935; + public const int InvokedTerraforming = 73628505; + public const int InvokedAlmiraj = 2220237; + public const int InvokedGardna = 98506199; + public const int InvokedMechaba = 75286621; + public const int InvokedCaliga = 60303688; + public const int InvokedTower = 97300502; + + public const int DragmaEcclesia = 60303688; + public const int DragmaMaximus = 95679145; + public const int DragmaFleur = 69680031; + public const int DragmaNadir = 1984618; + public const int DragmaPunish = 82956214; + public const int DragmaBastard = 41373230; + + public const int ShaddollApkallone = 50907446; + public const int ShaddollConstruct = 20366274; + public const int ShaddollWinda = 94977269; + public const int ShaddollRuq = 21011044; + public const int ShaddollBeast = 3717252; + + public const int StapleAsh = 14558127; + public const int StapleTalents = 25311006; + public const int StapleCalled = 24224830; + public const int StapleSuperPoly = 48130397; + public const int StapleImperm = 10045474; + public const int StapleJudgment = 41420027; + public const int StapleVenom = 41209827; + public const int StapleDragostapelia = 69946549; + public const int StapleNtss = 80532587; + public const int StapleOmega = 13529466; + public const int StaplePegasus = 74586817; + } + + public DragmaExecutor(GameAI ai, Duel duel) + : base(ai, duel) + { + // priority 1 - interaction + AddExecutor(ExecutorType.Activate, CardId.StapleAsh, DefaultAshBlossomAndJoyousSpring); + AddExecutor(ExecutorType.Activate, CardId.StapleCalled, DefaultCalledByTheGrave); + AddExecutor(ExecutorType.Activate, CardId.StapleImperm, DefaultInfiniteImpermanence); + AddExecutor(ExecutorType.Activate, CardId.StapleJudgment, DefaultSolemnJudgment); + AddExecutor(ExecutorType.Activate, CardId.StapleSuperPoly, SuperPolyEffect); + AddExecutor(ExecutorType.Activate, CardId.InvokedMechaba, MechabaNegate); + AddExecutor(ExecutorType.Activate, CardId.InvokedAleister, AleisterBuff); + AddExecutor(ExecutorType.Activate, CardId.DragmaFleur, FleurSummon); + AddExecutor(ExecutorType.Activate, CardId.ShaddollRuq, RuqEffect); + AddExecutor(ExecutorType.Activate, CardId.DragmaPunish, PunishEffect); + AddExecutor(ExecutorType.Activate, CardId.StapleTalents, TalentsEffect); + AddExecutor(ExecutorType.Activate, CardId.StapleDragostapelia, DragoEff); + + // priority 2 - primary combo (invoked) + AddExecutor(ExecutorType.Activate, CardId.InvokedTerraforming, TerraformingEffect); + AddExecutor(ExecutorType.Activate, CardId.InvokedMeltdown, MeltdownEffect); + AddExecutor(ExecutorType.Activate, CardId.InvokedAleister, AleisterSearch); + AddExecutor(ExecutorType.Summon, CardId.InvokedAleister, AleisterSummon); + AddExecutor(ExecutorType.Summon, CardId.InvokedAlmiraj, AlmirajSummon); + AddExecutor(ExecutorType.Summon, CardId.InvokedGardna, GardnaSummon); + AddExecutor(ExecutorType.Activate, CardId.InvokedInvocation, InvocationFuse); + AddExecutor(ExecutorType.Activate, CardId.InvokedInvocation, InvocationRecur); + + // priority 3 - primary combo (dragma) + AddExecutor(ExecutorType.Activate, CardId.DragmaNadir, NadirEffect); + AddExecutor(ExecutorType.Activate, CardId.DragmaEcclesia, EcclesiaSummon); + AddExecutor(ExecutorType.Summon, CardId.DragmaEcclesia, EcclesiaNormal); + AddExecutor(ExecutorType.Activate, CardId.DragmaEcclesia, EcclesiaSearch); + AddExecutor(ExecutorType.Activate, CardId.ShaddollApkallone, ApkalloneSearch); + AddExecutor(ExecutorType.Activate, CardId.DragmaMaximus, MaximusSummon); + AddExecutor(ExecutorType.Activate, CardId.DragmaMaximus, MaximusMill); + AddExecutor(ExecutorType.Activate, CardId.ShaddollConstruct, ConstructRecover); + AddExecutor(ExecutorType.Activate, CardId.DragmaBastard, BastardSearch); + + // priority 4 - misc. nadir/maximus targets + //AddExecutor(ExecutorType.Activate, CardId.StapleOmega, OmegaRecur); + //AddExecutor(ExecutorType.Activate, CardId.StaplePegasus, PegasusSpin); + + // priority 5 - niche scenarios + //AddExecutor(ExecutorType.Activate, CardId.DragmaFleur, FleurAttack); + //AddExecutor(ExecutorType.Activate, CardId.ShaddollBeast, BeastDraw); + //AddExecutor(ExecutorType.Activate, CardId.ShaddollBeast, BeastFlip); + //AddExecutor(ExecutorType.Activate, CardId.ShaddollConstruct, ConstructMill); + //AddExecutor(ExecutorType.Activate, CardId.StapleVenom, VenomAtk); + //AddExecutor(ExecutorType.Activate, CardId.StapleVenom, VenomEff); + //AddExecutor(ExecutorType.Activate, CardId.StapleVenom, VenomNuke); + //AddExecutor(ExecutorType.Activate, CardId.InvokedTower, TowerDestroy); + //AddExecutor(ExecutorType.Activate, CardId.InvokedTower, TowerBuff); + + // priority 6 - set cards + AddExecutor(ExecutorType.SpellSet, CardId.StapleCalled, TrapSet); + AddExecutor(ExecutorType.SpellSet, CardId.StapleImperm, TrapSet); + AddExecutor(ExecutorType.SpellSet, CardId.StapleSuperPoly, TrapSet); + AddExecutor(ExecutorType.SpellSet, CardId.StapleJudgment, TrapSet); + AddExecutor(ExecutorType.SpellSet, CardId.DragmaPunish, TrapSet); + + } + + private bool FleurAttackUsed; + private bool RuqUsed; + private bool BastardSentThisTurn; + private bool MaximusUsed; + private bool EcclesiaUsed; + + // generic actions + public override bool OnSelectHand() + { + // go first + return true; + } + + public override void OnNewTurn() + { + // reset tracking variables + FleurAttackUsed = false; + BastardSentThisTurn = false; + MaximusUsed = false; + EcclesiaUsed = false; + // Ruq use is not reset because we only expect to use it once + } + + public override IList OnSelectCard(IList cards, int min, int max, long hint, bool cancelable) + { + // select cards + return null; + } + + public override CardPosition OnSelectPosition(int cardId, IList positions) + { + //YGOSharp.OCGWrapper.NamedCard cardData = YGOSharp.OCGWrapper.NamedCard.Get(cardId); + if (cardId == CardId.DragmaMaximus && !Duel.MainPhase.CanBattlePhase) + return CardPosition.FaceUpDefence; + return 0; + } + + public override int OnSelectPlace(long cardId, int player, CardLocation location, int available) + { + if (location == CardLocation.MonsterZone) + { + return available & ~Bot.GetLinkedZones(); + } + return 0; + } + + // update stats for battle prediction based on effects + public override bool OnPreBattleBetween(ClientCard attacker, ClientCard defender) + { + if (!defender.IsMonsterHasPreventActivationEffectInBattle()) + { + if (attacker.IsCode(CardId.ShaddollConstruct) && !attacker.IsDisabled() && defender.IsSpecialSummoned) // TODO: Possible to check destruction immunity? + attacker.RealPower = 9999; + if (attacker.IsCode(CardId.DragmaFleur) && !attacker.IsDisabled() && !FleurAttackUsed) + attacker.RealPower += 500; + if (attacker.HasType(CardType.Fusion) && Bot.HasInHand(CardId.InvokedAleister)) + attacker.RealPower += 1000; + } + return base.OnPreBattleBetween(attacker, defender); + } + + public override ClientCard OnSelectAttacker(IList attackers, IList defenders) + { + // attack with Winda first because if summoned by Ruq it cannot attack directly + ClientCard windaCard = attackers.GetFirstMatchingCard(card => card.IsCode(CardId.ShaddollWinda)); + if (!(windaCard == null) && defenders.IsExistingMatchingCard(card => card.Attack < windaCard.Attack)) + { + return windaCard; + } + + // attack with Construct first because if summoned by Ruq it cannot attack directly, and to use its effect + ClientCard constructCard = attackers.GetFirstMatchingCard(card => card.IsCode(CardId.ShaddollConstruct)); + if (!(constructCard == null) && !constructCard.IsDisabled() + && defenders.IsExistingMatchingCard(card => (card.IsSpecialSummoned && !card.IsMonsterHasPreventActivationEffectInBattle()) + || card.Attack < constructCard.Attack)) + { + return constructCard; + } + + // attack with Fleur-de-lis first to get attack buff on all Dragmas + ClientCard fleurCard = attackers.GetFirstMatchingCard(card => card.IsCode(CardId.DragmaFleur)); + if (!(fleurCard == null)) + { + if (defenders.IsExistingMatchingCard(card => card.Attack < fleurCard.RealPower)) + { + return fleurCard; + } + } + + return base.OnSelectAttacker(attackers, defenders); + } + + // priority 1 - interaction + + private readonly int[] discardBlacklist = + { + CardId.InvokedAleister, + CardId.InvokedInvocation, + CardId.DragmaFleur + }; + + private bool SuperPolyEffect() + { + // check that we won't be discarding an important card to activate + IList list = Bot.Hand.GetMatchingCards(card => !card.IsCode(discardBlacklist)); + if (list.Count == 0) + { + return false; + } + AI.SelectCard(list); + + // make sure we're using opponent's materials + List enemyMonsters = Enemy.GetMonsters(); + + // Dragostapelia = 1 Fusion + 1 DARK + if (Bot.ExtraDeck.ContainsCardWithId(CardId.StapleDragostapelia) + && enemyMonsters.IsExistingMatchingCard(card => card.HasType(CardType.Fusion)) + && (enemyMonsters.IsExistingMatchingCard(card => card.HasAttribute(CardAttribute.Dark) && !card.HasType(CardType.Fusion)) + || enemyMonsters.IsExistingMatchingCard(card => card.HasAttribute(CardAttribute.Dark), 2))) + { + AI.SelectNextCard(CardId.StapleDragostapelia); + AI.SelectMaterials(enemyMonsters); + return true; + } + + // Starving Venom = 2 DARK + if (Bot.ExtraDeck.ContainsCardWithId(CardId.StapleVenom) + && enemyMonsters.IsExistingMatchingCard(card => card.HasAttribute(CardAttribute.Dark), 2)) + { + AI.SelectNextCard(CardId.StapleVenom); + AI.SelectMaterials(enemyMonsters); + return true; + } + + return false; + } + + private bool MechabaNegate() + { + // don't negate self + if (Duel.LastChainPlayer == 0) + { + return false; + } + + // check that we won't be discarding an important card to activate + IList list = Bot.Hand.GetMatchingCards(card => !card.IsCode(discardBlacklist)); + if (list.Count == 0) + { + return false; + } + AI.SelectCard(list); + + return true; + } + + private bool AleisterBuff() + { + // activate only in damage calc + if (!(Duel.Phase == DuelPhase.DamageCal)) + return false; + + // activate only if fighting a monster where it makes the difference + ClientCard myMonster = Bot.BattlingMonster; + if (!myMonster.HasType(CardType.Fusion)) { + return false; + } + ClientCard oppMonster = Enemy.BattlingMonster; + if (oppMonster != null) + { + int diff = oppMonster.RealPower - myMonster.Attack; + if (diff > 0 && (diff < 1000) || Bot.LifePoints - diff < 0) + { + AI.SelectCard(myMonster); + return true; + } + } + if (Enemy.LifePoints - myMonster.Attack < 1000) + { + AI.SelectCard(myMonster); + return true; + } + + return false; + } + + private bool FleurSummon() + { + // summon for body at end of main + if (Duel.Player == 1 && (Duel.MainPhase.CanBattlePhase || Duel.MainPhase.CanEndPhase)) + { + AI.SelectCard(Util.GetBestEnemyMonster()); + return true; + } + + // summon to negate + ClientCard chainCard = Util.GetLastChainCard(); + if (Duel.LastChainPlayer == 1 && chainCard != null) + { + AI.SelectCard(chainCard); + return true; + } + + return false; + } + + private bool PunishEffect() + { + // don't lock ourselves out of extra if we have Ruq to use + if (Bot.HasInSpellZone(CardId.ShaddollRuq) && !RuqUsed) + { + return false; + } + + ClientCard enemyMon = Util.GetProblematicEnemyMonster(); + if (enemyMon != null) + { + // don't use if could wait for N'tss pop to be live + if (Bot.HasInExtra(CardId.StapleNtss) && enemyMon.Attack <= 2500 && Enemy.GetFieldCount() > 1) + { + AI.SelectCard(enemyMon); + AI.SelectNextCard(CardId.StapleNtss); + return true; + } + + if (Bot.HasInExtra(CardId.StapleOmega) && enemyMon.Attack <= 2800 && Bot.Graveyard.Count > 0) + { + AI.SelectCard(enemyMon); + AI.SelectNextCard(CardId.StapleOmega); + return true; + } + + if (Bot.HasInExtra(CardId.StaplePegasus) && enemyMon.Attack <= 2300) + { + AI.SelectCard(enemyMon); + AI.SelectNextCard(CardId.StaplePegasus); + } + } + + return false; + } + + private bool RuqEffect() + { + // Winda = 1 Shaddoll + 1 DARK + if (Bot.ExtraDeck.ContainsCardWithId(CardId.ShaddollWinda) + && Bot.Graveyard.IsExistingMatchingCard(card => card.HasSetcode(0x9d)) + && (Bot.Graveyard.IsExistingMatchingCard(card => card.HasAttribute(CardAttribute.Dark) && !card.HasSetcode(0x9d)) + || Bot.Graveyard.IsExistingMatchingCard(card => card.HasAttribute(CardAttribute.Dark), 2))) + { + AI.SelectCard(CardId.ShaddollWinda); + AI.SelectMaterials(CardLocation.Grave); + RuqUsed = true; + return true; + } + + // Construct = 1 Shaddoll + 1 LIGHT + if (Bot.ExtraDeck.ContainsCardWithId(CardId.ShaddollConstruct) + && Bot.Graveyard.IsExistingMatchingCard(card => card.HasSetcode(0x9d)) + && (Bot.Graveyard.IsExistingMatchingCard(card => card.HasAttribute(CardAttribute.Light) && !card.HasSetcode(0x9d)) + || Bot.Graveyard.IsExistingMatchingCard(card => card.HasAttribute(CardAttribute.Light), 2))) + { + AI.SelectCard(CardId.ShaddollConstruct); + AI.SelectMaterials(CardLocation.Grave); + RuqUsed = true; + return true; + } + + return false; + } + + private static class TalentOptions + { + public const int draw = 0; + public const int control = 1; + public const int hand = 2; + } + + private bool TalentsEffect() + { + + + // take control + ClientCard enemyMon = Util.GetBestEnemyMonster(); + if (enemyMon != null) + { + AI.SelectOption(TalentOptions.control); + AI.SelectCard(enemyMon); + return true; + } + + // hand loop + if (Enemy.GetHandCount() > 0) + { + AI.SelectOption(TalentOptions.hand); + AI.SelectCard(CardLocation.Hand); + return true; + } + + // draw + AI.SelectOption(TalentOptions.draw); + return true; + } + + private bool DragoEff() + { + // summon to negate + ClientCard chainCard = Util.GetLastChainCard(); + if (Duel.LastChainPlayer == 1 && chainCard != null && !chainCard.IsShouldNotBeMonsterTarget()) // TODO: Check for already has counter + { + AI.SelectCard(chainCard); + return true; + } + + if (Duel.Phase == DuelPhase.End) + { + ClientCard enemyMon = Util.GetBestEnemyMonster(); + if (enemyMon != null) + { + AI.SelectCard(enemyMon); + } + return true; + } + + return false; + } + + // priority 2 - combo (invoked) + + private bool TerraformingEffect() + { + AI.SelectCard(CardId.InvokedMeltdown); + return true; + } + + private bool MeltdownEffect() + { + AI.SelectYesNo(true); + return true; + } + + private bool AleisterSearch() + { + return true; + } + + private bool AleisterSummon() + { + return true; + } + + private bool AlmirajSummon() + { + if (Bot.HasInMonstersZone(CardId.InvokedAleister)) + { + AI.SelectMaterials(CardId.InvokedAleister); + return true; + } + + return false; + } + + private bool GardnaSummon() + { + if (Bot.HasInMonstersZone(CardId.InvokedAlmiraj)) + { + AI.SelectMaterials(CardId.InvokedAlmiraj); + return true; + } + + return false; + } + + private bool InvocationFuse() + { + IList lightCards = Enemy.Graveyard.GetMatchingCards(card => card.HasAttribute(CardAttribute.Light)); + if (lightCards.Count > 0) + { + AI.SelectCard(CardId.InvokedMechaba); + AI.SelectMaterials(lightCards); + return true; + } + + if (Bot.Graveyard.GetCardCount(CardId.InvokedGardna) > 0) + { + AI.SelectCard(CardId.InvokedMechaba); + AI.SelectMaterials(CardId.InvokedGardna); + return true; + } + + if (Bot.Graveyard.GetMatchingCardsCount(card => card.HasAttribute(CardAttribute.Light)) > 0) + { + AI.SelectCard(CardId.InvokedMechaba); + AI.SelectMaterials(CardLocation.Grave); + return true; + } + + IList darkCards = Enemy.Graveyard.GetMatchingCards(card => card.HasAttribute(CardAttribute.Dark)); + if (darkCards.Count > 0) + { + AI.SelectCard(CardId.InvokedCaliga); + AI.SelectMaterials(darkCards); + return true; + } + + return false; + } + + private bool InvocationRecur() + { + return true; + } + + // priority 3 - dragma combo + + private bool NadirEffect() + { + bool use = false; + if (Bot.HasInExtra(CardId.ShaddollApkallone)) + { + AI.SelectCard(CardId.ShaddollApkallone); + use = true; + } else if (Bot.HasInExtra(CardId.StapleNtss) && Enemy.GetFieldCount() > 0) + { + AI.SelectCard(CardId.StapleNtss); + use = true; + } else if (Bot.HasInExtra(CardId.StapleOmega) && Bot.Graveyard.Count > 0) + { + AI.SelectCard(CardId.StapleOmega); + use = true; + } else if (Bot.HasInExtra(CardId.StaplePegasus)) + { + AI.SelectCard(CardId.StaplePegasus); + use = true; + } else if (Bot.HasInExtra(CardId.StapleOmega)) + { + AI.SelectCard(CardId.StapleOmega); + use = true; + } + + if (!use) + return false; + + if (!Bot.HasInHand(CardId.DragmaEcclesia)) + { + AI.SelectNextCard(CardId.DragmaEcclesia); + } else if (!Bot.HasInHand(CardId.DragmaMaximus)) + { + AI.SelectNextCard(CardId.DragmaMaximus); + } else + { + AI.SelectNextCard(CardId.DragmaFleur); + } + + return true; + } + + private bool EcclesiaSummon() + { + return true; + } + + private bool EcclesiaNormal() + { + return true; + } + + private bool EcclesiaSearch() + { + if (!Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.DragmaMaximus)) + { + AI.SelectCard(CardId.DragmaMaximus); + EcclesiaUsed = true; + return true; + } + + if (!Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.DragmaFleur) && !BastardSentThisTurn) + { + AI.SelectCard(CardId.DragmaFleur); + EcclesiaUsed = true; + return true; + } + + if (!Bot.HasInHand(CardId.DragmaPunish)) + { + AI.SelectCard(CardId.DragmaPunish); + EcclesiaUsed = true; + return true; + } + + return false; + } + + private bool ApkalloneSearch() + { + if (!Bot.HasInHandOrInSpellZoneOrInGraveyard(CardId.ShaddollRuq)) + { + AI.SelectCard(CardId.ShaddollRuq); + if (!MaximusUsed && Bot.HasInExtra(CardId.ShaddollConstruct)) + { + // discard and fetch with construct + AI.SelectNextCard(CardId.ShaddollRuq); + } + // get hand before adding Ruq, so shouldn't discard + AI.SelectNextCard(Bot.Hand); + return true; + } + + AI.SelectCard(CardId.ShaddollBeast); + AI.SelectNextCard(CardId.ShaddollBeast); + return true; + } + + private bool MaximusSummon() + { + if (Bot.HasInGraveyard(CardId.InvokedAlmiraj)) + { + AI.SelectCard(CardId.InvokedAlmiraj); + return true; + } + + if (Bot.HasInGraveyard(CardId.ShaddollApkallone)) + { + AI.SelectCard(CardId.ShaddollApkallone); + return true; + } + + if (Bot.HasInGraveyard(CardId.DragmaBastard) && !BastardSentThisTurn) + { + AI.SelectCard(CardId.DragmaBastard); + return true; + } + + int[] miscBanishes = + { + CardId.StapleNtss, + CardId.StapleVenom, + CardId.StapleDragostapelia, + CardId.ShaddollWinda, + CardId.ShaddollConstruct, + CardId.InvokedMechaba, + CardId.InvokedCaliga, + CardId.InvokedTower + }; + + if (Bot.HasInGraveyard(miscBanishes)) + { + AI.SelectCard(miscBanishes); + return true; + } + + return false; + } + + private bool MaximusMill() + { + int cards = 0; + if (Bot.HasInExtra(CardId.ShaddollApkallone)) + { + AI.SelectCard(CardId.ShaddollApkallone); + cards++; + } + + if (Bot.HasInGraveyard(CardId.ShaddollRuq) && Bot.HasInExtra(CardId.ShaddollConstruct)) + { + if (cards == 0) + AI.SelectCard(CardId.ShaddollConstruct); + else + AI.SelectNextCard(CardId.ShaddollConstruct); + cards++; + } + if (cards == 2) + { + MaximusUsed = true; + return true; + } + + if (Bot.HasInExtra(CardId.DragmaBastard)) + { + if (cards == 0) + AI.SelectCard(CardId.DragmaBastard); + else + AI.SelectNextCard(CardId.DragmaBastard); + cards++; + BastardSentThisTurn = true; + } + if (cards == 2) + { + MaximusUsed = true; + return true; + } + + if (Bot.HasInExtra(CardId.StapleNtss) && Enemy.GetFieldCount() > 0) + { + if (cards == 0) + AI.SelectCard(CardId.StapleNtss); + else + AI.SelectNextCard(CardId.StapleNtss); + cards++; + } + if (cards == 2) + { + MaximusUsed = true; + return true; + } + + if (Bot.HasInExtra(CardId.StapleOmega) && Bot.Graveyard.Count > 0) + { + if (cards == 0) + AI.SelectCard(CardId.StapleOmega); + else + AI.SelectNextCard(CardId.StapleOmega); + cards++; + } + if (cards == 2) + { + MaximusUsed = true; + return true; + } + + if (Bot.HasInExtra(CardId.StaplePegasus)) + { + if (cards == 0) + AI.SelectCard(CardId.StaplePegasus); + else + AI.SelectNextCard(CardId.StaplePegasus); + cards++; + } + if (cards == 2) + { + MaximusUsed = true; + return true; + } + + if (cards == 1 && Bot.HasInExtra(CardId.StapleOmega)) + { + AI.SelectNextCard(CardId.DragmaBastard); + MaximusUsed = true; + return true; + } + + return false; + } + + private bool ConstructRecover() + { + if (Bot.HasInGraveyard(CardId.ShaddollRuq)) + { + AI.SelectCard(CardId.ShaddollRuq); + return true; + } + + return false; + } + + private static class BastardOptions + { + public const int search = 0; + public const int summon = 1; + } + + private bool BastardSearch() + { + if (!EcclesiaUsed) + { + AI.SelectCard(CardId.DragmaEcclesia); + AI.SelectOption(BastardOptions.summon); + return true; + } + + if (!Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.DragmaFleur)) + { + AI.SelectCard(CardId.DragmaFleur); + AI.SelectOption(BastardOptions.search); + return true; + } + + return false; + } + + // + + private bool TrapSet() + { + if ((Bot.HasInHandOrInSpellZone(CardId.StapleSuperPoly) || Bot.HasInMonstersZone(CardId.InvokedMechaba)) && Bot.GetHandCount() == 1) + return false; + if (!Util.IsTurn1OrMain2()) + return false; + AI.SelectPlace(Zones.z0 + Zones.z1 + Zones.z3 + Zones.z4); + return true; + } + } +} diff --git a/WindBot.csproj b/WindBot.csproj index 9e3f48a8..82e51ae1 100644 --- a/WindBot.csproj +++ b/WindBot.csproj @@ -139,6 +139,7 @@ + diff --git a/bots.json b/bots.json index 17e9efc8..b48f7e7c 100644 --- a/bots.json +++ b/bots.json @@ -47,6 +47,12 @@ "difficulty": 2, "masterRules": [3, 4, 5] }, + { + "name": "Dragma", + "deck": "Dragma", + "difficulty": 3, + "masterRules": [5] + } { "name": "Dragun of Red-Eyes", "deck": "Dragun", From 64b542de57f1f0cef812421d4f7252c766e02de2 Mon Sep 17 00:00:00 2001 From: AlphaKretin Date: Mon, 27 Apr 2020 18:56:55 +0930 Subject: [PATCH 02/13] Various fixes - Wrong IDs - Doubled up executors Still outstanding bugs - Shaddoll Ruq not activating - Fleur not summoning at end of MP --- Game/AI/Decks/DragmaExecutor.cs | 103 +++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 36 deletions(-) diff --git a/Game/AI/Decks/DragmaExecutor.cs b/Game/AI/Decks/DragmaExecutor.cs index 92d74b15..516056c4 100644 --- a/Game/AI/Decks/DragmaExecutor.cs +++ b/Game/AI/Decks/DragmaExecutor.cs @@ -19,10 +19,10 @@ public class CardId public const int InvokedInvocation = 74063034; public const int InvokedMeltdown = 47679935; public const int InvokedTerraforming = 73628505; - public const int InvokedAlmiraj = 2220237; - public const int InvokedGardna = 98506199; + public const int InvokedAlmiraj = 60303245; + public const int InvokedGardna = 2220237; public const int InvokedMechaba = 75286621; - public const int InvokedCaliga = 60303688; + public const int InvokedCaliga = 13529466; public const int InvokedTower = 97300502; public const int DragmaEcclesia = 60303688; @@ -47,8 +47,8 @@ public class CardId public const int StapleVenom = 41209827; public const int StapleDragostapelia = 69946549; public const int StapleNtss = 80532587; - public const int StapleOmega = 13529466; - public const int StaplePegasus = 74586817; + public const int StapleOmega = 74586817; + public const int StaplePegasus = 98506199; } public DragmaExecutor(GameAI ai, Duel duel) @@ -61,7 +61,7 @@ public DragmaExecutor(GameAI ai, Duel duel) AddExecutor(ExecutorType.Activate, CardId.StapleJudgment, DefaultSolemnJudgment); AddExecutor(ExecutorType.Activate, CardId.StapleSuperPoly, SuperPolyEffect); AddExecutor(ExecutorType.Activate, CardId.InvokedMechaba, MechabaNegate); - AddExecutor(ExecutorType.Activate, CardId.InvokedAleister, AleisterBuff); + AddExecutor(ExecutorType.Activate, CardId.InvokedAleister, AleisterEffect); AddExecutor(ExecutorType.Activate, CardId.DragmaFleur, FleurSummon); AddExecutor(ExecutorType.Activate, CardId.ShaddollRuq, RuqEffect); AddExecutor(ExecutorType.Activate, CardId.DragmaPunish, PunishEffect); @@ -71,18 +71,16 @@ public DragmaExecutor(GameAI ai, Duel duel) // priority 2 - primary combo (invoked) AddExecutor(ExecutorType.Activate, CardId.InvokedTerraforming, TerraformingEffect); AddExecutor(ExecutorType.Activate, CardId.InvokedMeltdown, MeltdownEffect); - AddExecutor(ExecutorType.Activate, CardId.InvokedAleister, AleisterSearch); AddExecutor(ExecutorType.Summon, CardId.InvokedAleister, AleisterSummon); - AddExecutor(ExecutorType.Summon, CardId.InvokedAlmiraj, AlmirajSummon); - AddExecutor(ExecutorType.Summon, CardId.InvokedGardna, GardnaSummon); - AddExecutor(ExecutorType.Activate, CardId.InvokedInvocation, InvocationFuse); - AddExecutor(ExecutorType.Activate, CardId.InvokedInvocation, InvocationRecur); + AddExecutor(ExecutorType.SpSummon, CardId.InvokedAlmiraj, AlmirajSummon); + // Aleister search handled above + AddExecutor(ExecutorType.SpSummon, CardId.InvokedGardna, GardnaSummon); + AddExecutor(ExecutorType.Activate, CardId.InvokedInvocation, InvocationEffect); // priority 3 - primary combo (dragma) AddExecutor(ExecutorType.Activate, CardId.DragmaNadir, NadirEffect); - AddExecutor(ExecutorType.Activate, CardId.DragmaEcclesia, EcclesiaSummon); AddExecutor(ExecutorType.Summon, CardId.DragmaEcclesia, EcclesiaNormal); - AddExecutor(ExecutorType.Activate, CardId.DragmaEcclesia, EcclesiaSearch); + AddExecutor(ExecutorType.Activate, CardId.DragmaEcclesia, EcclesiaEffect); AddExecutor(ExecutorType.Activate, CardId.ShaddollApkallone, ApkalloneSearch); AddExecutor(ExecutorType.Activate, CardId.DragmaMaximus, MaximusSummon); AddExecutor(ExecutorType.Activate, CardId.DragmaMaximus, MaximusMill); @@ -90,6 +88,7 @@ public DragmaExecutor(GameAI ai, Duel duel) AddExecutor(ExecutorType.Activate, CardId.DragmaBastard, BastardSearch); // priority 4 - misc. nadir/maximus targets + AddExecutor(ExecutorType.Activate, CardId.StapleNtss, NtssPop); //AddExecutor(ExecutorType.Activate, CardId.StapleOmega, OmegaRecur); //AddExecutor(ExecutorType.Activate, CardId.StaplePegasus, PegasusSpin); @@ -105,12 +104,12 @@ public DragmaExecutor(GameAI ai, Duel duel) //AddExecutor(ExecutorType.Activate, CardId.InvokedTower, TowerBuff); // priority 6 - set cards + AddExecutor(ExecutorType.SpellSet, CardId.DragmaPunish, RuqSet); AddExecutor(ExecutorType.SpellSet, CardId.StapleCalled, TrapSet); AddExecutor(ExecutorType.SpellSet, CardId.StapleImperm, TrapSet); AddExecutor(ExecutorType.SpellSet, CardId.StapleSuperPoly, TrapSet); AddExecutor(ExecutorType.SpellSet, CardId.StapleJudgment, TrapSet); AddExecutor(ExecutorType.SpellSet, CardId.DragmaPunish, TrapSet); - } private bool FleurAttackUsed; @@ -269,8 +268,14 @@ private bool MechabaNegate() return true; } - private bool AleisterBuff() + private bool AleisterEffect() { + // search effect + if (Card.Location == CardLocation.MonsterZone) + { + return true; + } + // activate only in damage calc if (!(Duel.Phase == DuelPhase.DamageCal)) return false; @@ -301,6 +306,12 @@ private bool AleisterBuff() private bool FleurSummon() { + // battle phase buff + if (Card.Location == CardLocation.MonsterZone) + { + return true; + } + // summon for body at end of main if (Duel.Player == 1 && (Duel.MainPhase.CanBattlePhase || Duel.MainPhase.CanEndPhase)) { @@ -310,7 +321,7 @@ private bool FleurSummon() // summon to negate ClientCard chainCard = Util.GetLastChainCard(); - if (Duel.LastChainPlayer == 1 && chainCard != null) + if (Duel.LastChainPlayer == 1 && chainCard != null && chainCard.Location == CardLocation.MonsterZone) { AI.SelectCard(chainCard); return true; @@ -357,6 +368,12 @@ private bool PunishEffect() private bool RuqEffect() { + if (Card.IsFacedown()) + { + AI.SelectYesNo(false); + return true; + } + // Winda = 1 Shaddoll + 1 DARK if (Bot.ExtraDeck.ContainsCardWithId(CardId.ShaddollWinda) && Bot.Graveyard.IsExistingMatchingCard(card => card.HasSetcode(0x9d)) @@ -454,11 +471,6 @@ private bool MeltdownEffect() return true; } - private bool AleisterSearch() - { - return true; - } - private bool AleisterSummon() { return true; @@ -486,8 +498,13 @@ private bool GardnaSummon() return false; } - private bool InvocationFuse() + private bool InvocationEffect() { + // shuffle effect + if (Card.Location == CardLocation.Grave) + { + return true; + } IList lightCards = Enemy.Graveyard.GetMatchingCards(card => card.HasAttribute(CardAttribute.Light)); if (lightCards.Count > 0) { @@ -496,7 +513,7 @@ private bool InvocationFuse() return true; } - if (Bot.Graveyard.GetCardCount(CardId.InvokedGardna) > 0) + if (Bot.HasInMonstersZoneOrInGraveyard(CardId.InvokedGardna)) { AI.SelectCard(CardId.InvokedMechaba); AI.SelectMaterials(CardId.InvokedGardna); @@ -521,11 +538,6 @@ private bool InvocationFuse() return false; } - private bool InvocationRecur() - { - return true; - } - // priority 3 - dragma combo private bool NadirEffect() @@ -570,18 +582,18 @@ private bool NadirEffect() return true; } - private bool EcclesiaSummon() - { - return true; - } - private bool EcclesiaNormal() { return true; } - private bool EcclesiaSearch() + private bool EcclesiaEffect() { + if (Card.Location == CardLocation.Hand) + { + return true; + } + if (!Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.DragmaMaximus)) { AI.SelectCard(CardId.DragmaMaximus); @@ -589,7 +601,7 @@ private bool EcclesiaSearch() return true; } - if (!Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.DragmaFleur) && !BastardSentThisTurn) + if (!Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.DragmaFleur) && !MaximusUsed && !BastardSentThisTurn) { AI.SelectCard(CardId.DragmaFleur); EcclesiaUsed = true; @@ -785,6 +797,8 @@ private bool BastardSearch() if (!Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.DragmaFleur)) { + // let ecclesia search fleur + BastardSentThisTurn = false; AI.SelectCard(CardId.DragmaFleur); AI.SelectOption(BastardOptions.search); return true; @@ -793,7 +807,24 @@ private bool BastardSearch() return false; } - // + // priority 5 - misc send targets + private bool NtssPop() + { + ClientCard bestCard = Util.GetBestEnemyCard(false, true); + if (bestCard != null) + { + AI.SelectCard(bestCard); + return true; + } + return false; + } + + // priority 6 - set backrow + + private bool RuqSet() + { + return true; + } private bool TrapSet() { From 66c5968904e1935544dd3e1cf95cad5d9b546158 Mon Sep 17 00:00:00 2001 From: AlphaKretin Date: Mon, 27 Apr 2020 19:05:42 +0930 Subject: [PATCH 03/13] Fix "bots.json" Typoes --- bots.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bots.json b/bots.json index b48f7e7c..35b2df08 100644 --- a/bots.json +++ b/bots.json @@ -29,7 +29,7 @@ "difficulty": 0, "masterRules": [3, 4, 5] }, - { + { "name": "Cyberse", "deck": "ST1732", "difficulty": 2, @@ -48,11 +48,11 @@ "masterRules": [3, 4, 5] }, { - "name": "Dragma", - "deck": "Dragma", - "difficulty": 3, - "masterRules": [5] - } + "name": "Dragma", + "deck": "Dragma", + "difficulty": 3, + "masterRules": [5] + }, { "name": "Dragun of Red-Eyes", "deck": "Dragun", From b056f1a44cebab7c205a76a709b21d4dff66766c Mon Sep 17 00:00:00 2001 From: AlphaKretin Date: Tue, 28 Apr 2020 22:28:21 +0930 Subject: [PATCH 04/13] Add executors for more scenarios GY targets, niche summons, etc. Also updated Invocation to be able to summon Augoeides. --- Game/AI/Decks/DragmaExecutor.cs | 125 ++++++++++++++++++++++++++++---- 1 file changed, 109 insertions(+), 16 deletions(-) diff --git a/Game/AI/Decks/DragmaExecutor.cs b/Game/AI/Decks/DragmaExecutor.cs index 516056c4..5dc12543 100644 --- a/Game/AI/Decks/DragmaExecutor.cs +++ b/Game/AI/Decks/DragmaExecutor.cs @@ -1,4 +1,4 @@ -using System; +using System; using YGOSharp.OCGWrapper.Enums; using System.Collections.Generic; using WindBot; @@ -89,19 +89,14 @@ public DragmaExecutor(GameAI ai, Duel duel) // priority 4 - misc. nadir/maximus targets AddExecutor(ExecutorType.Activate, CardId.StapleNtss, NtssPop); - //AddExecutor(ExecutorType.Activate, CardId.StapleOmega, OmegaRecur); - //AddExecutor(ExecutorType.Activate, CardId.StaplePegasus, PegasusSpin); + AddExecutor(ExecutorType.Activate, CardId.StapleOmega, OmegaRecur); + AddExecutor(ExecutorType.Activate, CardId.StaplePegasus, PegasusSpin); // priority 5 - niche scenarios - //AddExecutor(ExecutorType.Activate, CardId.DragmaFleur, FleurAttack); - //AddExecutor(ExecutorType.Activate, CardId.ShaddollBeast, BeastDraw); - //AddExecutor(ExecutorType.Activate, CardId.ShaddollBeast, BeastFlip); - //AddExecutor(ExecutorType.Activate, CardId.ShaddollConstruct, ConstructMill); - //AddExecutor(ExecutorType.Activate, CardId.StapleVenom, VenomAtk); - //AddExecutor(ExecutorType.Activate, CardId.StapleVenom, VenomEff); - //AddExecutor(ExecutorType.Activate, CardId.StapleVenom, VenomNuke); - //AddExecutor(ExecutorType.Activate, CardId.InvokedTower, TowerDestroy); - //AddExecutor(ExecutorType.Activate, CardId.InvokedTower, TowerBuff); + AddExecutor(ExecutorType.Activate, CardId.ShaddollBeast, BeastEffect); + AddExecutor(ExecutorType.Activate, CardId.ShaddollConstruct, ConstructMill); + AddExecutor(ExecutorType.Activate, CardId.StapleVenom, VenomEff); + AddExecutor(ExecutorType.Activate, CardId.InvokedTower, TowerEff); // priority 6 - set cards AddExecutor(ExecutorType.SpellSet, CardId.DragmaPunish, RuqSet); @@ -338,7 +333,7 @@ private bool PunishEffect() return false; } - ClientCard enemyMon = Util.GetProblematicEnemyMonster(); + ClientCard enemyMon = Util.GetProblematicEnemyMonster(0, true); if (enemyMon != null) { // don't use if could wait for N'tss pop to be live @@ -446,7 +441,7 @@ private bool DragoEff() if (Duel.Phase == DuelPhase.End) { - ClientCard enemyMon = Util.GetBestEnemyMonster(); + ClientCard enemyMon = Util.GetBestEnemyMonster(true, true); if (enemyMon != null) { AI.SelectCard(enemyMon); @@ -505,6 +500,7 @@ private bool InvocationEffect() { return true; } + IList lightCards = Enemy.Graveyard.GetMatchingCards(card => card.HasAttribute(CardAttribute.Light)); if (lightCards.Count > 0) { @@ -520,6 +516,14 @@ private bool InvocationEffect() return true; } + IList fusionCards = Enemy.Graveyard.GetMatchingCards(card => card.HasType(CardType.Fusion)); + if (fusionCards.Count > 0) + { + AI.SelectCard(CardId.InvokedTower); + AI.SelectMaterials(fusionCards); + return true; + } + if (Bot.Graveyard.GetMatchingCardsCount(card => card.HasAttribute(CardAttribute.Light)) > 0) { AI.SelectCard(CardId.InvokedMechaba); @@ -527,6 +531,21 @@ private bool InvocationEffect() return true; } + // don't banish a reserved combo piece for Augoeides + int[] reservedBanish = + { + CardId.ShaddollApkallone, + CardId.ShaddollConstruct, + CardId.DragmaBastard + }; + IList selfFusionCards = Bot.Graveyard.GetMatchingCards(card => card.HasType(CardType.Fusion) && !card.IsCode(reservedBanish)); + if (selfFusionCards.Count > 0) + { + AI.SelectCard(CardId.InvokedTower); + AI.SelectMaterials(selfFusionCards); + return true; + } + IList darkCards = Enemy.Graveyard.GetMatchingCards(card => card.HasAttribute(CardAttribute.Dark)); if (darkCards.Count > 0) { @@ -628,7 +647,8 @@ private bool ApkalloneSearch() // discard and fetch with construct AI.SelectNextCard(CardId.ShaddollRuq); } - // get hand before adding Ruq, so shouldn't discard + // get hand before adding Ruq, so shouldn't discard + // TODO: Refine what not to discard AI.SelectNextCard(Bot.Hand); return true; } @@ -807,7 +827,7 @@ private bool BastardSearch() return false; } - // priority 5 - misc send targets + // priority 4 - misc send targets private bool NtssPop() { ClientCard bestCard = Util.GetBestEnemyCard(false, true); @@ -819,6 +839,79 @@ private bool NtssPop() return false; } + private bool OmegaRecur() + { + // TODO: Add priority list + return true; + } + + private bool PegasusSpin() + { + AI.SelectCard(Util.GetBestEnemyCard(false, true)); + return true; + } + + // priority 5 - niche scenarios + private bool BeastEffect() + { + // draw 1 + if (Card.Location == CardLocation.Grave) + { + return true; + } + // draw 2 + // TODO: Refine what not to discard + return true; + } + + private bool ConstructMill() + { + if (!Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.ShaddollBeast)) + { + AI.SelectCard(CardId.ShaddollBeast); + return true; + } + return false; + } + + private bool VenomEff() + { + // float + if (Card.Location == CardLocation.Grave) + { + return true; + } + + AI.SelectCard(Util.GetBestEnemyMonster()); + return true; + } + + private bool TowerEff() + { + int[] reservedBanish = + { + CardId.ShaddollApkallone, + CardId.ShaddollConstruct, + CardId.DragmaBastard + }; + + // buff + if (ActivateDescription == Util.GetStringId(CardId.InvokedTower, 0)) + { + // don't banish a reserved combo piece for Augoeides + IList fusionCards = Bot.Graveyard.GetMatchingCards(card => card.HasType(CardType.Fusion) && !card.IsCode(reservedBanish)); + if (fusionCards.Count > 0) + { + AI.SelectCard(fusionCards); + return true; + } + return false; + } + + AI.SelectCard(Util.GetBestEnemyMonster(false, true)); + return true; + } + // priority 6 - set backrow private bool RuqSet() From e669f34b8da4c48872ceca5cc63b497a39241566 Mon Sep 17 00:00:00 2001 From: AlphaKretin Date: Tue, 28 Apr 2020 22:58:02 +0930 Subject: [PATCH 05/13] Minor improvements Will activate Shaddoll Ruq Don't chain Punishment to Ruq Prefer Aleisters in GY --- Game/AI/Decks/DragmaExecutor.cs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Game/AI/Decks/DragmaExecutor.cs b/Game/AI/Decks/DragmaExecutor.cs index 5dc12543..0016e031 100644 --- a/Game/AI/Decks/DragmaExecutor.cs +++ b/Game/AI/Decks/DragmaExecutor.cs @@ -99,7 +99,7 @@ public DragmaExecutor(GameAI ai, Duel duel) AddExecutor(ExecutorType.Activate, CardId.InvokedTower, TowerEff); // priority 6 - set cards - AddExecutor(ExecutorType.SpellSet, CardId.DragmaPunish, RuqSet); + AddExecutor(ExecutorType.SpellSet, CardId.ShaddollRuq, RuqSet); AddExecutor(ExecutorType.SpellSet, CardId.StapleCalled, TrapSet); AddExecutor(ExecutorType.SpellSet, CardId.StapleImperm, TrapSet); AddExecutor(ExecutorType.SpellSet, CardId.StapleSuperPoly, TrapSet); @@ -308,11 +308,12 @@ private bool FleurSummon() } // summon for body at end of main - if (Duel.Player == 1 && (Duel.MainPhase.CanBattlePhase || Duel.MainPhase.CanEndPhase)) - { - AI.SelectCard(Util.GetBestEnemyMonster()); - return true; - } + // TODO: Figure out end of Main Phase + //if (Duel.Player == 1 && (Duel.MainPhase.CanBattlePhase || Duel.MainPhase.CanEndPhase)) + //{ + // AI.SelectCard(Util.GetBestEnemyMonster(true)); + // return true; + //} // summon to negate ClientCard chainCard = Util.GetLastChainCard(); @@ -328,7 +329,7 @@ private bool FleurSummon() private bool PunishEffect() { // don't lock ourselves out of extra if we have Ruq to use - if (Bot.HasInSpellZone(CardId.ShaddollRuq) && !RuqUsed) + if (Bot.HasInSpellZone(CardId.ShaddollRuq) && (!RuqUsed || Duel.CurrentChain.ContainsCardWithId(CardId.ShaddollRuq))) { return false; } @@ -363,12 +364,12 @@ private bool PunishEffect() private bool RuqEffect() { + // flip faceup immediately to help the AI realise to fusion summon if (Card.IsFacedown()) { AI.SelectYesNo(false); return true; } - // Winda = 1 Shaddoll + 1 DARK if (Bot.ExtraDeck.ContainsCardWithId(CardId.ShaddollWinda) && Bot.Graveyard.IsExistingMatchingCard(card => card.HasSetcode(0x9d)) @@ -506,6 +507,7 @@ private bool InvocationEffect() { AI.SelectCard(CardId.InvokedMechaba); AI.SelectMaterials(lightCards); + AI.SelectMaterials(CardLocation.Grave); return true; } @@ -513,6 +515,7 @@ private bool InvocationEffect() { AI.SelectCard(CardId.InvokedMechaba); AI.SelectMaterials(CardId.InvokedGardna); + AI.SelectMaterials(CardLocation.Grave); return true; } @@ -521,6 +524,7 @@ private bool InvocationEffect() { AI.SelectCard(CardId.InvokedTower); AI.SelectMaterials(fusionCards); + AI.SelectMaterials(CardLocation.Grave); return true; } @@ -543,6 +547,7 @@ private bool InvocationEffect() { AI.SelectCard(CardId.InvokedTower); AI.SelectMaterials(selfFusionCards); + AI.SelectMaterials(CardLocation.Grave); return true; } @@ -551,6 +556,7 @@ private bool InvocationEffect() { AI.SelectCard(CardId.InvokedCaliga); AI.SelectMaterials(darkCards); + AI.SelectMaterials(CardLocation.Grave); return true; } From 3b7d5b06ffe3e021c79bd6782f7b240104923e86 Mon Sep 17 00:00:00 2001 From: AlphaKretin Date: Tue, 28 Apr 2020 23:54:01 +0930 Subject: [PATCH 06/13] Update Fleur handling According to IceYGO, checking for passed priority at the end of the Main Phase is currently impossible, so instead Fleur will be summoned during Main Phase 2. --- Game/AI/Decks/DragmaExecutor.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Game/AI/Decks/DragmaExecutor.cs b/Game/AI/Decks/DragmaExecutor.cs index 0016e031..b2711085 100644 --- a/Game/AI/Decks/DragmaExecutor.cs +++ b/Game/AI/Decks/DragmaExecutor.cs @@ -158,7 +158,7 @@ public override bool OnPreBattleBetween(ClientCard attacker, ClientCard defender { if (!defender.IsMonsterHasPreventActivationEffectInBattle()) { - if (attacker.IsCode(CardId.ShaddollConstruct) && !attacker.IsDisabled() && defender.IsSpecialSummoned) // TODO: Possible to check destruction immunity? + if (attacker.IsCode(CardId.ShaddollConstruct) && !attacker.IsDisabled() && defender.IsSpecialSummoned) // NOTE: Possible to check destruction immunity? attacker.RealPower = 9999; if (attacker.IsCode(CardId.DragmaFleur) && !attacker.IsDisabled() && !FleurAttackUsed) attacker.RealPower += 500; @@ -196,6 +196,12 @@ public override ClientCard OnSelectAttacker(IList attackers, IList Date: Thu, 30 Apr 2020 22:53:54 +0930 Subject: [PATCH 07/13] Fix ID Dictionary --- Game/AI/Decks/RushSpellcasterExecutor.cs | 40 ++++++++++++------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Game/AI/Decks/RushSpellcasterExecutor.cs b/Game/AI/Decks/RushSpellcasterExecutor.cs index 346930f8..f0c8d416 100644 --- a/Game/AI/Decks/RushSpellcasterExecutor.cs +++ b/Game/AI/Decks/RushSpellcasterExecutor.cs @@ -11,26 +11,26 @@ public class RushSpellcasterExecutor : DefaultExecutor { public class CardId { - public const int DarkSorceror = 1; - public const int Wolfram = 1; - public const int StrayCat = 1; - public const int FireGolem = 1; - public const int MysticDealer = 1; - public const int WhisperingFairy = 1; - public const int DefensiveDragonMage = 1; - public const int LuminousShaman = 1; - - public const int SevensRoad = 1; - public const int WindcasterTorna = 1; - public const int RoadWitch = 1; - - public const int RecoveryForce = 1; - public const int HammerCrush = 1; - public const int WindBlessing = 1; - public const int MagicalStream = 1; - - public const int DarkLiberation = 1; - public const int CurtainSparks = 1; + public const int DarkSorceror = 160301005; + public const int Wolfram = 160301007; + public const int StrayCat = 160301010; + public const int FireGolem = 160001005; + public const int MysticDealer = 160301006; + public const int WhisperingFairy = 160001018; + public const int DefensiveDragonMage = 160001031; + public const int LuminousShaman = 160301009; + + public const int SevensRoad = 160301001; + public const int WindcasterTorna = 160301002; + public const int RoadWitch = 160401001; + + public const int RecoveryForce = 160001038; + public const int HammerCrush = 160301041; + public const int WindBlessing = 160301011; + public const int MagicalStream = 160301012; + + public const int DarkLiberation = 160301013; + public const int CurtainSparks = 160301014; } public RushSpellcasterExecutor(GameAI ai, Duel duel) From 0b4e0386c92b590ce22c5ad00c83c2446e364fa0 Mon Sep 17 00:00:00 2001 From: AlphaKretin Date: Thu, 30 Apr 2020 22:57:41 +0930 Subject: [PATCH 08/13] Add Trap effect executors --- Game/AI/Decks/RushSpellcasterExecutor.cs | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Game/AI/Decks/RushSpellcasterExecutor.cs b/Game/AI/Decks/RushSpellcasterExecutor.cs index f0c8d416..70f0624b 100644 --- a/Game/AI/Decks/RushSpellcasterExecutor.cs +++ b/Game/AI/Decks/RushSpellcasterExecutor.cs @@ -79,6 +79,10 @@ public RushSpellcasterExecutor(GameAI ai, Duel duel) AddExecutor(ExecutorType.Activate, CardId.SevensRoad, SevensRoadEff); AddExecutor(ExecutorType.Activate, CardId.FireGolem, FireGolemEff); // after sroad for atk manip AddExecutor(ExecutorType.Activate, CardId.WindcasterTorna, WindTornaEff); + + // trap cards + AddExecutor(ExecutorType.Activate, CardId.DarkLiberation, DarkLibEff); + AddExecutor(ExecutorType.Activate, CardId.CurtainSparks, CurSparksEff); } public override bool OnSelectHand() @@ -291,5 +295,27 @@ private bool WindTornaEff() return false; } + private bool DarkLibEff() + { + IList okToShuffle = Bot.Graveyard.GetMatchingCards(c => !IsUniqueAttribute(c)); + List selections = new List(); + while (okToShuffle.Count > 0 && selections.Count < 4) + { + ClientCard sel = okToShuffle[0]; + selections.Add(sel); + okToShuffle.Remove(sel); + IList maybeRemove = okToShuffle.GetMatchingCards(c => c.HasAttribute((CardAttribute)sel.Attribute)); + if (maybeRemove.Count == 1) + okToShuffle.Remove(maybeRemove[0]); // if exactly 1 left with attribute, no longer OK to shuffle + } + + AI.SelectCard(selections); + return true; + } + + private bool CurSparksEff() + { + return true; + } } } \ No newline at end of file From d71301dfd8f4e70b6aee9e518cd194fc331b6169 Mon Sep 17 00:00:00 2001 From: AlphaKretin Date: Thu, 30 Apr 2020 23:05:21 +0930 Subject: [PATCH 09/13] Fix wrong IDs --- Game/AI/Decks/RushSpellcasterExecutor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Game/AI/Decks/RushSpellcasterExecutor.cs b/Game/AI/Decks/RushSpellcasterExecutor.cs index 70f0624b..c07a7736 100644 --- a/Game/AI/Decks/RushSpellcasterExecutor.cs +++ b/Game/AI/Decks/RushSpellcasterExecutor.cs @@ -14,7 +14,7 @@ public class CardId public const int DarkSorceror = 160301005; public const int Wolfram = 160301007; public const int StrayCat = 160301010; - public const int FireGolem = 160001005; + public const int FireGolem = 160001019; public const int MysticDealer = 160301006; public const int WhisperingFairy = 160001018; public const int DefensiveDragonMage = 160001031; @@ -25,7 +25,7 @@ public class CardId public const int RoadWitch = 160401001; public const int RecoveryForce = 160001038; - public const int HammerCrush = 160301041; + public const int HammerCrush = 160001041; public const int WindBlessing = 160301011; public const int MagicalStream = 160301012; From 282ee684a7823b52142be6d71898063b505dc67c Mon Sep 17 00:00:00 2001 From: AlphaKretin Date: Thu, 30 Apr 2020 23:10:07 +0930 Subject: [PATCH 10/13] Add null checks to "Luminous Shaman" --- Game/AI/Decks/RushSpellcasterExecutor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Game/AI/Decks/RushSpellcasterExecutor.cs b/Game/AI/Decks/RushSpellcasterExecutor.cs index c07a7736..4c70d208 100644 --- a/Game/AI/Decks/RushSpellcasterExecutor.cs +++ b/Game/AI/Decks/RushSpellcasterExecutor.cs @@ -243,7 +243,7 @@ private bool LumShamanEff() { ClientCard attacker = Bot.MonsterZone.GetMatchingCards(c => c.Level <= 4 && IsSpellcaster(c)).GetHighestAttackMonster(); ClientCard target = Util.GetWorstEnemyMonster(true); - return (attacker.Attack - target.Attack) > Card.Attack; + return !(attacker == null) && !(target == null) && (attacker.Attack - target.Attack) > Card.Attack; } private bool SevensRoadEff() From c86188ddf8545775b3d61aa647cf000e7eb443c4 Mon Sep 17 00:00:00 2001 From: AlphaKretin Date: Thu, 30 Apr 2020 23:22:55 +0930 Subject: [PATCH 11/13] Update normal sets --- Game/AI/Decks/RushSpellcasterExecutor.cs | 40 +++++++++++++++++++----- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/Game/AI/Decks/RushSpellcasterExecutor.cs b/Game/AI/Decks/RushSpellcasterExecutor.cs index 4c70d208..ab630ffd 100644 --- a/Game/AI/Decks/RushSpellcasterExecutor.cs +++ b/Game/AI/Decks/RushSpellcasterExecutor.cs @@ -37,6 +37,14 @@ public RushSpellcasterExecutor(GameAI ai, Duel duel) : base(ai, duel) { // summon weenies + AddExecutor(ExecutorType.MonsterSet, CardId.MysticDealer, NormalSet); + AddExecutor(ExecutorType.MonsterSet, CardId.FireGolem, NormalSet); + AddExecutor(ExecutorType.MonsterSet, CardId.WhisperingFairy, NormalSet); + AddExecutor(ExecutorType.MonsterSet, CardId.DefensiveDragonMage, NormalSet); + AddExecutor(ExecutorType.MonsterSet, CardId.LuminousShaman, NormalSet); + AddExecutor(ExecutorType.MonsterSet, CardId.DarkSorceror, NormalSet); + AddExecutor(ExecutorType.MonsterSet, CardId.Wolfram, NormalSet); + AddExecutor(ExecutorType.Summon, CardId.MysticDealer, NormalSummon); AddExecutor(ExecutorType.Summon, CardId.FireGolem, NormalSummon); AddExecutor(ExecutorType.Summon, CardId.WhisperingFairy, NormalSummon); @@ -44,7 +52,8 @@ public RushSpellcasterExecutor(GameAI ai, Duel duel) AddExecutor(ExecutorType.Summon, CardId.LuminousShaman, NormalSummon); AddExecutor(ExecutorType.Summon, CardId.DarkSorceror, NormalSummon); AddExecutor(ExecutorType.Summon, CardId.Wolfram, NormalSummon); - AddExecutor(ExecutorType.MonsterSet, CardId.StrayCat, NormalSummon); + + AddExecutor(ExecutorType.MonsterSet, CardId.StrayCat, StrayCatSet); AddExecutor(ExecutorType.Activate, CardId.MysticDealer, GenericDiscard); // use before being tributed @@ -99,6 +108,17 @@ private bool IsUniqueAttribute(ClientCard card, bool includeMzone = false) return includeMzone ? (uniqueInGY && !Bot.MonsterZone.IsExistingMatchingCard(c => c.HasAttribute(att))) : uniqueInGY; } + private bool NormalSet() + { + // only set if we need to cower + if (Enemy.MonsterZone.IsExistingMatchingCard(c => c.Attack < Card.Attack)) + { + return false; + } + // summon monsters with new attributes first + return IsUniqueAttribute(Card, true) || !Bot.Hand.IsExistingMatchingCard(c => IsUniqueAttribute(c, true)); + } + private bool NormalSummon() { // summon monsters with new attributes first @@ -138,7 +158,12 @@ private bool OneTributeSummon() if (discards.Count < 1) return false; - AI.SelectMaterials(discards.GetLowestAttackMonster()); + IList uniques = discards.GetMatchingCards(c => IsUniqueAttribute(c)); + if (uniques.Count > 0) + AI.SelectCard(uniques); + else + AI.SelectCard(discards); + return true; } @@ -148,11 +173,12 @@ private bool SevensRoadSummon() if (discards.Count < 2) return false; - ClientCard firstMat = discards.GetLowestAttackMonster(); - discards.Remove(firstMat); - ClientCard nextMat = discards.GetLowestAttackMonster(); - ClientCard[] mats = { firstMat, nextMat }; - AI.SelectMaterials(mats); + IList uniques = discards.GetMatchingCards(c => IsUniqueAttribute(c)); + if (uniques.Count > 1) + AI.SelectCard(uniques); + else + AI.SelectCard(discards); + return true; } From 37b8274d9fa8f7f573d5e6c401181294464823a0 Mon Sep 17 00:00:00 2001 From: AlphaKretin Date: Thu, 30 Apr 2020 23:23:25 +0930 Subject: [PATCH 12/13] Fix --- Game/AI/Decks/RushSpellcasterExecutor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Game/AI/Decks/RushSpellcasterExecutor.cs b/Game/AI/Decks/RushSpellcasterExecutor.cs index ab630ffd..0b4cf23a 100644 --- a/Game/AI/Decks/RushSpellcasterExecutor.cs +++ b/Game/AI/Decks/RushSpellcasterExecutor.cs @@ -52,8 +52,7 @@ public RushSpellcasterExecutor(GameAI ai, Duel duel) AddExecutor(ExecutorType.Summon, CardId.LuminousShaman, NormalSummon); AddExecutor(ExecutorType.Summon, CardId.DarkSorceror, NormalSummon); AddExecutor(ExecutorType.Summon, CardId.Wolfram, NormalSummon); - - AddExecutor(ExecutorType.MonsterSet, CardId.StrayCat, StrayCatSet); + AddExecutor(ExecutorType.MonsterSet, CardId.StrayCat, NormalSummon); AddExecutor(ExecutorType.Activate, CardId.MysticDealer, GenericDiscard); // use before being tributed From f649eb0a1d117aabc6bb2398391cd06e1bd231f4 Mon Sep 17 00:00:00 2001 From: AlphaKretin Date: Thu, 30 Apr 2020 23:26:49 +0930 Subject: [PATCH 13/13] Update Normal Set logic --- Game/AI/Decks/RushSpellcasterExecutor.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Game/AI/Decks/RushSpellcasterExecutor.cs b/Game/AI/Decks/RushSpellcasterExecutor.cs index 0b4cf23a..0de0a374 100644 --- a/Game/AI/Decks/RushSpellcasterExecutor.cs +++ b/Game/AI/Decks/RushSpellcasterExecutor.cs @@ -37,14 +37,6 @@ public RushSpellcasterExecutor(GameAI ai, Duel duel) : base(ai, duel) { // summon weenies - AddExecutor(ExecutorType.MonsterSet, CardId.MysticDealer, NormalSet); - AddExecutor(ExecutorType.MonsterSet, CardId.FireGolem, NormalSet); - AddExecutor(ExecutorType.MonsterSet, CardId.WhisperingFairy, NormalSet); - AddExecutor(ExecutorType.MonsterSet, CardId.DefensiveDragonMage, NormalSet); - AddExecutor(ExecutorType.MonsterSet, CardId.LuminousShaman, NormalSet); - AddExecutor(ExecutorType.MonsterSet, CardId.DarkSorceror, NormalSet); - AddExecutor(ExecutorType.MonsterSet, CardId.Wolfram, NormalSet); - AddExecutor(ExecutorType.Summon, CardId.MysticDealer, NormalSummon); AddExecutor(ExecutorType.Summon, CardId.FireGolem, NormalSummon); AddExecutor(ExecutorType.Summon, CardId.WhisperingFairy, NormalSummon); @@ -54,6 +46,14 @@ public RushSpellcasterExecutor(GameAI ai, Duel duel) AddExecutor(ExecutorType.Summon, CardId.Wolfram, NormalSummon); AddExecutor(ExecutorType.MonsterSet, CardId.StrayCat, NormalSummon); + AddExecutor(ExecutorType.MonsterSet, CardId.MysticDealer, NormalSet); + AddExecutor(ExecutorType.MonsterSet, CardId.FireGolem, NormalSet); + AddExecutor(ExecutorType.MonsterSet, CardId.WhisperingFairy, NormalSet); + AddExecutor(ExecutorType.MonsterSet, CardId.DefensiveDragonMage, NormalSet); + AddExecutor(ExecutorType.MonsterSet, CardId.LuminousShaman, NormalSet); + AddExecutor(ExecutorType.MonsterSet, CardId.DarkSorceror, NormalSet); + AddExecutor(ExecutorType.MonsterSet, CardId.Wolfram, NormalSet); + AddExecutor(ExecutorType.Activate, CardId.MysticDealer, GenericDiscard); // use before being tributed // tribute summons @@ -109,8 +109,8 @@ private bool IsUniqueAttribute(ClientCard card, bool includeMzone = false) private bool NormalSet() { - // only set if we need to cower - if (Enemy.MonsterZone.IsExistingMatchingCard(c => c.Attack < Card.Attack)) + // only set if we need to cower from every enemy + if (Enemy.GetMonsterCount() > 0 && !Enemy.MonsterZone.IsExistingMatchingCard(c => c.Attack < Card.Attack)) { return false; }