diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..616af734 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 + +[*.yml] +indent_size = 2 diff --git a/.gitignore b/.gitignore index 0c3d6b03..3314c9f6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ x64/ build/ [Bb]in/ [Oo]bj/ +output/ # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets !packages/*/build/ diff --git a/.travis.yml b/.travis.yml index 032ddc5b..c7778071 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,16 +2,50 @@ os: windows language: cpp git: depth: 1 -script: -- export PATH="/c/Program Files (x86)/Microsoft Visual Studio/2017/BuildTools/MSBuild/15.0/Bin":$PATH -- msbuild.exe -m -p:Configuration=Release WindBot.sln -after_success: -- cd bin -- mv Release WindBot -- git init -- git checkout --orphan $DEPLOY_BRANCH -- git config user.email deploy@travis-ci.org -- git config user.name "Deployment Bot (from Travis CI)" -- git add -A WindBot -- git commit -qm "Deploy $DEPLOY_REPO to $DEPLOY_REPO:$DEPLOY_BRANCH" -- git push -qf https://$DEPLOY_TOKEN@github.com/$DEPLOY_REPO.git $DEPLOY_BRANCH:$DEPLOY_BRANCH +env: + global: + - ARTIFACT="WindBotIgnite-Release-$(date +%Y%m%d)-$TRAVIS_COMMIT.zip" + # Visual Studio Installer location for the Android SDK + - ANDROID_SDK_ROOT='C:\Program Files (x86)\Android\android-sdk' + # Visual Studio Installer location for an OpenJDK for Android development + - JAVA_HOME='C:\Program Files\Android\jdk\microsoft_dist_openjdk_1.8.0.25' +jobs: + include: + - name: "Desktop exe" + script: dotnet build WindBot.csproj --configuration=Release + env: DESKTOP_BUILD=1 + before_deploy: + - cd bin && mv Release WindBot + - 7z a -tzip "$ARTIFACT" WindBot + - mv $ARTIFACT .. && cd .. + - name: "Android aar" + install: + - choco install nuget.commandline visualstudio2017-workload-xamarinbuildtools visualstudio2017-workload-nativemobile visualstudio2017-workload-netcrossplat + - ./ci/install-sdk-ndk.sh + - choco install mono --x86 # Unspecified dependency for Embeddinator; 64-bit does not work + script: + - export PATH="/c/Program Files (x86)/Microsoft Visual Studio/2017/BuildTools/MSBuild/15.0/Bin":$PATH + - nuget restore WindBot.sln + - msbuild.exe -m -p:Configuration=Release WindBot.sln + before_deploy: + - cd bin && mkdir -p WindBot + - cp -r Release/COPYING Release/LICENSE Release/bots.json Release/Decks/ Release/Dialogs/ WindBot/ + - 7z a WindBotIgnite-Resources.7z WindBot && cd .. + - mv output/libWindbot.aar bin/WindBotIgnite-Resources.7z . +before_script: cp Dialogs/default.json . +deploy: +- provider: script + skip_cleanup: true + script: bash ./ci/deploy.sh + on: + repo: ProjectIgnis/windbot + condition: $DESKTOP_BUILD == 1 +- provider: releases + skip_cleanup: true + api_key: $RELEASES_TOKEN + file: + - $ARTIFACT + - libWindbot.aar + - WindBotIgnite-Resources.7z + on: + tags: true diff --git a/BotWrapper/bot.conf b/BotWrapper/bot.conf index 56296ab2..6e8c6511 100644 --- a/BotWrapper/bot.conf +++ b/BotWrapper/bot.conf @@ -2,7 +2,7 @@ # !name # command # description -# flags (avail flags: SUPPORT_MASTER_RULE_3, SUPPORT_NEW_MASTER_RULE) +# flags (avail flags: SUPPORT_MASTER_RULE_3, SUPPORT_NEW_MASTER_RULE, SUPPORT_MASTER_RULE_2020) !随机-非常简单 Random=AI_LV1 @@ -86,7 +86,7 @@ AI_ANTI_META SUPPORT_MASTER_RULE_3 SUPPORT_NEW_MASTER_RULE SUPPORT_MASTER_RULE_2 !尼亚-淘气仙星 Name=尼亚 Deck=Trickstar Dialog=near.zh-CN -淘气仙星卡组。 +旧式淘气仙星卡组。 AI_LV3 SUPPORT_NEW_MASTER_RULE SUPPORT_MASTER_RULE_2020 !尼亚-幻变骚灵 @@ -101,7 +101,7 @@ AI_LV3 SUPPORT_NEW_MASTER_RULE SUPPORT_MASTER_RULE_2020 !复制梁龙-自奏圣乐 Name=复制梁龙 Deck=Orcust Dialog=anothercopy.zh-CN -自奏圣乐卡组。 +旧式自奏圣乐卡组。 AI_LV3 SUPPORT_NEW_MASTER_RULE SUPPORT_MASTER_RULE_2020 !复制梁龙-转生炎兽 @@ -122,7 +122,12 @@ AI_LV1 SUPPORT_MASTER_RULE_3 SUPPORT_NEW_MASTER_RULE SUPPORT_MASTER_RULE_2020 !永远之魂-荷鲁斯 Name=永远之魂 Deck=Horus Dialog=soul.zh-CN 老式龙族卡组。 -AI_LV1 SUPPORT_MASTER_RULE_3 SUPPORT_NEW_MASTER_RULE +AI_LV1 SUPPORT_MASTER_RULE_3 SUPPORT_NEW_MASTER_RULE SUPPORT_MASTER_RULE_2020 + +!比特机灵-微风 +Name=比特机灵 Deck=PureWinds Dialog=zh-CN +风属性卡组。 +AI_LV2 SUPPORT_MASTER_RULE_3 SUPPORT_NEW_MASTER_RULE SUPPORT_MASTER_RULE_2020 !试作型机器人1732 Name=试作型机器人1732 Deck=ST1732 Dialog=zh-CN @@ -157,9 +162,14 @@ AI_ANTI_META SUPPORT_MASTER_RULE_3 SUPPORT_NEW_MASTER_RULE SUPPORT_MASTER_RULE_2 !報社鬥士 Name=報社鬥士 Deck=GrenMajuThunderBoarder Dialog=kiwi.zh-TW 红莲雷王滑板卡组。 -AI_ANTI_META SUPPORT_NEW_MASTER_RULE SUPPORT_MASTER_RULE_2020 +AI_ANTI_META SUPPORT_MASTER_RULE_3 SUPPORT_NEW_MASTER_RULE SUPPORT_MASTER_RULE_2020 !我太帅了 Name=我太帅了 Deck=Dragun Dialog=smart.zh-CN 超魔导真红眼龙骑士卡组。 -AI_LV3 SUPPORT_NEW_MASTER_RULE SUPPORT_MASTER_RULE_2020 +AI_LV3 SUPPORT_MASTER_RULE_3 SUPPORT_NEW_MASTER_RULE SUPPORT_MASTER_RULE_2020 + +!玻璃女巫 +Name=玻璃女巫 Deck=Witchcraft Dialog=verre.zh-CN +魔女术卡组。 +AI_LV3 SUPPORT_MASTER_RULE_3 SUPPORT_NEW_MASTER_RULE SUPPORT_MASTER_RULE_2020 diff --git a/Decks/AI_ABC.ydk b/Decks/AI_ABC.ydk new file mode 100644 index 00000000..edbd8f37 --- /dev/null +++ b/Decks/AI_ABC.ydk @@ -0,0 +1,59 @@ +#created by FlameFlash +#main +99249638 +99249638 +46659709 +46659709 +46659709 +65367484 +65367484 +65367484 +43147039 +89132148 +30012506 +30012506 +30012506 +77411244 +77411244 +77411244 +3405259 +3405259 +3405259 +39890958 +14558128 +32807846 +73628505 +73628505 +73628505 +12524259 +12524259 +12524259 +24224830 +24224830 +24224830 +65681983 +65681983 +65681983 +66399653 +66399653 +66399653 +10045474 +10045474 +10045474 +#extra +1561110 +1561110 +1561110 +75286621 +10443957 +10443957 +58069384 +58069384 +21887175 +4280258 +38342335 +2857636 +75452921 +83152482 +65741786 +!side diff --git a/Decks/AI_Dogmatika.ydk b/Decks/AI_Dogmatika.ydk new file mode 100644 index 00000000..91e18312 --- /dev/null +++ b/Decks/AI_Dogmatika.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/Decks/AI_Mathmech.ydk b/Decks/AI_Mathmech.ydk new file mode 100644 index 00000000..01ff5178 --- /dev/null +++ b/Decks/AI_Mathmech.ydk @@ -0,0 +1,59 @@ +#created by ... +#main +8567955 +8567955 +8567955 +89743495 +89743495 +27182739 +27182739 +27182739 +53577438 +53577438 +53577438 +16360142 +16360142 +16360142 +80965043 +80965043 +80965043 +52354896 +52354896 +12580477 +14025912 +14532163 +14532163 +35261759 +35261759 +35261759 +57160136 +57160136 +57160136 +70368879 +70368879 +70368879 +81439173 +93104632 +93104632 +8267140 +41410651 +36361633 +36361633 +36361633 +#extra +42632209 +42632209 +42632209 +15248594 +15248594 +15248594 +61399402 +61399402 +61399402 +85692042 +85692042 +85692042 +88021907 +88021907 +88021907 +!side diff --git a/Decks/AI_PureWinds.ydk b/Decks/AI_PureWinds.ydk new file mode 100644 index 00000000..915a1173 --- /dev/null +++ b/Decks/AI_PureWinds.ydk @@ -0,0 +1,74 @@ +#created by ... +#main +71007216 +71007216 +71007216 +81275020 +71175527 +71175527 +71175527 +43722862 +43722862 +43722862 +53932291 +53932291 +65277087 +65277087 +65277087 +54455435 +54455435 +54455435 +91662792 +91662792 +91662792 +16725505 +70117860 +70117860 +70117860 +12580477 +27980138 +27980138 +58577036 +83764718 +8267140 +25789292 +67723438 +67723438 +8608979 +8608979 +8608979 +24590232 +40605147 +84749824 +#extra +27315304 +50954680 +50954680 +50954680 +82044279 +82044279 +14577226 +29552709 +29552709 +64880894 +84766279 +42110604 +70913714 +30674956 +90512490 +!side +19420830 +19420830 +19420830 +94145021 +94145021 +94145021 +25789292 +25789292 +43711255 +43711255 +43711255 +58921041 +58921041 +23002292 +23002292 diff --git a/Decks/AI_Timethief.ydk b/Decks/AI_Timethief.ydk new file mode 100644 index 00000000..121c3427 --- /dev/null +++ b/Decks/AI_Timethief.ydk @@ -0,0 +1,59 @@ +#created by ... +#main +65367484 +65367484 +65367484 +56308388 +56308388 +56308388 +67696066 +82496097 +82496097 +82496097 +74578720 +74578720 +74578720 +19891131 +19891131 +19891131 +70368879 +70368879 +70368879 +81439173 +10877309 +10877309 +10877309 +81670445 +81670445 +81670445 +18678554 +18678554 +18678554 +26708437 +26708437 +26708437 +57319935 +57319935 +98827725 +98827725 +98827725 +76587747 +76587747 +76587747 +#extra +56832966 +56832966 +56832966 +86532744 +86532744 +86532744 +84013237 +84013237 +84013237 +55285840 +55285840 +55285840 +59208943 +59208943 +59208943 +!side diff --git a/Decks/AI_Witchcraft.ydk b/Decks/AI_Witchcraft.ydk new file mode 100644 index 00000000..e7ee2790 --- /dev/null +++ b/Decks/AI_Witchcraft.ydk @@ -0,0 +1,81 @@ +#created by ... +#main +49036338 +71074418 +21522601 +84523092 +84523092 +21744288 +21744288 +21744288 +95245544 +95245544 +95245544 +14558127 +38814750 +38814750 +38814750 +23434538 +59851535 +64756282 +64756282 +10805153 +11110587 +11110587 +14532163 +14532163 +49238328 +49238328 +49238328 +54693926 +54693926 +57916305 +57916305 +57916305 +58577036 +58577036 +58577036 +73594093 +83301414 +83301414 +83301414 +24224830 +24224830 +24224830 +56894757 +65681983 +65681983 +70226289 +13758665 +19673561 +40252269 +40252269 +83289866 +83289866 +83289866 +87769556 +10045474 +10045474 +10045474 +55072170 +94553671 +94553671 +#extra +27548199 +5041348 +5041348 +74586817 +98558751 +85289965 +38342335 +2857636 +2857636 +8802510 +50588353 +60303245 +94259633 +94259633 +94259633 +!side +65681983 +40252269 diff --git a/Dialogs/smart.zh-CN.json b/Dialogs/smart.zh-CN.json index 32d85559..d376c40f 100644 --- a/Dialogs/smart.zh-CN.json +++ b/Dialogs/smart.zh-CN.json @@ -21,7 +21,8 @@ "消失吧,杂鱼" ], "ondirectattack": [ - "爸爸,饶命" + "爸爸,饶命", + "恐怖如斯" ], "facedownmonstername": "怪兽", "activate": [ diff --git a/Dialogs/verre.zh-CN.json b/Dialogs/verre.zh-CN.json new file mode 100644 index 00000000..3c423a17 --- /dev/null +++ b/Dialogs/verre.zh-CN.json @@ -0,0 +1,54 @@ +{ + "welcome": [ + "啊~~让我再睡一会嘛~", + "你要加入公会吗?", + "AI功能正在测试中,遇到问题请及时反馈。" + ], + "deckerror": [ + "我的卡组里{0}不能用,我回去睡觉了。" + ], + "duelstart": [ + "好困……", + "啊呀,对面看上去好厉害的样子。" + ], + "newturn": [ + "我的回合,抽卡!", + "魔力补充!" + ], + "endturn": [ + "不想干活怎么办……", + "就这样吧,该你了。" + ], + "directattack": [ + "{0},直接攻击!", + "不过如此嘛,直接攻击!", + "快走开,我要回去睡觉了。", + ], + "attack": [ + "{0},攻击{1}!", + "{0},替我打倒{1}!" + ], + "ondirectattack": [ + "啊啊……", + "啊啊啊……", + "{0}好可怕啊……", + "我累了……" + ], + "facedownmonstername": "怪兽", + "activate": [ + "{0}的效果发动!" + ], + "summon": [ + "召唤{0}!", + "出来吧,{0}!", + "{0},来帮我一下。" + ], + "setmonster": [ + "……" + ], + "chaining": [ + "发动{0}!", + "等一下,我发动{0}。", + "要不是有{0},我都快睡着了。" + ] +} diff --git a/ExecutorBase/ExecutorBase.csproj b/ExecutorBase/ExecutorBase.csproj new file mode 100644 index 00000000..8e1b6cf7 --- /dev/null +++ b/ExecutorBase/ExecutorBase.csproj @@ -0,0 +1,94 @@ + + + + + Debug + AnyCPU + {A1583FD7-7985-47DD-A835-8134DBF5811C} + Library + Properties + ExecutorBase + ExecutorBase + v4.0 + 512 + true + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\Mono.Data.Sqlite.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Game/AI/AIUtil.cs b/ExecutorBase/Game/AI/AIUtil.cs similarity index 96% rename from Game/AI/AIUtil.cs rename to ExecutorBase/Game/AI/AIUtil.cs index eeeb8bf0..ae208cf2 100644 --- a/Game/AI/AIUtil.cs +++ b/ExecutorBase/Game/AI/AIUtil.cs @@ -1,446 +1,446 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using YGOSharp.OCGWrapper.Enums; -namespace WindBot.Game.AI -{ - public class AIUtil - { - public Duel Duel { get; private set; } - public ClientField Bot { get; private set; } - public ClientField Enemy { get; private set; } - - public AIUtil(Duel duel) - { - Duel = duel; - Bot = Duel.Fields[0]; - Enemy = Duel.Fields[1]; - } - - /// - /// Get the total ATK Monster of the player. - /// - public int GetTotalAttackingMonsterAttack(int player) - { - return Duel.Fields[player].GetMonsters().Where(m => m.IsAttack()).Sum(m => (int?)m.Attack) ?? 0; - } - /// - /// Get the best ATK or DEF power of the field. - /// - /// Bot or Enemy. - /// Only calculate attack. - public int GetBestPower(ClientField field, bool onlyATK = false) - { - return field.MonsterZone.GetMonsters() - .Where(card => !onlyATK || card.IsAttack()) - .Max(card => (int?)card.GetDefensePower()) ?? -1; - } - - public int GetBestAttack(ClientField field) - { - return GetBestPower(field, true); - } - - public bool IsOneEnemyBetterThanValue(int value, bool onlyATK) - { - return Enemy.MonsterZone.GetMonsters() - .Any(card => card.GetDefensePower() > value && (!onlyATK || card.IsAttack())); - } - - public bool IsAllEnemyBetterThanValue(int value, bool onlyATK) - { - List monsters = Enemy.MonsterZone.GetMonsters(); - return monsters.Count > 0 && monsters - .All(card => card.GetDefensePower() > value && (!onlyATK || card.IsAttack())); - } - - /// - /// Deprecated, use IsOneEnemyBetter and IsAllEnemyBetter instead. - /// - public bool IsEnemyBetter(bool onlyATK, bool all) - { - if (all) - return IsAllEnemyBetter(onlyATK); - else - return IsOneEnemyBetter(onlyATK); - } - - /// - /// Is there an enemy monster who has better power than the best power of the bot's? - /// - /// Only calculate attack. - public bool IsOneEnemyBetter(bool onlyATK = false) - { - int bestBotPower = GetBestPower(Bot, onlyATK); - return IsOneEnemyBetterThanValue(bestBotPower, onlyATK); - } - - /// - /// Do all enemy monsters have better power than the best power of the bot's? - /// - /// Only calculate attack. - public bool IsAllEnemyBetter(bool onlyATK = false) - { - int bestBotPower = GetBestPower(Bot, onlyATK); - return IsAllEnemyBetterThanValue(bestBotPower, onlyATK); - } - - public ClientCard GetBestBotMonster(bool onlyATK = false) - { - return Bot.MonsterZone.GetMonsters() - .Where(card => !onlyATK || card.IsAttack()) - .OrderByDescending(card => card.GetDefensePower()) - .FirstOrDefault(); - } - - public ClientCard GetWorstBotMonster(bool onlyATK = false) - { - return Bot.MonsterZone.GetMonsters() - .Where(card => !onlyATK || card.IsAttack()) - .OrderBy(card => card.GetDefensePower()) - .FirstOrDefault(); - } - - public ClientCard GetOneEnemyBetterThanValue(int value, bool onlyATK = false, bool canBeTarget = false) - { - return Enemy.MonsterZone.GetMonsters() - .FirstOrDefault(card => card.GetDefensePower() >= value && (!onlyATK || card.IsAttack()) && (!canBeTarget || !card.IsShouldNotBeTarget())); - } - - public ClientCard GetOneEnemyBetterThanMyBest(bool onlyATK = false, bool canBeTarget = false) - { - int bestBotPower = GetBestPower(Bot, onlyATK); - return GetOneEnemyBetterThanValue(bestBotPower, onlyATK, canBeTarget); - } - - public ClientCard GetProblematicEnemyCard(int attack = 0, bool canBeTarget = false) - { - ClientCard card = Enemy.MonsterZone.GetFloodgate(canBeTarget); - if (card != null) - return card; - - card = Enemy.SpellZone.GetFloodgate(canBeTarget); - if (card != null) - return card; - - card = Enemy.MonsterZone.GetDangerousMonster(canBeTarget); - if (card != null) - return card; - - card = Enemy.MonsterZone.GetInvincibleMonster(canBeTarget); - if (card != null) - return card; - - if (attack == 0) - attack = GetBestAttack(Bot); - return GetOneEnemyBetterThanValue(attack, true, canBeTarget); - } - - public ClientCard GetProblematicEnemyMonster(int attack = 0, bool canBeTarget = false) - { - ClientCard card = Enemy.MonsterZone.GetFloodgate(canBeTarget); - if (card != null) - return card; - - card = Enemy.MonsterZone.GetDangerousMonster(canBeTarget); - if (card != null) - return card; - - card = Enemy.MonsterZone.GetInvincibleMonster(canBeTarget); - if (card != null) - return card; - - if (attack == 0) - attack = GetBestAttack(Bot); - return GetOneEnemyBetterThanValue(attack, true, canBeTarget); - } - - public ClientCard GetProblematicEnemySpell() - { - ClientCard card = Enemy.SpellZone.GetFloodgate(); - return card; - } - - public ClientCard GetBestEnemyCard(bool onlyFaceup = false, bool canBeTarget = false) - { - ClientCard card = GetBestEnemyMonster(onlyFaceup, canBeTarget); - if (card != null) - return card; - - card = GetBestEnemySpell(onlyFaceup); - if (card != null) - return card; - - return null; - } - - 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(); - - // after GetHighestAttackMonster, the left monsters must be face-down. - if (monsters.Count > 0 && !onlyFaceup) - return monsters[0]; - - return null; - } - - public ClientCard GetWorstEnemyMonster(bool onlyATK = false) - { - return Enemy.MonsterZone.GetMonsters() - .Where(card => !onlyATK || card.IsAttack()) - .OrderBy(card => card.GetDefensePower()) - .FirstOrDefault(); - } - - public ClientCard GetBestEnemySpell(bool onlyFaceup = false) - { - ClientCard card = GetProblematicEnemySpell(); - if (card != null) - return card; - - var spells = Enemy.GetSpells(); - - card = spells.FirstOrDefault(ecard => ecard.IsFaceup() && (ecard.HasType(CardType.Continuous) || ecard.HasType(CardType.Field))); - if (card != null) - return card; - - if (spells.Count > 0 && !onlyFaceup) - return spells[0]; - - return null; - } - - public ClientCard GetPZone(int player, int id) - { - if (Duel.IsNewRule) - { - return Duel.Fields[player].SpellZone[id * 4]; - } - else - { - return Duel.Fields[player].SpellZone[6 + id]; - } - } - - public int GetStringId(int id, int option) - { - return id * 16 + option; - } - - public bool IsTurn1OrMain2() - { - return Duel.Turn == 1 || Duel.Phase == DuelPhase.Main2; - } - - public int GetBotAvailZonesFromExtraDeck(IList remove) - { - ClientCard[] BotMZone = (ClientCard[])Bot.MonsterZone.Clone(); - ClientCard[] EnemyMZone = (ClientCard[])Enemy.MonsterZone.Clone(); - for (int i = 0; i < 7; i++) - { - if (remove.Contains(BotMZone[i])) BotMZone[i] = null; - if (remove.Contains(EnemyMZone[i])) EnemyMZone[i] = null; - } - - if (!Duel.IsNewRule || Duel.IsNewRule2020) - return Zones.MainMonsterZones; - - int result = 0; - - if (BotMZone[5] == null && BotMZone[6] == null) - { - if (EnemyMZone[5] == null) - result |= Zones.z6; - if (EnemyMZone[6] == null) - result |= Zones.z5; - } - - if (BotMZone[0] == null && - ((BotMZone[1]?.HasLinkMarker(CardLinkMarker.Left) ?? false) || - (BotMZone[5]?.HasLinkMarker(CardLinkMarker.BottomLeft) ?? false) || - (EnemyMZone[6]?.HasLinkMarker(CardLinkMarker.TopRight) ?? false))) - result |= Zones.z0; - - if (BotMZone[1] == null && - ((BotMZone[0]?.HasLinkMarker(CardLinkMarker.Right) ?? false) || - (BotMZone[2]?.HasLinkMarker(CardLinkMarker.Left) ?? false) || - (BotMZone[5]?.HasLinkMarker(CardLinkMarker.Bottom) ?? false) || - (EnemyMZone[6]?.HasLinkMarker(CardLinkMarker.Top) ?? false))) - result |= Zones.z1; - - if (BotMZone[2] == null && - ((BotMZone[1]?.HasLinkMarker(CardLinkMarker.Right) ?? false) || - (BotMZone[3]?.HasLinkMarker(CardLinkMarker.Left) ?? false) || - (BotMZone[5]?.HasLinkMarker(CardLinkMarker.BottomRight) ?? false) || - (EnemyMZone[6]?.HasLinkMarker(CardLinkMarker.TopLeft) ?? false) || - (BotMZone[6]?.HasLinkMarker(CardLinkMarker.BottomLeft) ?? false) || - (EnemyMZone[5]?.HasLinkMarker(CardLinkMarker.TopRight) ?? false))) - result |= Zones.z2; - - if (BotMZone[3] == null && - ((BotMZone[2]?.HasLinkMarker(CardLinkMarker.Right) ?? false) || - (BotMZone[4]?.HasLinkMarker(CardLinkMarker.Left) ?? false) || - (BotMZone[6]?.HasLinkMarker(CardLinkMarker.Bottom) ?? false) || - (EnemyMZone[5]?.HasLinkMarker(CardLinkMarker.Top) ?? false))) - result |= Zones.z3; - - if (BotMZone[4] == null && - ((BotMZone[3]?.HasLinkMarker(CardLinkMarker.Right) ?? false) || - (BotMZone[6]?.HasLinkMarker(CardLinkMarker.BottomRight) ?? false) || - (EnemyMZone[5]?.HasLinkMarker(CardLinkMarker.TopLeft) ?? false))) - result |= Zones.z4; - - return result; - } - - public int GetBotAvailZonesFromExtraDeck(ClientCard remove) - { - return GetBotAvailZonesFromExtraDeck(new[] { remove }); - } - - public int GetBotAvailZonesFromExtraDeck() - { - return GetBotAvailZonesFromExtraDeck(new List()); - } - - public bool IsChainTarget(ClientCard card) - { - return Duel.ChainTargets.Any(card.Equals); - } - - public bool IsChainTargetOnly(ClientCard card) - { - return Duel.ChainTargetOnly.Count == 1 && card.Equals(Duel.ChainTargetOnly[0]); - } - - public bool ChainContainsCard(int id) - { - return Duel.CurrentChain.Any(card => card.IsCode(id)); - } - - public bool ChainContainsCard(int[] ids) - { - return Duel.CurrentChain.Any(card => card.IsCode(ids)); - } - - public int ChainCountPlayer(int player) - { - return Duel.CurrentChain.Count(card => card.Controller == player); - } - - public bool ChainContainPlayer(int player) - { - return Duel.CurrentChain.Any(card => card.Controller == player); - } - - public bool HasChainedTrap(int player) - { - return Duel.CurrentChain.Any(card => card.Controller == player && card.HasType(CardType.Trap)); - } - - public ClientCard GetLastChainCard() - { - return Duel.CurrentChain.LastOrDefault(); - } - - /// - /// Select cards listed in preferred. - /// - public IList SelectPreferredCards(ClientCard preferred, IList cards, int min, int max) - { - IList selected = new List(); - if (cards.IndexOf(preferred) > 0 && selected.Count < max) - { - selected.Add(preferred); - } - - return selected; - } - - /// - /// Select cards listed in preferred. - /// - public IList SelectPreferredCards(int preferred, IList cards, int min, int max) - { - IList selected = new List(); - foreach (ClientCard card in cards) - { - if (card.IsCode(preferred) && selected.Count < max) - selected.Add(card); - } - - return selected; - } - - /// - /// Select cards listed in preferred. - /// - public IList SelectPreferredCards(IList preferred, IList cards, int min, int max) - { - IList selected = new List(); - IList avail = cards.ToList(); // clone - while (preferred.Count > 0 && avail.IndexOf(preferred[0]) > 0 && selected.Count < max) - { - ClientCard card = preferred[0]; - preferred.Remove(card); - avail.Remove(card); - selected.Add(card); - } - - return selected; - } - - /// - /// Select cards listed in preferred. - /// - public IList SelectPreferredCards(IList preferred, IList cards, int min, int max) - { - IList selected = new List(); - foreach (int id in preferred) - { - foreach (ClientCard card in cards) - { - if (card.IsCode(id) && selected.Count < max && selected.IndexOf(card) <= 0) - selected.Add(card); - } - if (selected.Count >= max) - break; - } - - return selected; - } - - /// - /// Check and fix selected to make sure it meet the count requirement. - /// - public IList CheckSelectCount(IList _selected, IList cards, int min, int max) - { - var selected = _selected.ToList(); - if (selected.Count < min) - { - foreach (ClientCard card in cards) - { - if (!selected.Contains(card)) - selected.Add(card); - if (selected.Count >= max) - break; - } - } - while (selected.Count > max) - { - selected.RemoveAt(selected.Count - 1); - } - - return selected; - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using YGOSharp.OCGWrapper.Enums; +namespace WindBot.Game.AI +{ + public class AIUtil + { + public Duel Duel { get; private set; } + public ClientField Bot { get; private set; } + public ClientField Enemy { get; private set; } + + public AIUtil(Duel duel) + { + Duel = duel; + Bot = Duel.Fields[0]; + Enemy = Duel.Fields[1]; + } + + /// + /// Get the total ATK Monster of the player. + /// + public int GetTotalAttackingMonsterAttack(int player) + { + return Duel.Fields[player].GetMonsters().Where(m => m.IsAttack()).Sum(m => (int?)m.Attack) ?? 0; + } + /// + /// Get the best ATK or DEF power of the field. + /// + /// Bot or Enemy. + /// Only calculate attack. + public int GetBestPower(ClientField field, bool onlyATK = false) + { + return field.MonsterZone.GetMonsters() + .Where(card => !onlyATK || card.IsAttack()) + .Max(card => (int?)card.GetDefensePower()) ?? -1; + } + + public int GetBestAttack(ClientField field) + { + return GetBestPower(field, true); + } + + public bool IsOneEnemyBetterThanValue(int value, bool onlyATK) + { + return Enemy.MonsterZone.GetMonsters() + .Any(card => card.GetDefensePower() > value && (!onlyATK || card.IsAttack())); + } + + public bool IsAllEnemyBetterThanValue(int value, bool onlyATK) + { + List monsters = Enemy.MonsterZone.GetMonsters(); + return monsters.Count > 0 && monsters + .All(card => card.GetDefensePower() > value && (!onlyATK || card.IsAttack())); + } + + /// + /// Deprecated, use IsOneEnemyBetter and IsAllEnemyBetter instead. + /// + public bool IsEnemyBetter(bool onlyATK, bool all) + { + if (all) + return IsAllEnemyBetter(onlyATK); + else + return IsOneEnemyBetter(onlyATK); + } + + /// + /// Is there an enemy monster who has better power than the best power of the bot's? + /// + /// Only calculate attack. + public bool IsOneEnemyBetter(bool onlyATK = false) + { + int bestBotPower = GetBestPower(Bot, onlyATK); + return IsOneEnemyBetterThanValue(bestBotPower, onlyATK); + } + + /// + /// Do all enemy monsters have better power than the best power of the bot's? + /// + /// Only calculate attack. + public bool IsAllEnemyBetter(bool onlyATK = false) + { + int bestBotPower = GetBestPower(Bot, onlyATK); + return IsAllEnemyBetterThanValue(bestBotPower, onlyATK); + } + + public ClientCard GetBestBotMonster(bool onlyATK = false) + { + return Bot.MonsterZone.GetMonsters() + .Where(card => !onlyATK || card.IsAttack()) + .OrderByDescending(card => card.GetDefensePower()) + .FirstOrDefault(); + } + + public ClientCard GetWorstBotMonster(bool onlyATK = false) + { + return Bot.MonsterZone.GetMonsters() + .Where(card => !onlyATK || card.IsAttack()) + .OrderBy(card => card.GetDefensePower()) + .FirstOrDefault(); + } + + public ClientCard GetOneEnemyBetterThanValue(int value, bool onlyATK = false, bool canBeTarget = false) + { + return Enemy.MonsterZone.GetMonsters() + .FirstOrDefault(card => card.GetDefensePower() >= value && (!onlyATK || card.IsAttack()) && (!canBeTarget || !card.IsShouldNotBeTarget())); + } + + public ClientCard GetOneEnemyBetterThanMyBest(bool onlyATK = false, bool canBeTarget = false) + { + int bestBotPower = GetBestPower(Bot, onlyATK); + return GetOneEnemyBetterThanValue(bestBotPower, onlyATK, canBeTarget); + } + + public ClientCard GetProblematicEnemyCard(int attack = 0, bool canBeTarget = false) + { + ClientCard card = Enemy.MonsterZone.GetFloodgate(canBeTarget); + if (card != null) + return card; + + card = Enemy.SpellZone.GetFloodgate(canBeTarget); + if (card != null) + return card; + + card = Enemy.MonsterZone.GetDangerousMonster(canBeTarget); + if (card != null) + return card; + + card = Enemy.MonsterZone.GetInvincibleMonster(canBeTarget); + if (card != null) + return card; + + if (attack == 0) + attack = GetBestAttack(Bot); + return GetOneEnemyBetterThanValue(attack, true, canBeTarget); + } + + public ClientCard GetProblematicEnemyMonster(int attack = 0, bool canBeTarget = false) + { + ClientCard card = Enemy.MonsterZone.GetFloodgate(canBeTarget); + if (card != null) + return card; + + card = Enemy.MonsterZone.GetDangerousMonster(canBeTarget); + if (card != null) + return card; + + card = Enemy.MonsterZone.GetInvincibleMonster(canBeTarget); + if (card != null) + return card; + + if (attack == 0) + attack = GetBestAttack(Bot); + return GetOneEnemyBetterThanValue(attack, true, canBeTarget); + } + + public ClientCard GetProblematicEnemySpell() + { + ClientCard card = Enemy.SpellZone.GetFloodgate(); + return card; + } + + public ClientCard GetBestEnemyCard(bool onlyFaceup = false, bool canBeTarget = false) + { + ClientCard card = GetBestEnemyMonster(onlyFaceup, canBeTarget); + if (card != null) + return card; + + card = GetBestEnemySpell(onlyFaceup); + if (card != null) + return card; + + return null; + } + + 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(); + + // after GetHighestAttackMonster, the left monsters must be face-down. + if (monsters.Count > 0 && !onlyFaceup) + return monsters[0]; + + return null; + } + + public ClientCard GetWorstEnemyMonster(bool onlyATK = false) + { + return Enemy.MonsterZone.GetMonsters() + .Where(card => !onlyATK || card.IsAttack()) + .OrderBy(card => card.GetDefensePower()) + .FirstOrDefault(); + } + + public ClientCard GetBestEnemySpell(bool onlyFaceup = false) + { + ClientCard card = GetProblematicEnemySpell(); + if (card != null) + return card; + + var spells = Enemy.GetSpells(); + + card = spells.FirstOrDefault(ecard => ecard.IsFaceup() && (ecard.HasType(CardType.Continuous) || ecard.HasType(CardType.Field))); + if (card != null) + return card; + + if (spells.Count > 0 && !onlyFaceup) + return spells[0]; + + return null; + } + + public ClientCard GetPZone(int player, int id) + { + if (Duel.IsNewRule) + { + return Duel.Fields[player].SpellZone[id * 4]; + } + else + { + return Duel.Fields[player].SpellZone[6 + id]; + } + } + + public long GetStringId(long id, int option) + { + return (option & 0xfffff) | (id << 20); + } + + public bool IsTurn1OrMain2() + { + return Duel.Turn == 1 || Duel.Phase == DuelPhase.Main2; + } + + public int GetBotAvailZonesFromExtraDeck(IList remove) + { + ClientCard[] BotMZone = (ClientCard[])Bot.MonsterZone.Clone(); + ClientCard[] EnemyMZone = (ClientCard[])Enemy.MonsterZone.Clone(); + for (int i = 0; i < 7; i++) + { + if (remove.Contains(BotMZone[i])) BotMZone[i] = null; + if (remove.Contains(EnemyMZone[i])) EnemyMZone[i] = null; + } + + if (!Duel.IsNewRule || Duel.IsNewRule2020) + return Zones.MainMonsterZones; + + int result = 0; + + if (BotMZone[5] == null && BotMZone[6] == null) + { + if (EnemyMZone[5] == null) + result |= Zones.z6; + if (EnemyMZone[6] == null) + result |= Zones.z5; + } + + if (BotMZone[0] == null && + ((BotMZone[1]?.HasLinkMarker(CardLinkMarker.Left) ?? false) || + (BotMZone[5]?.HasLinkMarker(CardLinkMarker.BottomLeft) ?? false) || + (EnemyMZone[6]?.HasLinkMarker(CardLinkMarker.TopRight) ?? false))) + result |= Zones.z0; + + if (BotMZone[1] == null && + ((BotMZone[0]?.HasLinkMarker(CardLinkMarker.Right) ?? false) || + (BotMZone[2]?.HasLinkMarker(CardLinkMarker.Left) ?? false) || + (BotMZone[5]?.HasLinkMarker(CardLinkMarker.Bottom) ?? false) || + (EnemyMZone[6]?.HasLinkMarker(CardLinkMarker.Top) ?? false))) + result |= Zones.z1; + + if (BotMZone[2] == null && + ((BotMZone[1]?.HasLinkMarker(CardLinkMarker.Right) ?? false) || + (BotMZone[3]?.HasLinkMarker(CardLinkMarker.Left) ?? false) || + (BotMZone[5]?.HasLinkMarker(CardLinkMarker.BottomRight) ?? false) || + (EnemyMZone[6]?.HasLinkMarker(CardLinkMarker.TopLeft) ?? false) || + (BotMZone[6]?.HasLinkMarker(CardLinkMarker.BottomLeft) ?? false) || + (EnemyMZone[5]?.HasLinkMarker(CardLinkMarker.TopRight) ?? false))) + result |= Zones.z2; + + if (BotMZone[3] == null && + ((BotMZone[2]?.HasLinkMarker(CardLinkMarker.Right) ?? false) || + (BotMZone[4]?.HasLinkMarker(CardLinkMarker.Left) ?? false) || + (BotMZone[6]?.HasLinkMarker(CardLinkMarker.Bottom) ?? false) || + (EnemyMZone[5]?.HasLinkMarker(CardLinkMarker.Top) ?? false))) + result |= Zones.z3; + + if (BotMZone[4] == null && + ((BotMZone[3]?.HasLinkMarker(CardLinkMarker.Right) ?? false) || + (BotMZone[6]?.HasLinkMarker(CardLinkMarker.BottomRight) ?? false) || + (EnemyMZone[5]?.HasLinkMarker(CardLinkMarker.TopLeft) ?? false))) + result |= Zones.z4; + + return result; + } + + public int GetBotAvailZonesFromExtraDeck(ClientCard remove) + { + return GetBotAvailZonesFromExtraDeck(new[] { remove }); + } + + public int GetBotAvailZonesFromExtraDeck() + { + return GetBotAvailZonesFromExtraDeck(new List()); + } + + public bool IsChainTarget(ClientCard card) + { + return Duel.ChainTargets.Any(card.Equals); + } + + public bool IsChainTargetOnly(ClientCard card) + { + return Duel.ChainTargetOnly.Count == 1 && card.Equals(Duel.ChainTargetOnly[0]); + } + + public bool ChainContainsCard(int id) + { + return Duel.CurrentChain.Any(card => card.IsCode(id)); + } + + public bool ChainContainsCard(int[] ids) + { + return Duel.CurrentChain.Any(card => card.IsCode(ids)); + } + + public int ChainCountPlayer(int player) + { + return Duel.CurrentChain.Count(card => card.Controller == player); + } + + public bool ChainContainPlayer(int player) + { + return Duel.CurrentChain.Any(card => card.Controller == player); + } + + public bool HasChainedTrap(int player) + { + return Duel.CurrentChain.Any(card => card.Controller == player && card.HasType(CardType.Trap)); + } + + public ClientCard GetLastChainCard() + { + return Duel.CurrentChain.LastOrDefault(); + } + + /// + /// Select cards listed in preferred. + /// + public IList SelectPreferredCards(ClientCard preferred, IList cards, int min, int max) + { + IList selected = new List(); + if (cards.IndexOf(preferred) > 0 && selected.Count < max) + { + selected.Add(preferred); + } + + return selected; + } + + /// + /// Select cards listed in preferred. + /// + public IList SelectPreferredCards(int preferred, IList cards, int min, int max) + { + IList selected = new List(); + foreach (ClientCard card in cards) + { + if (card.IsCode(preferred) && selected.Count < max) + selected.Add(card); + } + + return selected; + } + + /// + /// Select cards listed in preferred. + /// + public IList SelectPreferredCards(IList preferred, IList cards, int min, int max) + { + IList selected = new List(); + IList avail = cards.ToList(); // clone + while (preferred.Count > 0 && avail.IndexOf(preferred[0]) > 0 && selected.Count < max) + { + ClientCard card = preferred[0]; + preferred.Remove(card); + avail.Remove(card); + selected.Add(card); + } + + return selected; + } + + /// + /// Select cards listed in preferred. + /// + public IList SelectPreferredCards(IList preferred, IList cards, int min, int max) + { + IList selected = new List(); + foreach (int id in preferred) + { + foreach (ClientCard card in cards) + { + if (card.IsCode(id) && selected.Count < max && selected.IndexOf(card) <= 0) + selected.Add(card); + } + if (selected.Count >= max) + break; + } + + return selected; + } + + /// + /// Check and fix selected to make sure it meet the count requirement. + /// + public IList CheckSelectCount(IList _selected, IList cards, int min, int max) + { + var selected = _selected.ToList(); + if (selected.Count < min) + { + foreach (ClientCard card in cards) + { + if (!selected.Contains(card)) + selected.Add(card); + if (selected.Count >= max) + break; + } + } + while (selected.Count > max) + { + selected.RemoveAt(selected.Count - 1); + } + + return selected; + } + } } \ No newline at end of file diff --git a/Game/AI/CardContainer.cs b/ExecutorBase/Game/AI/CardContainer.cs similarity index 97% rename from Game/AI/CardContainer.cs rename to ExecutorBase/Game/AI/CardContainer.cs index 1b263e59..0a09a506 100644 --- a/Game/AI/CardContainer.cs +++ b/ExecutorBase/Game/AI/CardContainer.cs @@ -1,155 +1,155 @@ -using System.Collections.Generic; -using YGOSharp.OCGWrapper.Enums; -using System; -using System.Linq; - -namespace WindBot.Game.AI -{ - public static class CardContainer - { - public static int CompareCardAttack(ClientCard cardA, ClientCard cardB) - { - if (cardA.Attack < cardB.Attack) - return -1; - if (cardA.Attack == cardB.Attack) - return 0; - return 1; - } - - public static int CompareCardLevel(ClientCard cardA, ClientCard cardB) - { - if (cardA.Level < cardB.Level) - return -1; - if (cardA.Level == cardB.Level) - return 0; - return 1; - } - - public static int CompareDefensePower(ClientCard cardA, ClientCard cardB) - { - if (cardA == null && cardB == null) - return 0; - if (cardA == null) - return -1; - if (cardB == null) - return 1; - int powerA = cardA.GetDefensePower(); - int powerB = cardB.GetDefensePower(); - if (powerA < powerB) - return -1; - if (powerA == powerB) - return 0; - return 1; - } - - public static ClientCard GetHighestAttackMonster(this IEnumerable cards, bool canBeTarget = false) - { - return cards - .Where(card => card?.Data != null && card.HasType(CardType.Monster) && card.IsFaceup() && !(canBeTarget && card.IsShouldNotBeTarget())) - .OrderBy(card => card.Attack).FirstOrDefault(); - } - - public static ClientCard GetHighestDefenseMonster(this IEnumerable cards, bool canBeTarget = false) - { - return cards - .Where(card => card?.Data != null && card.HasType(CardType.Monster) && card.IsFaceup() && !(canBeTarget && card.IsShouldNotBeTarget())) - .OrderBy(card => card.Defense).FirstOrDefault(); - } - - public static ClientCard GetLowestAttackMonster(this IEnumerable cards, bool canBeTarget = false) - { - return cards - .Where(card => card?.Data != null && card.HasType(CardType.Monster) && card.IsFaceup() && !(canBeTarget && card.IsShouldNotBeTarget())) - .OrderByDescending(card => card.Attack).FirstOrDefault(); - } - - public static ClientCard GetLowestDefenseMonster(this IEnumerable cards, bool canBeTarget = false) - { - return cards - .Where(card => card?.Data != null && card.HasType(CardType.Monster) && card.IsFaceup() && !(canBeTarget && card.IsShouldNotBeTarget())) - .OrderByDescending(card => card.Defense).FirstOrDefault(); - } - - public static bool ContainsMonsterWithLevel(this IEnumerable cards, int level) - { - return cards.Where(card => card?.Data != null).Any(card => !card.HasType(CardType.Xyz) && card.Level == level); - } - - public static bool ContainsMonsterWithRank(this IEnumerable cards, int rank) - { - return cards.Where(card => card?.Data != null).Any(card => card.HasType(CardType.Xyz) && card.Rank == rank); - } - - public static bool ContainsCardWithId(this IEnumerable cards, int id) - { - return cards.Where(card => card?.Data != null).Any(card => card.IsCode(id)); - } - - public static int GetCardCount(this IEnumerable cards, int id) - { - return cards.Where(card => card?.Data != null).Count(card => card.IsCode(id)); - } - - public static List GetMonsters(this IEnumerable cards) - { - return cards.Where(card => card?.Data != null && card.HasType(CardType.Monster)).ToList(); - } - - public static List GetFaceupPendulumMonsters(this IEnumerable cards) - { - return cards.Where(card => card?.Data != null && card.HasType(CardType.Monster) && card.IsFaceup() && card.HasType(CardType.Pendulum)).ToList(); - } - - public static ClientCard GetInvincibleMonster(this IEnumerable cards, bool canBeTarget = false) - { - return cards.FirstOrDefault(card => card?.Data != null && card.IsMonsterInvincible() && card.IsFaceup() && (!canBeTarget || !card.IsShouldNotBeTarget())); - } - - public static ClientCard GetDangerousMonster(this IEnumerable cards, bool canBeTarget = false) - { - return cards.FirstOrDefault(card => card?.Data != null && card.IsMonsterDangerous() && card.IsFaceup() && (!canBeTarget || !card.IsShouldNotBeTarget())); - } - - public static ClientCard GetFloodgate(this IEnumerable cards, bool canBeTarget = false) - { - return cards.FirstOrDefault(card => card?.Data != null && card.IsFloodgate() && card.IsFaceup() && (!canBeTarget || !card.IsShouldNotBeTarget())); - } - - public static ClientCard GetFirstMatchingCard(this IEnumerable cards, Func filter) - { - return cards.FirstOrDefault(card => card?.Data != null && filter.Invoke(card)); - } - - public static ClientCard GetFirstMatchingFaceupCard(this IEnumerable cards, Func filter) - { - return cards.FirstOrDefault(card => card?.Data != null && card.IsFaceup() && filter.Invoke(card)); - } - - public static IList GetMatchingCards(this IEnumerable cards, Func filter) - { - return cards.Where(card => card?.Data != null && filter.Invoke(card)).ToList(); - } - - public static int GetMatchingCardsCount(this IEnumerable cards, Func filter) - { - return cards.Count(card => card?.Data != null && filter.Invoke(card)); - } - - public static bool IsExistingMatchingCard(this IEnumerable cards, Func filter, int count = 1) - { - return cards.GetMatchingCardsCount(filter) >= count; - } - - public static ClientCard GetShouldBeDisabledBeforeItUseEffectMonster(this IEnumerable cards, bool canBeTarget = true) - { - return cards.FirstOrDefault(card => card?.Data != null && card.IsMonsterShouldBeDisabledBeforeItUseEffect() && card.IsFaceup() && (!canBeTarget || !card.IsShouldNotBeTarget())); - } - - public static IEnumerable> GetCombinations(this IEnumerable elements, int k) - { - return k == 0 ? new[] { new T[0] } : - elements.SelectMany((e, i) => - elements.Skip(i + 1).GetCombinations(k - 1).Select(c => (new[] { e }).Concat(c))); - } - } +using System.Collections.Generic; +using YGOSharp.OCGWrapper.Enums; +using System; +using System.Linq; + +namespace WindBot.Game.AI +{ + public static class CardContainer + { + public static int CompareCardAttack(ClientCard cardA, ClientCard cardB) + { + if (cardA.Attack < cardB.Attack) + return -1; + if (cardA.Attack == cardB.Attack) + return 0; + return 1; + } + + public static int CompareCardLevel(ClientCard cardA, ClientCard cardB) + { + if (cardA.Level < cardB.Level) + return -1; + if (cardA.Level == cardB.Level) + return 0; + return 1; + } + + public static int CompareDefensePower(ClientCard cardA, ClientCard cardB) + { + if (cardA == null && cardB == null) + return 0; + if (cardA == null) + return -1; + if (cardB == null) + return 1; + int powerA = cardA.GetDefensePower(); + int powerB = cardB.GetDefensePower(); + if (powerA < powerB) + return -1; + if (powerA == powerB) + return 0; + return 1; + } + + public static ClientCard GetHighestAttackMonster(this IEnumerable cards, bool canBeTarget = false) + { + return cards + .Where(card => card?.Data != null && card.HasType(CardType.Monster) && card.IsFaceup() && !(canBeTarget && card.IsShouldNotBeTarget())) + .OrderBy(card => card.Attack).FirstOrDefault(); + } + + public static ClientCard GetHighestDefenseMonster(this IEnumerable cards, bool canBeTarget = false) + { + return cards + .Where(card => card?.Data != null && card.HasType(CardType.Monster) && card.IsFaceup() && !(canBeTarget && card.IsShouldNotBeTarget())) + .OrderBy(card => card.Defense).FirstOrDefault(); + } + + public static ClientCard GetLowestAttackMonster(this IEnumerable cards, bool canBeTarget = false) + { + return cards + .Where(card => card?.Data != null && card.HasType(CardType.Monster) && card.IsFaceup() && !(canBeTarget && card.IsShouldNotBeTarget())) + .OrderByDescending(card => card.Attack).FirstOrDefault(); + } + + public static ClientCard GetLowestDefenseMonster(this IEnumerable cards, bool canBeTarget = false) + { + return cards + .Where(card => card?.Data != null && card.HasType(CardType.Monster) && card.IsFaceup() && !(canBeTarget && card.IsShouldNotBeTarget())) + .OrderByDescending(card => card.Defense).FirstOrDefault(); + } + + public static bool ContainsMonsterWithLevel(this IEnumerable cards, int level) + { + return cards.Where(card => card?.Data != null).Any(card => !card.HasType(CardType.Xyz) && card.Level == level); + } + + public static bool ContainsMonsterWithRank(this IEnumerable cards, int rank) + { + return cards.Where(card => card?.Data != null).Any(card => card.HasType(CardType.Xyz) && card.Rank == rank); + } + + public static bool ContainsCardWithId(this IEnumerable cards, int id) + { + return cards.Where(card => card?.Data != null).Any(card => card.IsCode(id)); + } + + public static int GetCardCount(this IEnumerable cards, int id) + { + return cards.Where(card => card?.Data != null).Count(card => card.IsCode(id)); + } + + public static List GetMonsters(this IEnumerable cards) + { + return cards.Where(card => card?.Data != null && card.HasType(CardType.Monster)).ToList(); + } + + public static List GetFaceupPendulumMonsters(this IEnumerable cards) + { + return cards.Where(card => card?.Data != null && card.HasType(CardType.Monster) && card.IsFaceup() && card.HasType(CardType.Pendulum)).ToList(); + } + + public static ClientCard GetInvincibleMonster(this IEnumerable cards, bool canBeTarget = false) + { + return cards.FirstOrDefault(card => card?.Data != null && card.IsMonsterInvincible() && card.IsFaceup() && (!canBeTarget || !card.IsShouldNotBeTarget())); + } + + public static ClientCard GetDangerousMonster(this IEnumerable cards, bool canBeTarget = false) + { + return cards.FirstOrDefault(card => card?.Data != null && card.IsMonsterDangerous() && card.IsFaceup() && (!canBeTarget || !card.IsShouldNotBeTarget())); + } + + public static ClientCard GetFloodgate(this IEnumerable cards, bool canBeTarget = false) + { + return cards.FirstOrDefault(card => card?.Data != null && card.IsFloodgate() && card.IsFaceup() && (!canBeTarget || !card.IsShouldNotBeTarget())); + } + + public static ClientCard GetFirstMatchingCard(this IEnumerable cards, Func filter) + { + return cards.FirstOrDefault(card => card?.Data != null && filter.Invoke(card)); + } + + public static ClientCard GetFirstMatchingFaceupCard(this IEnumerable cards, Func filter) + { + return cards.FirstOrDefault(card => card?.Data != null && card.IsFaceup() && filter.Invoke(card)); + } + + public static IList GetMatchingCards(this IEnumerable cards, Func filter) + { + return cards.Where(card => card?.Data != null && filter.Invoke(card)).ToList(); + } + + public static int GetMatchingCardsCount(this IEnumerable cards, Func filter) + { + return cards.Count(card => card?.Data != null && filter.Invoke(card)); + } + + public static bool IsExistingMatchingCard(this IEnumerable cards, Func filter, int count = 1) + { + return cards.GetMatchingCardsCount(filter) >= count; + } + + public static ClientCard GetShouldBeDisabledBeforeItUseEffectMonster(this IEnumerable cards, bool canBeTarget = true) + { + return cards.FirstOrDefault(card => card?.Data != null && card.IsMonsterShouldBeDisabledBeforeItUseEffect() && card.IsFaceup() && (!canBeTarget || !card.IsShouldNotBeTarget())); + } + + public static IEnumerable> GetCombinations(this IEnumerable elements, int k) + { + return k == 0 ? new[] { new T[0] } : + elements.SelectMany((e, i) => + elements.Skip(i + 1).GetCombinations(k - 1).Select(c => (new[] { e }).Concat(c))); + } + } } \ No newline at end of file diff --git a/Game/AI/CardExecutor.cs b/ExecutorBase/Game/AI/CardExecutor.cs similarity index 96% rename from Game/AI/CardExecutor.cs rename to ExecutorBase/Game/AI/CardExecutor.cs index 75509a77..d5ec43dd 100644 --- a/Game/AI/CardExecutor.cs +++ b/ExecutorBase/Game/AI/CardExecutor.cs @@ -1,18 +1,18 @@ -using System; - -namespace WindBot.Game.AI -{ - public class CardExecutor - { - public int CardId { get; private set; } - public ExecutorType Type { get; private set; } - public Func Func { get; private set; } - - public CardExecutor(ExecutorType type, int cardId, Func func) - { - CardId = cardId; - Type = type; - Func = func; - } - } +using System; + +namespace WindBot.Game.AI +{ + public class CardExecutor + { + public int CardId { get; private set; } + public ExecutorType Type { get; private set; } + public Func Func { get; private set; } + + public CardExecutor(ExecutorType type, int cardId, Func func) + { + CardId = cardId; + Type = type; + Func = func; + } + } } \ No newline at end of file diff --git a/Game/AI/CardExtension.cs b/ExecutorBase/Game/AI/CardExtension.cs similarity index 97% rename from Game/AI/CardExtension.cs rename to ExecutorBase/Game/AI/CardExtension.cs index 8b30394b..3e45daf1 100644 --- a/Game/AI/CardExtension.cs +++ b/ExecutorBase/Game/AI/CardExtension.cs @@ -1,80 +1,80 @@ -using System; -using WindBot.Game.AI.Enums; -using YGOSharp.OCGWrapper.Enums; - -namespace WindBot.Game.AI -{ - public static class CardExtension - { - /// - /// Is this monster is invincible to battle? - /// - public static bool IsMonsterInvincible(this ClientCard card) - { - return !card.IsDisabled() && Enum.IsDefined(typeof(InvincibleMonster), card.Id); - } - - /// - /// Is this monster is dangerous to attack? - /// - public static bool IsMonsterDangerous(this ClientCard card) - { - return !card.IsDisabled() && Enum.IsDefined(typeof(DangerousMonster), card.Id); - } - - /// - /// Do this monster prevents activation of opponent's effect monsters in battle? - /// - public static bool IsMonsterHasPreventActivationEffectInBattle(this ClientCard card) - { - return !card.IsDisabled() && Enum.IsDefined(typeof(PreventActivationEffectInBattle), card.Id); - } - - /// - /// Is this card shouldn't be tried to be selected as target? - /// - public static bool IsShouldNotBeTarget(this ClientCard card) - { - return !card.IsDisabled() && !card.HasType(CardType.Normal) && Enum.IsDefined(typeof(ShouldNotBeTarget), card.Id); - } - - /// - /// Is this card shouldn't be tried to be selected as target of monster? - /// - public static bool IsShouldNotBeMonsterTarget(this ClientCard card) - { - return !card.IsDisabled() && Enum.IsDefined(typeof(ShouldNotBeMonsterTarget), card.Id); - } - - /// - /// Is this card shouldn't be tried to be selected as target of spell & trap? - /// - public static bool IsShouldNotBeSpellTrapTarget(this ClientCard card) - { - return !card.IsDisabled() && Enum.IsDefined(typeof(ShouldNotBeSpellTrapTarget), card.Id); - } - - /// - /// Is this monster should be disabled (with Breakthrough Skill) before it use effect and release or banish itself? - /// - public static bool IsMonsterShouldBeDisabledBeforeItUseEffect(this ClientCard card) - { - return !card.IsDisabled() && Enum.IsDefined(typeof(ShouldBeDisabledBeforeItUseEffectMonster), card.Id); - } - - public static bool IsFloodgate(this ClientCard card) - { - return Enum.IsDefined(typeof(Floodgate), card.Id); - } - - public static bool IsOneForXyz(this ClientCard card) - { - return Enum.IsDefined(typeof(OneForXyz), card.Id); - } - - public static bool IsFusionSpell(this ClientCard card) - { - return Enum.IsDefined(typeof(FusionSpell), card.Id); - } - } +using System; +using WindBot.Game.AI.Enums; +using YGOSharp.OCGWrapper.Enums; + +namespace WindBot.Game.AI +{ + public static class CardExtension + { + /// + /// Is this monster is invincible to battle? + /// + public static bool IsMonsterInvincible(this ClientCard card) + { + return !card.IsDisabled() && Enum.IsDefined(typeof(InvincibleMonster), card.Id); + } + + /// + /// Is this monster is dangerous to attack? + /// + public static bool IsMonsterDangerous(this ClientCard card) + { + return !card.IsDisabled() && Enum.IsDefined(typeof(DangerousMonster), card.Id); + } + + /// + /// Do this monster prevents activation of opponent's effect monsters in battle? + /// + public static bool IsMonsterHasPreventActivationEffectInBattle(this ClientCard card) + { + return !card.IsDisabled() && Enum.IsDefined(typeof(PreventActivationEffectInBattle), card.Id); + } + + /// + /// Is this card shouldn't be tried to be selected as target? + /// + public static bool IsShouldNotBeTarget(this ClientCard card) + { + return !card.IsDisabled() && !card.HasType(CardType.Normal) && Enum.IsDefined(typeof(ShouldNotBeTarget), card.Id); + } + + /// + /// Is this card shouldn't be tried to be selected as target of monster? + /// + public static bool IsShouldNotBeMonsterTarget(this ClientCard card) + { + return !card.IsDisabled() && Enum.IsDefined(typeof(ShouldNotBeMonsterTarget), card.Id); + } + + /// + /// Is this card shouldn't be tried to be selected as target of spell & trap? + /// + public static bool IsShouldNotBeSpellTrapTarget(this ClientCard card) + { + return !card.IsDisabled() && Enum.IsDefined(typeof(ShouldNotBeSpellTrapTarget), card.Id); + } + + /// + /// Is this monster should be disabled (with Breakthrough Skill) before it use effect and release or banish itself? + /// + public static bool IsMonsterShouldBeDisabledBeforeItUseEffect(this ClientCard card) + { + return !card.IsDisabled() && Enum.IsDefined(typeof(ShouldBeDisabledBeforeItUseEffectMonster), card.Id); + } + + public static bool IsFloodgate(this ClientCard card) + { + return Enum.IsDefined(typeof(Floodgate), card.Id); + } + + public static bool IsOneForXyz(this ClientCard card) + { + return Enum.IsDefined(typeof(OneForXyz), card.Id); + } + + public static bool IsFusionSpell(this ClientCard card) + { + return Enum.IsDefined(typeof(FusionSpell), card.Id); + } + } } \ No newline at end of file diff --git a/Game/AI/CardSelector.cs b/ExecutorBase/Game/AI/CardSelector.cs similarity index 96% rename from Game/AI/CardSelector.cs rename to ExecutorBase/Game/AI/CardSelector.cs index 5a0cf2ba..fda0bb55 100644 --- a/Game/AI/CardSelector.cs +++ b/ExecutorBase/Game/AI/CardSelector.cs @@ -1,104 +1,104 @@ -using System.Collections.Generic; -using YGOSharp.OCGWrapper.Enums; - -namespace WindBot.Game.AI -{ - public class CardSelector - { - private enum SelectType - { - Card, - Cards, - Id, - Ids, - Location - } - - private SelectType _type; - private ClientCard _card; - private IList _cards; - private int _id; - private IList _ids; - private CardLocation _location; - - public CardSelector(ClientCard card) - { - _type = SelectType.Card; - _card = card; - } - - public CardSelector(IList cards) - { - _type = SelectType.Cards; - _cards = cards; - } - - public CardSelector(int cardId) - { - _type = SelectType.Id; - _id = cardId; - } - - public CardSelector(IList ids) - { - _type = SelectType.Ids; - _ids = ids; - } - - public CardSelector(CardLocation location) - { - _type = SelectType.Location; - _location = location; - } - - public IList Select(IList cards, int min, int max) - { - IList result = new List(); - - switch (_type) - { - case SelectType.Card: - if (cards.Contains(_card)) - result.Add(_card); - break; - case SelectType.Cards: - foreach (ClientCard card in _cards) - if (cards.Contains(card) && !result.Contains(card)) - result.Add(card); - break; - case SelectType.Id: - foreach (ClientCard card in cards) - if (card.IsCode(_id)) - result.Add(card); - break; - case SelectType.Ids: - foreach (int id in _ids) - foreach (ClientCard card in cards) - if (card.IsCode(id) && !result.Contains(card)) - result.Add(card); - break; - case SelectType.Location: - foreach (ClientCard card in cards) - if (card.Location == _location) - result.Add(card); - break; - } - - if (result.Count < min) - { - foreach (ClientCard card in cards) - { - if (!result.Contains(card)) - result.Add(card); - if (result.Count >= min) - break; - } - } - - while (result.Count > max) - result.RemoveAt(result.Count - 1); - - return result; - } - } +using System.Collections.Generic; +using YGOSharp.OCGWrapper.Enums; + +namespace WindBot.Game.AI +{ + public class CardSelector + { + private enum SelectType + { + Card, + Cards, + Id, + Ids, + Location + } + + private SelectType _type; + private ClientCard _card; + private IList _cards; + private int _id; + private IList _ids; + private CardLocation _location; + + public CardSelector(ClientCard card) + { + _type = SelectType.Card; + _card = card; + } + + public CardSelector(IList cards) + { + _type = SelectType.Cards; + _cards = cards; + } + + public CardSelector(int cardId) + { + _type = SelectType.Id; + _id = cardId; + } + + public CardSelector(IList ids) + { + _type = SelectType.Ids; + _ids = ids; + } + + public CardSelector(CardLocation location) + { + _type = SelectType.Location; + _location = location; + } + + public IList Select(IList cards, int min, int max) + { + IList result = new List(); + + switch (_type) + { + case SelectType.Card: + if (cards.Contains(_card)) + result.Add(_card); + break; + case SelectType.Cards: + foreach (ClientCard card in _cards) + if (cards.Contains(card) && !result.Contains(card)) + result.Add(card); + break; + case SelectType.Id: + foreach (ClientCard card in cards) + if (card.IsCode(_id)) + result.Add(card); + break; + case SelectType.Ids: + foreach (int id in _ids) + foreach (ClientCard card in cards) + if (card.IsCode(id) && !result.Contains(card)) + result.Add(card); + break; + case SelectType.Location: + foreach (ClientCard card in cards) + if (card.Location == _location) + result.Add(card); + break; + } + + if (result.Count < min) + { + foreach (ClientCard card in cards) + { + if (!result.Contains(card)) + result.Add(card); + if (result.Count >= min) + break; + } + } + + while (result.Count > max) + result.RemoveAt(result.Count - 1); + + return result; + } + } } \ No newline at end of file diff --git a/Game/AI/DeckAttribute.cs b/ExecutorBase/Game/AI/DeckAttribute.cs similarity index 96% rename from Game/AI/DeckAttribute.cs rename to ExecutorBase/Game/AI/DeckAttribute.cs index 49bae161..89a87a9c 100644 --- a/Game/AI/DeckAttribute.cs +++ b/ExecutorBase/Game/AI/DeckAttribute.cs @@ -1,22 +1,22 @@ -using System; - -namespace WindBot.Game.AI -{ - [AttributeUsage(AttributeTargets.Class)] - public class DeckAttribute : Attribute - { - public string Name { get; private set; } - public string File { get; private set; } - public string Level { get; private set; } - - public DeckAttribute(string name, string file = null, string level = "Normal") - { - if (String.IsNullOrEmpty(file)) - file = name; - - Name = name; - File = file; - Level = level; - } - } -} +using System; + +namespace WindBot.Game.AI +{ + [AttributeUsage(AttributeTargets.Class)] + public class DeckAttribute : Attribute + { + public string Name { get; private set; } + public string File { get; private set; } + public string Level { get; private set; } + + public DeckAttribute(string name, string file = null, string level = "Normal") + { + if (String.IsNullOrEmpty(file)) + file = name; + + Name = name; + File = file; + Level = level; + } + } +} diff --git a/Game/AI/DefaultExecutor.cs b/ExecutorBase/Game/AI/DefaultExecutor.cs similarity index 97% rename from Game/AI/DefaultExecutor.cs rename to ExecutorBase/Game/AI/DefaultExecutor.cs index 6c5be767..fd63c8a6 100644 --- a/Game/AI/DefaultExecutor.cs +++ b/ExecutorBase/Game/AI/DefaultExecutor.cs @@ -1,1094 +1,1098 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using YGOSharp.OCGWrapper.Enums; -using WindBot; -using WindBot.Game; -using WindBot.Game.AI; - -namespace WindBot.Game.AI -{ - public abstract class DefaultExecutor : Executor - { - protected class _CardId - { - public const int JizukirutheStarDestroyingKaiju = 63941210; - public const int ThunderKingtheLightningstrikeKaiju = 48770333; - public const int DogorantheMadFlameKaiju = 93332803; - public const int RadiantheMultidimensionalKaiju = 28674152; - public const int GadarlatheMysteryDustKaiju = 36956512; - public const int KumongoustheStickyStringKaiju = 29726552; - public const int GamecieltheSeaTurtleKaiju = 55063751; - public const int SuperAntiKaijuWarMachineMechaDogoran = 84769941; - - public const int UltimateConductorTytanno = 18940556; - public const int ElShaddollConstruct = 20366274; - public const int AllyOfJusticeCatastor = 26593852; - - public const int DupeFrog = 46239604; - public const int MaraudingCaptain = 2460565; - - public const int BlackRoseDragon = 73580471; - public const int JudgmentDragon = 57774843; - public const int TopologicTrisbaena = 72529749; - public const int EvilswarmExcitonKnight = 46772449; - public const int HarpiesFeatherDuster = 18144506; - public const int DarkMagicAttack = 2314238; - public const int MysticalSpaceTyphoon = 5318639; - public const int CosmicCyclone = 8267140; - public const int ChickenGame = 67616300; - - public const int CastelTheSkyblasterMusketeer = 82633039; - public const int CrystalWingSynchroDragon = 50954680; - public const int NumberS39UtopiaTheLightning = 56832966; - public const int Number39Utopia = 84013237; - public const int UltimayaTzolkin = 1686814; - public const int MekkKnightCrusadiaAstram = 21887175; - - public const int MoonMirrorShield = 19508728; - public const int PhantomKnightsFogBlade = 25542642; - - public const int VampireFraeulein = 6039967; - public const int InjectionFairyLily = 79575620; - - public const int BlueEyesChaosMAXDragon = 55410871; - - public const int AshBlossom = 14558127; - public const int MaxxC = 23434538; - public const int LockBird = 94145021; - public const int GhostOgreAndSnowRabbit = 59438930; - public const int GhostBelle = 73642296; - public const int EffectVeiler = 63845230; - public const int ArtifactLancea = 34267821; - - public const int CalledByTheGrave = 24224830; - public const int InfiniteImpermanence = 10045474; - public const int GalaxySoldier = 46659709; - public const int MacroCosmos = 30241314; - public const int UpstartGoblin = 70368879; - public const int CyberEmergency = 60600126; - - public const int EaterOfMillions = 63845230; - - public const int InvokedPurgatrio = 12307878; - public const int ChaosAncientGearGiant = 51788412; - public const int UltimateAncientGearGolem = 12652643; - - public const int RedDragonArchfiend = 70902743; - - public const int ImperialOrder = 61740673; - public const int NaturiaBeast = 33198837; - public const int AntiSpellFragrance = 58921041; - } - - int HonestEffectCount = 0; - - protected DefaultExecutor(GameAI ai, Duel duel) - : base(ai, duel) - { - AddExecutor(ExecutorType.Activate, _CardId.ChickenGame, DefaultChickenGame); - } - - /// - /// Decide which card should the attacker attack. - /// - /// Card that attack. - /// Cards that defend. - /// BattlePhaseAction including the target, or null (in this situation, GameAI will check the next attacker) - public override BattlePhaseAction OnSelectAttackTarget(ClientCard attacker, IList defenders) - { - foreach (ClientCard defender in defenders) - { - attacker.RealPower = attacker.Attack; - defender.RealPower = defender.GetDefensePower(); - if (!OnPreBattleBetween(attacker, defender)) - continue; - - if (attacker.RealPower > defender.RealPower || (attacker.RealPower >= defender.RealPower && attacker.IsLastAttacker && defender.IsAttack())) - return AI.Attack(attacker, defender); - } - - if (attacker.CanDirectAttack) - return AI.Attack(attacker, null); - - return null; - } - - /// - /// Decide whether to declare attack between attacker and defender. - /// Can be overrided to update the RealPower of attacker for cards like Honest. - /// - /// Card that attack. - /// Card that defend. - /// false if the attack shouldn't be done. - public override bool OnPreBattleBetween(ClientCard attacker, ClientCard defender) - { - if (attacker.RealPower <= 0) - return false; - - if (!attacker.IsMonsterHasPreventActivationEffectInBattle()) - { - if (defender.IsMonsterInvincible() && defender.IsDefense()) - return false; - - if (defender.IsMonsterDangerous()) - { - bool canIgnoreIt = !attacker.IsDisabled() && ( - attacker.IsCode(_CardId.UltimateConductorTytanno) && defender.IsDefense() || - attacker.IsCode(_CardId.ElShaddollConstruct) && defender.IsSpecialSummoned || - attacker.IsCode(_CardId.AllyOfJusticeCatastor) && !defender.HasAttribute(CardAttribute.Dark)); - if (!canIgnoreIt) - return false; - } - - foreach (ClientCard equip in defender.EquipCards) - { - if (equip.IsCode(_CardId.MoonMirrorShield) && !equip.IsDisabled()) - { - return false; - } - } - - if (!defender.IsDisabled()) - { - if (defender.IsCode(_CardId.MekkKnightCrusadiaAstram) && defender.IsAttack() && attacker.IsSpecialSummoned) - return false; - - if (defender.IsCode(_CardId.CrystalWingSynchroDragon) && defender.IsAttack() && attacker.Level >= 5) - return false; - - if (defender.IsCode(_CardId.AllyOfJusticeCatastor) && !attacker.HasAttribute(CardAttribute.Dark)) - return false; - - if (defender.IsCode(_CardId.NumberS39UtopiaTheLightning) && defender.IsAttack() && defender.HasXyzMaterial(2, _CardId.Number39Utopia)) - defender.RealPower = 5000; - - if (defender.IsCode(_CardId.VampireFraeulein)) - defender.RealPower += (Enemy.LifePoints > 3000) ? 3000 : (Enemy.LifePoints - 100); - - if (defender.IsCode(_CardId.InjectionFairyLily) && Enemy.LifePoints > 2000) - defender.RealPower += 3000; - } - } - - if (!defender.IsMonsterHasPreventActivationEffectInBattle()) - { - if (attacker.IsCode(_CardId.NumberS39UtopiaTheLightning) && !attacker.IsDisabled() && attacker.HasXyzMaterial(2, _CardId.Number39Utopia)) - attacker.RealPower = 5000; - - foreach (ClientCard equip in attacker.EquipCards) - { - if (equip.IsCode(_CardId.MoonMirrorShield) && !equip.IsDisabled()) - { - attacker.RealPower = defender.RealPower + 100; - } - } - } - - if (Enemy.HasInMonstersZone(_CardId.MekkKnightCrusadiaAstram, true) && !(defender).IsCode(_CardId.MekkKnightCrusadiaAstram)) - return false; - - if (Enemy.HasInMonstersZone(_CardId.DupeFrog, true) && !(defender).IsCode(_CardId.DupeFrog)) - return false; - - if (Enemy.HasInMonstersZone(_CardId.MaraudingCaptain, true) && !defender.IsCode(_CardId.MaraudingCaptain) && defender.Race == (int)CardRace.Warrior) - return false; - - if (defender.IsCode(_CardId.UltimayaTzolkin) && !defender.IsDisabled() && Enemy.GetMonsters().Any(monster => !monster.Equals(defender) && monster.HasType(CardType.Synchro))) - return false; - - if (defender.OwnTargets.Any(card => card.IsCode(_CardId.PhantomKnightsFogBlade) && !card.IsDisabled())) - return false; - - return true; - } - - /// - /// Called when the AI has to select a card position. - /// - /// Id of the card to position on the field. - /// List of available positions. - /// Selected position, or 0 if no position is set for this card. - public override CardPosition OnSelectPosition(int cardId, IList positions) - { - YGOSharp.OCGWrapper.NamedCard cardData = YGOSharp.OCGWrapper.NamedCard.Get(cardId); - if (cardData != null) - { - if (cardData.Attack == 0) - return CardPosition.FaceUpDefence; - } - return 0; - } - - public override bool OnSelectBattleReplay() - { - if (Bot.BattlingMonster == null) - return false; - List defenders = new List(Duel.Fields[1].GetMonsters()); - defenders.Sort(CardContainer.CompareDefensePower); - defenders.Reverse(); - BattlePhaseAction result = OnSelectAttackTarget(Bot.BattlingMonster, defenders); - if (result != null && result.Action == BattlePhaseAction.BattleAction.Attack) - { - return true; - } - return false; - } - - public override void OnNewTurn() - { - HonestEffectCount = 0; - } - - /// - /// Destroy face-down cards first, in our turn. - /// - protected bool DefaultMysticalSpaceTyphoon() - { - if (Duel.CurrentChain.Any(card => card.IsCode(_CardId.MysticalSpaceTyphoon))) - { - return false; - } - - List spells = Enemy.GetSpells(); - if (spells.Count == 0) - return false; - - ClientCard selected = Enemy.SpellZone.GetFloodgate(); - - if (selected == null) - { - if (Duel.Player == 0) - selected = spells.FirstOrDefault(card => card.IsFacedown()); - if (Duel.Player == 1) - selected = spells.FirstOrDefault(card => card.HasType(CardType.Continuous) || card.HasType(CardType.Equip) || card.HasType(CardType.Field)); - } - - if (selected == null) - return false; - AI.SelectCard(selected); - return true; - } - - /// - /// Destroy face-down cards first, in our turn. - /// - protected bool DefaultCosmicCyclone() - { - foreach (ClientCard card in Duel.CurrentChain) - if (card.IsCode(_CardId.CosmicCyclone)) - return false; - return (Bot.LifePoints > 1000) && DefaultMysticalSpaceTyphoon(); - } - - /// - /// Activate if avail. - /// - protected bool DefaultGalaxyCyclone() - { - List spells = Enemy.GetSpells(); - if (spells.Count == 0) - return false; - - ClientCard selected = null; - - if (Card.Location == CardLocation.Grave) - { - selected = Util.GetBestEnemySpell(true); - } - else - { - selected = spells.FirstOrDefault(card => card.IsFacedown()); - } - - if (selected == null) - return false; - - AI.SelectCard(selected); - return true; - } - - /// - /// Set the highest ATK level 4+ effect enemy monster. - /// - protected bool DefaultBookOfMoon() - { - if (Util.IsAllEnemyBetter(true)) - { - ClientCard monster = Enemy.GetMonsters().GetHighestAttackMonster(true); - if (monster != null && monster.HasType(CardType.Effect) && !monster.HasType(CardType.Link) && (monster.HasType(CardType.Xyz) || monster.Level > 4)) - { - AI.SelectCard(monster); - return true; - } - } - return false; - } - - /// - /// Return problematic monster, and if this card become target, return any enemy monster. - /// - protected bool DefaultCompulsoryEvacuationDevice() - { - ClientCard target = Util.GetProblematicEnemyMonster(0, true); - if (target != null) - { - AI.SelectCard(target); - return true; - } - if (Util.IsChainTarget(Card)) - { - ClientCard monster = Util.GetBestEnemyMonster(false, true); - if (monster != null) - { - AI.SelectCard(monster); - return true; - } - } - return false; - } - - /// - /// Revive the best monster when we don't have better one in field. - /// - protected bool DefaultCallOfTheHaunted() - { - if (!Util.IsAllEnemyBetter(true)) - return false; - ClientCard selected = Bot.Graveyard.GetMatchingCards(card => card.IsCanRevive()).OrderByDescending(card => card.Attack).FirstOrDefault(); - AI.SelectCard(selected); - return true; - } - - /// - /// Default Scapegoat effect - /// - protected bool DefaultScapegoat() - { - if (DefaultSpellWillBeNegated()) return false; - if (Duel.Player == 0) return false; - if (Duel.Phase == DuelPhase.End) return true; - if (DefaultOnBecomeTarget()) return true; - if (Duel.Phase > DuelPhase.Main1 && Duel.Phase < DuelPhase.Main2) - { - if (Enemy.HasInMonstersZone(new[] - { - _CardId.UltimateConductorTytanno, - _CardId.InvokedPurgatrio, - _CardId.ChaosAncientGearGiant, - _CardId.UltimateAncientGearGolem, - _CardId.RedDragonArchfiend - }, true)) return false; - if (Util.GetTotalAttackingMonsterAttack(1) >= Bot.LifePoints) return true; - } - return false; - } - /// - /// Always active in opponent's turn. - /// - protected bool DefaultMaxxC() - { - return Duel.Player == 1; - } - /// - /// Always disable opponent's effect except some cards like UpstartGoblin - /// - protected bool DefaultAshBlossomAndJoyousSpring() - { - int[] ignoreList = { - _CardId.MacroCosmos, - _CardId.UpstartGoblin, - _CardId.CyberEmergency - }; - if (Util.GetLastChainCard().IsCode(ignoreList)) - return false; - if (Util.GetLastChainCard().HasSetcode(0x11e) && Util.GetLastChainCard().Location == CardLocation.Hand) // Danger! archtype hand effect - return false; - return Duel.LastChainPlayer == 1; - } - /// - /// Always activate unless the activating card is disabled - /// - protected bool DefaultGhostOgreAndSnowRabbit() - { - if (Util.GetLastChainCard() != null && Util.GetLastChainCard().IsDisabled()) - return false; - return DefaultTrap(); - } - /// - /// Always disable opponent's effect - /// - protected bool DefaultGhostBelleAndHauntedMansion() - { - return DefaultTrap(); - } - /// - /// Same as DefaultBreakthroughSkill - /// - protected bool DefaultEffectVeiler() - { - if (Util.GetLastChainCard() != null && Util.GetLastChainCard().IsCode(_CardId.GalaxySoldier) && Enemy.Hand.Count >= 3) return false; - if (Util.ChainContainsCard(_CardId.EffectVeiler)) - return false; - return DefaultBreakthroughSkill(); - } - /// - /// Chain common hand traps - /// - protected bool DefaultCalledByTheGrave() - { - int[] targetList = - { - _CardId.MaxxC, - _CardId.LockBird, - _CardId.GhostOgreAndSnowRabbit, - _CardId.AshBlossom, - _CardId.GhostBelle, - _CardId.EffectVeiler, - _CardId.ArtifactLancea - }; - if (Duel.LastChainPlayer == 1) - { - foreach (int id in targetList) - { - if (Util.GetLastChainCard().IsCode(id)) - { - AI.SelectCard(id); - return UniqueFaceupSpell(); - } - } - } - return false; - } - /// - /// Default InfiniteImpermanence effect - /// - protected bool DefaultInfiniteImpermanence() - { - // TODO: disable s & t - if (!DefaultUniqueTrap()) - return false; - return DefaultDisableMonster(); - } - /// - /// Chain the enemy monster, or disable monster like Rescue Rabbit. - /// - protected bool DefaultBreakthroughSkill() - { - if (!DefaultUniqueTrap()) - return false; - return DefaultDisableMonster(); - } - /// - /// Chain the enemy monster, or disable monster like Rescue Rabbit. - /// - protected bool DefaultDisableMonster() - { - if (Duel.Player == 1) - { - ClientCard target = Enemy.MonsterZone.GetShouldBeDisabledBeforeItUseEffectMonster(); - if (target != null) - { - AI.SelectCard(target); - return true; - } - } - - ClientCard LastChainCard = Util.GetLastChainCard(); - - if (LastChainCard != null && LastChainCard.Controller == 1 && LastChainCard.Location == CardLocation.MonsterZone && - !LastChainCard.IsDisabled() && !LastChainCard.IsShouldNotBeTarget() && !LastChainCard.IsShouldNotBeSpellTrapTarget()) - { - AI.SelectCard(LastChainCard); - return true; - } - - if (Bot.BattlingMonster != null && Enemy.BattlingMonster != null) - { - if (!Enemy.BattlingMonster.IsDisabled() && Enemy.BattlingMonster.IsCode(_CardId.EaterOfMillions)) - { - AI.SelectCard(Enemy.BattlingMonster); - return true; - } - } - - if (Duel.Phase == DuelPhase.BattleStart && Duel.Player == 1 && - Enemy.HasInMonstersZone(_CardId.NumberS39UtopiaTheLightning, true)) - { - AI.SelectCard(_CardId.NumberS39UtopiaTheLightning); - return true; - } - - return false; - } - - /// - /// Activate only except this card is the target or we summon monsters. - /// - protected bool DefaultSolemnJudgment() - { - return !Util.IsChainTargetOnly(Card) && !(Duel.Player == 0 && Duel.LastChainPlayer == -1) && DefaultTrap(); - } - - /// - /// Activate only except we summon monsters. - /// - protected bool DefaultSolemnWarning() - { - return (Bot.LifePoints > 2000) && !(Duel.Player == 0 && Duel.LastChainPlayer == -1) && DefaultTrap(); - } - - /// - /// Activate only except we summon monsters. - /// - protected bool DefaultSolemnStrike() - { - return (Bot.LifePoints > 1500) && !(Duel.Player == 0 && Duel.LastChainPlayer == -1) && DefaultTrap(); - } - - /// - /// Activate when all enemy monsters have better ATK. - /// - protected bool DefaultTorrentialTribute() - { - return !Util.HasChainedTrap(0) && Util.IsAllEnemyBetter(true); - } - - /// - /// Activate enemy have more S&T. - /// - protected bool DefaultHeavyStorm() - { - return Bot.GetSpellCount() < Enemy.GetSpellCount(); - } - - /// - /// Activate before other winds, if enemy have more than 2 S&T. - /// - protected bool DefaultHarpiesFeatherDusterFirst() - { - return Enemy.GetSpellCount() >= 2; - } - - /// - /// Activate when one enemy monsters have better ATK. - /// - protected bool DefaultHammerShot() - { - return Util.IsOneEnemyBetter(true); - } - - /// - /// Activate when one enemy monsters have better ATK or DEF. - /// - protected bool DefaultDarkHole() - { - return Util.IsOneEnemyBetter(); - } - - /// - /// Activate when one enemy monsters have better ATK or DEF. - /// - protected bool DefaultRaigeki() - { - return Util.IsOneEnemyBetter(); - } - - /// - /// Activate when one enemy monsters have better ATK or DEF. - /// - protected bool DefaultSmashingGround() - { - return Util.IsOneEnemyBetter(); - } - - /// - /// Activate when we have more than 15 cards in deck. - /// - protected bool DefaultPotOfDesires() - { - return Bot.Deck.Count > 15; - } - - /// - /// Set traps only and avoid block the activation of other cards. - /// - protected bool DefaultSpellSet() - { - return (Card.IsTrap() || Card.HasType(CardType.QuickPlay)) && Bot.GetSpellCountWithoutField() < 4; - } - - /// - /// Summon with tributes ATK lower. - /// - protected bool DefaultTributeSummon() - { - if (!UniqueFaceupMonster()) - return false; - int tributecount = (int)Math.Ceiling((Card.Level - 4.0d) / 2.0d); - for (int j = 0; j < 7; ++j) - { - ClientCard tributeCard = Bot.MonsterZone[j]; - if (tributeCard == null) continue; - if (tributeCard.GetDefensePower() < Card.Attack) - tributecount--; - } - return tributecount <= 0; - } - - /// - /// Activate when we have no field. - /// - protected bool DefaultField() - { - return Bot.SpellZone[5] == null; - } - - /// - /// Turn if all enemy is better. - /// - protected bool DefaultMonsterRepos() - { - if (Card.IsFaceup() && Card.IsDefense() && Card.Attack == 0) - return false; - - if (Enemy.HasInMonstersZone(_CardId.BlueEyesChaosMAXDragon, true) && - Card.IsAttack() && (4000 - Card.Defense) * 2 > (4000 - Card.Attack)) - return false; - if (Enemy.HasInMonstersZone(_CardId.BlueEyesChaosMAXDragon, true) && - Card.IsDefense() && Card.IsFaceup() && - (4000 - Card.Defense) * 2 > (4000 - Card.Attack)) - return true; - - bool enemyBetter = Util.IsAllEnemyBetter(true); - if (Card.IsAttack() && enemyBetter) - return true; - if (Card.IsDefense() && !enemyBetter && Card.Attack >= Card.Defense) - return true; - - return false; - } - - /// - /// If spell will be negated - /// - protected bool DefaultSpellWillBeNegated() - { - return Bot.HasInSpellZone(_CardId.ImperialOrder, true, true) || Enemy.HasInSpellZone(_CardId.ImperialOrder, true) || Enemy.HasInMonstersZone(_CardId.NaturiaBeast, true); - } - - /// - /// If spell must set first to activate - /// - protected bool DefaultSpellMustSetFirst() - { - ClientCard card = null; - foreach (ClientCard check in Bot.GetSpells()) - { - if (check.IsCode(_CardId.AntiSpellFragrance) && !check.IsDisabled()) - card = check; - } - if (card != null && card.IsFaceup()) - return true; - return Bot.HasInSpellZone(_CardId.AntiSpellFragrance, true, true) || Enemy.HasInSpellZone(_CardId.AntiSpellFragrance, true); - } - - /// - /// if spell/trap is the target or enermy activate HarpiesFeatherDuster - /// - protected bool DefaultOnBecomeTarget() - { - if (Util.IsChainTarget(Card)) return true; - int[] destroyAllList = - { - _CardId.EvilswarmExcitonKnight, - _CardId.BlackRoseDragon, - _CardId.JudgmentDragon, - _CardId.TopologicTrisbaena - }; - int[] destroyAllOpponentList = - { - _CardId.HarpiesFeatherDuster, - _CardId.DarkMagicAttack - }; - - if (Util.ChainContainsCard(destroyAllList)) return true; - if (Enemy.HasInSpellZone(destroyAllOpponentList, true)) return true; - // TODO: ChainContainsCard(id, player) - return false; - } - /// - /// Chain enemy activation or summon. - /// - protected bool DefaultTrap() - { - return (Duel.LastChainPlayer == -1 && Duel.LastSummonPlayer != 0) || Duel.LastChainPlayer == 1; - } - - /// - /// Activate when avail and no other our trap card in this chain or face-up. - /// - protected bool DefaultUniqueTrap() - { - if (Util.HasChainedTrap(0)) - return false; - - return UniqueFaceupSpell(); - } - - /// - /// Check no other our spell or trap card with same name face-up. - /// - protected bool UniqueFaceupSpell() - { - return !Bot.GetSpells().Any(card => card.IsCode(Card.Id) && card.IsFaceup()); - } - - /// - /// Check no other our monster card with same name face-up. - /// - protected bool UniqueFaceupMonster() - { - return !Bot.GetMonsters().Any(card => card.IsCode(Card.Id) && card.IsFaceup()); - } - - /// - /// Dumb way to avoid the bot chain in mess. - /// - protected bool DefaultDontChainMyself() - { - if (Executors.Any(exec => exec.Type == Type && exec.CardId == Card.Id)) - return false; - return Duel.LastChainPlayer != 0; - } - - /// - /// Draw when we have lower LP, or destroy it. Can be overrided. - /// - protected bool DefaultChickenGame() - { - if (Executors.Count(exec => exec.Type == Type && exec.CardId == Card.Id) > 1) - return false; - if (Bot.LifePoints <= 1000) - return false; - if (Bot.LifePoints <= Enemy.LifePoints && ActivateDescription == Util.GetStringId(_CardId.ChickenGame, 0)) - return true; - if (Bot.LifePoints > Enemy.LifePoints && ActivateDescription == Util.GetStringId(_CardId.ChickenGame, 1)) - return true; - return false; - } - - /// - /// Draw when we have Dark monster in hand,and banish random one. Can be overrided. - /// - protected bool DefaultAllureofDarkness() - { - ClientCard target = Bot.Hand.FirstOrDefault(card => card.HasAttribute(CardAttribute.Dark)); - return target != null; - } - - /// - /// Clever enough. - /// - protected bool DefaultDimensionalBarrier() - { - const int RITUAL = 0; - const int FUSION = 1; - const int SYNCHRO = 2; - const int XYZ = 3; - const int PENDULUM = 4; - if (Duel.Player != 0) - { - List monsters = Enemy.GetMonsters(); - int[] levels = new int[13]; - bool tuner = false; - bool nontuner = false; - foreach (ClientCard monster in monsters) - { - if (monster.HasType(CardType.Tuner)) - tuner = true; - else if (!monster.HasType(CardType.Xyz) && !monster.HasType(CardType.Link)) - { - nontuner = true; - levels[monster.Level] = levels[monster.Level] + 1; - } - - if (monster.IsOneForXyz()) - { - AI.SelectOption(XYZ); - return true; - } - } - if (tuner && nontuner) - { - AI.SelectOption(SYNCHRO); - return true; - } - for (int i=1; i<=12; i++) - { - if (levels[i]>1) - { - AI.SelectOption(XYZ); - return true; - } - } - ClientCard l = Enemy.SpellZone[6]; - ClientCard r = Enemy.SpellZone[7]; - if (l != null && r != null && l.LScale != r.RScale) - { - AI.SelectOption(PENDULUM); - return true; - } - } - ClientCard lastchaincard = Util.GetLastChainCard(); - if (Duel.LastChainPlayer == 1 && lastchaincard != null && !lastchaincard.IsDisabled()) - { - if (lastchaincard.HasType(CardType.Ritual)) - { - AI.SelectOption(RITUAL); - return true; - } - if (lastchaincard.HasType(CardType.Fusion)) - { - AI.SelectOption(FUSION); - return true; - } - if (lastchaincard.HasType(CardType.Synchro)) - { - AI.SelectOption(SYNCHRO); - return true; - } - if (lastchaincard.HasType(CardType.Xyz)) - { - AI.SelectOption(XYZ); - return true; - } - if (lastchaincard.IsFusionSpell()) - { - AI.SelectOption(FUSION); - return true; - } - } - if (Util.IsChainTarget(Card)) - { - AI.SelectOption(XYZ); - return true; - } - return false; - } - - /// - /// Clever enough - /// - protected bool DefaultInterruptedKaijuSlumber() - { - if (Card.Location == CardLocation.Grave) - { - AI.SelectCard( - _CardId.GamecieltheSeaTurtleKaiju, - _CardId.KumongoustheStickyStringKaiju, - _CardId.GadarlatheMysteryDustKaiju, - _CardId.RadiantheMultidimensionalKaiju, - _CardId.DogorantheMadFlameKaiju, - _CardId.ThunderKingtheLightningstrikeKaiju, - _CardId.JizukirutheStarDestroyingKaiju - ); - return true; - } - - if (DefaultDarkHole()) - { - AI.SelectCard( - _CardId.JizukirutheStarDestroyingKaiju, - _CardId.ThunderKingtheLightningstrikeKaiju, - _CardId.DogorantheMadFlameKaiju, - _CardId.RadiantheMultidimensionalKaiju, - _CardId.GadarlatheMysteryDustKaiju, - _CardId.KumongoustheStickyStringKaiju, - _CardId.GamecieltheSeaTurtleKaiju - ); - AI.SelectNextCard( - _CardId.SuperAntiKaijuWarMachineMechaDogoran, - _CardId.GamecieltheSeaTurtleKaiju, - _CardId.KumongoustheStickyStringKaiju, - _CardId.GadarlatheMysteryDustKaiju, - _CardId.RadiantheMultidimensionalKaiju, - _CardId.DogorantheMadFlameKaiju, - _CardId.ThunderKingtheLightningstrikeKaiju - ); - return true; - } - - return false; - } - - /// - /// Clever enough. - /// - protected bool DefaultKaijuSpsummon() - { - IList kaijus = new[] { - _CardId.JizukirutheStarDestroyingKaiju, - _CardId.GadarlatheMysteryDustKaiju, - _CardId.GamecieltheSeaTurtleKaiju, - _CardId.RadiantheMultidimensionalKaiju, - _CardId.KumongoustheStickyStringKaiju, - _CardId.ThunderKingtheLightningstrikeKaiju, - _CardId.DogorantheMadFlameKaiju, - _CardId.SuperAntiKaijuWarMachineMechaDogoran - }; - foreach (ClientCard monster in Enemy.GetMonsters()) - { - if (monster.IsCode(kaijus)) - return Card.GetDefensePower() > monster.GetDefensePower(); - } - ClientCard card = Enemy.MonsterZone.GetFloodgate(); - if (card != null) - { - AI.SelectCard(card); - return true; - } - card = Enemy.MonsterZone.GetDangerousMonster(); - if (card != null) - { - AI.SelectCard(card); - return true; - } - card = Util.GetOneEnemyBetterThanValue(Card.GetDefensePower()); - if (card != null) - { - AI.SelectCard(card); - return true; - } - return false; - } - - /// - /// Summon when we don't have monster attack higher than enemy's. - /// - protected bool DefaultNumberS39UtopiaTheLightningSummon() - { - int bestBotAttack = Util.GetBestAttack(Bot); - return Util.IsOneEnemyBetterThanValue(bestBotAttack, false); - } - - /// - /// Activate if the card is attack pos, and its attack is below 5000, when the enemy monster is attack pos or not useless faceup defense pos - /// - protected bool DefaultNumberS39UtopiaTheLightningEffect() - { - return Card.IsAttack() && Card.Attack < 5000 && (Enemy.BattlingMonster.IsAttack() || Enemy.BattlingMonster.IsFacedown() || Enemy.BattlingMonster.GetDefensePower() >= Card.Attack); - } - - /// - /// Summon when it can and should use effect. - /// - protected bool DefaultEvilswarmExcitonKnightSummon() - { - int selfCount = Bot.GetMonsterCount() + Bot.GetSpellCount() + Bot.GetHandCount(); - int oppoCount = Enemy.GetMonsterCount() + Enemy.GetSpellCount() + Enemy.GetHandCount(); - return (selfCount - 1 < oppoCount) && DefaultEvilswarmExcitonKnightEffect(); - } - - /// - /// Activate when we have less cards than enemy's, or the atk sum of we is lower than enemy's. - /// - protected bool DefaultEvilswarmExcitonKnightEffect() - { - int selfCount = Bot.GetMonsterCount() + Bot.GetSpellCount(); - int oppoCount = Enemy.GetMonsterCount() + Enemy.GetSpellCount(); - - if (selfCount < oppoCount) - return true; - - int selfAttack = Bot.GetMonsters().Sum(monster => (int?)monster.GetDefensePower()) ?? 0; - int oppoAttack = Enemy.GetMonsters().Sum(monster => (int?)monster.GetDefensePower()) ?? 0; - - return selfAttack < oppoAttack; - } - - /// - /// Summon in main2, or when the attack of we is lower than enemy's, but not when enemy have monster higher than 2500. - /// - protected bool DefaultStardustDragonSummon() - { - int selfBestAttack = Util.GetBestAttack(Bot); - int oppoBestAttack = Util.GetBestPower(Enemy); - return (selfBestAttack <= oppoBestAttack && oppoBestAttack <= 2500) || Util.IsTurn1OrMain2(); - } - - /// - /// Negate enemy's destroy effect, and revive from grave. - /// - protected bool DefaultStardustDragonEffect() - { - return (Card.Location == CardLocation.Grave) || Duel.LastChainPlayer == 1; - } - - /// - /// Summon when enemy have card which we must solve. - /// - protected bool DefaultCastelTheSkyblasterMusketeerSummon() - { - return Util.GetProblematicEnemyCard() != null; - } - - /// - /// Bounce the problematic enemy card. Ignore the 1st effect. - /// - protected bool DefaultCastelTheSkyblasterMusketeerEffect() - { - if (ActivateDescription == Util.GetStringId(_CardId.CastelTheSkyblasterMusketeer, 0)) - return false; - ClientCard target = Util.GetProblematicEnemyCard(); - if (target != null) - { - AI.SelectCard(0); - AI.SelectNextCard(target); - return true; - } - return false; - } - - /// - /// Summon when it should use effect, or when the attack of we is lower than enemy's, but not when enemy have monster higher than 3000. - /// - protected bool DefaultScarlightRedDragonArchfiendSummon() - { - int selfBestAttack = Util.GetBestAttack(Bot); - int oppoBestAttack = Util.GetBestPower(Enemy); - return (selfBestAttack <= oppoBestAttack && oppoBestAttack <= 3000) || DefaultScarlightRedDragonArchfiendEffect(); - } - - /// - /// Activate when we have less monsters than enemy, or when enemy have more than 3 monsters. - /// - protected bool DefaultScarlightRedDragonArchfiendEffect() - { - int selfCount = Bot.GetMonsters().Count(monster => !monster.Equals(Card) && monster.IsSpecialSummoned && monster.HasType(CardType.Effect) && monster.Attack <= Card.Attack); - int oppoCount = Enemy.GetMonsters().Count(monster => monster.IsSpecialSummoned && monster.HasType(CardType.Effect) && monster.Attack <= Card.Attack); - return selfCount <= oppoCount && oppoCount > 0 || oppoCount >= 3; - } - - /// - /// Clever enough. - /// - protected bool DefaultHonestEffect() - { - if (Card.Location == CardLocation.Hand) - { - return Bot.BattlingMonster.IsAttack() && - (((Bot.BattlingMonster.Attack < Enemy.BattlingMonster.Attack) || Bot.BattlingMonster.Attack >= Enemy.LifePoints) - || ((Bot.BattlingMonster.Attack < Enemy.BattlingMonster.Defense) && (Bot.BattlingMonster.Attack + Enemy.BattlingMonster.Attack > Enemy.BattlingMonster.Defense))); - } - - if (Util.IsTurn1OrMain2() && HonestEffectCount <= 5) - { - HonestEffectCount++; - return true; - } - - return false; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using YGOSharp.OCGWrapper.Enums; + +namespace WindBot.Game.AI +{ + public abstract class DefaultExecutor : Executor + { + protected class _CardId + { + public const int JizukirutheStarDestroyingKaiju = 63941210; + public const int ThunderKingtheLightningstrikeKaiju = 48770333; + public const int DogorantheMadFlameKaiju = 93332803; + public const int RadiantheMultidimensionalKaiju = 28674152; + public const int GadarlatheMysteryDustKaiju = 36956512; + public const int KumongoustheStickyStringKaiju = 29726552; + public const int GamecieltheSeaTurtleKaiju = 55063751; + public const int SuperAntiKaijuWarMachineMechaDogoran = 84769941; + + public const int UltimateConductorTytanno = 18940556; + public const int ElShaddollConstruct = 20366274; + public const int AllyOfJusticeCatastor = 26593852; + + public const int DupeFrog = 46239604; + public const int MaraudingCaptain = 2460565; + + public const int BlackRoseDragon = 73580471; + public const int JudgmentDragon = 57774843; + public const int TopologicTrisbaena = 72529749; + public const int EvilswarmExcitonKnight = 46772449; + public const int HarpiesFeatherDuster = 18144506; + public const int DarkMagicAttack = 2314238; + public const int MysticalSpaceTyphoon = 5318639; + public const int CosmicCyclone = 8267140; + public const int ChickenGame = 67616300; + + public const int SantaClaws = 46565218; + + public const int CastelTheSkyblasterMusketeer = 82633039; + public const int CrystalWingSynchroDragon = 50954680; + public const int NumberS39UtopiaTheLightning = 56832966; + public const int Number39Utopia = 84013237; + public const int UltimayaTzolkin = 1686814; + public const int MekkKnightCrusadiaAstram = 21887175; + public const int HamonLordofStrikingThunder = 32491822; + + public const int MoonMirrorShield = 19508728; + public const int PhantomKnightsFogBlade = 25542642; + + public const int VampireFraeulein = 6039967; + public const int InjectionFairyLily = 79575620; + + public const int BlueEyesChaosMAXDragon = 55410871; + + public const int AshBlossom = 14558127; + public const int MaxxC = 23434538; + public const int LockBird = 94145021; + public const int GhostOgreAndSnowRabbit = 59438930; + public const int GhostBelle = 73642296; + public const int EffectVeiler = 63845230; + public const int ArtifactLancea = 34267821; + + public const int CalledByTheGrave = 24224830; + public const int InfiniteImpermanence = 10045474; + public const int GalaxySoldier = 46659709; + public const int MacroCosmos = 30241314; + public const int UpstartGoblin = 70368879; + public const int CyberEmergency = 60600126; + + public const int EaterOfMillions = 63845230; + + public const int InvokedPurgatrio = 12307878; + public const int ChaosAncientGearGiant = 51788412; + public const int UltimateAncientGearGolem = 12652643; + + public const int RedDragonArchfiend = 70902743; + + public const int ImperialOrder = 61740673; + public const int NaturiaBeast = 33198837; + public const int AntiSpellFragrance = 58921041; + } + + int HonestEffectCount = 0; + + protected DefaultExecutor(GameAI ai, Duel duel) + : base(ai, duel) + { + AddExecutor(ExecutorType.Activate, _CardId.ChickenGame, DefaultChickenGame); + AddExecutor(ExecutorType.Activate, _CardId.SantaClaws); + } + + /// + /// Decide which card should the attacker attack. + /// + /// Card that attack. + /// Cards that defend. + /// BattlePhaseAction including the target, or null (in this situation, GameAI will check the next attacker) + public override BattlePhaseAction OnSelectAttackTarget(ClientCard attacker, IList defenders) + { + foreach (ClientCard defender in defenders) + { + attacker.RealPower = attacker.Attack; + defender.RealPower = defender.GetDefensePower(); + if (!OnPreBattleBetween(attacker, defender)) + continue; + + if (attacker.RealPower > defender.RealPower || (attacker.RealPower >= defender.RealPower && attacker.IsLastAttacker && defender.IsAttack())) + return AI.Attack(attacker, defender); + } + + if (attacker.CanDirectAttack) + return AI.Attack(attacker, null); + + return null; + } + + /// + /// Decide whether to declare attack between attacker and defender. + /// Can be overrided to update the RealPower of attacker for cards like Honest. + /// + /// Card that attack. + /// Card that defend. + /// false if the attack shouldn't be done. + public override bool OnPreBattleBetween(ClientCard attacker, ClientCard defender) + { + if (attacker.RealPower <= 0) + return false; + + if (!attacker.IsMonsterHasPreventActivationEffectInBattle()) + { + if (defender.IsMonsterInvincible() && defender.IsDefense()) + return false; + + if (defender.IsMonsterDangerous()) + { + bool canIgnoreIt = !attacker.IsDisabled() && ( + attacker.IsCode(_CardId.UltimateConductorTytanno) && defender.IsDefense() || + attacker.IsCode(_CardId.ElShaddollConstruct) && defender.IsSpecialSummoned || + attacker.IsCode(_CardId.AllyOfJusticeCatastor) && !defender.HasAttribute(CardAttribute.Dark)); + if (!canIgnoreIt) + return false; + } + + foreach (ClientCard equip in defender.EquipCards) + { + if (equip.IsCode(_CardId.MoonMirrorShield) && !equip.IsDisabled()) + { + return false; + } + } + + if (!defender.IsDisabled()) + { + if (defender.IsCode(_CardId.MekkKnightCrusadiaAstram) && defender.IsAttack() && attacker.IsSpecialSummoned) + return false; + + if (defender.IsCode(_CardId.CrystalWingSynchroDragon) && defender.IsAttack() && attacker.Level >= 5) + return false; + + if (defender.IsCode(_CardId.AllyOfJusticeCatastor) && !attacker.HasAttribute(CardAttribute.Dark)) + return false; + + if (defender.IsCode(_CardId.NumberS39UtopiaTheLightning) && defender.IsAttack() && defender.HasXyzMaterial(2, _CardId.Number39Utopia)) + defender.RealPower = 5000; + + if (defender.IsCode(_CardId.VampireFraeulein)) + defender.RealPower += (Enemy.LifePoints > 3000) ? 3000 : (Enemy.LifePoints - 100); + + if (defender.IsCode(_CardId.InjectionFairyLily) && Enemy.LifePoints > 2000) + defender.RealPower += 3000; + } + } + + if (!defender.IsMonsterHasPreventActivationEffectInBattle()) + { + if (attacker.IsCode(_CardId.NumberS39UtopiaTheLightning) && !attacker.IsDisabled() && attacker.HasXyzMaterial(2, _CardId.Number39Utopia)) + attacker.RealPower = 5000; + + foreach (ClientCard equip in attacker.EquipCards) + { + if (equip.IsCode(_CardId.MoonMirrorShield) && !equip.IsDisabled()) + { + attacker.RealPower = defender.RealPower + 100; + } + } + } + + if (Enemy.HasInMonstersZone(_CardId.MekkKnightCrusadiaAstram, true) && !(defender).IsCode(_CardId.MekkKnightCrusadiaAstram)) + return false; + + if (Enemy.HasInMonstersZone(_CardId.DupeFrog, true) && !(defender).IsCode(_CardId.DupeFrog)) + return false; + + if (Enemy.HasInMonstersZone(_CardId.MaraudingCaptain, true) && !defender.IsCode(_CardId.MaraudingCaptain) && defender.Race == (int)CardRace.Warrior) + return false; + + if (defender.IsCode(_CardId.UltimayaTzolkin) && !defender.IsDisabled() && Enemy.GetMonsters().Any(monster => !monster.Equals(defender) && monster.HasType(CardType.Synchro))) + return false; + + if (Enemy.GetMonsters().Any(monster => !monster.Equals(defender) && monster.IsCode(_CardId.HamonLordofStrikingThunder) && !monster.IsDisabled() && monster.IsDefense())) + return false; + + if (defender.OwnTargets.Any(card => card.IsCode(_CardId.PhantomKnightsFogBlade) && !card.IsDisabled())) + return false; + + return true; + } + + /// + /// Called when the AI has to select a card position. + /// + /// Id of the card to position on the field. + /// List of available positions. + /// Selected position, or 0 if no position is set for this card. + public override CardPosition OnSelectPosition(int cardId, IList positions) + { + YGOSharp.OCGWrapper.NamedCard cardData = YGOSharp.OCGWrapper.NamedCard.Get(cardId); + if (cardData != null) + { + if (cardData.Attack == 0) + return CardPosition.FaceUpDefence; + } + return 0; + } + + public override bool OnSelectBattleReplay() + { + if (Bot.BattlingMonster == null) + return false; + List defenders = new List(Duel.Fields[1].GetMonsters()); + defenders.Sort(CardContainer.CompareDefensePower); + defenders.Reverse(); + BattlePhaseAction result = OnSelectAttackTarget(Bot.BattlingMonster, defenders); + if (result != null && result.Action == BattlePhaseAction.BattleAction.Attack) + { + return true; + } + return false; + } + + public override void OnNewTurn() + { + HonestEffectCount = 0; + } + + /// + /// Destroy face-down cards first, in our turn. + /// + protected bool DefaultMysticalSpaceTyphoon() + { + if (Duel.CurrentChain.Any(card => card.IsCode(_CardId.MysticalSpaceTyphoon))) + { + return false; + } + + List spells = Enemy.GetSpells(); + if (spells.Count == 0) + return false; + + ClientCard selected = Enemy.SpellZone.GetFloodgate(); + + if (selected == null) + { + if (Duel.Player == 0) + selected = spells.FirstOrDefault(card => card.IsFacedown()); + if (Duel.Player == 1) + selected = spells.FirstOrDefault(card => card.HasType(CardType.Continuous) || card.HasType(CardType.Equip) || card.HasType(CardType.Field)); + } + + if (selected == null) + return false; + AI.SelectCard(selected); + return true; + } + + /// + /// Destroy face-down cards first, in our turn. + /// + protected bool DefaultCosmicCyclone() + { + foreach (ClientCard card in Duel.CurrentChain) + if (card.IsCode(_CardId.CosmicCyclone)) + return false; + return (Bot.LifePoints > 1000) && DefaultMysticalSpaceTyphoon(); + } + + /// + /// Activate if avail. + /// + protected bool DefaultGalaxyCyclone() + { + List spells = Enemy.GetSpells(); + if (spells.Count == 0) + return false; + + ClientCard selected = null; + + if (Card.Location == CardLocation.Grave) + { + selected = Util.GetBestEnemySpell(true); + } + else + { + selected = spells.FirstOrDefault(card => card.IsFacedown()); + } + + if (selected == null) + return false; + + AI.SelectCard(selected); + return true; + } + + /// + /// Set the highest ATK level 4+ effect enemy monster. + /// + protected bool DefaultBookOfMoon() + { + if (Util.IsAllEnemyBetter(true)) + { + ClientCard monster = Enemy.GetMonsters().GetHighestAttackMonster(true); + if (monster != null && monster.HasType(CardType.Effect) && !monster.HasType(CardType.Link) && (monster.HasType(CardType.Xyz) || monster.Level > 4)) + { + AI.SelectCard(monster); + return true; + } + } + return false; + } + + /// + /// Return problematic monster, and if this card become target, return any enemy monster. + /// + protected bool DefaultCompulsoryEvacuationDevice() + { + ClientCard target = Util.GetProblematicEnemyMonster(0, true); + if (target != null) + { + AI.SelectCard(target); + return true; + } + if (Util.IsChainTarget(Card)) + { + ClientCard monster = Util.GetBestEnemyMonster(false, true); + if (monster != null) + { + AI.SelectCard(monster); + return true; + } + } + return false; + } + + /// + /// Revive the best monster when we don't have better one in field. + /// + protected bool DefaultCallOfTheHaunted() + { + if (!Util.IsAllEnemyBetter(true)) + return false; + ClientCard selected = Bot.Graveyard.GetMatchingCards(card => card.IsCanRevive()).OrderByDescending(card => card.Attack).FirstOrDefault(); + AI.SelectCard(selected); + return true; + } + + /// + /// Default Scapegoat effect + /// + protected bool DefaultScapegoat() + { + if (DefaultSpellWillBeNegated()) return false; + if (Duel.Player == 0) return false; + if (Duel.Phase == DuelPhase.End) return true; + if (DefaultOnBecomeTarget()) return true; + if (Duel.Phase > DuelPhase.Main1 && Duel.Phase < DuelPhase.Main2) + { + if (Enemy.HasInMonstersZone(new[] + { + _CardId.UltimateConductorTytanno, + _CardId.InvokedPurgatrio, + _CardId.ChaosAncientGearGiant, + _CardId.UltimateAncientGearGolem, + _CardId.RedDragonArchfiend + }, true)) return false; + if (Util.GetTotalAttackingMonsterAttack(1) >= Bot.LifePoints) return true; + } + return false; + } + /// + /// Always active in opponent's turn. + /// + protected bool DefaultMaxxC() + { + return Duel.Player == 1; + } + /// + /// Always disable opponent's effect except some cards like UpstartGoblin + /// + protected bool DefaultAshBlossomAndJoyousSpring() + { + int[] ignoreList = { + _CardId.MacroCosmos, + _CardId.UpstartGoblin, + _CardId.CyberEmergency + }; + if (Util.GetLastChainCard().IsCode(ignoreList)) + return false; + if (Util.GetLastChainCard().HasSetcode(0x11e) && Util.GetLastChainCard().Location == CardLocation.Hand) // Danger! archtype hand effect + return false; + return Duel.LastChainPlayer == 1; + } + /// + /// Always activate unless the activating card is disabled + /// + protected bool DefaultGhostOgreAndSnowRabbit() + { + if (Util.GetLastChainCard() != null && Util.GetLastChainCard().IsDisabled()) + return false; + return DefaultTrap(); + } + /// + /// Always disable opponent's effect + /// + protected bool DefaultGhostBelleAndHauntedMansion() + { + return DefaultTrap(); + } + /// + /// Same as DefaultBreakthroughSkill + /// + protected bool DefaultEffectVeiler() + { + if (Util.GetLastChainCard() != null && Util.GetLastChainCard().IsCode(_CardId.GalaxySoldier) && Enemy.Hand.Count >= 3) return false; + if (Util.ChainContainsCard(_CardId.EffectVeiler)) + return false; + return DefaultBreakthroughSkill(); + } + /// + /// Chain common hand traps + /// + protected bool DefaultCalledByTheGrave() + { + int[] targetList = + { + _CardId.MaxxC, + _CardId.LockBird, + _CardId.GhostOgreAndSnowRabbit, + _CardId.AshBlossom, + _CardId.GhostBelle, + _CardId.EffectVeiler, + _CardId.ArtifactLancea + }; + if (Duel.LastChainPlayer == 1) + { + foreach (int id in targetList) + { + if (Util.GetLastChainCard().IsCode(id)) + { + AI.SelectCard(id); + return UniqueFaceupSpell(); + } + } + } + return false; + } + /// + /// Default InfiniteImpermanence effect + /// + protected bool DefaultInfiniteImpermanence() + { + // TODO: disable s & t + if (!DefaultUniqueTrap()) + return false; + return DefaultDisableMonster(); + } + /// + /// Chain the enemy monster, or disable monster like Rescue Rabbit. + /// + protected bool DefaultBreakthroughSkill() + { + if (!DefaultUniqueTrap()) + return false; + return DefaultDisableMonster(); + } + /// + /// Chain the enemy monster, or disable monster like Rescue Rabbit. + /// + protected bool DefaultDisableMonster() + { + if (Duel.Player == 1) + { + ClientCard target = Enemy.MonsterZone.GetShouldBeDisabledBeforeItUseEffectMonster(); + if (target != null) + { + AI.SelectCard(target); + return true; + } + } + + ClientCard LastChainCard = Util.GetLastChainCard(); + + if (LastChainCard != null && LastChainCard.Controller == 1 && LastChainCard.Location == CardLocation.MonsterZone && + !LastChainCard.IsDisabled() && !LastChainCard.IsShouldNotBeTarget() && !LastChainCard.IsShouldNotBeSpellTrapTarget()) + { + AI.SelectCard(LastChainCard); + return true; + } + + if (Bot.BattlingMonster != null && Enemy.BattlingMonster != null) + { + if (!Enemy.BattlingMonster.IsDisabled() && Enemy.BattlingMonster.IsCode(_CardId.EaterOfMillions)) + { + AI.SelectCard(Enemy.BattlingMonster); + return true; + } + } + + if (Duel.Phase == DuelPhase.BattleStart && Duel.Player == 1 && + Enemy.HasInMonstersZone(_CardId.NumberS39UtopiaTheLightning, true)) + { + AI.SelectCard(_CardId.NumberS39UtopiaTheLightning); + return true; + } + + return false; + } + + /// + /// Activate only except this card is the target or we summon monsters. + /// + protected bool DefaultSolemnJudgment() + { + return !Util.IsChainTargetOnly(Card) && !(Duel.Player == 0 && Duel.LastChainPlayer == -1) && DefaultTrap(); + } + + /// + /// Activate only except we summon monsters. + /// + protected bool DefaultSolemnWarning() + { + return (Bot.LifePoints > 2000) && !(Duel.Player == 0 && Duel.LastChainPlayer == -1) && DefaultTrap(); + } + + /// + /// Activate only except we summon monsters. + /// + protected bool DefaultSolemnStrike() + { + return (Bot.LifePoints > 1500) && !(Duel.Player == 0 && Duel.LastChainPlayer == -1) && DefaultTrap(); + } + + /// + /// Activate when all enemy monsters have better ATK. + /// + protected bool DefaultTorrentialTribute() + { + return !Util.HasChainedTrap(0) && Util.IsAllEnemyBetter(true); + } + + /// + /// Activate enemy have more S&T. + /// + protected bool DefaultHeavyStorm() + { + return Bot.GetSpellCount() < Enemy.GetSpellCount(); + } + + /// + /// Activate before other winds, if enemy have more than 2 S&T. + /// + protected bool DefaultHarpiesFeatherDusterFirst() + { + return Enemy.GetSpellCount() >= 2; + } + + /// + /// Activate when one enemy monsters have better ATK. + /// + protected bool DefaultHammerShot() + { + return Util.IsOneEnemyBetter(true); + } + + /// + /// Activate when one enemy monsters have better ATK or DEF. + /// + protected bool DefaultDarkHole() + { + return Util.IsOneEnemyBetter(); + } + + /// + /// Activate when one enemy monsters have better ATK or DEF. + /// + protected bool DefaultRaigeki() + { + return Util.IsOneEnemyBetter(); + } + + /// + /// Activate when one enemy monsters have better ATK or DEF. + /// + protected bool DefaultSmashingGround() + { + return Util.IsOneEnemyBetter(); + } + + /// + /// Activate when we have more than 15 cards in deck. + /// + protected bool DefaultPotOfDesires() + { + return Bot.Deck.Count > 15; + } + + /// + /// Set traps only and avoid block the activation of other cards. + /// + protected bool DefaultSpellSet() + { + return (Card.IsTrap() || Card.HasType(CardType.QuickPlay)) && Bot.GetSpellCountWithoutField() < 4; + } + + /// + /// Summon with tributes ATK lower. + /// + protected bool DefaultTributeSummon() + { + if (!UniqueFaceupMonster()) + return false; + int tributecount = (int)Math.Ceiling((Card.Level - 4.0d) / 2.0d); + for (int j = 0; j < 7; ++j) + { + ClientCard tributeCard = Bot.MonsterZone[j]; + if (tributeCard == null) continue; + if (tributeCard.GetDefensePower() < Card.Attack) + tributecount--; + } + return tributecount <= 0; + } + + /// + /// Activate when we have no field. + /// + protected bool DefaultField() + { + return Bot.SpellZone[5] == null; + } + + /// + /// Turn if all enemy is better. + /// + protected bool DefaultMonsterRepos() + { + if (Card.IsFaceup() && Card.IsDefense() && Card.Attack == 0) + return false; + + if (Enemy.HasInMonstersZone(_CardId.BlueEyesChaosMAXDragon, true) && + Card.IsAttack() && (4000 - Card.Defense) * 2 > (4000 - Card.Attack)) + return false; + if (Enemy.HasInMonstersZone(_CardId.BlueEyesChaosMAXDragon, true) && + Card.IsDefense() && Card.IsFaceup() && + (4000 - Card.Defense) * 2 > (4000 - Card.Attack)) + return true; + + bool enemyBetter = Util.IsAllEnemyBetter(true); + if (Card.IsAttack() && enemyBetter) + return true; + if (Card.IsDefense() && !enemyBetter && Card.Attack >= Card.Defense) + return true; + + return false; + } + + /// + /// If spell will be negated + /// + protected bool DefaultSpellWillBeNegated() + { + return Bot.HasInSpellZone(_CardId.ImperialOrder, true, true) || Enemy.HasInSpellZone(_CardId.ImperialOrder, true) || Enemy.HasInMonstersZone(_CardId.NaturiaBeast, true); + } + + /// + /// If spell must set first to activate + /// + protected bool DefaultSpellMustSetFirst() + { + ClientCard card = null; + foreach (ClientCard check in Bot.GetSpells()) + { + if (check.IsCode(_CardId.AntiSpellFragrance) && !check.IsDisabled()) + card = check; + } + if (card != null && card.IsFaceup()) + return true; + return Bot.HasInSpellZone(_CardId.AntiSpellFragrance, true, true) || Enemy.HasInSpellZone(_CardId.AntiSpellFragrance, true); + } + + /// + /// if spell/trap is the target or enermy activate HarpiesFeatherDuster + /// + protected bool DefaultOnBecomeTarget() + { + if (Util.IsChainTarget(Card)) return true; + int[] destroyAllList = + { + _CardId.EvilswarmExcitonKnight, + _CardId.BlackRoseDragon, + _CardId.JudgmentDragon, + _CardId.TopologicTrisbaena + }; + int[] destroyAllOpponentList = + { + _CardId.HarpiesFeatherDuster, + _CardId.DarkMagicAttack + }; + + if (Util.ChainContainsCard(destroyAllList)) return true; + if (Enemy.HasInSpellZone(destroyAllOpponentList, true)) return true; + // TODO: ChainContainsCard(id, player) + return false; + } + /// + /// Chain enemy activation or summon. + /// + protected bool DefaultTrap() + { + return (Duel.LastChainPlayer == -1 && Duel.LastSummonPlayer != 0) || Duel.LastChainPlayer == 1; + } + + /// + /// Activate when avail and no other our trap card in this chain or face-up. + /// + protected bool DefaultUniqueTrap() + { + if (Util.HasChainedTrap(0)) + return false; + + return UniqueFaceupSpell(); + } + + /// + /// Check no other our spell or trap card with same name face-up. + /// + protected bool UniqueFaceupSpell() + { + return !Bot.GetSpells().Any(card => card.IsCode(Card.Id) && card.IsFaceup()); + } + + /// + /// Check no other our monster card with same name face-up. + /// + protected bool UniqueFaceupMonster() + { + return !Bot.GetMonsters().Any(card => card.IsCode(Card.Id) && card.IsFaceup()); + } + + /// + /// Dumb way to avoid the bot chain in mess. + /// + protected bool DefaultDontChainMyself() + { + if (Executors.Any(exec => exec.Type == Type && exec.CardId == Card.Id)) + return false; + return Duel.LastChainPlayer != 0; + } + + /// + /// Draw when we have lower LP, or destroy it. Can be overrided. + /// + protected bool DefaultChickenGame() + { + if (Executors.Count(exec => exec.Type == Type && exec.CardId == Card.Id) > 1) + return false; + if (Bot.LifePoints <= 1000) + return false; + if (Bot.LifePoints <= Enemy.LifePoints && ActivateDescription == Util.GetStringId(_CardId.ChickenGame, 0)) + return true; + if (Bot.LifePoints > Enemy.LifePoints && ActivateDescription == Util.GetStringId(_CardId.ChickenGame, 1)) + return true; + return false; + } + + /// + /// Draw when we have Dark monster in hand,and banish random one. Can be overrided. + /// + protected bool DefaultAllureofDarkness() + { + ClientCard target = Bot.Hand.FirstOrDefault(card => card.HasAttribute(CardAttribute.Dark)); + return target != null; + } + + /// + /// Clever enough. + /// + protected bool DefaultDimensionalBarrier() + { + const int RITUAL = 0; + const int FUSION = 1; + const int SYNCHRO = 2; + const int XYZ = 3; + const int PENDULUM = 4; + if (Duel.Player != 0) + { + List monsters = Enemy.GetMonsters(); + int[] levels = new int[13]; + bool tuner = false; + bool nontuner = false; + foreach (ClientCard monster in monsters) + { + if (monster.HasType(CardType.Tuner)) + tuner = true; + else if (!monster.HasType(CardType.Xyz) && !monster.HasType(CardType.Link)) + { + nontuner = true; + levels[monster.Level] = levels[monster.Level] + 1; + } + + if (monster.IsOneForXyz()) + { + AI.SelectOption(XYZ); + return true; + } + } + if (tuner && nontuner) + { + AI.SelectOption(SYNCHRO); + return true; + } + for (int i=1; i<=12; i++) + { + if (levels[i]>1) + { + AI.SelectOption(XYZ); + return true; + } + } + ClientCard l = Enemy.SpellZone[6]; + ClientCard r = Enemy.SpellZone[7]; + if (l != null && r != null && l.LScale != r.RScale) + { + AI.SelectOption(PENDULUM); + return true; + } + } + ClientCard lastchaincard = Util.GetLastChainCard(); + if (Duel.LastChainPlayer == 1 && lastchaincard != null && !lastchaincard.IsDisabled()) + { + if (lastchaincard.HasType(CardType.Ritual)) + { + AI.SelectOption(RITUAL); + return true; + } + if (lastchaincard.HasType(CardType.Fusion)) + { + AI.SelectOption(FUSION); + return true; + } + if (lastchaincard.HasType(CardType.Synchro)) + { + AI.SelectOption(SYNCHRO); + return true; + } + if (lastchaincard.HasType(CardType.Xyz)) + { + AI.SelectOption(XYZ); + return true; + } + if (lastchaincard.IsFusionSpell()) + { + AI.SelectOption(FUSION); + return true; + } + } + if (Util.IsChainTarget(Card)) + { + AI.SelectOption(XYZ); + return true; + } + return false; + } + + /// + /// Clever enough + /// + protected bool DefaultInterruptedKaijuSlumber() + { + if (Card.Location == CardLocation.Grave) + { + AI.SelectCard( + _CardId.GamecieltheSeaTurtleKaiju, + _CardId.KumongoustheStickyStringKaiju, + _CardId.GadarlatheMysteryDustKaiju, + _CardId.RadiantheMultidimensionalKaiju, + _CardId.DogorantheMadFlameKaiju, + _CardId.ThunderKingtheLightningstrikeKaiju, + _CardId.JizukirutheStarDestroyingKaiju + ); + return true; + } + + if (DefaultDarkHole()) + { + AI.SelectCard( + _CardId.JizukirutheStarDestroyingKaiju, + _CardId.ThunderKingtheLightningstrikeKaiju, + _CardId.DogorantheMadFlameKaiju, + _CardId.RadiantheMultidimensionalKaiju, + _CardId.GadarlatheMysteryDustKaiju, + _CardId.KumongoustheStickyStringKaiju, + _CardId.GamecieltheSeaTurtleKaiju + ); + AI.SelectNextCard( + _CardId.SuperAntiKaijuWarMachineMechaDogoran, + _CardId.GamecieltheSeaTurtleKaiju, + _CardId.KumongoustheStickyStringKaiju, + _CardId.GadarlatheMysteryDustKaiju, + _CardId.RadiantheMultidimensionalKaiju, + _CardId.DogorantheMadFlameKaiju, + _CardId.ThunderKingtheLightningstrikeKaiju + ); + return true; + } + + return false; + } + + /// + /// Clever enough. + /// + protected bool DefaultKaijuSpsummon() + { + IList kaijus = new[] { + _CardId.JizukirutheStarDestroyingKaiju, + _CardId.GadarlatheMysteryDustKaiju, + _CardId.GamecieltheSeaTurtleKaiju, + _CardId.RadiantheMultidimensionalKaiju, + _CardId.KumongoustheStickyStringKaiju, + _CardId.ThunderKingtheLightningstrikeKaiju, + _CardId.DogorantheMadFlameKaiju, + _CardId.SuperAntiKaijuWarMachineMechaDogoran + }; + foreach (ClientCard monster in Enemy.GetMonsters()) + { + if (monster.IsCode(kaijus)) + return Card.GetDefensePower() > monster.GetDefensePower(); + } + ClientCard card = Enemy.MonsterZone.GetFloodgate(); + if (card != null) + { + AI.SelectCard(card); + return true; + } + card = Enemy.MonsterZone.GetDangerousMonster(); + if (card != null) + { + AI.SelectCard(card); + return true; + } + card = Util.GetOneEnemyBetterThanValue(Card.GetDefensePower()); + if (card != null) + { + AI.SelectCard(card); + return true; + } + return false; + } + + /// + /// Summon when we don't have monster attack higher than enemy's. + /// + protected bool DefaultNumberS39UtopiaTheLightningSummon() + { + int bestBotAttack = Util.GetBestAttack(Bot); + return Util.IsOneEnemyBetterThanValue(bestBotAttack, false); + } + + /// + /// Activate if the card is attack pos, and its attack is below 5000, when the enemy monster is attack pos or not useless faceup defense pos + /// + protected bool DefaultNumberS39UtopiaTheLightningEffect() + { + return Card.IsAttack() && Card.Attack < 5000 && (Enemy.BattlingMonster.IsAttack() || Enemy.BattlingMonster.IsFacedown() || Enemy.BattlingMonster.GetDefensePower() >= Card.Attack); + } + + /// + /// Summon when it can and should use effect. + /// + protected bool DefaultEvilswarmExcitonKnightSummon() + { + int selfCount = Bot.GetMonsterCount() + Bot.GetSpellCount() + Bot.GetHandCount(); + int oppoCount = Enemy.GetMonsterCount() + Enemy.GetSpellCount() + Enemy.GetHandCount(); + return (selfCount - 1 < oppoCount) && DefaultEvilswarmExcitonKnightEffect(); + } + + /// + /// Activate when we have less cards than enemy's, or the atk sum of we is lower than enemy's. + /// + protected bool DefaultEvilswarmExcitonKnightEffect() + { + int selfCount = Bot.GetMonsterCount() + Bot.GetSpellCount(); + int oppoCount = Enemy.GetMonsterCount() + Enemy.GetSpellCount(); + + if (selfCount < oppoCount) + return true; + + int selfAttack = Bot.GetMonsters().Sum(monster => (int?)monster.GetDefensePower()) ?? 0; + int oppoAttack = Enemy.GetMonsters().Sum(monster => (int?)monster.GetDefensePower()) ?? 0; + + return selfAttack < oppoAttack; + } + + /// + /// Summon in main2, or when the attack of we is lower than enemy's, but not when enemy have monster higher than 2500. + /// + protected bool DefaultStardustDragonSummon() + { + int selfBestAttack = Util.GetBestAttack(Bot); + int oppoBestAttack = Util.GetBestPower(Enemy); + return (selfBestAttack <= oppoBestAttack && oppoBestAttack <= 2500) || Util.IsTurn1OrMain2(); + } + + /// + /// Negate enemy's destroy effect, and revive from grave. + /// + protected bool DefaultStardustDragonEffect() + { + return (Card.Location == CardLocation.Grave) || Duel.LastChainPlayer == 1; + } + + /// + /// Summon when enemy have card which we must solve. + /// + protected bool DefaultCastelTheSkyblasterMusketeerSummon() + { + return Util.GetProblematicEnemyCard() != null; + } + + /// + /// Bounce the problematic enemy card. Ignore the 1st effect. + /// + protected bool DefaultCastelTheSkyblasterMusketeerEffect() + { + if (ActivateDescription == Util.GetStringId(_CardId.CastelTheSkyblasterMusketeer, 0)) + return false; + ClientCard target = Util.GetProblematicEnemyCard(); + if (target != null) + { + AI.SelectCard(0); + AI.SelectNextCard(target); + return true; + } + return false; + } + + /// + /// Summon when it should use effect, or when the attack of we is lower than enemy's, but not when enemy have monster higher than 3000. + /// + protected bool DefaultScarlightRedDragonArchfiendSummon() + { + int selfBestAttack = Util.GetBestAttack(Bot); + int oppoBestAttack = Util.GetBestPower(Enemy); + return (selfBestAttack <= oppoBestAttack && oppoBestAttack <= 3000) || DefaultScarlightRedDragonArchfiendEffect(); + } + + /// + /// Activate when we have less monsters than enemy, or when enemy have more than 3 monsters. + /// + protected bool DefaultScarlightRedDragonArchfiendEffect() + { + int selfCount = Bot.GetMonsters().Count(monster => !monster.Equals(Card) && monster.IsSpecialSummoned && monster.HasType(CardType.Effect) && monster.Attack <= Card.Attack); + int oppoCount = Enemy.GetMonsters().Count(monster => monster.IsSpecialSummoned && monster.HasType(CardType.Effect) && monster.Attack <= Card.Attack); + return selfCount <= oppoCount && oppoCount > 0 || oppoCount >= 3; + } + + /// + /// Clever enough. + /// + protected bool DefaultHonestEffect() + { + if (Card.Location == CardLocation.Hand) + { + return Bot.BattlingMonster.IsAttack() && + (((Bot.BattlingMonster.Attack < Enemy.BattlingMonster.Attack) || Bot.BattlingMonster.Attack >= Enemy.LifePoints) + || ((Bot.BattlingMonster.Attack < Enemy.BattlingMonster.Defense) && (Bot.BattlingMonster.Attack + Enemy.BattlingMonster.Attack > Enemy.BattlingMonster.Defense))); + } + + if (Util.IsTurn1OrMain2() && HonestEffectCount <= 5) + { + HonestEffectCount++; + return true; + } + + return false; + } + } +} diff --git a/Game/AI/Enums/DangerousMonster.cs b/ExecutorBase/Game/AI/Enums/DangerousMonster.cs similarity index 95% rename from Game/AI/Enums/DangerousMonster.cs rename to ExecutorBase/Game/AI/Enums/DangerousMonster.cs index 996b0062..ffd231c8 100644 --- a/Game/AI/Enums/DangerousMonster.cs +++ b/ExecutorBase/Game/AI/Enums/DangerousMonster.cs @@ -1,27 +1,29 @@ -namespace WindBot.Game.AI.Enums -{ - /// - /// Cards that are dangerous to attack. - /// - public enum DangerousMonster - { - LionHeart = 54366836, - Yubel = 78371393, - YubelIncarnate = 4779091, - YubelNightmare = 31764700, - ZaphionTheTimelord = 28929131, - SadionTheTimelord = 65314286, - MetaionTheTimelord = 74530899, - KamionTheTimelord = 91712985, - LazionTheTimelord = 92435533, - MichionTheTimelord = 7733560, - HailonTheTimelord = 34137269, - RaphionTheTimelord = 60222213, - GabrionTheTimelord = 6616912, - SandaionTheTimelord = 33015627, - EaterOfMillions = 63845230, - ElShaddollConstruct = 20366274, - ZushintheSleepingGiant = 67547370, - Heart_eartHDragon = 97403510, - } -} +namespace WindBot.Game.AI.Enums +{ + /// + /// Cards that are dangerous to attack. + /// + public enum DangerousMonster + { + LionHeart = 54366836, + Yubel = 78371393, + YubelIncarnate = 4779091, + YubelNightmare = 31764700, + ZaphionTheTimelord = 28929131, + SadionTheTimelord = 65314286, + MetaionTheTimelord = 74530899, + KamionTheTimelord = 91712985, + LazionTheTimelord = 92435533, + MichionTheTimelord = 7733560, + HailonTheTimelord = 34137269, + RaphionTheTimelord = 60222213, + GabrionTheTimelord = 6616912, + SandaionTheTimelord = 33015627, + EaterOfMillions = 63845230, + ElShaddollConstruct = 20366274, + ZushintheSleepingGiant = 67547370, + Heart_eartHDragon = 97403510, + DaigustoSphreeze = 29552709, + } +} + diff --git a/Game/AI/Enums/Floodgate.cs b/ExecutorBase/Game/AI/Enums/Floodgate.cs similarity index 73% rename from Game/AI/Enums/Floodgate.cs rename to ExecutorBase/Game/AI/Enums/Floodgate.cs index fa300a30..9141c503 100644 --- a/Game/AI/Enums/Floodgate.cs +++ b/ExecutorBase/Game/AI/Enums/Floodgate.cs @@ -82,6 +82,30 @@ public enum Floodgate InspectorBoarder = 15397015, Mashoudou = 76375976, EternalSoul = 48680970, - MarincessBattleOcean = 91027843 + MarincessBattleOcean = 91027843, + TopologicZeroboros = 66403530, + GladiatorBeastDomitianus = 33652635, + SerzielWatcheroftheEvilEye = 82466274, + ZerrzielRuleroftheEvilEyed = 17739335, + GorgonEmpressoftheEvilEyed = 29357687, + UnchainedSoulofRage = 67680512, + DracoBerserkeroftheTenyi = 5041348, + NidhoggGeneraiderBossofIce = 49275969, + UtgardaGeneraiderBossofDelusion = 744887, + FrodiGeneraiderBossofSwords = 40998517, + HoarrGeneraiderBossofRumbling = 68199168, + GodPhoenixGearfried = 22091647, + BrotherhoodoftheFireFistEland = 61472381, + PredaplantVerteAnaconda = 70369116, + RedSupernovaDragon = 99585850, + NumberF0UtopicFutureDragon = 26973555, + InvokedAugoeides = 97300502, + DragonmaidStrahl = 24799107, + RavenousCrocodragonArchethys = 87188910, + AdamancipatorRisenRaptite = 73079836, + AdamancipatorRisenDragite = 9464441, + TeardroptheRikkaQueen = 33779875, + CeruleanSkyFire = 54828837, + SacredBeastAwakening = 53701259 } } diff --git a/Game/AI/Enums/FusionSpell.cs b/ExecutorBase/Game/AI/Enums/FusionSpell.cs similarity index 100% rename from Game/AI/Enums/FusionSpell.cs rename to ExecutorBase/Game/AI/Enums/FusionSpell.cs diff --git a/Game/AI/Enums/InvincibleMonster.cs b/ExecutorBase/Game/AI/Enums/InvincibleMonster.cs similarity index 95% rename from Game/AI/Enums/InvincibleMonster.cs rename to ExecutorBase/Game/AI/Enums/InvincibleMonster.cs index 11561562..9399125e 100644 --- a/Game/AI/Enums/InvincibleMonster.cs +++ b/ExecutorBase/Game/AI/Enums/InvincibleMonster.cs @@ -1,69 +1,71 @@ -namespace WindBot.Game.AI.Enums -{ - /// - /// Cards that are invincible to battle. - /// - public enum InvincibleMonster - { - SpiritReaper = 23205979, - YubelTheUltimateNightmare = 31764700, - YubelTerrorIncarnate = 4779091, - SandaionTheTimelord = 33015627, - DarknessNeosphere = 60417395, - GabrionTheTimelord = 6616912, - MichionTheTimelord = 7733560, - ZaphionTheTimelord = 28929131, - HailonTheTimelord = 34137269, - RaphionTheTimelord = 60222213, - SadionTheTimelord = 65314286, - MetaionTheTimelord = 74530899, - Yubel = 78371393, - KamionTheTimelord = 91712985, - LazionTheTimelord = 92435533, - CloudianEyeofTheTyphoon = 57610714, - GimmickPuppetShadowFeeler = 34620088, - TheLegendaryFishermanIII = 44968687, - CastleGate = 36931229, - CloudianNimbusman = 20003527, - ExodiaNecross = 12600382, - Gellenduo = 11662742, - CloudianAltus = 79703905, - CloudianStormDragon = 13474291, - CloudianCirrostratus = 43318266, - CloudianTurbulence = 16197610, - CloudianAcidCloud = 17810268, - SuperheavySamuraiBlueBrawler = 41628550, - DinoSewing = 27143874, - Marshmallon = 31305911, - ShibaWarriorTaro = 27416701, - XSaberPashuul = 23093604, - SuperheavySamuraiBlowtorch = 7864030, - VijamTheCubicSeed = 15610297, - ArcanaForce0TheFool = 62892347, - ReptilianneNaga = 79491903, - AbyssStungray = 97232518, - ArmityleTheChaosPhantom = 43378048, - BlueEyesTwinBurstDragon = 2129638, - GladiatorBeastNerokius = 29357956, - MaskedHERODivineWind = 22093873, - ElementalHEROShiningPhoenixEnforcer = 88820235, - LunalightCatDancer = 51777272, - ElementalHEROPhoenixEnforcer = 41436536, - BloomDivaTheMelodiousChoir = 84988419, - ReaperonTheNightmare = 85684223, - BeelzeusofTheDiabolicDragons = 8763963, - DragocytosCorruptedNethersoulDragon = 21435914, - BeelzeofTheDiabolicDragons = 34408491, - BlackwingArmorMaster = 69031175, - DaigustoSphreeze = 29552709, - DarkDiviner = 31919988, - NumberC92HearteartHChaosDragon = 47017574, - Number92HearteartHDragon = 97403510, - Number51FinisherTheStrongArm = 56292140, - NumberC96DarkStorm = 77205367, - NumberF0UtopicFutureFutureSlash = 43490025, - NumberF0UtopicFuture = 65305468, - GoukiTheGiantOgre = 47946130, - BorrelswordDragon = 85289965 - } -} +namespace WindBot.Game.AI.Enums +{ + /// + /// Cards that are invincible to battle. + /// + public enum InvincibleMonster + { + SpiritReaper = 23205979, + YubelTheUltimateNightmare = 31764700, + YubelTerrorIncarnate = 4779091, + SandaionTheTimelord = 33015627, + DarknessNeosphere = 60417395, + GabrionTheTimelord = 6616912, + MichionTheTimelord = 7733560, + ZaphionTheTimelord = 28929131, + HailonTheTimelord = 34137269, + RaphionTheTimelord = 60222213, + SadionTheTimelord = 65314286, + MetaionTheTimelord = 74530899, + Yubel = 78371393, + KamionTheTimelord = 91712985, + LazionTheTimelord = 92435533, + CloudianEyeofTheTyphoon = 57610714, + GimmickPuppetShadowFeeler = 34620088, + TheLegendaryFishermanIII = 44968687, + CastleGate = 36931229, + CloudianNimbusman = 20003527, + ExodiaNecross = 12600382, + Gellenduo = 11662742, + CloudianAltus = 79703905, + CloudianStormDragon = 13474291, + CloudianCirrostratus = 43318266, + CloudianTurbulence = 16197610, + CloudianAcidCloud = 17810268, + SuperheavySamuraiBlueBrawler = 41628550, + DinoSewing = 27143874, + Marshmallon = 31305911, + ShibaWarriorTaro = 27416701, + XSaberPashuul = 23093604, + SuperheavySamuraiBlowtorch = 7864030, + VijamTheCubicSeed = 15610297, + ArcanaForce0TheFool = 62892347, + ReptilianneNaga = 79491903, + AbyssStungray = 97232518, + ArmityleTheChaosPhantom = 43378048, + BlueEyesTwinBurstDragon = 2129638, + GladiatorBeastNerokius = 29357956, + MaskedHERODivineWind = 22093873, + ElementalHEROShiningPhoenixEnforcer = 88820235, + LunalightCatDancer = 51777272, + ElementalHEROPhoenixEnforcer = 41436536, + BloomDivaTheMelodiousChoir = 84988419, + ReaperonTheNightmare = 85684223, + BeelzeusofTheDiabolicDragons = 8763963, + DragocytosCorruptedNethersoulDragon = 21435914, + BeelzeofTheDiabolicDragons = 34408491, + BlackwingArmorMaster = 69031175, + DaigustoSphreeze = 29552709, + DarkDiviner = 31919988, + NumberC92HearteartHChaosDragon = 47017574, + Number92HearteartHDragon = 97403510, + Number51FinisherTheStrongArm = 56292140, + NumberC96DarkStorm = 77205367, + NumberF0UtopicFutureFutureSlash = 43490025, + NumberF0UtopicFuture = 65305468, + GoukiTheGiantOgre = 47946130, + BorrelswordDragon = 85289965, + NumberF0UtopicFutureDragon = 26973555, + BorrelendDragon = 98630720 + } +} diff --git a/Game/AI/Enums/OneForXyz.cs b/ExecutorBase/Game/AI/Enums/OneForXyz.cs similarity index 100% rename from Game/AI/Enums/OneForXyz.cs rename to ExecutorBase/Game/AI/Enums/OneForXyz.cs diff --git a/Game/AI/Enums/PreventActivationEffectInBattle.cs b/ExecutorBase/Game/AI/Enums/PreventActivationEffectInBattle.cs similarity index 100% rename from Game/AI/Enums/PreventActivationEffectInBattle.cs rename to ExecutorBase/Game/AI/Enums/PreventActivationEffectInBattle.cs diff --git a/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs b/ExecutorBase/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs similarity index 87% rename from Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs rename to ExecutorBase/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs index b219d850..547e0c16 100644 --- a/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs +++ b/ExecutorBase/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs @@ -1,54 +1,61 @@ -namespace WindBot.Game.AI.Enums -{ - /// - /// Monsters that release or banish itself to use effect. So them should be disabled (with Breakthrough Skill) before it use effect. - /// - public enum ShouldBeDisabledBeforeItUseEffectMonster - { - MachinaMegaform = 51617185, - DarkSummoningBeast = 87917187, - GemKnightAlexandrite = 90019393, - RedEyesRetroDragon = 53485634, - DeepSweeper = 8649148, - BeastWarriorPuma = 16796157, - ZefrasaberSwordmasteroftheNekroz = 84388461, - CipherWing = 81974607, - MadolcheAnjelly = 34680482, - PlanetPathfinder = 97526666, - RescueCat = 14878871, - RescueHamster = 50485594, - RescueFerret = 56343672, - RescueRabbit = 85138716, - GalaxyWizard = 98555327, - Backlinker = 71172240, - Merlin = 3580032, - CrystalVanguard = 87475570, - TemperanceofProphecy = 87608852, - Kuribandit = 16404809, - PhotonLizard = 38973775, - SuperheavySamuraiFlutist = 27978707, - ConstellarRasalhague = 70624184, - CardcarD = 45812361, - UnifloraMysticalBeastoftheForest = 36318200, - BusterWhelpoftheDestructionSwordsman = 49823708, - GalaxyEyesCloudragon = 9260791, - SylvanPrincessprout = 20579538, - AltergeistPixiel = 57769391, - AbyssActorExtras = 88412339, - PerformapalTrumpWitch = 91584698, - RaidraptorLastStrix = 97219708, - MythicalBeastJackal = 91182675, - TimeMaiden = 27107590, - SuperQuantalFairyAlphan = 58753372, - TheBlackStoneofLegend = 66574418, - PaladinofDarkDragon = 71408082, - PaladinofPhotonDragon = 85346853, - TwinPhotonLizard = 29455728, - - CosmoBrain = 85679527, - ShiranuiSolitaire = 94801854, - Mixeroid = 71340250, - LonefireBlossom = 48686504, - BrotherhoodoftheFireFist_Leopard = 39699564 - } -} +namespace WindBot.Game.AI.Enums +{ + /// + /// Monsters that release or banish itself to use effect. So them should be disabled (with Breakthrough Skill) before it use effect. + /// + public enum ShouldBeDisabledBeforeItUseEffectMonster + { + MachinaMegaform = 51617185, + DarkSummoningBeast = 87917187, + GemKnightAlexandrite = 90019393, + RedEyesRetroDragon = 53485634, + DeepSweeper = 8649148, + BeastWarriorPuma = 16796157, + ZefrasaberSwordmasteroftheNekroz = 84388461, + CipherWing = 81974607, + MadolcheAnjelly = 34680482, + PlanetPathfinder = 97526666, + RescueCat = 14878871, + RescueHamster = 50485594, + RescueFerret = 56343672, + RescueRabbit = 85138716, + GalaxyWizard = 98555327, + Backlinker = 71172240, + Merlin = 3580032, + CrystalVanguard = 87475570, + TemperanceofProphecy = 87608852, + Kuribandit = 16404809, + PhotonLizard = 38973775, + SuperheavySamuraiFlutist = 27978707, + ConstellarRasalhague = 70624184, + CardcarD = 45812361, + UnifloraMysticalBeastoftheForest = 36318200, + BusterWhelpoftheDestructionSwordsman = 49823708, + GalaxyEyesCloudragon = 9260791, + SylvanPrincessprout = 20579538, + AltergeistPixiel = 57769391, + AbyssActorExtras = 88412339, + PerformapalTrumpWitch = 91584698, + RaidraptorLastStrix = 97219708, + MythicalBeastJackal = 91182675, + TimeMaiden = 27107590, + SuperQuantalFairyAlphan = 58753372, + TheBlackStoneofLegend = 66574418, + PaladinofDarkDragon = 71408082, + PaladinofPhotonDragon = 85346853, + TwinPhotonLizard = 29455728, + TimeThiefRegulator = 19891131, + MathmechNabla = 53577438, + NidhoggGeneraiderBossofIce = 49275969, + HoarrGeneraiderBossofRumbling = 68199168, + RedFamiliar = 8372133, + AccesscodeTalker = 86066372, + ChaosSummoningBeast = 27439792, + + CosmoBrain = 85679527, + ShiranuiSolitaire = 94801854, + Mixeroid = 71340250, + LonefireBlossom = 48686504, + BrotherhoodoftheFireFist_Leopard = 39699564 + } +} diff --git a/Game/AI/Enums/ShouldNotBeMonsterTarget.cs b/ExecutorBase/Game/AI/Enums/ShouldNotBeMonsterTarget.cs similarity index 89% rename from Game/AI/Enums/ShouldNotBeMonsterTarget.cs rename to ExecutorBase/Game/AI/Enums/ShouldNotBeMonsterTarget.cs index 3a9b33d4..855824e2 100644 --- a/Game/AI/Enums/ShouldNotBeMonsterTarget.cs +++ b/ExecutorBase/Game/AI/Enums/ShouldNotBeMonsterTarget.cs @@ -1,18 +1,19 @@ -namespace WindBot.Game.AI.Enums -{ - /// - /// Cards that are can't be selected as target of monster's effect, or immuned to monster's effect. - /// So them shouldn't be tried to be selected as target of monster at most times. - /// - public enum ShouldNotBeMonsterTarget - { - TheLegendaryFishermanII = 19801646, - GaiaDraketheUniversalForce = 58601383, - FirstoftheDragons = 10817524, - Tatsunoko = 55863245, - CXyzSimontheGreatMoralLeader = 41147577, - PaleozoicAnomalocaris = 61307542, - PaleozoicOpabinia = 37649320, - BorreloadDragon = 31833038 - } -} +namespace WindBot.Game.AI.Enums +{ + /// + /// Cards that are can't be selected as target of monster's effect, or immuned to monster's effect. + /// So them shouldn't be tried to be selected as target of monster at most times. + /// + public enum ShouldNotBeMonsterTarget + { + TheLegendaryFishermanII = 19801646, + GaiaDraketheUniversalForce = 58601383, + FirstoftheDragons = 10817524, + Tatsunoko = 55863245, + CXyzSimontheGreatMoralLeader = 41147577, + PaleozoicAnomalocaris = 61307542, + PaleozoicOpabinia = 37649320, + BorreloadDragon = 31833038, + BorrelendDragon = 98630720 + } +} diff --git a/Game/AI/Enums/ShouldNotBeSpellTarget.cs b/ExecutorBase/Game/AI/Enums/ShouldNotBeSpellTarget.cs similarity index 97% rename from Game/AI/Enums/ShouldNotBeSpellTarget.cs rename to ExecutorBase/Game/AI/Enums/ShouldNotBeSpellTarget.cs index ba8e69c6..fa78eaf9 100644 --- a/Game/AI/Enums/ShouldNotBeSpellTarget.cs +++ b/ExecutorBase/Game/AI/Enums/ShouldNotBeSpellTarget.cs @@ -1,14 +1,14 @@ -namespace WindBot.Game.AI.Enums -{ - /// - /// Cards that are can't be selected as target of spell&trap's effect, or immuned to spell&trap's effect. - /// So them shouldn't be tried to be selected as target of spell&trap at most times. - /// - public enum ShouldNotBeSpellTrapTarget - { - ApoqliphortTowers = 27279764, - ApoqliphortSkybase = 40061558, - TheLegendaryFishermanIII = 44968687, - ChaosAncientGearGiant = 51788412 - } -} +namespace WindBot.Game.AI.Enums +{ + /// + /// Cards that are can't be selected as target of spell&trap's effect, or immuned to spell&trap's effect. + /// So them shouldn't be tried to be selected as target of spell&trap at most times. + /// + public enum ShouldNotBeSpellTrapTarget + { + ApoqliphortTowers = 27279764, + ApoqliphortSkybase = 40061558, + TheLegendaryFishermanIII = 44968687, + ChaosAncientGearGiant = 51788412 + } +} diff --git a/Game/AI/Enums/ShouldNotBeTarget.cs b/ExecutorBase/Game/AI/Enums/ShouldNotBeTarget.cs similarity index 97% rename from Game/AI/Enums/ShouldNotBeTarget.cs rename to ExecutorBase/Game/AI/Enums/ShouldNotBeTarget.cs index 58fc6167..c3e066d7 100644 --- a/Game/AI/Enums/ShouldNotBeTarget.cs +++ b/ExecutorBase/Game/AI/Enums/ShouldNotBeTarget.cs @@ -1,53 +1,54 @@ -namespace WindBot.Game.AI.Enums -{ - /// - /// Cards that are can't be selected as target, or immuned to most effect. - /// So them shouldn't be tried to be selected as target at most times. - /// - public enum ShouldNotBeTarget - { - DivineSerpentGeh = 82103466, - ObelisktheTormentor = 10000000, - TheWingedDragonofRaSphereMode = 10000080, - TheWingedDragonofRaImmortalPhoenix = 10000090, - KozmoDarkPlanet = 85991529, - ZushintheSleepingGiant = 67547370, - TheLegendaryExodiaIncarnate = 58604027, - KozmoDarkEclipser = 64063868, - KozmoDarkDestroyer = 55885348, - KozmoForerunner = 20849090, - MajespecterUnicornKirin = 31178212, - WorldLegacyWorldShield = 55787576, - KiwiMagicianGirl = 82627406, - MajespecterFoxKyubi = 94784213, - MajespecterToadOgama = 645794, - MajespecterCrowYata = 68395509, - MajespecterRaccoonBunbuku = 31991800, - MajespecterCatNekomata = 5506791, - HazyFlameHydra = 8696773, - HazyFlameMantikor = 96051150, - HazyFlameHyppogrif = 31303283, - HazyFlameCerbereus = 38525760, - HazyFlameSphynx = 1409474, - HazyFlamePeryton = 37803172, - HazyFlameGriffin = 74010769, - BlueEyesChaosMAXDragon = 55410871, - BlueEyesChaosDragon = 20654247, - SupremeKingZARC = 13331639, - CrimsonNovaTrinitytheDarkCubicLord = 72664875, - LunalightLeoDancer = 24550676, - TimaeustheKnightofDestiny = 53315891, - DantePilgrimoftheBurningAbyss = 18386170, - AncientGearHowitzer = 87182127, - InvokedCocytus = 85908279, - LyriluscIndependentNightingale = 76815942, - FlowerCardianLightshower = 42291297, - YaziEviloftheYangZing = 43202238, - RaidraptorUltimateFalcon = 86221741, - DisdainfulBirdofParadise = 27240101, - DarkestDiabolosLordOfTheLair = 50383626, - Blackwing_FullArmoredWing = 54082269, - DragunofRedEyes = 37818794, - RedEyesBDragon = 74677422, // sometimes the name of DragunofRedEyes will be changed to RedEyesBDragon - } -} +namespace WindBot.Game.AI.Enums +{ + /// + /// Cards that are can't be selected as target, or immuned to most effect. + /// So them shouldn't be tried to be selected as target at most times. + /// + public enum ShouldNotBeTarget + { + DivineSerpentGeh = 82103466, + ObelisktheTormentor = 10000000, + TheWingedDragonofRaSphereMode = 10000080, + TheWingedDragonofRaImmortalPhoenix = 10000090, + KozmoDarkPlanet = 85991529, + ZushintheSleepingGiant = 67547370, + TheLegendaryExodiaIncarnate = 58604027, + KozmoDarkEclipser = 64063868, + KozmoDarkDestroyer = 55885348, + KozmoForerunner = 20849090, + MajespecterUnicornKirin = 31178212, + WorldLegacyWorldShield = 55787576, + KiwiMagicianGirl = 82627406, + MajespecterFoxKyubi = 94784213, + MajespecterToadOgama = 645794, + MajespecterCrowYata = 68395509, + MajespecterRaccoonBunbuku = 31991800, + MajespecterCatNekomata = 5506791, + HazyFlameHydra = 8696773, + HazyFlameMantikor = 96051150, + HazyFlameHyppogrif = 31303283, + HazyFlameCerbereus = 38525760, + HazyFlameSphynx = 1409474, + HazyFlamePeryton = 37803172, + HazyFlameGriffin = 74010769, + BlueEyesChaosMAXDragon = 55410871, + BlueEyesChaosDragon = 20654247, + SupremeKingZARC = 13331639, + CrimsonNovaTrinitytheDarkCubicLord = 72664875, + LunalightLeoDancer = 24550676, + TimaeustheKnightofDestiny = 53315891, + DantePilgrimoftheBurningAbyss = 18386170, + AncientGearHowitzer = 87182127, + InvokedCocytus = 85908279, + LyriluscIndependentNightingale = 76815942, + FlowerCardianLightshower = 42291297, + YaziEviloftheYangZing = 43202238, + RaidraptorUltimateFalcon = 86221741, + DisdainfulBirdofParadise = 27240101, + DarkestDiabolosLordOfTheLair = 50383626, + Blackwing_FullArmoredWing = 54082269, + DragunofRedEyes = 37818794, + RedEyesBDragon = 74677422, // sometimes the name of DragunofRedEyes will be changed to RedEyesBDragon + TheArrivalCyberseIgnister = 11738489 + } +} diff --git a/Game/AI/Executor.cs b/ExecutorBase/Game/AI/Executor.cs similarity index 92% rename from Game/AI/Executor.cs rename to ExecutorBase/Game/AI/Executor.cs index b4c5bc4c..6265438f 100644 --- a/Game/AI/Executor.cs +++ b/ExecutorBase/Game/AI/Executor.cs @@ -1,248 +1,251 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using YGOSharp.OCGWrapper.Enums; -using WindBot; -using WindBot.Game; -using WindBot.Game.AI; - -namespace WindBot.Game.AI -{ - public abstract class Executor - { - public string Deck { get; set; } - public Duel Duel { get; private set; } - public IList Executors { get; private set; } - public GameAI AI { get; private set; } - public AIUtil Util { get; private set; } - - protected MainPhase Main { get; private set; } - protected BattlePhase Battle { get; private set; } - - protected ExecutorType Type { get; private set; } - protected ClientCard Card { get; private set; } - protected long ActivateDescription { get; private set; } - - protected ClientField Bot { get; private set; } - protected ClientField Enemy { get; private set; } - - protected Executor(GameAI ai, Duel duel) - { - Duel = duel; - AI = ai; - Util = new AIUtil(duel); - Executors = new List(); - - Bot = Duel.Fields[0]; - Enemy = Duel.Fields[1]; - } - - public virtual int OnRockPaperScissors() - { - return Program.Rand.Next(1, 4); - } - - public virtual bool OnSelectHand() - { - return Program.Rand.Next(2) > 0; - } - - /// - /// Called when the AI has to decide if it should attack - /// - /// List of monsters that can attcack. - /// List of monsters of enemy. - /// A new BattlePhaseAction containing the action to do. - public virtual BattlePhaseAction OnBattle(IList attackers, IList defenders) - { - // For overriding - return null; - } - - /// - /// Called when the AI has to decide which card to attack first - /// - /// List of monsters that can attcack. - /// List of monsters of enemy. - /// The card to attack first. - public virtual ClientCard OnSelectAttacker(IList attackers, IList defenders) - { - // For overriding - return null; - } - - public virtual BattlePhaseAction OnSelectAttackTarget(ClientCard attacker, IList defenders) - { - // Overrided in DefalultExecutor - return null; - } - - public virtual bool OnPreBattleBetween(ClientCard attacker, ClientCard defender) - { - // Overrided in DefalultExecutor - return true; - } - - public virtual void OnChaining(int player, ClientCard card) - { - // For overriding - } - - public virtual void OnChainEnd() - { - // For overriding - } - public virtual void OnNewPhase() - { - // Some AI need do something on new phase - } - public virtual void OnNewTurn() - { - // Some AI need do something on new turn - } - - public virtual void OnDraw(int player) - { - // Some AI need do something on draw - } - - public virtual IList OnSelectCard(IList cards, int min, int max, int hint, bool cancelable) - { - // For overriding - return null; - } - - public virtual IList OnSelectSum(IList cards, int sum, int min, int max, int hint, bool mode) - { - // For overriding - return null; - } - - public virtual IList OnSelectFusionMaterial(IList cards, int min, int max) - { - // For overriding - return null; - } - - public virtual IList OnSelectSynchroMaterial(IList cards, int sum, int min, int max) - { - // For overriding - return null; - } - - public virtual IList OnSelectXyzMaterial(IList cards, int min, int max) - { - // For overriding - return null; - } - - public virtual IList OnSelectLinkMaterial(IList cards, int min, int max) - { - // For overriding - return null; - } - - public virtual IList OnSelectRitualTribute(IList cards, int sum, int min, int max) - { - // For overriding - return null; - } - - public virtual IList OnSelectPendulumSummon(IList cards, int max) - { - // For overriding - return null; - } - - public virtual IList OnCardSorting(IList cards) - { - // For overriding - return null; - } - - public virtual bool OnSelectYesNo(long desc) - { - return true; - } - - public virtual int OnSelectOption(IList options) - { - return -1; - } - - public virtual int OnSelectPlace(int cardId, int player, CardLocation location, int available) - { - // For overriding - return 0; - } - - public virtual CardPosition OnSelectPosition(int cardId, IList positions) - { - // Overrided in DefalultExecutor - return 0; - } - - public virtual bool OnSelectBattleReplay() - { - // Overrided in DefalultExecutor - return false; - } - - public void SetMain(MainPhase main) - { - Main = main; - } - - public void SetBattle(BattlePhase battle) - { - Battle = battle; - } - - /// - /// Set global variables Type, Card, ActivateDescription for Executor - /// - public void SetCard(ExecutorType type, ClientCard card, long description) - { - Type = type; - Card = card; - ActivateDescription = description; - } - - /// - /// Do the action for the card if func return true. - /// - public void AddExecutor(ExecutorType type, int cardId, Func func) - { - Executors.Add(new CardExecutor(type, cardId, func)); - } - - /// - /// Do the action for the card if available. - /// - public void AddExecutor(ExecutorType type, int cardId) - { - Executors.Add(new CardExecutor(type, cardId, null)); - } - - /// - /// Do the action for every card if func return true. - /// - public void AddExecutor(ExecutorType type, Func func) - { - Executors.Add(new CardExecutor(type, -1, func)); - } - - /// - /// Do the action for every card if no other Executor is added to it. - /// - public void AddExecutor(ExecutorType type) - { - Executors.Add(new CardExecutor(type, -1, DefaultNoExecutor)); - } - - private bool DefaultNoExecutor() - { - return Executors.All(exec => exec.Type != Type || exec.CardId != Card.Id); - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using YGOSharp.OCGWrapper.Enums; +using WindBot; +using WindBot.Game; +using WindBot.Game.AI; + +namespace WindBot.Game.AI +{ + public abstract class Executor + { + public string Deck { get; set; } + public Duel Duel { get; private set; } + public IList Executors { get; private set; } + public GameAI AI { get; private set; } + public AIUtil Util { get; private set; } + + protected MainPhase Main { get; private set; } + protected BattlePhase Battle { get; private set; } + + protected ExecutorType Type { get; private set; } + protected ClientCard Card { get; private set; } + protected long ActivateDescription { get; private set; } + + protected ClientField Bot { get; private set; } + protected ClientField Enemy { get; private set; } + + public Random Rand; + + protected Executor(GameAI ai, Duel duel) + { + Rand = new Random(); + Duel = duel; + AI = ai; + Util = new AIUtil(duel); + Executors = new List(); + + Bot = Duel.Fields[0]; + Enemy = Duel.Fields[1]; + } + + public virtual int OnRockPaperScissors() + { + return Rand.Next(1, 4); + } + + public virtual bool OnSelectHand() + { + return Rand.Next(2) > 0; + } + + /// + /// Called when the AI has to decide if it should attack + /// + /// List of monsters that can attcack. + /// List of monsters of enemy. + /// A new BattlePhaseAction containing the action to do. + public virtual BattlePhaseAction OnBattle(IList attackers, IList defenders) + { + // For overriding + return null; + } + + /// + /// Called when the AI has to decide which card to attack first + /// + /// List of monsters that can attcack. + /// List of monsters of enemy. + /// The card to attack first. + public virtual ClientCard OnSelectAttacker(IList attackers, IList defenders) + { + // For overriding + return null; + } + + public virtual BattlePhaseAction OnSelectAttackTarget(ClientCard attacker, IList defenders) + { + // Overrided in DefalultExecutor + return null; + } + + public virtual bool OnPreBattleBetween(ClientCard attacker, ClientCard defender) + { + // Overrided in DefalultExecutor + return true; + } + + public virtual void OnChaining(int player, ClientCard card) + { + // For overriding + } + + public virtual void OnChainEnd() + { + // For overriding + } + public virtual void OnNewPhase() + { + // Some AI need do something on new phase + } + public virtual void OnNewTurn() + { + // Some AI need do something on new turn + } + + public virtual void OnDraw(int player) + { + // Some AI need do something on draw + } + + public virtual IList OnSelectCard(IList cards, int min, int max, long hint, bool cancelable) + { + // For overriding + return null; + } + + public virtual IList OnSelectSum(IList cards, int sum, int min, int max, long hint, bool mode) + { + // For overriding + return null; + } + + public virtual IList OnSelectFusionMaterial(IList cards, int min, int max) + { + // For overriding + return null; + } + + public virtual IList OnSelectSynchroMaterial(IList cards, int sum, int min, int max) + { + // For overriding + return null; + } + + public virtual IList OnSelectXyzMaterial(IList cards, int min, int max) + { + // For overriding + return null; + } + + public virtual IList OnSelectLinkMaterial(IList cards, int min, int max) + { + // For overriding + return null; + } + + public virtual IList OnSelectRitualTribute(IList cards, int sum, int min, int max) + { + // For overriding + return null; + } + + public virtual IList OnSelectPendulumSummon(IList cards, int max) + { + // For overriding + return null; + } + + public virtual IList OnCardSorting(IList cards) + { + // For overriding + return null; + } + + public virtual bool OnSelectYesNo(long desc) + { + return true; + } + + public virtual int OnSelectOption(IList options) + { + return -1; + } + + public virtual int OnSelectPlace(long cardId, int player, CardLocation location, int available) + { + // For overriding + return 0; + } + + public virtual CardPosition OnSelectPosition(int cardId, IList positions) + { + // Overrided in DefalultExecutor + return 0; + } + + public virtual bool OnSelectBattleReplay() + { + // Overrided in DefalultExecutor + return false; + } + + public void SetMain(MainPhase main) + { + Main = main; + } + + public void SetBattle(BattlePhase battle) + { + Battle = battle; + } + + /// + /// Set global variables Type, Card, ActivateDescription for Executor + /// + public void SetCard(ExecutorType type, ClientCard card, long description) + { + Type = type; + Card = card; + ActivateDescription = description; + } + + /// + /// Do the action for the card if func return true. + /// + public void AddExecutor(ExecutorType type, int cardId, Func func) + { + Executors.Add(new CardExecutor(type, cardId, func)); + } + + /// + /// Do the action for the card if available. + /// + public void AddExecutor(ExecutorType type, int cardId) + { + Executors.Add(new CardExecutor(type, cardId, null)); + } + + /// + /// Do the action for every card if func return true. + /// + public void AddExecutor(ExecutorType type, Func func) + { + Executors.Add(new CardExecutor(type, -1, func)); + } + + /// + /// Do the action for every card if no other Executor is added to it. + /// + public void AddExecutor(ExecutorType type) + { + Executors.Add(new CardExecutor(type, -1, DefaultNoExecutor)); + } + + private bool DefaultNoExecutor() + { + return Executors.All(exec => exec.Type != Type || exec.CardId != Card.Id); + } + } } \ No newline at end of file diff --git a/Game/AI/ExecutorType.cs b/ExecutorBase/Game/AI/ExecutorType.cs similarity index 94% rename from Game/AI/ExecutorType.cs rename to ExecutorBase/Game/AI/ExecutorType.cs index 47a557d3..dc4b8083 100644 --- a/Game/AI/ExecutorType.cs +++ b/ExecutorBase/Game/AI/ExecutorType.cs @@ -1,16 +1,16 @@ -namespace WindBot.Game.AI -{ - public enum ExecutorType - { - Summon, - SpSummon, - Repos, - MonsterSet, - SpellSet, - Activate, - SummonOrSet, - GoToBattlePhase, - GoToMainPhase2, - GoToEndPhase - } +namespace WindBot.Game.AI +{ + public enum ExecutorType + { + Summon, + SpSummon, + Repos, + MonsterSet, + SpellSet, + Activate, + SummonOrSet, + GoToBattlePhase, + GoToMainPhase2, + GoToEndPhase + } } \ No newline at end of file diff --git a/Game/AI/Zones.cs b/ExecutorBase/Game/AI/Zones.cs similarity index 95% rename from Game/AI/Zones.cs rename to ExecutorBase/Game/AI/Zones.cs index 05944de4..f4721c0b 100644 --- a/Game/AI/Zones.cs +++ b/ExecutorBase/Game/AI/Zones.cs @@ -1,24 +1,24 @@ -namespace WindBot.Game.AI -{ - public static class Zones - { - public const int z0 = 0x1, - z1 = 0x2, - z2 = 0x4, - z3 = 0x8, - z4 = 0x10, - z5 = 0x20, - z6 = 0x40, - - MonsterZones = 0x7f, - MainMonsterZones = 0x1f, - ExtraMonsterZones = 0x60, - - SpellZones = 0x1f, - - PendulumZones = 0x3, - - LinkedZones = 0x10000, - NotLinkedZones = 0x20000; - } +namespace WindBot.Game.AI +{ + public static class Zones + { + public const int z0 = 0x1, + z1 = 0x2, + z2 = 0x4, + z3 = 0x8, + z4 = 0x10, + z5 = 0x20, + z6 = 0x40, + + MonsterZones = 0x7f, + MainMonsterZones = 0x1f, + ExtraMonsterZones = 0x60, + + SpellZones = 0x1f, + + PendulumZones = 0x3, + + LinkedZones = 0x10000, + NotLinkedZones = 0x20000; + } } \ No newline at end of file diff --git a/Game/BattlePhase.cs b/ExecutorBase/Game/BattlePhase.cs similarity index 96% rename from Game/BattlePhase.cs rename to ExecutorBase/Game/BattlePhase.cs index 38e72d44..a7b102ab 100644 --- a/Game/BattlePhase.cs +++ b/ExecutorBase/Game/BattlePhase.cs @@ -1,20 +1,20 @@ -using System.Collections.Generic; - -namespace WindBot.Game -{ - public class BattlePhase - { - public IList AttackableCards { get; private set; } - public IList ActivableCards { get; private set; } - public IList ActivableDescs { get; private set; } - public bool CanMainPhaseTwo { get; set; } - public bool CanEndPhase { get; set; } - - public BattlePhase() - { - AttackableCards = new List(); - ActivableCards = new List(); - ActivableDescs = new List(); - } - } +using System.Collections.Generic; + +namespace WindBot.Game +{ + public class BattlePhase + { + public IList AttackableCards { get; private set; } + public IList ActivableCards { get; private set; } + public IList ActivableDescs { get; private set; } + public bool CanMainPhaseTwo { get; set; } + public bool CanEndPhase { get; set; } + + public BattlePhase() + { + AttackableCards = new List(); + ActivableCards = new List(); + ActivableDescs = new List(); + } + } } \ No newline at end of file diff --git a/Game/BattlePhaseAction.cs b/ExecutorBase/Game/BattlePhaseAction.cs similarity index 95% rename from Game/BattlePhaseAction.cs rename to ExecutorBase/Game/BattlePhaseAction.cs index 3238a22b..f11d148b 100644 --- a/Game/BattlePhaseAction.cs +++ b/ExecutorBase/Game/BattlePhaseAction.cs @@ -1,33 +1,33 @@ -namespace WindBot.Game -{ - public class BattlePhaseAction - { - public enum BattleAction - { - Activate = 0, - Attack = 1, - ToMainPhaseTwo = 2, - ToEndPhase = 3 - } - - public BattleAction Action { get; private set; } - public int Index { get; private set; } - - public BattlePhaseAction(BattleAction action) - { - Action = action; - Index = 0; - } - - public BattlePhaseAction(BattleAction action, int[] indexes) - { - Action = action; - Index = indexes[(int)action]; - } - - public int ToValue() - { - return (Index << 16) + (int)Action; - } - } +namespace WindBot.Game +{ + public class BattlePhaseAction + { + public enum BattleAction + { + Activate = 0, + Attack = 1, + ToMainPhaseTwo = 2, + ToEndPhase = 3 + } + + public BattleAction Action { get; private set; } + public int Index { get; private set; } + + public BattlePhaseAction(BattleAction action) + { + Action = action; + Index = 0; + } + + public BattlePhaseAction(BattleAction action, int[] indexes) + { + Action = action; + Index = indexes[(int)action]; + } + + public int ToValue() + { + return (Index << 16) + (int)Action; + } + } } \ No newline at end of file diff --git a/Game/ClientCard.cs b/ExecutorBase/Game/ClientCard.cs similarity index 95% rename from Game/ClientCard.cs rename to ExecutorBase/Game/ClientCard.cs index 78707f38..98207ba0 100644 --- a/Game/ClientCard.cs +++ b/ExecutorBase/Game/ClientCard.cs @@ -1,412 +1,418 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using YGOSharp.OCGWrapper; -using YGOSharp.OCGWrapper.Enums; - -namespace WindBot.Game -{ - public class ClientCard - { - public int Id { get; private set; } - public NamedCard Data { get; private set; } - public string Name { get; private set; } - - public int Position { get; set; } - public int Sequence { get; set; } - public CardLocation Location { get; set; } - public int Alias { get; private set; } - public int Level { get; private set; } - public int Rank { get; private set; } - public int Type { get; private set; } - public int Attribute { get; private set; } - public int Race { get; private set; } - public int Attack { get; private set; } - public int Defense { get; private set; } - public int LScale { get; private set; } - public int RScale { get; private set; } - public int LinkCount { get; private set; } - public int LinkMarker { get; private set; } - public int BaseAttack { get; private set; } - public int BaseDefense { get; private set; } - public int RealPower { get; set; } - public List Overlays { get; private set; } - public int Owner { get; private set; } - public int Controller { get; private set; } - public int Disabled { get; private set; } - public int ProcCompleted { get; private set; } - public int SelectSeq { get; set; } - public int OpParam1 { get; set; } - public int OpParam2 { get; set; } - - public List EquipCards { get; set; } - public ClientCard EquipTarget; - public List OwnTargets { get; set; } - public List TargetCards { get; set; } - - public bool CanDirectAttack { get; set; } - public bool ShouldDirectAttack { get; set; } - public bool Attacked { get; set; } - public bool IsLastAttacker { get; set; } - public bool IsSpecialSummoned { get; set; } - - public int[] ActionIndex { get; set; } - public IDictionary ActionActivateIndex { get; private set; } - - public ClientCard(int id, CardLocation loc, int sequence) - : this(id, loc, -1 , 0) - { - } - - public ClientCard(int id, CardLocation loc, int sequence, int position) - { - SetId(id); - Sequence = sequence; - Position = position; - Overlays = new List(); - EquipCards = new List(); - OwnTargets = new List(); - TargetCards = new List(); - ActionIndex = new int[16]; - ActionActivateIndex = new Dictionary(); - Location = loc; - } - - public void SetId(int id) - { - if (Id == id) return; - Id = id; - Data = NamedCard.Get(Id); - if (Data != null) - { - Name = Data.Name; - if (Data.Alias != 0) - Alias = Data.Alias; - } - } - - public long Update(BinaryReader packet, Duel duel) - { - long pos = packet.BaseStream.Position; - while (true) - { - int size = packet.ReadInt16(); - if (size == 0) - return packet.BaseStream.Position - pos; - uint flag = packet.ReadUInt32(); - switch (flag) - { - case (uint)Query.Code: - { - SetId(packet.ReadInt32()); - break; - } - case (uint)Query.Position: - { - Position = packet.ReadInt32(); - break; - } - case (uint)Query.Alias: - { - Alias = packet.ReadInt32(); - break; - } - case (uint)Query.Type: - { - Type = packet.ReadInt32(); - break; - } - case (uint)Query.Level: - { - Level = packet.ReadInt32(); - break; - } - case (uint)Query.Rank: - { - Rank = packet.ReadInt32(); - break; - } - case (uint)Query.Attribute: - { - Attribute = packet.ReadInt32(); - break; - } - case (uint)Query.Race: - { - Race = packet.ReadInt32(); - break; - } - case (uint)Query.Attack: - { - Attack = packet.ReadInt32(); - break; - } - case (uint)Query.Defence: - { - Defense = packet.ReadInt32(); - break; - } - case (uint)Query.BaseAttack: - { - BaseAttack = packet.ReadInt32(); - break; - } - case (uint)Query.BaseDefence: - { - BaseDefense = packet.ReadInt32(); - break; - } - case (uint)Query.OverlayCard: - { - Overlays.Clear(); - int count = packet.ReadInt32(); - for (int i = 0; i < count; ++i) - Overlays.Add(packet.ReadInt32()); - } - break; - case (uint)Query.Owner: - { - Owner = duel.GetLocalPlayer(packet.ReadByte()); - break; - } - case (uint)Query.Status: - { - int status = packet.ReadInt32(); - const int STATUS_DISABLED = 0x0001; - const int STATUS_PROC_COMPLETE = 0x0008; - Disabled = status & STATUS_DISABLED; - ProcCompleted = status & STATUS_PROC_COMPLETE; - break; - } - case (uint)Query.LScale: - { - LScale = packet.ReadInt32(); - break; - } - case (uint)Query.RScale: - { - RScale = packet.ReadInt32(); - break; - } - case (uint)Query.Link: - { - LinkCount = packet.ReadInt32(); - LinkMarker = packet.ReadInt32(); - break; - } - case 0x80000000: //Query.End - return packet.BaseStream.Position - pos; - default: - { - packet.ReadChars(size - sizeof(uint)); - break; - } - } - } - } - - public void ClearCardTargets() - { - foreach (ClientCard card in TargetCards) - { - card.OwnTargets.Remove(this); - } - foreach (ClientCard card in OwnTargets) - { - card.TargetCards.Remove(this); - } - OwnTargets.Clear(); - TargetCards.Clear(); - } - - public bool HasLinkMarker(int dir) - { - return (LinkMarker & dir) != 0; - } - - public bool HasLinkMarker(CardLinkMarker dir) - { - return (LinkMarker & (int)dir) != 0; - } - - public int GetLinkedZones() - { - if (!HasType(CardType.Link) || Location != CardLocation.MonsterZone) - return 0; - int zones = 0; - if (Sequence > 0 && Sequence <= 4 && HasLinkMarker(CardLinkMarker.Left)) - zones |= 1 << (Sequence - 1); - if (Sequence <= 3 && HasLinkMarker(CardLinkMarker.Right)) - zones |= 1 << (Sequence + 1); - if (Sequence == 0 && HasLinkMarker(CardLinkMarker.TopRight) - || Sequence == 1 && HasLinkMarker(CardLinkMarker.Top) - || Sequence == 2 && HasLinkMarker(CardLinkMarker.TopLeft)) - zones |= (1 << 5) | (1 << (16 + 6)); - if (Sequence == 2 && HasLinkMarker(CardLinkMarker.TopRight) - || Sequence == 3 && HasLinkMarker(CardLinkMarker.Top) - || Sequence == 4 && HasLinkMarker(CardLinkMarker.TopLeft)) - zones |= (1 << 6) | (1 << (16 + 5)); - if (Sequence == 5) - { - if (HasLinkMarker(CardLinkMarker.BottomLeft)) - zones |= 1 << 0; - if (HasLinkMarker(CardLinkMarker.Bottom)) - zones |= 1 << 1; - if (HasLinkMarker(CardLinkMarker.BottomRight)) - zones |= 1 << 2; - if (HasLinkMarker(CardLinkMarker.TopLeft)) - zones |= 1 << (16 + 4); - if (HasLinkMarker(CardLinkMarker.Top)) - zones |= 1 << (16 + 3); - if (HasLinkMarker(CardLinkMarker.TopRight)) - zones |= 1 << (16 + 2); - } - if (Sequence == 6) - { - if (HasLinkMarker(CardLinkMarker.BottomLeft)) - zones |= 1 << 2; - if (HasLinkMarker(CardLinkMarker.Bottom)) - zones |= 1 << 3; - if (HasLinkMarker(CardLinkMarker.BottomRight)) - zones |= 1 << 4; - if (HasLinkMarker(CardLinkMarker.TopLeft)) - zones |= 1 << (16 + 2); - if (HasLinkMarker(CardLinkMarker.Top)) - zones |= 1 << (16 + 1); - if (HasLinkMarker(CardLinkMarker.TopRight)) - zones |= 1 << (16 + 0); - } - return zones; - } - - public bool HasType(CardType type) - { - return (Type & (int)type) != 0; - } - - public bool HasPosition(CardPosition position) - { - return (Position & (int)position) != 0; - } - - public bool HasAttribute(CardAttribute attribute) - { - return (Attribute & (int)attribute) != 0; - } - - public bool HasSetcode(int setcode) - { - if (Data == null) return false; - long setcodes = Data.Setcode; - int settype = setcode & 0xfff; - int setsubtype = setcode & 0xf000; - while (setcodes > 0) - { - long check_setcode = setcodes & 0xffff; - setcodes >>= 16; - if ((check_setcode & 0xfff) == settype && (check_setcode & 0xf000 & setsubtype) == setsubtype) return true; - } - return false; - } - - public bool IsMonster() - { - return HasType(CardType.Monster); - } - - public bool IsTuner() - { - return HasType(CardType.Tuner); - } - - public bool IsSpell() - { - return HasType(CardType.Spell); - } - - public bool IsTrap() - { - return HasType(CardType.Trap); - } - - public bool IsExtraCard() - { - return HasType(CardType.Fusion) || HasType(CardType.Synchro) || HasType(CardType.Xyz) || HasType(CardType.Link); - } - - public bool IsFaceup() - { - return HasPosition(CardPosition.FaceUp); - } - - public bool IsFacedown() - { - return HasPosition(CardPosition.FaceDown); - } - - public bool IsAttack() - { - return HasPosition(CardPosition.Attack); - } - - public bool IsDefense() - { - return HasPosition(CardPosition.Defence); - } - - public bool IsDisabled() - { - return Disabled != 0; - } - - public bool IsCanRevive() - { - return ProcCompleted != 0 || !(IsExtraCard() || HasType(CardType.Ritual) || HasType(CardType.SpSummon)); - } - - public bool IsCode(int id) - { - return Id == id || Alias != 0 && Alias == id; - } - - public bool IsCode(IList ids) - { - return ids.Contains(Id) || Alias != 0 && ids.Contains(Alias); - } - - public bool IsCode(params int[] ids) - { - return ids.Contains(Id) || Alias != 0 && ids.Contains(Alias); - } - - public bool IsOriginalCode(int id) - { - return Id == id || Alias - Id < 10 && Alias == id; - } - - public bool HasXyzMaterial() - { - return Overlays.Count > 0; - } - - public bool HasXyzMaterial(int count) - { - return Overlays.Count >= count; - } - - public bool HasXyzMaterial(int count, int cardid) - { - return Overlays.Count >= count && Overlays.Contains(cardid); - } - - public int GetDefensePower() - { - return IsAttack() ? Attack : Defense; - } - - public bool Equals(ClientCard card) - { - return ReferenceEquals(this, card); - } - } -} \ No newline at end of file +using System.Collections.Generic; +using System.IO; +using System.Linq; +using YGOSharp.OCGWrapper; +using YGOSharp.OCGWrapper.Enums; + +namespace WindBot.Game +{ + public class ClientCard + { + public int Id { get; private set; } + public NamedCard Data { get; private set; } + public string Name { get; private set; } + + public int Position { get; set; } + public int Sequence { get; set; } + public CardLocation Location { get; set; } + public int Alias { get; private set; } + public int Level { get; private set; } + public int Rank { get; private set; } + public int Type { get; private set; } + public int Attribute { get; private set; } + public int Race { get; private set; } + public int Attack { get; private set; } + public int Defense { get; private set; } + public int LScale { get; private set; } + public int RScale { get; private set; } + public int LinkCount { get; private set; } + public int LinkMarker { get; private set; } + public int BaseAttack { get; private set; } + public int BaseDefense { get; private set; } + public int RealPower { get; set; } + public List Overlays { get; private set; } + public int Owner { get; private set; } + public int Controller { get; set; } + public int Disabled { get; private set; } + public int ProcCompleted { get; private set; } + public int SelectSeq { get; set; } + public int OpParam1 { get; set; } + public int OpParam2 { get; set; } + + public List EquipCards { get; set; } + public ClientCard EquipTarget; + public List OwnTargets { get; set; } + public List TargetCards { get; set; } + + public bool CanDirectAttack { get; set; } + public bool ShouldDirectAttack { get; set; } + public bool Attacked { get; set; } + public bool IsLastAttacker { get; set; } + public bool IsSpecialSummoned { get; set; } + + public int[] ActionIndex { get; set; } + public IDictionary ActionActivateIndex { get; private set; } + + public ClientCard(int id, CardLocation loc, int sequence, int controller) + : this(id, loc, -1, 0, controller) + { + } + + public ClientCard(int id, CardLocation loc, int sequence, int position, int controller) + { + SetId(id); + Sequence = sequence; + Position = position; + Controller = controller; + Overlays = new List(); + EquipCards = new List(); + OwnTargets = new List(); + TargetCards = new List(); + ActionIndex = new int[16]; + ActionActivateIndex = new Dictionary(); + Location = loc; + } + + public void SetId(int id) + { + if (Id == id) return; + Id = id; + Data = NamedCard.Get(Id); + if (Data != null) + { + Name = Data.Name; + if (Data.Alias != 0) + Alias = Data.Alias; + } + } + + public long Update(BinaryReader packet, Duel duel) + { + long pos = packet.BaseStream.Position; + while (true) + { + int size = packet.ReadInt16(); + if (size == 0) + return packet.BaseStream.Position - pos; + uint flag = packet.ReadUInt32(); + switch (flag) + { + case (uint)Query.Code: + { + SetId(packet.ReadInt32()); + break; + } + case (uint)Query.Position: + { + Position = packet.ReadInt32(); + break; + } + case (uint)Query.Alias: + { + Alias = packet.ReadInt32(); + break; + } + case (uint)Query.Type: + { + Type = packet.ReadInt32(); + break; + } + case (uint)Query.Level: + { + Level = packet.ReadInt32(); + break; + } + case (uint)Query.Rank: + { + Rank = packet.ReadInt32(); + break; + } + case (uint)Query.Attribute: + { + Attribute = packet.ReadInt32(); + break; + } + case (uint)Query.Race: + { + Race = packet.ReadInt32(); + break; + } + case (uint)Query.Attack: + { + Attack = packet.ReadInt32(); + break; + } + case (uint)Query.Defence: + { + Defense = packet.ReadInt32(); + break; + } + case (uint)Query.BaseAttack: + { + BaseAttack = packet.ReadInt32(); + break; + } + case (uint)Query.BaseDefence: + { + BaseDefense = packet.ReadInt32(); + break; + } + case (uint)Query.OverlayCard: + { + Overlays.Clear(); + int count = packet.ReadInt32(); + for (int i = 0; i < count; ++i) + Overlays.Add(packet.ReadInt32()); + } + break; + case (uint)Query.Owner: + { + Owner = duel.GetLocalPlayer(packet.ReadByte()); + break; + } + case (uint)Query.Status: + { + int status = packet.ReadInt32(); + const int STATUS_DISABLED = 0x0001; + const int STATUS_PROC_COMPLETE = 0x0008; + Disabled = status & STATUS_DISABLED; + ProcCompleted = status & STATUS_PROC_COMPLETE; + break; + } + case (uint)Query.LScale: + { + LScale = packet.ReadInt32(); + break; + } + case (uint)Query.RScale: + { + RScale = packet.ReadInt32(); + break; + } + case (uint)Query.Link: + { + LinkCount = packet.ReadInt32(); + LinkMarker = packet.ReadInt32(); + break; + } + case (uint)Query.End: //Query.End + return packet.BaseStream.Position - pos; + default: + { + packet.ReadChars(size - sizeof(uint)); + break; + } + } + } + } + + public void ClearCardTargets() + { + foreach (ClientCard card in TargetCards) + { + card.OwnTargets.Remove(this); + } + foreach (ClientCard card in OwnTargets) + { + card.TargetCards.Remove(this); + } + OwnTargets.Clear(); + TargetCards.Clear(); + } + + public bool HasLinkMarker(int dir) + { + return (LinkMarker & dir) != 0; + } + + public bool HasLinkMarker(CardLinkMarker dir) + { + return (LinkMarker & (int)dir) != 0; + } + + public int GetLinkedZones() + { + if (!HasType(CardType.Link) || Location != CardLocation.MonsterZone) + return 0; + int zones = 0; + if (Sequence > 0 && Sequence <= 4 && HasLinkMarker(CardLinkMarker.Left)) + zones |= 1 << (Sequence - 1); + if (Sequence <= 3 && HasLinkMarker(CardLinkMarker.Right)) + zones |= 1 << (Sequence + 1); + if (Sequence == 0 && HasLinkMarker(CardLinkMarker.TopRight) + || Sequence == 1 && HasLinkMarker(CardLinkMarker.Top) + || Sequence == 2 && HasLinkMarker(CardLinkMarker.TopLeft)) + zones |= (1 << 5) | (1 << (16 + 6)); + if (Sequence == 2 && HasLinkMarker(CardLinkMarker.TopRight) + || Sequence == 3 && HasLinkMarker(CardLinkMarker.Top) + || Sequence == 4 && HasLinkMarker(CardLinkMarker.TopLeft)) + zones |= (1 << 6) | (1 << (16 + 5)); + if (Sequence == 5) + { + if (HasLinkMarker(CardLinkMarker.BottomLeft)) + zones |= 1 << 0; + if (HasLinkMarker(CardLinkMarker.Bottom)) + zones |= 1 << 1; + if (HasLinkMarker(CardLinkMarker.BottomRight)) + zones |= 1 << 2; + if (HasLinkMarker(CardLinkMarker.TopLeft)) + zones |= 1 << (16 + 4); + if (HasLinkMarker(CardLinkMarker.Top)) + zones |= 1 << (16 + 3); + if (HasLinkMarker(CardLinkMarker.TopRight)) + zones |= 1 << (16 + 2); + } + if (Sequence == 6) + { + if (HasLinkMarker(CardLinkMarker.BottomLeft)) + zones |= 1 << 2; + if (HasLinkMarker(CardLinkMarker.Bottom)) + zones |= 1 << 3; + if (HasLinkMarker(CardLinkMarker.BottomRight)) + zones |= 1 << 4; + if (HasLinkMarker(CardLinkMarker.TopLeft)) + zones |= 1 << (16 + 2); + if (HasLinkMarker(CardLinkMarker.Top)) + zones |= 1 << (16 + 1); + if (HasLinkMarker(CardLinkMarker.TopRight)) + zones |= 1 << (16 + 0); + } + return zones; + } + + public bool HasType(CardType type) + { + return (Type & (int)type) != 0; + } + + public bool HasPosition(CardPosition position) + { + return (Position & (int)position) != 0; + } + + public bool HasAttribute(CardAttribute attribute) + { + return (Attribute & (int)attribute) != 0; + } + + public bool HasRace(CardRace race) + { + return (Race & (int)race) != 0; + } + + public bool HasSetcode(int setcode) + { + if (Data == null) return false; + long setcodes = Data.Setcode; + int settype = setcode & 0xfff; + int setsubtype = setcode & 0xf000; + while (setcodes > 0) + { + long check_setcode = setcodes & 0xffff; + setcodes >>= 16; + if ((check_setcode & 0xfff) == settype && (check_setcode & 0xf000 & setsubtype) == setsubtype) return true; + } + return false; + } + + public bool IsMonster() + { + return HasType(CardType.Monster); + } + + public bool IsTuner() + { + return HasType(CardType.Tuner); + } + + public bool IsSpell() + { + return HasType(CardType.Spell); + } + + public bool IsTrap() + { + return HasType(CardType.Trap); + } + + public bool IsExtraCard() + { + return HasType(CardType.Fusion) || HasType(CardType.Synchro) || HasType(CardType.Xyz) || HasType(CardType.Link); + } + + public bool IsFaceup() + { + return HasPosition(CardPosition.FaceUp); + } + + public bool IsFacedown() + { + return HasPosition(CardPosition.FaceDown); + } + + public bool IsAttack() + { + return HasPosition(CardPosition.Attack); + } + + public bool IsDefense() + { + return HasPosition(CardPosition.Defence); + } + + public bool IsDisabled() + { + return Disabled != 0; + } + + public bool IsCanRevive() + { + return ProcCompleted != 0 || !(IsExtraCard() || HasType(CardType.Ritual) || HasType(CardType.SpSummon)); + } + + public bool IsCode(int id) + { + return Id == id || Alias != 0 && Alias == id; + } + + public bool IsCode(IList ids) + { + return ids.Contains(Id) || Alias != 0 && ids.Contains(Alias); + } + + public bool IsCode(params int[] ids) + { + return ids.Contains(Id) || Alias != 0 && ids.Contains(Alias); + } + + public bool IsOriginalCode(int id) + { + return Id == id || Alias - Id < 10 && Alias == id; + } + + public bool HasXyzMaterial() + { + return Overlays.Count > 0; + } + + public bool HasXyzMaterial(int count) + { + return Overlays.Count >= count; + } + + public bool HasXyzMaterial(int count, int cardid) + { + return Overlays.Count >= count && Overlays.Contains(cardid); + } + + public int GetDefensePower() + { + return IsAttack() ? Attack : Defense; + } + + public bool Equals(ClientCard card) + { + return ReferenceEquals(this, card); + } + } +} diff --git a/Game/ClientField.cs b/ExecutorBase/Game/ClientField.cs similarity index 96% rename from Game/ClientField.cs rename to ExecutorBase/Game/ClientField.cs index 6b4eff5e..871c9d5c 100644 --- a/Game/ClientField.cs +++ b/ExecutorBase/Game/ClientField.cs @@ -1,352 +1,352 @@ -using System.Collections.Generic; -using System.Linq; -using WindBot.Game.AI; -using YGOSharp.OCGWrapper.Enums; - -namespace WindBot.Game -{ - public class ClientField - { - public IList Hand { get; private set; } - public ClientCard[] MonsterZone { get; private set; } - public ClientCard[] SpellZone { get; private set; } - public IList Graveyard { get; private set; } - public IList Banished { get; private set; } - public IList Deck { get; private set; } - public IList ExtraDeck { get; private set; } - - public int LifePoints; - public ClientCard BattlingMonster; - public bool UnderAttack; - - public ClientField() - { - } - - public void Init(int deck, int extra) - { - Hand = new List(); - MonsterZone = new ClientCard[7]; - SpellZone = new ClientCard[8]; - Graveyard = new List(); - Banished = new List(); - Deck = new List(); - ExtraDeck = new List(); - - for (int i = 0; i < deck; ++i) - Deck.Add(new ClientCard(0, CardLocation.Deck, -1)); - for (int i = 0; i < extra; ++i) - ExtraDeck.Add(new ClientCard(0, CardLocation.Extra, -1)); - } - - public int GetMonstersExtraZoneCount() - { - int count = 0; - if (MonsterZone[5] != null) - count++; - if (MonsterZone[6] != null) - count++; - return count; - } - public int GetMonsterCount() - { - return GetCount(MonsterZone); - } - - public int GetSpellCount() - { - return GetCount(SpellZone); - } - - public int GetHandCount() - { - return GetCount(Hand); - } - - public int GetSpellCountWithoutField() - { - int count = 0; - for (int i = 0; i < 5; ++i) - { - if (SpellZone[i] != null) - ++count; - } - return count; - } - - /// - /// Count Column - /// - /// range of zone 0-4 - public int GetColumnCount(int zone, bool IncludeExtraMonsterZone = true) - { - int count = 0; - if (SpellZone[zone] != null) - count++; - if (MonsterZone[zone] != null) - count++; - if(zone == 1 && IncludeExtraMonsterZone) - { - if (MonsterZone[5] != null) - count++; - } - if (zone == 3 && IncludeExtraMonsterZone) - { - if (MonsterZone[6] != null) - count++; - } - return count; - } - - public int GetFieldCount() - { - return GetSpellCount() + GetMonsterCount(); - } - - public int GetFieldHandCount() - { - return GetSpellCount() + GetMonsterCount() + GetHandCount(); - } - - public bool IsFieldEmpty() - { - return GetMonsters().Count == 0 && GetSpells().Count == 0; - } - - public int GetLinkedZones() - { - int zones = 0; - for (int i = 0; i < 7; i++) - { - zones |= MonsterZone[i]?.GetLinkedZones() ?? 0; - } - return zones; - } - - public List GetMonsters() - { - return GetCards(MonsterZone); - } - - public List GetGraveyardMonsters() - { - return GetCards(Graveyard, CardType.Monster); - } - - public List GetGraveyardSpells() - { - return GetCards(Graveyard, CardType.Spell); - } - - public List GetGraveyardTraps() - { - return GetCards(Graveyard, CardType.Trap); - } - - public List GetSpells() - { - return GetCards(SpellZone); - } - - public List GetMonstersInExtraZone() - { - return GetMonsters().Where(card => card.Sequence >= 5).ToList(); - } - - public List GetMonstersInMainZone() - { - return GetMonsters().Where(card => card.Sequence < 5).ToList(); - } - - public ClientCard GetFieldSpellCard() - { - return SpellZone[5]; - } - - public bool HasInHand(int cardId) - { - return HasInCards(Hand, cardId); - } - - public bool HasInHand(IList cardId) - { - return HasInCards(Hand, cardId); - } - - public bool HasInGraveyard(int cardId) - { - return HasInCards(Graveyard, cardId); - } - - public bool HasInGraveyard(IList cardId) - { - return HasInCards(Graveyard, cardId); - } - - public bool HasInBanished(int cardId) - { - return HasInCards(Banished, cardId); - } - - public bool HasInBanished(IList cardId) - { - return HasInCards(Banished, cardId); - } - - public bool HasInExtra(int cardId) - { - return HasInCards(ExtraDeck, cardId); - } - - public bool HasInExtra(IList cardId) - { - return HasInCards(ExtraDeck, cardId); - } - - public bool HasAttackingMonster() - { - return GetMonsters().Any(card => card.IsAttack()); - } - - public bool HasDefendingMonster() - { - return GetMonsters().Any(card => card.IsDefense()); - } - - public bool HasInMonstersZone(int cardId, bool notDisabled = false, bool hasXyzMaterial = false, bool faceUp = false) - { - return HasInCards(MonsterZone, cardId, notDisabled, hasXyzMaterial, faceUp); - } - - public bool HasInMonstersZone(IList cardId, bool notDisabled = false, bool hasXyzMaterial = false, bool faceUp = false) - { - return HasInCards(MonsterZone, cardId, notDisabled, hasXyzMaterial, faceUp); - } - - public bool HasInSpellZone(int cardId, bool notDisabled = false, bool faceUp = false) - { - return HasInCards(SpellZone, cardId, notDisabled, false, faceUp); - } - - public bool HasInSpellZone(IList cardId, bool notDisabled = false, bool faceUp = false) - { - return HasInCards(SpellZone, cardId, notDisabled, false, faceUp); - } - - public bool HasInHandOrInSpellZone(int cardId) - { - return HasInHand(cardId) || HasInSpellZone(cardId); - } - - public bool HasInHandOrHasInMonstersZone(int cardId) - { - return HasInHand(cardId) || HasInMonstersZone(cardId); - } - - public bool HasInHandOrInGraveyard(int cardId) - { - return HasInHand(cardId) || HasInGraveyard(cardId); - } - - public bool HasInMonstersZoneOrInGraveyard(int cardId) - { - return HasInMonstersZone(cardId) || HasInGraveyard(cardId); - } - - public bool HasInSpellZoneOrInGraveyard(int cardId) - { - return HasInSpellZone(cardId) || HasInGraveyard(cardId); - } - - public bool HasInHandOrInMonstersZoneOrInGraveyard(int cardId) - { - return HasInHand(cardId) || HasInMonstersZone(cardId) || HasInGraveyard(cardId); - } - - public bool HasInHandOrInSpellZoneOrInGraveyard(int cardId) - { - return HasInHand(cardId) || HasInSpellZone(cardId) || HasInGraveyard(cardId); - } - - public bool HasInHandOrInSpellZone(IList cardId) - { - return HasInHand(cardId) || HasInSpellZone(cardId); - } - - public bool HasInHandOrHasInMonstersZone(IList cardId) - { - return HasInHand(cardId) || HasInMonstersZone(cardId); - } - - public bool HasInHandOrInGraveyard(IList cardId) - { - return HasInHand(cardId) || HasInGraveyard(cardId); - } - - public bool HasInMonstersZoneOrInGraveyard(IList cardId) - { - return HasInMonstersZone(cardId) || HasInGraveyard(cardId); - } - - public bool HasInSpellZoneOrInGraveyard(IList cardId) - { - return HasInSpellZone(cardId) || HasInGraveyard(cardId); - } - - public bool HasInHandOrInMonstersZoneOrInGraveyard(IList cardId) - { - return HasInHand(cardId) || HasInMonstersZone(cardId) || HasInGraveyard(cardId); - } - - public bool HasInHandOrInSpellZoneOrInGraveyard(IList cardId) - { - return HasInHand(cardId) || HasInSpellZone(cardId) || HasInGraveyard(cardId); - } - - public int GetRemainingCount(int cardId, int initialCount) - { - int remaining = initialCount; - remaining = remaining - Hand.Count(card => card != null && card.IsOriginalCode(cardId)); - remaining = remaining - SpellZone.Count(card => card != null && card.IsOriginalCode(cardId)); - remaining = remaining - MonsterZone.Count(card => card != null && card.IsOriginalCode(cardId)); - remaining = remaining - Graveyard.Count(card => card != null && card.IsOriginalCode(cardId)); - remaining = remaining - Banished.Count(card => card != null && card.IsOriginalCode(cardId)); - return (remaining < 0) ? 0 : remaining; - } - - private static int GetCount(IEnumerable cards) - { - return cards.Count(card => card != null); - } - - public int GetCountCardInZone(IEnumerable cards, int cardId) - { - return cards.Count(card => card != null && card.IsCode(cardId)); - } - - public int GetCountCardInZone(IEnumerable cards, List cardId) - { - return cards.Count(card => card != null && card.IsCode(cardId)); - } - - private static List GetCards(IEnumerable cards, CardType type) - { - return cards.Where(card => card != null && card.HasType(type)).ToList(); - } - - private static List GetCards(IEnumerable cards) - { - return cards.Where(card => card != null).ToList(); - } - - private static bool HasInCards(IEnumerable cards, int cardId, bool notDisabled = false, bool hasXyzMaterial = false, bool faceUp = false) - { - return cards.Any(card => card != null && card.IsCode(cardId) && !(notDisabled && card.IsDisabled()) && !(hasXyzMaterial && !card.HasXyzMaterial()) && !(faceUp && card.IsFacedown())); - } - - private static bool HasInCards(IEnumerable cards, IList cardId, bool notDisabled = false, bool hasXyzMaterial = false, bool faceUp = false) - { - return cards.Any(card => card != null && card.IsCode(cardId) && !(notDisabled && card.IsDisabled()) && !(hasXyzMaterial && !card.HasXyzMaterial()) && !(faceUp && card.IsFacedown())); - } - } +using System.Collections.Generic; +using System.Linq; +using WindBot.Game.AI; +using YGOSharp.OCGWrapper.Enums; + +namespace WindBot.Game +{ + public class ClientField + { + public IList Hand { get; private set; } + public ClientCard[] MonsterZone { get; private set; } + public ClientCard[] SpellZone { get; private set; } + public IList Graveyard { get; private set; } + public IList Banished { get; private set; } + public IList Deck { get; private set; } + public IList ExtraDeck { get; private set; } + + public int LifePoints; + public ClientCard BattlingMonster; + public bool UnderAttack; + + public ClientField() + { + } + + public void Init(int deck, int extra) + { + Hand = new List(); + MonsterZone = new ClientCard[7]; + SpellZone = new ClientCard[8]; + Graveyard = new List(); + Banished = new List(); + Deck = new List(); + ExtraDeck = new List(); + + for (int i = 0; i < deck; ++i) + Deck.Add(new ClientCard(0, CardLocation.Deck, -1, 0)); + for (int i = 0; i < extra; ++i) + ExtraDeck.Add(new ClientCard(0, CardLocation.Extra, -1, 0)); + } + + public int GetMonstersExtraZoneCount() + { + int count = 0; + if (MonsterZone[5] != null) + count++; + if (MonsterZone[6] != null) + count++; + return count; + } + public int GetMonsterCount() + { + return GetCount(MonsterZone); + } + + public int GetSpellCount() + { + return GetCount(SpellZone); + } + + public int GetHandCount() + { + return GetCount(Hand); + } + + public int GetSpellCountWithoutField() + { + int count = 0; + for (int i = 0; i < 5; ++i) + { + if (SpellZone[i] != null) + ++count; + } + return count; + } + + /// + /// Count Column + /// + /// range of zone 0-4 + public int GetColumnCount(int zone, bool IncludeExtraMonsterZone = true) + { + int count = 0; + if (SpellZone[zone] != null) + count++; + if (MonsterZone[zone] != null) + count++; + if(zone == 1 && IncludeExtraMonsterZone) + { + if (MonsterZone[5] != null) + count++; + } + if (zone == 3 && IncludeExtraMonsterZone) + { + if (MonsterZone[6] != null) + count++; + } + return count; + } + + public int GetFieldCount() + { + return GetSpellCount() + GetMonsterCount(); + } + + public int GetFieldHandCount() + { + return GetSpellCount() + GetMonsterCount() + GetHandCount(); + } + + public bool IsFieldEmpty() + { + return GetMonsters().Count == 0 && GetSpells().Count == 0; + } + + public int GetLinkedZones() + { + int zones = 0; + for (int i = 0; i < 7; i++) + { + zones |= MonsterZone[i]?.GetLinkedZones() ?? 0; + } + return zones; + } + + public List GetMonsters() + { + return GetCards(MonsterZone); + } + + public List GetGraveyardMonsters() + { + return GetCards(Graveyard, CardType.Monster); + } + + public List GetGraveyardSpells() + { + return GetCards(Graveyard, CardType.Spell); + } + + public List GetGraveyardTraps() + { + return GetCards(Graveyard, CardType.Trap); + } + + public List GetSpells() + { + return GetCards(SpellZone); + } + + public List GetMonstersInExtraZone() + { + return GetMonsters().Where(card => card.Sequence >= 5).ToList(); + } + + public List GetMonstersInMainZone() + { + return GetMonsters().Where(card => card.Sequence < 5).ToList(); + } + + public ClientCard GetFieldSpellCard() + { + return SpellZone[5]; + } + + public bool HasInHand(int cardId) + { + return HasInCards(Hand, cardId); + } + + public bool HasInHand(IList cardId) + { + return HasInCards(Hand, cardId); + } + + public bool HasInGraveyard(int cardId) + { + return HasInCards(Graveyard, cardId); + } + + public bool HasInGraveyard(IList cardId) + { + return HasInCards(Graveyard, cardId); + } + + public bool HasInBanished(int cardId) + { + return HasInCards(Banished, cardId); + } + + public bool HasInBanished(IList cardId) + { + return HasInCards(Banished, cardId); + } + + public bool HasInExtra(int cardId) + { + return HasInCards(ExtraDeck, cardId); + } + + public bool HasInExtra(IList cardId) + { + return HasInCards(ExtraDeck, cardId); + } + + public bool HasAttackingMonster() + { + return GetMonsters().Any(card => card.IsAttack()); + } + + public bool HasDefendingMonster() + { + return GetMonsters().Any(card => card.IsDefense()); + } + + public bool HasInMonstersZone(int cardId, bool notDisabled = false, bool hasXyzMaterial = false, bool faceUp = false) + { + return HasInCards(MonsterZone, cardId, notDisabled, hasXyzMaterial, faceUp); + } + + public bool HasInMonstersZone(IList cardId, bool notDisabled = false, bool hasXyzMaterial = false, bool faceUp = false) + { + return HasInCards(MonsterZone, cardId, notDisabled, hasXyzMaterial, faceUp); + } + + public bool HasInSpellZone(int cardId, bool notDisabled = false, bool faceUp = false) + { + return HasInCards(SpellZone, cardId, notDisabled, false, faceUp); + } + + public bool HasInSpellZone(IList cardId, bool notDisabled = false, bool faceUp = false) + { + return HasInCards(SpellZone, cardId, notDisabled, false, faceUp); + } + + public bool HasInHandOrInSpellZone(int cardId) + { + return HasInHand(cardId) || HasInSpellZone(cardId); + } + + public bool HasInHandOrHasInMonstersZone(int cardId) + { + return HasInHand(cardId) || HasInMonstersZone(cardId); + } + + public bool HasInHandOrInGraveyard(int cardId) + { + return HasInHand(cardId) || HasInGraveyard(cardId); + } + + public bool HasInMonstersZoneOrInGraveyard(int cardId) + { + return HasInMonstersZone(cardId) || HasInGraveyard(cardId); + } + + public bool HasInSpellZoneOrInGraveyard(int cardId) + { + return HasInSpellZone(cardId) || HasInGraveyard(cardId); + } + + public bool HasInHandOrInMonstersZoneOrInGraveyard(int cardId) + { + return HasInHand(cardId) || HasInMonstersZone(cardId) || HasInGraveyard(cardId); + } + + public bool HasInHandOrInSpellZoneOrInGraveyard(int cardId) + { + return HasInHand(cardId) || HasInSpellZone(cardId) || HasInGraveyard(cardId); + } + + public bool HasInHandOrInSpellZone(IList cardId) + { + return HasInHand(cardId) || HasInSpellZone(cardId); + } + + public bool HasInHandOrHasInMonstersZone(IList cardId) + { + return HasInHand(cardId) || HasInMonstersZone(cardId); + } + + public bool HasInHandOrInGraveyard(IList cardId) + { + return HasInHand(cardId) || HasInGraveyard(cardId); + } + + public bool HasInMonstersZoneOrInGraveyard(IList cardId) + { + return HasInMonstersZone(cardId) || HasInGraveyard(cardId); + } + + public bool HasInSpellZoneOrInGraveyard(IList cardId) + { + return HasInSpellZone(cardId) || HasInGraveyard(cardId); + } + + public bool HasInHandOrInMonstersZoneOrInGraveyard(IList cardId) + { + return HasInHand(cardId) || HasInMonstersZone(cardId) || HasInGraveyard(cardId); + } + + public bool HasInHandOrInSpellZoneOrInGraveyard(IList cardId) + { + return HasInHand(cardId) || HasInSpellZone(cardId) || HasInGraveyard(cardId); + } + + public int GetRemainingCount(int cardId, int initialCount) + { + int remaining = initialCount; + remaining = remaining - Hand.Count(card => card != null && card.IsOriginalCode(cardId)); + remaining = remaining - SpellZone.Count(card => card != null && card.IsOriginalCode(cardId)); + remaining = remaining - MonsterZone.Count(card => card != null && card.IsOriginalCode(cardId)); + remaining = remaining - Graveyard.Count(card => card != null && card.IsOriginalCode(cardId)); + remaining = remaining - Banished.Count(card => card != null && card.IsOriginalCode(cardId)); + return (remaining < 0) ? 0 : remaining; + } + + private static int GetCount(IEnumerable cards) + { + return cards.Count(card => card != null); + } + + public int GetCountCardInZone(IEnumerable cards, int cardId) + { + return cards.Count(card => card != null && card.IsCode(cardId)); + } + + public int GetCountCardInZone(IEnumerable cards, List cardId) + { + return cards.Count(card => card != null && card.IsCode(cardId)); + } + + private static List GetCards(IEnumerable cards, CardType type) + { + return cards.Where(card => card != null && card.HasType(type)).ToList(); + } + + private static List GetCards(IEnumerable cards) + { + return cards.Where(card => card != null).ToList(); + } + + private static bool HasInCards(IEnumerable cards, int cardId, bool notDisabled = false, bool hasXyzMaterial = false, bool faceUp = false) + { + return cards.Any(card => card != null && card.IsCode(cardId) && !(notDisabled && card.IsDisabled()) && !(hasXyzMaterial && !card.HasXyzMaterial()) && !(faceUp && card.IsFacedown())); + } + + private static bool HasInCards(IEnumerable cards, IList cardId, bool notDisabled = false, bool hasXyzMaterial = false, bool faceUp = false) + { + return cards.Any(card => card != null && card.IsCode(cardId) && !(notDisabled && card.IsDisabled()) && !(hasXyzMaterial && !card.HasXyzMaterial()) && !(faceUp && card.IsFacedown())); + } + } } \ No newline at end of file diff --git a/Game/AI/Dialogs.cs b/ExecutorBase/Game/Dialogs.cs similarity index 86% rename from Game/AI/Dialogs.cs rename to ExecutorBase/Game/Dialogs.cs index d0f7fa30..1e04799d 100644 --- a/Game/AI/Dialogs.cs +++ b/ExecutorBase/Game/Dialogs.cs @@ -1,177 +1,177 @@ -using System; -using System.IO; -using System.Collections.Generic; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Json; - -namespace WindBot.Game.AI -{ - [DataContract] - public class DialogsData - { - [DataMember] - public string[] welcome { get; set; } - [DataMember] - public string[] deckerror { get; set; } - [DataMember] - public string[] duelstart { get; set; } - [DataMember] - public string[] newturn { get; set; } - [DataMember] - public string[] endturn { get; set; } - [DataMember] - public string[] directattack { get; set; } - [DataMember] - public string[] attack { get; set; } - [DataMember] - public string[] ondirectattack { get; set; } - [DataMember] - public string facedownmonstername { get; set; } - [DataMember] - public string[] activate { get; set; } - [DataMember] - public string[] summon { get; set; } - [DataMember] - public string[] setmonster { get; set; } - [DataMember] - public string[] chaining { get; set; } - } - public class Dialogs - { - private GameClient _game; - - private string[] _welcome; - private string[] _deckerror; - private string[] _duelstart; - private string[] _newturn; - private string[] _endturn; - private string[] _directattack; - private string[] _attack; - private string[] _ondirectattack; - private string _facedownmonstername; - private string[] _activate; - private string[] _summon; - private string[] _setmonster; - private string[] _chaining; - - public Dialogs(GameClient game) - { - _game = game; - DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(DialogsData)); - string dialogfilename = game.Dialog; - using (FileStream fs = File.OpenRead("Dialogs/" + dialogfilename + ".json")) - { - DialogsData data = (DialogsData)serializer.ReadObject(fs); - _welcome = data.welcome; - _deckerror = data.deckerror; - _duelstart = data.duelstart; - _newturn = data.newturn; - _endturn = data.endturn; - _directattack = data.directattack; - _attack = data.attack; - _ondirectattack = data.ondirectattack; - _facedownmonstername = data.facedownmonstername; - _activate = data.activate; - _summon = data.summon; - _setmonster = data.setmonster; - _chaining = data.chaining; - } - } - - public void SendSorry() - { - InternalSendMessageForced(new[] { "Sorry, an error occurs." }); - } - - public void SendDeckSorry(string card) - { - if (card == "DECK") - InternalSendMessageForced(new[] { "Deck illegal. Please check the database of your YGOPro and WindBot." }); - else - InternalSendMessageForced(_deckerror, card); - } - - public void SendWelcome() - { - InternalSendMessage(_welcome); - } - - public void SendDuelStart() - { - InternalSendMessage(_duelstart); - } - - public void SendNewTurn() - { - InternalSendMessage(_newturn); - } - - public void SendEndTurn() - { - InternalSendMessage(_endturn); - } - - public void SendDirectAttack(string attacker) - { - InternalSendMessage(_directattack, attacker); - } - - public void SendAttack(string attacker, string defender) - { - if (defender=="monster") - { - defender = _facedownmonstername; - } - InternalSendMessage(_attack, attacker, defender); - } - - public void SendOnDirectAttack(string attacker) - { - if (string.IsNullOrEmpty(attacker)) - { - attacker = _facedownmonstername; - } - InternalSendMessage(_ondirectattack, attacker); - } - public void SendOnDirectAttack() - { - InternalSendMessage(_ondirectattack); - } - - public void SendActivate(string spell) - { - InternalSendMessage(_activate, spell); - } - - public void SendSummon(string monster) - { - InternalSendMessage(_summon, monster); - } - - public void SendSetMonster() - { - InternalSendMessage(_setmonster); - } - - public void SendChaining(string card) - { - InternalSendMessage(_chaining, card); - } - - private void InternalSendMessage(IList array, params object[] opts) - { - if (!_game._chat) - return; - string message = string.Format(array[Program.Rand.Next(array.Count)], opts); - if (message != "") - _game.Chat(message); - } - - private void InternalSendMessageForced(IList array, params object[] opts) - { - string message = string.Format(array[Program.Rand.Next(array.Count)], opts); - if (message != "") - _game.Chat(message); - } - } -} +using System; +using System.IO; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; + +namespace WindBot.Game.AI +{ + [DataContract] + public class DialogsData + { + [DataMember] + public string[] welcome { get; set; } + [DataMember] + public string[] deckerror { get; set; } + [DataMember] + public string[] duelstart { get; set; } + [DataMember] + public string[] newturn { get; set; } + [DataMember] + public string[] endturn { get; set; } + [DataMember] + public string[] directattack { get; set; } + [DataMember] + public string[] attack { get; set; } + [DataMember] + public string[] ondirectattack { get; set; } + [DataMember] + public string facedownmonstername { get; set; } + [DataMember] + public string[] activate { get; set; } + [DataMember] + public string[] summon { get; set; } + [DataMember] + public string[] setmonster { get; set; } + [DataMember] + public string[] chaining { get; set; } + } + public class Dialogs + { + + private string[] _welcome; + private string[] _deckerror; + private string[] _duelstart; + private string[] _newturn; + private string[] _endturn; + private string[] _directattack; + private string[] _attack; + private string[] _ondirectattack; + private string _facedownmonstername; + private string[] _activate; + private string[] _summon; + private string[] _setmonster; + private string[] _chaining; + + private Action Chat; + + private static Random Rand = new Random(); + + public Dialogs(string dialogfilename, Action chat, string path) + { + Chat = chat; + DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(DialogsData)); + using (FileStream fs = File.OpenRead(Path.Combine(path, "Dialogs/", dialogfilename + ".json"))) + { + DialogsData data = (DialogsData)serializer.ReadObject(fs); + _welcome = data.welcome; + _deckerror = data.deckerror; + _duelstart = data.duelstart; + _newturn = data.newturn; + _endturn = data.endturn; + _directattack = data.directattack; + _attack = data.attack; + _ondirectattack = data.ondirectattack; + _facedownmonstername = data.facedownmonstername; + _activate = data.activate; + _summon = data.summon; + _setmonster = data.setmonster; + _chaining = data.chaining; + } + } + + public void SendSorry() + { + InternalSendMessageForced(new[] { "Sorry, an error occurs." }); + } + + public void SendDeckSorry(string card) + { + if (card == "DECK") + InternalSendMessageForced(new[] { "Deck illegal. Please check the database of your YGOPro and WindBot." }); + else + InternalSendMessageForced(_deckerror, card); + } + + public void SendWelcome() + { + InternalSendMessage(_welcome); + } + + public void SendDuelStart() + { + InternalSendMessage(_duelstart); + } + + public void SendNewTurn() + { + InternalSendMessage(_newturn); + } + + public void SendEndTurn() + { + InternalSendMessage(_endturn); + } + + public void SendDirectAttack(string attacker) + { + InternalSendMessage(_directattack, attacker); + } + + public void SendAttack(string attacker, string defender) + { + if (defender=="monster") + { + defender = _facedownmonstername; + } + InternalSendMessage(_attack, attacker, defender); + } + + public void SendOnDirectAttack(string attacker) + { + if (string.IsNullOrEmpty(attacker)) + { + attacker = _facedownmonstername; + } + InternalSendMessage(_ondirectattack, attacker); + } + public void SendOnDirectAttack() + { + InternalSendMessage(_ondirectattack); + } + + public void SendActivate(string spell) + { + InternalSendMessage(_activate, spell); + } + + public void SendSummon(string monster) + { + InternalSendMessage(_summon, monster); + } + + public void SendSetMonster() + { + InternalSendMessage(_setmonster); + } + + public void SendChaining(string card) + { + InternalSendMessage(_chaining, card); + } + + private void InternalSendMessage(IList array, params object[] opts) + { + string message = string.Format(array[Rand.Next(array.Count)], opts); + if (message != "") + Chat(message, false); + } + + private void InternalSendMessageForced(IList array, params object[] opts) + { + string message = string.Format(array[Rand.Next(array.Count)], opts); + if (message != "") + Chat(message, true); + } + } +} diff --git a/Game/Duel.cs b/ExecutorBase/Game/Duel.cs similarity index 94% rename from Game/Duel.cs rename to ExecutorBase/Game/Duel.cs index 115547bb..8d678146 100644 --- a/Game/Duel.cs +++ b/ExecutorBase/Game/Duel.cs @@ -1,190 +1,193 @@ -using System.Collections.Generic; -using YGOSharp.OCGWrapper.Enums; - -namespace WindBot.Game -{ - public class Duel - { - public bool IsFirst { get; set; } - public bool IsNewRule { get; set; } - public bool IsNewRule2020 { get; set; } - - public ClientField[] Fields { get; private set; } - - public int Turn { get; set; } - public int Player { get; set; } - public DuelPhase Phase { get; set; } - public MainPhase MainPhase { get; set; } - public BattlePhase BattlePhase { get; set; } - - public int LastChainPlayer { get; set; } - public IList CurrentChain { get; set; } - public IList ChainTargets { get; set; } - public IList ChainTargetOnly { get; set; } - public int LastSummonPlayer { get; set; } - public IList SummoningCards { get; set; } - public IList LastSummonedCards { get; set; } - - public Duel() - { - Fields = new ClientField[2]; - Fields[0] = new ClientField(); - Fields[1] = new ClientField(); - LastChainPlayer = -1; - CurrentChain = new List(); - ChainTargets = new List(); - ChainTargetOnly = new List(); - LastSummonPlayer = -1; - SummoningCards = new List(); - LastSummonedCards = new List(); - } - - public ClientCard GetCard(int player, CardLocation loc, int seq) - { - return GetCard(player, (int)loc, seq, 0); - } - - public ClientCard GetCard(int player, int loc, int seq, int subSeq) - { - if (player < 0 || player > 1) - return null; - - bool isXyz = (loc & 0x80) != 0; - CardLocation location = (CardLocation)(loc & 0x7f); - - IList cards = null; - switch (location) - { - case CardLocation.Deck: - cards = Fields[player].Deck; - break; - case CardLocation.Hand: - cards = Fields[player].Hand; - break; - case CardLocation.MonsterZone: - cards = Fields[player].MonsterZone; - break; - case CardLocation.SpellZone: - cards = Fields[player].SpellZone; - break; - case CardLocation.Grave: - cards = Fields[player].Graveyard; - break; - case CardLocation.Removed: - cards = Fields[player].Banished; - break; - case CardLocation.Extra: - cards = Fields[player].ExtraDeck; - break; - } - if (cards == null) - return null; - - if (seq >= cards.Count) - return null; - - if (isXyz) - { - ClientCard card = cards[seq]; - if (card == null || subSeq >= card.Overlays.Count) - return null; - return null; // TODO card.Overlays[subSeq] - } - - return cards[seq]; - } - - public void AddCard(CardLocation loc, int cardId, int player, int seq, int pos) - { - switch (loc) - { - case CardLocation.Hand: - Fields[player].Hand.Add(new ClientCard(cardId, loc, -1, pos)); - break; - case CardLocation.Grave: - Fields[player].Graveyard.Add(new ClientCard(cardId, loc,-1, pos)); - break; - case CardLocation.Removed: - Fields[player].Banished.Add(new ClientCard(cardId, loc, -1, pos)); - break; - case CardLocation.MonsterZone: - Fields[player].MonsterZone[seq] = new ClientCard(cardId, loc, seq, pos); - break; - case CardLocation.SpellZone: - Fields[player].SpellZone[seq] = new ClientCard(cardId, loc, seq, pos); - break; - case CardLocation.Deck: - Fields[player].Deck.Add(new ClientCard(cardId, loc, -1, pos)); - break; - case CardLocation.Extra: - Fields[player].ExtraDeck.Add(new ClientCard(cardId, loc, -1, pos)); - break; - } - } - - public void AddCard(CardLocation loc, ClientCard card, int player, int seq, int pos, int id) - { - card.Location = loc; - card.Sequence = seq; - card.Position = pos; - card.SetId(id); - switch (loc) - { - case CardLocation.Hand: - Fields[player].Hand.Add(card); - break; - case CardLocation.Grave: - Fields[player].Graveyard.Add(card); - break; - case CardLocation.Removed: - Fields[player].Banished.Add(card); - break; - case CardLocation.MonsterZone: - Fields[player].MonsterZone[seq] = card; - break; - case CardLocation.SpellZone: - Fields[player].SpellZone[seq] = card; - break; - case CardLocation.Deck: - Fields[player].Deck.Add(card); - break; - case CardLocation.Extra: - Fields[player].ExtraDeck.Add(card); - break; - } - } - - public void RemoveCard(CardLocation loc, ClientCard card, int player, int seq) - { - switch (loc) - { - case CardLocation.Hand: - Fields[player].Hand.Remove(card); - break; - case CardLocation.Grave: - Fields[player].Graveyard.Remove(card); - break; - case CardLocation.Removed: - Fields[player].Banished.Remove(card); - break; - case CardLocation.MonsterZone: - Fields[player].MonsterZone[seq] = null; - break; - case CardLocation.SpellZone: - Fields[player].SpellZone[seq] = null; - break; - case CardLocation.Deck: - Fields[player].Deck.Remove(card); - break; - case CardLocation.Extra: - Fields[player].ExtraDeck.Remove(card); - break; - } - } - - public int GetLocalPlayer(int player) - { - return IsFirst ? player : 1 - player; - } - } -} \ No newline at end of file +using System.Collections.Generic; +using YGOSharp.OCGWrapper.Enums; + +namespace WindBot.Game +{ + public class Duel + { + public bool IsFirst { get; set; } + public bool IsNewRule { get; set; } + public bool IsNewRule2020 { get; set; } + + public ClientField[] Fields { get; private set; } + + public int Turn { get; set; } + public int Player { get; set; } + public DuelPhase Phase { get; set; } + public MainPhase MainPhase { get; set; } + public BattlePhase BattlePhase { get; set; } + + public int LastChainPlayer { get; set; } + public IList CurrentChain { get; set; } + public IList ChainTargets { get; set; } + public IList ChainTargetOnly { get; set; } + public int LastSummonPlayer { get; set; } + public IList SummoningCards { get; set; } + public IList LastSummonedCards { get; set; } + public bool MainPhaseEnd { get; set; } + + public Duel() + { + Fields = new ClientField[2]; + Fields[0] = new ClientField(); + Fields[1] = new ClientField(); + LastChainPlayer = -1; + MainPhaseEnd = false; + CurrentChain = new List(); + ChainTargets = new List(); + ChainTargetOnly = new List(); + LastSummonPlayer = -1; + SummoningCards = new List(); + LastSummonedCards = new List(); + } + + public ClientCard GetCard(int player, CardLocation loc, int seq) + { + return GetCard(player, (int)loc, seq, 0); + } + + public ClientCard GetCard(int player, int loc, int seq, int subSeq) + { + if (player < 0 || player > 1) + return null; + + bool isXyz = (loc & 0x80) != 0; + CardLocation location = (CardLocation)(loc & 0x7f); + + IList cards = null; + switch (location) + { + case CardLocation.Deck: + cards = Fields[player].Deck; + break; + case CardLocation.Hand: + cards = Fields[player].Hand; + break; + case CardLocation.MonsterZone: + cards = Fields[player].MonsterZone; + break; + case CardLocation.SpellZone: + cards = Fields[player].SpellZone; + break; + case CardLocation.Grave: + cards = Fields[player].Graveyard; + break; + case CardLocation.Removed: + cards = Fields[player].Banished; + break; + case CardLocation.Extra: + cards = Fields[player].ExtraDeck; + break; + } + if (cards == null) + return null; + + if (seq >= cards.Count) + return null; + + if (isXyz) + { + ClientCard card = cards[seq]; + if (card == null || subSeq >= card.Overlays.Count) + return null; + return null; // TODO card.Overlays[subSeq] + } + + return cards[seq]; + } + + public void AddCard(CardLocation loc, int cardId, int player, int seq, int pos) + { + switch (loc) + { + case CardLocation.Hand: + Fields[player].Hand.Add(new ClientCard(cardId, loc, -1, pos, player)); + break; + case CardLocation.Grave: + Fields[player].Graveyard.Add(new ClientCard(cardId, loc,-1, pos, player)); + break; + case CardLocation.Removed: + Fields[player].Banished.Add(new ClientCard(cardId, loc, -1, pos, player)); + break; + case CardLocation.MonsterZone: + Fields[player].MonsterZone[seq] = new ClientCard(cardId, loc, seq, pos, player); + break; + case CardLocation.SpellZone: + Fields[player].SpellZone[seq] = new ClientCard(cardId, loc, seq, pos, player); + break; + case CardLocation.Deck: + Fields[player].Deck.Add(new ClientCard(cardId, loc, -1, pos, player)); + break; + case CardLocation.Extra: + Fields[player].ExtraDeck.Add(new ClientCard(cardId, loc, -1, pos, player)); + break; + } + } + + public void AddCard(CardLocation loc, ClientCard card, int player, int seq, int pos, int id) + { + card.Location = loc; + card.Sequence = seq; + card.Position = pos; + card.Controller = player; + card.SetId(id); + switch (loc) + { + case CardLocation.Hand: + Fields[player].Hand.Add(card); + break; + case CardLocation.Grave: + Fields[player].Graveyard.Add(card); + break; + case CardLocation.Removed: + Fields[player].Banished.Add(card); + break; + case CardLocation.MonsterZone: + Fields[player].MonsterZone[seq] = card; + break; + case CardLocation.SpellZone: + Fields[player].SpellZone[seq] = card; + break; + case CardLocation.Deck: + Fields[player].Deck.Add(card); + break; + case CardLocation.Extra: + Fields[player].ExtraDeck.Add(card); + break; + } + } + + public void RemoveCard(CardLocation loc, ClientCard card, int player, int seq) + { + switch (loc) + { + case CardLocation.Hand: + Fields[player].Hand.Remove(card); + break; + case CardLocation.Grave: + Fields[player].Graveyard.Remove(card); + break; + case CardLocation.Removed: + Fields[player].Banished.Remove(card); + break; + case CardLocation.MonsterZone: + Fields[player].MonsterZone[seq] = null; + break; + case CardLocation.SpellZone: + Fields[player].SpellZone[seq] = null; + break; + case CardLocation.Deck: + Fields[player].Deck.Remove(card); + break; + case CardLocation.Extra: + Fields[player].ExtraDeck.Remove(card); + break; + } + } + + public int GetLocalPlayer(int player) + { + return IsFirst ? player : 1 - player; + } + } +} diff --git a/Game/GameAI.cs b/ExecutorBase/Game/GameAI.cs similarity index 91% rename from Game/GameAI.cs rename to ExecutorBase/Game/GameAI.cs index 1249e3d9..9547139d 100644 --- a/Game/GameAI.cs +++ b/ExecutorBase/Game/GameAI.cs @@ -1,1105 +1,1118 @@ -using System.Linq; -using System.Collections.Generic; -using WindBot.Game.AI; -using YGOSharp.OCGWrapper.Enums; - -namespace WindBot.Game -{ - public class GameAI - { - public GameClient Game { get; private set; } - public Duel Duel { get; private set; } - public Executor Executor { get; set; } - - private Dialogs _dialogs; - - public GameAI(GameClient game, Duel duel) - { - Game = game; - Duel = duel; - - _dialogs = new Dialogs(game); - } - - /// - /// Called when the AI got the error message. - /// - public void OnRetry() - { - _dialogs.SendSorry(); - } - - public void OnDeckError(string card) - { - _dialogs.SendDeckSorry(card); - } - - /// - /// Called when the AI join the game. - /// - public void OnJoinGame() - { - _dialogs.SendWelcome(); - } - - /// - /// Called when the duel starts. - /// - public void OnStart() - { - _dialogs.SendDuelStart(); - } - - /// - /// Called when the AI do the rock-paper-scissors. - /// - /// 1 for Scissors, 2 for Rock, 3 for Paper. - public int OnRockPaperScissors() - { - return Executor.OnRockPaperScissors(); - } - - /// - /// Called when the AI won the rock-paper-scissors. - /// - /// True if the AI should begin first, false otherwise. - public bool OnSelectHand() - { - return Executor.OnSelectHand(); - } - - /// - /// Called when any player draw card. - /// - public void OnDraw(int player) - { - Executor.OnDraw(player); - } - - /// - /// Called when it's a new turn. - /// - public void OnNewTurn() - { - Executor.OnNewTurn(); - } - - /// - /// Called when it's a new phase. - /// - public void OnNewPhase() - { - m_selector.Clear(); - m_position.Clear(); - m_selector_pointer = -1; - m_materialSelector = null; - m_option = -1; - m_yesno = -1; - - m_place = 0; - if (Duel.Player == 0 && Duel.Phase == DuelPhase.Draw) - { - _dialogs.SendNewTurn(); - } - Executor.OnNewPhase(); - } - - /// - /// Called when the AI got attack directly. - /// - public void OnDirectAttack(ClientCard card) - { - _dialogs.SendOnDirectAttack(card.Name); - } - - /// - /// Called when a chain is executed. - /// - /// Card who is chained. - /// Player who is currently chaining. - public void OnChaining(ClientCard card, int player) - { - Executor.OnChaining(player,card); - } - - /// - /// Called when a chain has been solved. - /// - public void OnChainEnd() - { - m_selector.Clear(); - m_selector_pointer = -1; - Executor.OnChainEnd(); - } - - /// - /// Called when the AI has to do something during the battle phase. - /// - /// Informations about usable cards. - /// A new BattlePhaseAction containing the action to do. - public BattlePhaseAction OnSelectBattleCmd(BattlePhase battle) - { - Executor.SetBattle(battle); - foreach (CardExecutor exec in Executor.Executors) - { - if (exec.Type == ExecutorType.GoToMainPhase2 && battle.CanMainPhaseTwo && exec.Func()) // check if should enter main phase 2 directly - { - return ToMainPhase2(); - } - if (exec.Type == ExecutorType.GoToEndPhase && battle.CanEndPhase && exec.Func()) // check if should enter end phase directly - { - return ToEndPhase(); - } - for (int i = 0; i < battle.ActivableCards.Count; ++i) - { - ClientCard card = battle.ActivableCards[i]; - if (ShouldExecute(exec, card, ExecutorType.Activate, battle.ActivableDescs[i])) - { - _dialogs.SendChaining(card.Name); - return new BattlePhaseAction(BattlePhaseAction.BattleAction.Activate, card.ActionIndex); - } - } - } - - // Sort the attackers and defenders, make monster with higher attack go first. - List attackers = new List(battle.AttackableCards); - attackers.Sort(CardContainer.CompareCardAttack); - attackers.Reverse(); - - List defenders = new List(Duel.Fields[1].GetMonsters()); - defenders.Sort(CardContainer.CompareDefensePower); - defenders.Reverse(); - - // Let executor decide which card should attack first. - ClientCard selected = Executor.OnSelectAttacker(attackers, defenders); - if (selected != null && attackers.Contains(selected)) - { - attackers.Remove(selected); - attackers.Insert(0, selected); - } - - // Check for the executor. - BattlePhaseAction result = Executor.OnBattle(attackers, defenders); - if (result != null) - return result; - - if (attackers.Count == 0) - return ToMainPhase2(); - - if (defenders.Count == 0) - { - // Attack with the monster with the lowest attack first - for (int i = attackers.Count - 1; i >= 0; --i) - { - ClientCard attacker = attackers[i]; - if (attacker.Attack > 0) - return Attack(attacker, null); - } - } - else - { - for (int k = 0; k < attackers.Count; ++k) - { - ClientCard attacker = attackers[k]; - attacker.IsLastAttacker = (k == attackers.Count - 1); - result = Executor.OnSelectAttackTarget(attacker, defenders); - if (result != null) - return result; - } - } - - if (!battle.CanMainPhaseTwo) - return Attack(attackers[0], (defenders.Count == 0) ? null : defenders[0]); - - return ToMainPhase2(); - } - - /// - /// Called when the AI has to select one or more cards. - /// - /// List of available cards. - /// Minimal quantity. - /// Maximal quantity. - /// The hint message of the select. - /// True if you can return an empty list. - /// A new list containing the selected cards. - public IList OnSelectCard(IList cards, int min, int max, int hint, bool cancelable) - { - const int HINTMSG_FMATERIAL = 511; - const int HINTMSG_SMATERIAL = 512; - const int HINTMSG_XMATERIAL = 513; - const int HINTMSG_LMATERIAL = 533; - const int HINTMSG_SPSUMMON = 509; - - // Check for the executor. - IList result = Executor.OnSelectCard(cards, min, max, hint, cancelable); - if (result != null) - return result; - - if (hint == HINTMSG_SPSUMMON && min == 1 && max > min) // pendulum summon - { - result = Executor.OnSelectPendulumSummon(cards, max); - if (result != null) - return result; - } - - CardSelector selector = null; - if (hint == HINTMSG_FMATERIAL || hint == HINTMSG_SMATERIAL || hint == HINTMSG_XMATERIAL || hint == HINTMSG_LMATERIAL) - { - if (m_materialSelector != null) - { - //Logger.DebugWriteLine("m_materialSelector"); - selector = m_materialSelector; - } - else - { - if (hint == HINTMSG_FMATERIAL) - result = Executor.OnSelectFusionMaterial(cards, min, max); - if (hint == HINTMSG_SMATERIAL) - result = Executor.OnSelectSynchroMaterial(cards, 0, min, max); - if (hint == HINTMSG_XMATERIAL) - result = Executor.OnSelectXyzMaterial(cards, min, max); - if (hint == HINTMSG_LMATERIAL) - result = Executor.OnSelectLinkMaterial(cards, min, max); - - if (result != null) - return result; - - // Update the next selector. - selector = GetSelectedCards(); - } - } - else - { - // Update the next selector. - selector = GetSelectedCards(); - } - - // If we selected a card, use this card. - if (selector != null) - return selector.Select(cards, min, max); - - // Always select the first available cards and choose the minimum. - IList selected = new List(); - - if (cards.Count >= min) - { - for (int i = 0; i < min; ++i) - selected.Add(cards[i]); - } - return selected; - } - - /// - /// Called when the AI can chain (activate) a card. - /// - /// List of activable cards. - /// List of effect descriptions. - /// You can't return -1 if this param is true. - /// Index of the activated card or -1. - public int OnSelectChain(IList cards, IList descs, bool forced) - { - foreach (CardExecutor exec in Executor.Executors) - { - for (int i = 0; i < cards.Count; ++i) - { - ClientCard card = cards[i]; - if (ShouldExecute(exec, card, ExecutorType.Activate, descs[i])) - { - _dialogs.SendChaining(card.Name); - return i; - } - } - } - // If we're forced to chain, we chain the first card. However don't do anything. - return forced ? 0 : -1; - } - - /// - /// Called when the AI has to use one or more counters. - /// - /// Type of counter to use. - /// Quantity of counter to select. - /// List of available cards. - /// List of available counters. - /// List of used counters. - public IList OnSelectCounter(int type, int quantity, IList cards, IList counters) - { - // Always select the first available counters. - int[] used = new int[counters.Count]; - int i = 0; - while (quantity > 0) - { - if (counters[i] >= quantity) - { - used[i] = quantity; - quantity = 0; - } - else - { - used[i] = counters[i]; - quantity -= counters[i]; - } - i++; - } - return used; - } - - /// - /// Called when the AI has to sort cards. - /// - /// Cards to sort. - /// List of sorted cards. - public IList OnCardSorting(IList cards) - { - - IList result = Executor.OnCardSorting(cards); - if (result != null) - return result; - result = new List(); - // TODO: use selector - result = cards.ToList(); - return result; - } - - /// - /// Called when the AI has to choose to activate or not an effect. - /// - /// Card to activate. - /// True for yes, false for no. - public bool OnSelectEffectYn(ClientCard card, long desc) - { - foreach (CardExecutor exec in Executor.Executors) - { - if (ShouldExecute(exec, card, ExecutorType.Activate, desc)) - return true; - } - return false; - } - - /// - /// Called when the AI has to do something during the main phase. - /// - /// A lot of informations about the available actions. - /// A new MainPhaseAction containing the action to do. - public MainPhaseAction OnSelectIdleCmd(MainPhase main) - { - Executor.SetMain(main); - foreach (CardExecutor exec in Executor.Executors) - { - if (exec.Type == ExecutorType.GoToEndPhase && main.CanEndPhase && exec.Func()) // check if should enter end phase directly - { - _dialogs.SendEndTurn(); - return new MainPhaseAction(MainPhaseAction.MainAction.ToEndPhase); - } - if (exec.Type==ExecutorType.GoToBattlePhase && main.CanBattlePhase && exec.Func()) // check if should enter battle phase directly - { - return new MainPhaseAction(MainPhaseAction.MainAction.ToBattlePhase); - } - // NOTICE: GoToBattlePhase and GoToEndPhase has no "card" can be accessed to ShouldExecute(), so instead use exec.Func() to check ... - // enter end phase and enter battle pahse is in higher priority. - - for (int i = 0; i < main.ActivableCards.Count; ++i) - { - ClientCard card = main.ActivableCards[i]; - if (ShouldExecute(exec, card, ExecutorType.Activate, main.ActivableDescs[i])) - { - _dialogs.SendActivate(card.Name); - return new MainPhaseAction(MainPhaseAction.MainAction.Activate, card.ActionActivateIndex[main.ActivableDescs[i]]); - } - } - foreach (ClientCard card in main.MonsterSetableCards) - { - if (ShouldExecute(exec, card, ExecutorType.MonsterSet)) - { - _dialogs.SendSetMonster(); - return new MainPhaseAction(MainPhaseAction.MainAction.SetMonster, card.ActionIndex); - } - } - foreach (ClientCard card in main.ReposableCards) - { - if (ShouldExecute(exec, card, ExecutorType.Repos)) - return new MainPhaseAction(MainPhaseAction.MainAction.Repos, card.ActionIndex); - } - foreach (ClientCard card in main.SpecialSummonableCards) - { - if (ShouldExecute(exec, card, ExecutorType.SpSummon)) - { - _dialogs.SendSummon(card.Name); - return new MainPhaseAction(MainPhaseAction.MainAction.SpSummon, card.ActionIndex); - } - } - foreach (ClientCard card in main.SummonableCards) - { - if (ShouldExecute(exec, card, ExecutorType.Summon)) - { - _dialogs.SendSummon(card.Name); - return new MainPhaseAction(MainPhaseAction.MainAction.Summon, card.ActionIndex); - } - if (ShouldExecute(exec, card, ExecutorType.SummonOrSet)) - { - if (Executor.Util.IsAllEnemyBetter(true) && Executor.Util.IsAllEnemyBetterThanValue(card.Attack + 300, false) && - main.MonsterSetableCards.Contains(card)) - { - _dialogs.SendSetMonster(); - return new MainPhaseAction(MainPhaseAction.MainAction.SetMonster, card.ActionIndex); - } - _dialogs.SendSummon(card.Name); - return new MainPhaseAction(MainPhaseAction.MainAction.Summon, card.ActionIndex); - } - } - foreach (ClientCard card in main.SpellSetableCards) - { - if (ShouldExecute(exec, card, ExecutorType.SpellSet)) - return new MainPhaseAction(MainPhaseAction.MainAction.SetSpell, card.ActionIndex); - } - } - - if (main.CanBattlePhase && Duel.Fields[0].HasAttackingMonster()) - return new MainPhaseAction(MainPhaseAction.MainAction.ToBattlePhase); - - _dialogs.SendEndTurn(); - return new MainPhaseAction(MainPhaseAction.MainAction.ToEndPhase); - } - - /// - /// Called when the AI has to select an option. - /// - /// List of available options. - /// Index of the selected option. - public int OnSelectOption(IList options) - { - if (m_option != -1 && m_option < options.Count) - return m_option; - - int result = Executor.OnSelectOption(options); - if (result != -1) - return result; - - return 0; // Always select the first option. - } - - public int OnSelectPlace(int cardId, int player, CardLocation location, int available) - { - int selector_selected = m_place; - m_place = 0; - - int executor_selected = Executor.OnSelectPlace(cardId, player, location, available); - - if ((executor_selected & available) > 0) - return executor_selected & available; - if ((selector_selected & available) > 0) - return selector_selected & available; - - // TODO: LinkedZones - - return 0; - } - - /// - /// Called when the AI has to select a card position. - /// - /// Id of the card to position on the field. - /// List of available positions. - /// Selected position. - public CardPosition OnSelectPosition(int cardId, IList positions) - { - CardPosition selector_selected = GetSelectedPosition(); - - CardPosition executor_selected = Executor.OnSelectPosition(cardId, positions); - - // Selects the selected position if available, the first available otherwise. - if (positions.Contains(executor_selected)) - return executor_selected; - if (positions.Contains(selector_selected)) - return selector_selected; - - return positions[0]; - } - - /// - /// Called when the AI has to tribute for a synchro monster or ritual monster. - /// - /// Available cards. - /// Result of the operation. - /// Minimum cards. - /// Maximum cards. - /// True for exact equal. - /// - public IList OnSelectSum(IList cards, int sum, int min, int max, int hint, bool mode) - { - const int HINTMSG_RELEASE = 500; - const int HINTMSG_SMATERIAL = 512; - - IList selected = Executor.OnSelectSum(cards, sum, min, max, hint, mode); - if (selected != null) - { - return selected; - } - - if (hint == HINTMSG_RELEASE || hint == HINTMSG_SMATERIAL) - { - if (m_materialSelector != null) - { - selected = m_materialSelector.Select(cards, min, max); - } - else - { - switch (hint) - { - case HINTMSG_SMATERIAL: - selected = Executor.OnSelectSynchroMaterial(cards, sum, min, max); - break; - case HINTMSG_RELEASE: - selected = Executor.OnSelectRitualTribute(cards, sum, min, max); - break; - } - } - if (selected != null) - { - int s1 = 0, s2 = 0; - foreach (ClientCard card in selected) - { - s1 += card.OpParam1; - s2 += (card.OpParam2 != 0) ? card.OpParam2 : card.OpParam1; - } - if ((mode && (s1 == sum || s2 == sum)) || (!mode && (s1 >= sum || s2 >= sum))) - { - return selected; - } - } - } - - if (mode) - { - // equal - - if (sum == 0 && min == 0) - { - return new List(); - } - - if (min <= 1) - { - // try special level first - foreach (ClientCard card in cards) - { - if (card.OpParam2 == sum) - { - return new[] { card }; - } - } - // try level equal - foreach (ClientCard card in cards) - { - if (card.OpParam1 == sum) - { - return new[] { card }; - } - } - } - - // try all - int s1 = 0, s2 = 0; - foreach (ClientCard card in cards) - { - s1 += card.OpParam1; - s2 += (card.OpParam2 != 0) ? card.OpParam2 : card.OpParam1; - } - if (s1 == sum || s2 == sum) - { - return cards; - } - - // try all combinations - int i = (min <= 1) ? 2 : min; - while (i <= max && i <= cards.Count) - { - IEnumerable> combos = CardContainer.GetCombinations(cards, i); - - foreach (IEnumerable combo in combos) - { - Logger.DebugWriteLine("--"); - s1 = 0; - s2 = 0; - foreach (ClientCard card in combo) - { - s1 += card.OpParam1; - s2 += (card.OpParam2 != 0) ? card.OpParam2 : card.OpParam1; - } - if (s1 == sum || s2 == sum) - { - return combo.ToList(); - } - } - i++; - } - } - else - { - // larger - if (min <= 1) - { - // try special level first - foreach (ClientCard card in cards) - { - if (card.OpParam2 >= sum) - { - return new[] { card }; - } - } - // try level equal - foreach (ClientCard card in cards) - { - if (card.OpParam1 >= sum) - { - return new[] { card }; - } - } - } - - // try all combinations - int i = (min <= 1) ? 2 : min; - while (i <= max && i <= cards.Count) - { - IEnumerable> combos = CardContainer.GetCombinations(cards, i); - - foreach (IEnumerable combo in combos) - { - Logger.DebugWriteLine("----"); - int s1 = 0, s2 = 0; - foreach (ClientCard card in combo) - { - s1 += card.OpParam1; - s2 += (card.OpParam2 != 0) ? card.OpParam2 : card.OpParam1; - } - if (s1 >= sum || s2 >= sum) - { - return combo.ToList(); - } - } - i++; - } - } - - Logger.WriteErrorLine("Fail to select sum."); - return new List(); - } - - /// - /// Called when the AI has to tribute one or more cards. - /// - /// List of available cards. - /// Minimal quantity. - /// Maximal quantity. - /// The hint message of the select. - /// True if you can return an empty list. - /// A new list containing the tributed cards. - public IList OnSelectTribute(IList cards, int min, int max, int hint, bool cancelable) - { - // Always choose the minimum and lowest atk. - List sorted = new List(); - sorted.AddRange(cards); - sorted.Sort(CardContainer.CompareCardAttack); - - IList selected = new List(); - - for (int i = 0; i < min && i < sorted.Count; ++i) - selected.Add(sorted[i]); - - return selected; - } - - /// - /// Called when the AI has to select yes or no. - /// - /// Id of the question. - /// True for yes, false for no. - public bool OnSelectYesNo(long desc) - { - if (m_yesno != -1) - return m_yesno > 0; - return Executor.OnSelectYesNo(desc); - } - - /// - /// Called when the AI has to select if to continue attacking when replay. - /// - /// True for yes, false for no. - public bool OnSelectBattleReplay() - { - return Executor.OnSelectBattleReplay(); - } - - /// - /// Called when the AI has to declare a card. - /// - /// Id of the selected card. - public int OnAnnounceCard() - { - if (m_announce == 0) - return 89631139; // Blue-eyes white dragon - return m_announce; - } - - // _ Others functions _ - // Those functions are used by the AI behavior. - - - private CardSelector m_materialSelector; - private int m_place; - private int m_option; - private int m_number; - private int m_announce; - private int m_yesno; - private IList m_attributes = new List(); - private IList m_selector = new List(); - private IList m_position = new List(); - private int m_selector_pointer = -1; - private IList m_races = new List(); - - public void SelectCard(ClientCard card) - { - m_selector_pointer = m_selector.Count(); - m_selector.Add(new CardSelector(card)); - } - - public void SelectCard(IList cards) - { - m_selector_pointer = m_selector.Count(); - m_selector.Add(new CardSelector(cards)); - } - - public void SelectCard(int cardId) - { - m_selector_pointer = m_selector.Count(); - m_selector.Add(new CardSelector(cardId)); - } - - public void SelectCard(IList ids) - { - m_selector_pointer = m_selector.Count(); - m_selector.Add(new CardSelector(ids)); - } - - public void SelectCard(params int[] ids) - { - m_selector_pointer = m_selector.Count(); - m_selector.Add(new CardSelector(ids)); - } - - public void SelectCard(CardLocation loc) - { - m_selector_pointer = m_selector.Count(); - m_selector.Add(new CardSelector(loc)); - } - - public void SelectNextCard(ClientCard card) - { - if (m_selector_pointer == -1) - { - Logger.WriteErrorLine("Error: Call SelectNextCard() before SelectCard()"); - m_selector_pointer = 0; - } - m_selector.Insert(m_selector_pointer, new CardSelector(card)); - } - - public void SelectNextCard(IList cards) - { - if (m_selector_pointer == -1) - { - Logger.WriteErrorLine("Error: Call SelectNextCard() before SelectCard()"); - m_selector_pointer = 0; - } - m_selector.Insert(m_selector_pointer, new CardSelector(cards)); - } - - public void SelectNextCard(int cardId) - { - if (m_selector_pointer == -1) - { - Logger.WriteErrorLine("Error: Call SelectNextCard() before SelectCard()"); - m_selector_pointer = 0; - } - m_selector.Insert(m_selector_pointer, new CardSelector(cardId)); - } - - public void SelectNextCard(IList ids) - { - if (m_selector_pointer == -1) - { - Logger.WriteErrorLine("Error: Call SelectNextCard() before SelectCard()"); - m_selector_pointer = 0; - } - m_selector.Insert(m_selector_pointer, new CardSelector(ids)); - } - - public void SelectNextCard(params int[] ids) - { - if (m_selector_pointer == -1) - { - Logger.WriteErrorLine("Error: Call SelectNextCard() before SelectCard()"); - m_selector_pointer = 0; - } - m_selector.Insert(m_selector_pointer, new CardSelector(ids)); - } - - public void SelectNextCard(CardLocation loc) - { - if (m_selector_pointer == -1) - { - Logger.WriteErrorLine("Error: Call SelectNextCard() before SelectCard()"); - m_selector_pointer = 0; - } - m_selector.Insert(m_selector_pointer, new CardSelector(loc)); - } - - public void SelectThirdCard(ClientCard card) - { - if (m_selector_pointer == -1) - { - Logger.WriteErrorLine("Error: Call SelectThirdCard() before SelectCard()"); - m_selector_pointer = 0; - } - m_selector.Insert(m_selector_pointer, new CardSelector(card)); - } - - public void SelectThirdCard(IList cards) - { - if (m_selector_pointer == -1) - { - Logger.WriteErrorLine("Error: Call SelectThirdCard() before SelectCard()"); - m_selector_pointer = 0; - } - m_selector.Insert(m_selector_pointer, new CardSelector(cards)); - } - - public void SelectThirdCard(int cardId) - { - if (m_selector_pointer == -1) - { - Logger.WriteErrorLine("Error: Call SelectThirdCard() before SelectCard()"); - m_selector_pointer = 0; - } - m_selector.Insert(m_selector_pointer, new CardSelector(cardId)); - } - - public void SelectThirdCard(IList ids) - { - if (m_selector_pointer == -1) - { - Logger.WriteErrorLine("Error: Call SelectThirdCard() before SelectCard()"); - m_selector_pointer = 0; - } - m_selector.Insert(m_selector_pointer, new CardSelector(ids)); - } - - public void SelectThirdCard(params int[] ids) - { - if (m_selector_pointer == -1) - { - Logger.WriteErrorLine("Error: Call SelectThirdCard() before SelectCard()"); - m_selector_pointer = 0; - } - m_selector.Insert(m_selector_pointer, new CardSelector(ids)); - } - - public void SelectThirdCard(CardLocation loc) - { - if (m_selector_pointer == -1) - { - Logger.WriteErrorLine("Error: Call SelectThirdCard() before SelectCard()"); - m_selector_pointer = 0; - } - m_selector.Insert(m_selector_pointer, new CardSelector(loc)); - } - - public void SelectMaterials(ClientCard card) - { - m_materialSelector = new CardSelector(card); - } - - public void SelectMaterials(IList cards) - { - m_materialSelector = new CardSelector(cards); - } - - public void SelectMaterials(int cardId) - { - m_materialSelector = new CardSelector(cardId); - } - - public void SelectMaterials(IList ids) - { - m_materialSelector = new CardSelector(ids); - } - - public void SelectMaterials(CardLocation loc) - { - m_materialSelector = new CardSelector(loc); - } - - public void CleanSelectMaterials() - { - m_materialSelector = null; - } - - public CardSelector GetSelectedCards() - { - CardSelector selected = null; - if (m_selector.Count > 0) - { - selected = m_selector[m_selector.Count - 1]; - m_selector.RemoveAt(m_selector.Count - 1); - } - return selected; - } - - public CardPosition GetSelectedPosition() - { - CardPosition selected = CardPosition.FaceUpAttack; - if (m_position.Count > 0) - { - selected = m_position[0]; - m_position.RemoveAt(0); - } - return selected; - } - - public void SelectPosition(CardPosition pos) - { - m_position.Add(pos); - } - - public void SelectPlace(int zones) - { - m_place = zones; - } - - public void SelectOption(int opt) - { - m_option = opt; - } - - public void SelectNumber(int number) - { - m_number = number; - } - - public void SelectAttribute(CardAttribute attribute) - { - m_attributes.Clear(); - m_attributes.Add(attribute); - } - - public void SelectAttributes(CardAttribute[] attributes) - { - m_attributes.Clear(); - foreach (CardAttribute attribute in attributes) - m_attributes.Add(attribute); - } - - public void SelectRace(CardRace race) - { - m_races.Clear(); - m_races.Add(race); - } - - public void SelectRaces(CardRace[] races) - { - m_races.Clear(); - foreach (CardRace race in races) - m_races.Add(race); - } - - public void SelectAnnounceID(int id) - { - m_announce = id; - } - - public void SelectYesNo(bool opt) - { - m_yesno = opt ? 1 : 0; - } - - /// - /// Called when the AI has to declare a number. - /// - /// List of available numbers. - /// Index of the selected number. - public int OnAnnounceNumber(IList numbers) - { - if (numbers.Contains(m_number)) - return numbers.IndexOf(m_number); - - return Program.Rand.Next(0, numbers.Count); // Returns a random number. - } - - /// - /// Called when the AI has to declare one or more attributes. - /// - /// Quantity of attributes to declare. - /// List of available attributes. - /// A list of the selected attributes. - public virtual IList OnAnnounceAttrib(int count, IList attributes) - { - IList foundAttributes = m_attributes.Where(attributes.Contains).ToList(); - if (foundAttributes.Count > 0) - return foundAttributes; - - return attributes; // Returns the first available Attribute. - } - - /// - /// Called when the AI has to declare one or more races. - /// - /// Quantity of races to declare. - /// List of available races. - /// A list of the selected races. - public virtual IList OnAnnounceRace(int count, IList races) - { - IList foundRaces = m_races.Where(races.Contains).ToList(); - if (foundRaces.Count > 0) - return foundRaces; - - return races; // Returns the first available Races. - } - - public BattlePhaseAction Attack(ClientCard attacker, ClientCard defender) - { - Executor.SetCard(0, attacker, -1); - if (defender != null) - { - string cardName = defender.Name ?? "monster"; - attacker.ShouldDirectAttack = false; - _dialogs.SendAttack(attacker.Name, cardName); - SelectCard(defender); - } - else - { - attacker.ShouldDirectAttack = true; - _dialogs.SendDirectAttack(attacker.Name); - } - return new BattlePhaseAction(BattlePhaseAction.BattleAction.Attack, attacker.ActionIndex); - } - - public BattlePhaseAction ToEndPhase() - { - _dialogs.SendEndTurn(); - return new BattlePhaseAction(BattlePhaseAction.BattleAction.ToEndPhase); - } - public BattlePhaseAction ToMainPhase2() - { - return new BattlePhaseAction(BattlePhaseAction.BattleAction.ToMainPhaseTwo); - } - - private bool ShouldExecute(CardExecutor exec, ClientCard card, ExecutorType type, long desc = -1) - { - Executor.SetCard(type, card, desc); - return card != null && - exec.Type == type && - (exec.CardId == -1 || exec.CardId == card.Id) && - (exec.Func == null || exec.Func()); - } - } -} +using System.Linq; +using System.Collections.Generic; +using WindBot.Game.AI; +using YGOSharp.OCGWrapper.Enums; + +namespace WindBot.Game +{ + public enum LogLevel : int + { + Info, + Debug, + Error + } + public class GameAI + { + public Duel Duel { get; private set; } + public Executor Executor { get; set; } + + public Dialogs _dialogs; + private System.Action _log; + + public void Log(LogLevel level, string message) + { + _log(message, (int)level); + } + + public GameAI(Duel duel, string dialog, System.Action chat, System.Action log, string path) + { + Duel = duel; + _log = log; + _dialogs = new Dialogs(dialog, chat, path); + } + + /// + /// Called when the AI got the error message. + /// + public void OnRetry() + { + _dialogs.SendSorry(); + } + + public void OnDeckError(string card) + { + _dialogs.SendDeckSorry(card); + } + + /// + /// Called when the AI join the game. + /// + public void OnJoinGame() + { + _dialogs.SendWelcome(); + } + + /// + /// Called when the duel starts. + /// + public void OnStart() + { + _dialogs.SendDuelStart(); + } + + /// + /// Called when the AI do the rock-paper-scissors. + /// + /// 1 for Scissors, 2 for Rock, 3 for Paper. + public int OnRockPaperScissors() + { + return Executor.OnRockPaperScissors(); + } + + /// + /// Called when the AI won the rock-paper-scissors. + /// + /// True if the AI should begin first, false otherwise. + public bool OnSelectHand() + { + return Executor.OnSelectHand(); + } + + /// + /// Called when any player draw card. + /// + public void OnDraw(int player) + { + Executor.OnDraw(player); + } + + /// + /// Called when it's a new turn. + /// + public void OnNewTurn() + { + Executor.OnNewTurn(); + } + + /// + /// Called when it's a new phase. + /// + public void OnNewPhase() + { + m_selector.Clear(); + m_position.Clear(); + m_selector_pointer = -1; + m_materialSelector = null; + m_option = -1; + m_yesno = -1; + + m_place = 0; + if (Duel.Player == 0 && Duel.Phase == DuelPhase.Draw) + { + _dialogs.SendNewTurn(); + } + Executor.OnNewPhase(); + } + + /// + /// Called when the AI got attack directly. + /// + public void OnDirectAttack(ClientCard card) + { + _dialogs.SendOnDirectAttack(card.Name); + } + + /// + /// Called when a chain is executed. + /// + /// Card who is chained. + /// Player who is currently chaining. + public void OnChaining(ClientCard card, int player) + { + Executor.OnChaining(player,card); + } + + /// + /// Called when a chain has been solved. + /// + public void OnChainEnd() + { + m_selector.Clear(); + m_selector_pointer = -1; + Executor.OnChainEnd(); + } + + /// + /// Called when the AI has to do something during the battle phase. + /// + /// Informations about usable cards. + /// A new BattlePhaseAction containing the action to do. + public BattlePhaseAction OnSelectBattleCmd(BattlePhase battle) + { + Executor.SetBattle(battle); + foreach (CardExecutor exec in Executor.Executors) + { + if (exec.Type == ExecutorType.GoToMainPhase2 && battle.CanMainPhaseTwo && exec.Func()) // check if should enter main phase 2 directly + { + return ToMainPhase2(); + } + if (exec.Type == ExecutorType.GoToEndPhase && battle.CanEndPhase && exec.Func()) // check if should enter end phase directly + { + return ToEndPhase(); + } + for (int i = 0; i < battle.ActivableCards.Count; ++i) + { + ClientCard card = battle.ActivableCards[i]; + if (ShouldExecute(exec, card, ExecutorType.Activate, battle.ActivableDescs[i])) + { + _dialogs.SendChaining(card.Name); + return new BattlePhaseAction(BattlePhaseAction.BattleAction.Activate, card.ActionIndex); + } + } + } + + // Sort the attackers and defenders, make monster with higher attack go first. + List attackers = new List(battle.AttackableCards); + attackers.Sort(CardContainer.CompareCardAttack); + attackers.Reverse(); + + List defenders = new List(Duel.Fields[1].GetMonsters()); + defenders.Sort(CardContainer.CompareDefensePower); + defenders.Reverse(); + + // Let executor decide which card should attack first. + ClientCard selected = Executor.OnSelectAttacker(attackers, defenders); + if (selected != null && attackers.Contains(selected)) + { + attackers.Remove(selected); + attackers.Insert(0, selected); + } + + // Check for the executor. + BattlePhaseAction result = Executor.OnBattle(attackers, defenders); + if (result != null) + return result; + + if (attackers.Count == 0) + { + if (battle.CanMainPhaseTwo) return ToMainPhase2(); + else if (battle.CanEndPhase) return ToEndPhase(); + } + + if (defenders.Count == 0) + { + // Attack with the monster with the lowest attack first + for (int i = attackers.Count - 1; i >= 0; --i) + { + ClientCard attacker = attackers[i]; + if (attacker.Attack > 0) + return Attack(attacker, null); + } + } + else + { + for (int k = 0; k < attackers.Count; ++k) + { + ClientCard attacker = attackers[k]; + attacker.IsLastAttacker = (k == attackers.Count - 1); + result = Executor.OnSelectAttackTarget(attacker, defenders); + if (result != null) + return result; + } + } + + if (!battle.CanMainPhaseTwo && !battle.CanEndPhase) + return Attack(attackers[0], (defenders.Count == 0) ? null : defenders[0]); + + return battle.CanMainPhaseTwo ? ToMainPhase2() : ToEndPhase(); + } + + /// + /// Called when the AI has to select one or more cards. + /// + /// List of available cards. + /// Minimal quantity. + /// Maximal quantity. + /// The hint message of the select. + /// True if you can return an empty list. + /// A new list containing the selected cards. + public IList OnSelectCard(IList cards, int min, int max, long hint, bool cancelable) + { + const long HINTMSG_FMATERIAL = 511; + const long HINTMSG_SMATERIAL = 512; + const long HINTMSG_XMATERIAL = 513; + const long HINTMSG_LMATERIAL = 533; + const long HINTMSG_SPSUMMON = 509; + + // Check for the executor. + IList result = Executor.OnSelectCard(cards, min, max, hint, cancelable); + if (result != null) + return result; + + if (hint == HINTMSG_SPSUMMON && min == 1 && max > min) // pendulum summon + { + result = Executor.OnSelectPendulumSummon(cards, max); + if (result != null) + return result; + } + + CardSelector selector = null; + if (hint == HINTMSG_FMATERIAL || hint == HINTMSG_SMATERIAL || hint == HINTMSG_XMATERIAL || hint == HINTMSG_LMATERIAL) + { + if (m_materialSelector != null) + { + //Logger.DebugWriteLine("m_materialSelector"); + selector = m_materialSelector; + } + else + { + if (hint == HINTMSG_FMATERIAL) + result = Executor.OnSelectFusionMaterial(cards, min, max); + if (hint == HINTMSG_SMATERIAL) + result = Executor.OnSelectSynchroMaterial(cards, 0, min, max); + if (hint == HINTMSG_XMATERIAL) + result = Executor.OnSelectXyzMaterial(cards, min, max); + if (hint == HINTMSG_LMATERIAL) + result = Executor.OnSelectLinkMaterial(cards, min, max); + + if (result != null) + return result; + + // Update the next selector. + selector = GetSelectedCards(); + } + } + else + { + // Update the next selector. + selector = GetSelectedCards(); + } + + // If we selected a card, use this card. + if (selector != null) + return selector.Select(cards, min, max); + + // Always select the first available cards and choose the minimum. + IList selected = new List(); + + if (cards.Count >= min) + { + for (int i = 0; i < min; ++i) + selected.Add(cards[i]); + } + return selected; + } + + /// + /// Called when the AI can chain (activate) a card. + /// + /// List of activable cards. + /// List of effect descriptions. + /// You can't return -1 if this param is true. + /// Index of the activated card or -1. + public int OnSelectChain(IList cards, IList descs, bool forced) + { + foreach (CardExecutor exec in Executor.Executors) + { + for (int i = 0; i < cards.Count; ++i) + { + ClientCard card = cards[i]; + if (ShouldExecute(exec, card, ExecutorType.Activate, descs[i])) + { + _dialogs.SendChaining(card.Name); + return i; + } + } + } + // If we're forced to chain, we chain the first card. However don't do anything. + return forced ? 0 : -1; + } + + /// + /// Called when the AI has to use one or more counters. + /// + /// Type of counter to use. + /// Quantity of counter to select. + /// List of available cards. + /// List of available counters. + /// List of used counters. + public IList OnSelectCounter(int type, int quantity, IList cards, IList counters) + { + // Always select the first available counters. + int[] used = new int[counters.Count]; + int i = 0; + while (quantity > 0) + { + if (counters[i] >= quantity) + { + used[i] = quantity; + quantity = 0; + } + else + { + used[i] = counters[i]; + quantity -= counters[i]; + } + i++; + } + return used; + } + + /// + /// Called when the AI has to sort cards. + /// + /// Cards to sort. + /// List of sorted cards. + public IList OnCardSorting(IList cards) + { + + IList result = Executor.OnCardSorting(cards); + if (result != null) + return result; + result = new List(); + // TODO: use selector + result = cards.ToList(); + return result; + } + + /// + /// Called when the AI has to choose to activate or not an effect. + /// + /// Card to activate. + /// True for yes, false for no. + public bool OnSelectEffectYn(ClientCard card, long desc) + { + foreach (CardExecutor exec in Executor.Executors) + { + if (ShouldExecute(exec, card, ExecutorType.Activate, desc)) + return true; + } + return false; + } + + /// + /// Called when the AI has to do something during the main phase. + /// + /// A lot of informations about the available actions. + /// A new MainPhaseAction containing the action to do. + public MainPhaseAction OnSelectIdleCmd(MainPhase main) + { + Executor.SetMain(main); + foreach (CardExecutor exec in Executor.Executors) + { + if (exec.Type == ExecutorType.GoToEndPhase && main.CanEndPhase && exec.Func()) // check if should enter end phase directly + { + _dialogs.SendEndTurn(); + return new MainPhaseAction(MainPhaseAction.MainAction.ToEndPhase); + } + if (exec.Type==ExecutorType.GoToBattlePhase && main.CanBattlePhase && exec.Func()) // check if should enter battle phase directly + { + return new MainPhaseAction(MainPhaseAction.MainAction.ToBattlePhase); + } + // NOTICE: GoToBattlePhase and GoToEndPhase has no "card" can be accessed to ShouldExecute(), so instead use exec.Func() to check ... + // enter end phase and enter battle pahse is in higher priority. + + for (int i = 0; i < main.ActivableCards.Count; ++i) + { + ClientCard card = main.ActivableCards[i]; + if (ShouldExecute(exec, card, ExecutorType.Activate, main.ActivableDescs[i])) + { + _dialogs.SendActivate(card.Name); + return new MainPhaseAction(MainPhaseAction.MainAction.Activate, card.ActionActivateIndex[main.ActivableDescs[i]]); + } + } + foreach (ClientCard card in main.MonsterSetableCards) + { + if (ShouldExecute(exec, card, ExecutorType.MonsterSet)) + { + _dialogs.SendSetMonster(); + return new MainPhaseAction(MainPhaseAction.MainAction.SetMonster, card.ActionIndex); + } + } + foreach (ClientCard card in main.ReposableCards) + { + if (ShouldExecute(exec, card, ExecutorType.Repos)) + return new MainPhaseAction(MainPhaseAction.MainAction.Repos, card.ActionIndex); + } + foreach (ClientCard card in main.SpecialSummonableCards) + { + if (ShouldExecute(exec, card, ExecutorType.SpSummon)) + { + _dialogs.SendSummon(card.Name); + return new MainPhaseAction(MainPhaseAction.MainAction.SpSummon, card.ActionIndex); + } + } + foreach (ClientCard card in main.SummonableCards) + { + if (ShouldExecute(exec, card, ExecutorType.Summon)) + { + _dialogs.SendSummon(card.Name); + return new MainPhaseAction(MainPhaseAction.MainAction.Summon, card.ActionIndex); + } + if (ShouldExecute(exec, card, ExecutorType.SummonOrSet)) + { + if (Executor.Util.IsAllEnemyBetter(true) && Executor.Util.IsAllEnemyBetterThanValue(card.Attack + 300, false) && + main.MonsterSetableCards.Contains(card)) + { + _dialogs.SendSetMonster(); + return new MainPhaseAction(MainPhaseAction.MainAction.SetMonster, card.ActionIndex); + } + _dialogs.SendSummon(card.Name); + return new MainPhaseAction(MainPhaseAction.MainAction.Summon, card.ActionIndex); + } + } + foreach (ClientCard card in main.SpellSetableCards) + { + if (ShouldExecute(exec, card, ExecutorType.SpellSet)) + return new MainPhaseAction(MainPhaseAction.MainAction.SetSpell, card.ActionIndex); + } + } + + if (main.CanBattlePhase && Duel.Fields[0].HasAttackingMonster()) + return new MainPhaseAction(MainPhaseAction.MainAction.ToBattlePhase); + + _dialogs.SendEndTurn(); + return new MainPhaseAction(MainPhaseAction.MainAction.ToEndPhase); + } + + /// + /// Called when the AI has to select an option. + /// + /// List of available options. + /// Index of the selected option. + public int OnSelectOption(IList options) + { + if (m_option != -1 && m_option < options.Count) + return m_option; + + int result = Executor.OnSelectOption(options); + if (result != -1) + return result; + + return 0; // Always select the first option. + } + + public int OnSelectPlace(long cardId, int player, CardLocation location, int available) + { + int selector_selected = m_place; + m_place = 0; + + int executor_selected = Executor.OnSelectPlace(cardId, player, location, available); + + if ((executor_selected & available) > 0) + return executor_selected & available; + if ((selector_selected & available) > 0) + return selector_selected & available; + + // TODO: LinkedZones + + return 0; + } + + /// + /// Called when the AI has to select a card position. + /// + /// Id of the card to position on the field. + /// List of available positions. + /// Selected position. + public CardPosition OnSelectPosition(int cardId, IList positions) + { + CardPosition selector_selected = GetSelectedPosition(); + + CardPosition executor_selected = Executor.OnSelectPosition(cardId, positions); + + // Selects the selected position if available, the first available otherwise. + if (positions.Contains(executor_selected)) + return executor_selected; + if (positions.Contains(selector_selected)) + return selector_selected; + + return positions[0]; + } + + /// + /// Called when the AI has to tribute for a synchro monster or ritual monster. + /// + /// Available cards. + /// Result of the operation. + /// Minimum cards. + /// Maximum cards. + /// True for exact equal. + /// + public IList OnSelectSum(IList cards, int sum, int min, int max, long hint, bool mode) + { + const long HINTMSG_RELEASE = 500; + const long HINTMSG_SMATERIAL = 512; + + IList selected = Executor.OnSelectSum(cards, sum, min, max, hint, mode); + if (selected != null) + { + return selected; + } + + if (hint == HINTMSG_RELEASE || hint == HINTMSG_SMATERIAL) + { + if (m_materialSelector != null) + { + selected = m_materialSelector.Select(cards, min, max); + } + else + { + switch (hint) + { + case HINTMSG_SMATERIAL: + selected = Executor.OnSelectSynchroMaterial(cards, sum, min, max); + break; + case HINTMSG_RELEASE: + selected = Executor.OnSelectRitualTribute(cards, sum, min, max); + break; + } + } + if (selected != null) + { + int s1 = 0, s2 = 0; + foreach (ClientCard card in selected) + { + s1 += card.OpParam1; + s2 += (card.OpParam2 != 0) ? card.OpParam2 : card.OpParam1; + } + if ((mode && (s1 == sum || s2 == sum)) || (!mode && (s1 >= sum || s2 >= sum))) + { + return selected; + } + } + } + + if (mode) + { + // equal + + if (sum == 0 && min == 0) + { + return new List(); + } + + if (min <= 1) + { + // try special level first + foreach (ClientCard card in cards) + { + if (card.OpParam2 == sum) + { + return new[] { card }; + } + } + // try level equal + foreach (ClientCard card in cards) + { + if (card.OpParam1 == sum) + { + return new[] { card }; + } + } + } + + // try all + int s1 = 0, s2 = 0; + foreach (ClientCard card in cards) + { + s1 += card.OpParam1; + s2 += (card.OpParam2 != 0) ? card.OpParam2 : card.OpParam1; + } + if (s1 == sum || s2 == sum) + { + return cards; + } + + // try all combinations + int i = (min <= 1) ? 2 : min; + while (i <= max && i <= cards.Count) + { + IEnumerable> combos = CardContainer.GetCombinations(cards, i); + + foreach (IEnumerable combo in combos) + { + Log(LogLevel.Debug, "--"); + s1 = 0; + s2 = 0; + foreach (ClientCard card in combo) + { + s1 += card.OpParam1; + s2 += (card.OpParam2 != 0) ? card.OpParam2 : card.OpParam1; + } + if (s1 == sum || s2 == sum) + { + return combo.ToList(); + } + } + i++; + } + } + else + { + // larger + if (min <= 1) + { + // try special level first + foreach (ClientCard card in cards) + { + if (card.OpParam2 >= sum) + { + return new[] { card }; + } + } + // try level equal + foreach (ClientCard card in cards) + { + if (card.OpParam1 >= sum) + { + return new[] { card }; + } + } + } + + // try all combinations + int i = (min <= 1) ? 2 : min; + while (i <= max && i <= cards.Count) + { + IEnumerable> combos = CardContainer.GetCombinations(cards, i); + + foreach (IEnumerable combo in combos) + { + Log(LogLevel.Debug, "----"); + int s1 = 0, s2 = 0; + foreach (ClientCard card in combo) + { + s1 += card.OpParam1; + s2 += (card.OpParam2 != 0) ? card.OpParam2 : card.OpParam1; + } + if (s1 >= sum || s2 >= sum) + { + return combo.ToList(); + } + } + i++; + } + } + + Log(LogLevel.Error, "Fail to select sum."); + return new List(); + } + + /// + /// Called when the AI has to tribute one or more cards. + /// + /// List of available cards. + /// Minimal quantity. + /// Maximal quantity. + /// The hint message of the select. + /// True if you can return an empty list. + /// A new list containing the tributed cards. + public IList OnSelectTribute(IList cards, int min, int max, long hint, bool cancelable) + { + // Always choose the minimum and lowest atk. + List sorted = new List(); + sorted.AddRange(cards); + sorted.Sort(CardContainer.CompareCardAttack); + + IList selected = new List(); + + for (int i = 0; i < min && i < sorted.Count; ++i) + selected.Add(sorted[i]); + + return selected; + } + + /// + /// Called when the AI has to select yes or no. + /// + /// Id of the question. + /// True for yes, false for no. + public bool OnSelectYesNo(long desc) + { + if (m_yesno != -1) + return m_yesno > 0; + return Executor.OnSelectYesNo(desc); + } + + /// + /// Called when the AI has to select if to continue attacking when replay. + /// + /// True for yes, false for no. + public bool OnSelectBattleReplay() + { + return Executor.OnSelectBattleReplay(); + } + + /// + /// Called when the AI has to declare a card. + /// + /// Id of the selected card. + public int OnAnnounceCard() + { + if (m_announce == 0) + return 89631139; // Blue-eyes white dragon + return m_announce; + } + + // _ Others functions _ + // Those functions are used by the AI behavior. + + + private CardSelector m_materialSelector; + private int m_place; + private int m_option; + private int m_number; + private int m_announce; + private int m_yesno; + private IList m_attributes = new List(); + private IList m_selector = new List(); + private IList m_position = new List(); + private int m_selector_pointer = -1; + private IList m_races = new List(); + + public void SelectCard(ClientCard card) + { + m_selector_pointer = m_selector.Count(); + m_selector.Add(new CardSelector(card)); + } + + public void SelectCard(IList cards) + { + m_selector_pointer = m_selector.Count(); + m_selector.Add(new CardSelector(cards)); + } + + public void SelectCard(int cardId) + { + m_selector_pointer = m_selector.Count(); + m_selector.Add(new CardSelector(cardId)); + } + + public void SelectCard(IList ids) + { + m_selector_pointer = m_selector.Count(); + m_selector.Add(new CardSelector(ids)); + } + + public void SelectCard(params int[] ids) + { + m_selector_pointer = m_selector.Count(); + m_selector.Add(new CardSelector(ids)); + } + + public void SelectCard(CardLocation loc) + { + m_selector_pointer = m_selector.Count(); + m_selector.Add(new CardSelector(loc)); + } + + public void SelectNextCard(ClientCard card) + { + if (m_selector_pointer == -1) + { + Log(LogLevel.Error, "Error: Call SelectNextCard() before SelectCard()"); + m_selector_pointer = 0; + } + m_selector.Insert(m_selector_pointer, new CardSelector(card)); + } + + public void SelectNextCard(IList cards) + { + if (m_selector_pointer == -1) + { + Log(LogLevel.Error, "Error: Call SelectNextCard() before SelectCard()"); + m_selector_pointer = 0; + } + m_selector.Insert(m_selector_pointer, new CardSelector(cards)); + } + + public void SelectNextCard(int cardId) + { + if (m_selector_pointer == -1) + { + Log(LogLevel.Error, "Error: Call SelectNextCard() before SelectCard()"); + m_selector_pointer = 0; + } + m_selector.Insert(m_selector_pointer, new CardSelector(cardId)); + } + + public void SelectNextCard(IList ids) + { + if (m_selector_pointer == -1) + { + Log(LogLevel.Error, "Error: Call SelectNextCard() before SelectCard()"); + m_selector_pointer = 0; + } + m_selector.Insert(m_selector_pointer, new CardSelector(ids)); + } + + public void SelectNextCard(params int[] ids) + { + if (m_selector_pointer == -1) + { + Log(LogLevel.Error, "Error: Call SelectNextCard() before SelectCard()"); + m_selector_pointer = 0; + } + m_selector.Insert(m_selector_pointer, new CardSelector(ids)); + } + + public void SelectNextCard(CardLocation loc) + { + if (m_selector_pointer == -1) + { + Log(LogLevel.Error, "Error: Call SelectNextCard() before SelectCard()"); + m_selector_pointer = 0; + } + m_selector.Insert(m_selector_pointer, new CardSelector(loc)); + } + + public void SelectThirdCard(ClientCard card) + { + if (m_selector_pointer == -1) + { + Log(LogLevel.Error, "Error: Call SelectThirdCard() before SelectCard()"); + m_selector_pointer = 0; + } + m_selector.Insert(m_selector_pointer, new CardSelector(card)); + } + + public void SelectThirdCard(IList cards) + { + if (m_selector_pointer == -1) + { + Log(LogLevel.Error, "Error: Call SelectThirdCard() before SelectCard()"); + m_selector_pointer = 0; + } + m_selector.Insert(m_selector_pointer, new CardSelector(cards)); + } + + public void SelectThirdCard(int cardId) + { + if (m_selector_pointer == -1) + { + Log(LogLevel.Error, "Error: Call SelectThirdCard() before SelectCard()"); + m_selector_pointer = 0; + } + m_selector.Insert(m_selector_pointer, new CardSelector(cardId)); + } + + public void SelectThirdCard(IList ids) + { + if (m_selector_pointer == -1) + { + Log(LogLevel.Error, "Error: Call SelectThirdCard() before SelectCard()"); + m_selector_pointer = 0; + } + m_selector.Insert(m_selector_pointer, new CardSelector(ids)); + } + + public void SelectThirdCard(params int[] ids) + { + if (m_selector_pointer == -1) + { + Log(LogLevel.Error, "Error: Call SelectThirdCard() before SelectCard()"); + m_selector_pointer = 0; + } + m_selector.Insert(m_selector_pointer, new CardSelector(ids)); + } + + public void SelectThirdCard(CardLocation loc) + { + if (m_selector_pointer == -1) + { + Log(LogLevel.Error, "Error: Call SelectThirdCard() before SelectCard()"); + m_selector_pointer = 0; + } + m_selector.Insert(m_selector_pointer, new CardSelector(loc)); + } + + public void SelectMaterials(ClientCard card) + { + m_materialSelector = new CardSelector(card); + } + + public void SelectMaterials(IList cards) + { + m_materialSelector = new CardSelector(cards); + } + + public void SelectMaterials(int cardId) + { + m_materialSelector = new CardSelector(cardId); + } + + public void SelectMaterials(IList ids) + { + m_materialSelector = new CardSelector(ids); + } + + public void SelectMaterials(CardLocation loc) + { + m_materialSelector = new CardSelector(loc); + } + + public void CleanSelectMaterials() + { + m_materialSelector = null; + } + + public CardSelector GetSelectedCards() + { + CardSelector selected = null; + if (m_selector.Count > 0) + { + selected = m_selector[m_selector.Count - 1]; + m_selector.RemoveAt(m_selector.Count - 1); + } + return selected; + } + + public CardPosition GetSelectedPosition() + { + CardPosition selected = CardPosition.FaceUpAttack; + if (m_position.Count > 0) + { + selected = m_position[0]; + m_position.RemoveAt(0); + } + return selected; + } + + public void SelectPosition(CardPosition pos) + { + m_position.Add(pos); + } + + public void SelectPlace(int zones) + { + m_place = zones; + } + + public void SelectOption(int opt) + { + m_option = opt; + } + + public void SelectNumber(int number) + { + m_number = number; + } + + public void SelectAttribute(CardAttribute attribute) + { + m_attributes.Clear(); + m_attributes.Add(attribute); + } + + public void SelectAttributes(CardAttribute[] attributes) + { + m_attributes.Clear(); + foreach (CardAttribute attribute in attributes) + m_attributes.Add(attribute); + } + + public void SelectRace(CardRace race) + { + m_races.Clear(); + m_races.Add(race); + } + + public void SelectRaces(CardRace[] races) + { + m_races.Clear(); + foreach (CardRace race in races) + m_races.Add(race); + } + + public void SelectAnnounceID(int id) + { + m_announce = id; + } + + public void SelectYesNo(bool opt) + { + m_yesno = opt ? 1 : 0; + } + + /// + /// Called when the AI has to declare a number. + /// + /// List of available numbers. + /// Index of the selected number. + public int OnAnnounceNumber(IList numbers) + { + if (numbers.Contains(m_number)) + return numbers.IndexOf(m_number); + + return Executor.Rand.Next(0, numbers.Count); // Returns a random number. + } + + /// + /// Called when the AI has to declare one or more attributes. + /// + /// Quantity of attributes to declare. + /// List of available attributes. + /// A list of the selected attributes. + public virtual IList OnAnnounceAttrib(int count, IList attributes) + { + IList foundAttributes = m_attributes.Where(attributes.Contains).ToList(); + if (foundAttributes.Count > 0) + return foundAttributes; + + return attributes; // Returns the first available Attribute. + } + + /// + /// Called when the AI has to declare one or more races. + /// + /// Quantity of races to declare. + /// List of available races. + /// A list of the selected races. + public virtual IList OnAnnounceRace(int count, IList races) + { + IList foundRaces = m_races.Where(races.Contains).ToList(); + if (foundRaces.Count > 0) + return foundRaces; + + return races; // Returns the first available Races. + } + + public BattlePhaseAction Attack(ClientCard attacker, ClientCard defender) + { + Executor.SetCard(0, attacker, -1); + if (defender != null) + { + string cardName = defender.Name ?? "monster"; + attacker.ShouldDirectAttack = false; + _dialogs.SendAttack(attacker.Name, cardName); + SelectCard(defender); + } + else + { + attacker.ShouldDirectAttack = true; + _dialogs.SendDirectAttack(attacker.Name); + } + return new BattlePhaseAction(BattlePhaseAction.BattleAction.Attack, attacker.ActionIndex); + } + + public BattlePhaseAction ToEndPhase() + { + _dialogs.SendEndTurn(); + return new BattlePhaseAction(BattlePhaseAction.BattleAction.ToEndPhase); + } + public BattlePhaseAction ToMainPhase2() + { + return new BattlePhaseAction(BattlePhaseAction.BattleAction.ToMainPhaseTwo); + } + + private bool ShouldExecute(CardExecutor exec, ClientCard card, ExecutorType type, long desc = -1) + { + Executor.SetCard(type, card, desc); + return card != null && + exec.Type == type && + (exec.CardId == -1 || exec.CardId == card.Id) && + (exec.Func == null || exec.Func()); + } + } +} diff --git a/Game/MainPhase.cs b/ExecutorBase/Game/MainPhase.cs similarity index 97% rename from Game/MainPhase.cs rename to ExecutorBase/Game/MainPhase.cs index 3c3c266a..4ca81906 100644 --- a/Game/MainPhase.cs +++ b/ExecutorBase/Game/MainPhase.cs @@ -1,28 +1,28 @@ -using System.Collections.Generic; - -namespace WindBot.Game -{ - public class MainPhase - { - public IList SummonableCards { get; private set; } - public IList SpecialSummonableCards { get; private set; } - public IList ReposableCards { get; private set; } - public IList MonsterSetableCards { get; private set; } - public IList SpellSetableCards { get; private set; } - public IList ActivableCards { get; private set; } - public IList ActivableDescs { get; private set; } - public bool CanBattlePhase { get; set; } - public bool CanEndPhase { get; set; } - - public MainPhase() - { - SummonableCards = new List(); - SpecialSummonableCards = new List(); - ReposableCards = new List(); - MonsterSetableCards = new List(); - SpellSetableCards = new List(); - ActivableCards = new List(); - ActivableDescs = new List(); - } - } +using System.Collections.Generic; + +namespace WindBot.Game +{ + public class MainPhase + { + public IList SummonableCards { get; private set; } + public IList SpecialSummonableCards { get; private set; } + public IList ReposableCards { get; private set; } + public IList MonsterSetableCards { get; private set; } + public IList SpellSetableCards { get; private set; } + public IList ActivableCards { get; private set; } + public IList ActivableDescs { get; private set; } + public bool CanBattlePhase { get; set; } + public bool CanEndPhase { get; set; } + + public MainPhase() + { + SummonableCards = new List(); + SpecialSummonableCards = new List(); + ReposableCards = new List(); + MonsterSetableCards = new List(); + SpellSetableCards = new List(); + ActivableCards = new List(); + ActivableDescs = new List(); + } + } } \ No newline at end of file diff --git a/Game/MainPhaseAction.cs b/ExecutorBase/Game/MainPhaseAction.cs similarity index 95% rename from Game/MainPhaseAction.cs rename to ExecutorBase/Game/MainPhaseAction.cs index 46fa2c8a..00746cd7 100644 --- a/Game/MainPhaseAction.cs +++ b/ExecutorBase/Game/MainPhaseAction.cs @@ -1,43 +1,43 @@ -namespace WindBot.Game -{ - public class MainPhaseAction - { - public enum MainAction - { - Summon = 0, - SpSummon = 1, - Repos = 2, - SetMonster = 3, - SetSpell = 4, - Activate = 5, - ToBattlePhase = 6, - ToEndPhase = 7 - } - - public MainAction Action { get; private set; } - public int Index { get; private set; } - - public MainPhaseAction(MainAction action) - { - Action = action; - Index = 0; - } - - public MainPhaseAction(MainAction action, int index) - { - Action = action; - Index = index; - } - - public MainPhaseAction(MainAction action, int[] indexes) - { - Action = action; - Index = indexes[(int)action]; - } - - public int ToValue() - { - return (Index << 16) + (int)Action; - } - } +namespace WindBot.Game +{ + public class MainPhaseAction + { + public enum MainAction + { + Summon = 0, + SpSummon = 1, + Repos = 2, + SetMonster = 3, + SetSpell = 4, + Activate = 5, + ToBattlePhase = 6, + ToEndPhase = 7 + } + + public MainAction Action { get; private set; } + public int Index { get; private set; } + + public MainPhaseAction(MainAction action) + { + Action = action; + Index = 0; + } + + public MainPhaseAction(MainAction action, int index) + { + Action = action; + Index = index; + } + + public MainPhaseAction(MainAction action, int[] indexes) + { + Action = action; + Index = indexes[(int)action]; + } + + public int ToValue() + { + return (Index << 16) + (int)Action; + } + } } \ No newline at end of file diff --git a/ExecutorBase/Properties/AssemblyInfo.cs b/ExecutorBase/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..df9159a2 --- /dev/null +++ b/ExecutorBase/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Le informazioni generali relative a un assembly sono controllate dal seguente +// set di attributi. Modificare i valori di questi attributi per modificare le informazioni +// associate a un assembly. +[assembly: AssemblyTitle("ExecutorBase")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ExecutorBase")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Se si imposta ComVisible su false, i tipi in questo assembly non saranno visibili +// ai componenti COM. Se è necessario accedere a un tipo in questo assembly da +// COM, impostare su true l'attributo ComVisible per tale tipo. +[assembly: ComVisible(false)] + +// Se il progetto viene esposto a COM, il GUID seguente verrà utilizzato come ID della libreria dei tipi +[assembly: Guid("a1583fd7-7985-47dd-a835-8134dbf5811c")] + +// Le informazioni sulla versione di un assembly sono costituite dai seguenti quattro valori: +// +// Versione principale +// Versione secondaria +// Numero di build +// Revisione +// +// È possibile specificare tutti i valori oppure impostare valori predefiniti per i numeri relativi alla revisione e alla build +// usando l'asterisco '*' come illustrato di seguito: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardAttribute.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardAttribute.cs new file mode 100644 index 00000000..a8d9b433 --- /dev/null +++ b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardAttribute.cs @@ -0,0 +1,13 @@ +namespace YGOSharp.OCGWrapper.Enums +{ + public enum CardAttribute + { + Earth = 0x01, + Water = 0x02, + Fire = 0x04, + Wind = 0x08, + Light = 0x10, + Dark = 0x20, + Divine = 0x40, + } +} \ No newline at end of file diff --git a/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardLinkMarker.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardLinkMarker.cs new file mode 100644 index 00000000..434cd8c3 --- /dev/null +++ b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardLinkMarker.cs @@ -0,0 +1,15 @@ +namespace YGOSharp.OCGWrapper.Enums +{ + public enum CardLinkMarker + { + BottomLeft = 0x01, + Bottom = 0x02, + BottomRight = 0x04, + Left = 0x08, + + Right = 0x20, + TopLeft = 0x40, + Top = 0x80, + TopRight = 0x100 + } +} \ No newline at end of file diff --git a/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardLocation.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardLocation.cs new file mode 100644 index 00000000..3155eda0 --- /dev/null +++ b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardLocation.cs @@ -0,0 +1,17 @@ +namespace YGOSharp.OCGWrapper.Enums +{ + public enum CardLocation + { + Deck = 0x01, + Hand = 0x02, + MonsterZone = 0x04, + SpellZone = 0x08, + Grave = 0x10, + Removed = 0x20, + Extra = 0x40, + Overlay = 0x80, + Onfield = 0x0C, + FieldZone = 0x100, + PendulumZone = 0x200 + } +} \ No newline at end of file diff --git a/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardPosition.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardPosition.cs new file mode 100644 index 00000000..147f326f --- /dev/null +++ b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardPosition.cs @@ -0,0 +1,14 @@ +namespace YGOSharp.OCGWrapper.Enums +{ + public enum CardPosition + { + FaceUpAttack = 0x1, + FaceDownAttack = 0x2, + FaceUpDefence = 0x4, + FaceDownDefence = 0x8, + FaceUp = 0x5, + FaceDown = 0xA, + Attack = 0x3, + Defence = 0xC + } +} \ No newline at end of file diff --git a/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardRace.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardRace.cs new file mode 100644 index 00000000..c1f104ab --- /dev/null +++ b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardRace.cs @@ -0,0 +1,30 @@ +namespace YGOSharp.OCGWrapper.Enums +{ + public enum CardRace + { + Warrior = 0x1, + SpellCaster = 0x2, + Fairy = 0x4, + Fiend = 0x8, + Zombie = 0x10, + Machine = 0x20, + Aqua = 0x40, + Pyro = 0x80, + Rock = 0x100, + WindBeast = 0x200, + Plant = 0x400, + Insect = 0x800, + Thunder = 0x1000, + Dragon = 0x2000, + Beast = 0x4000, + BestWarrior = 0x8000, + Dinosaur = 0x10000, + Fish = 0x20000, + SeaSerpent = 0x40000, + Reptile = 0x80000, + Psycho = 0x100000, + DivineBeast = 0x200000, + Wyrm = 0x800000, + Cyberse = 0x1000000 + } +} \ No newline at end of file diff --git a/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardType.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardType.cs new file mode 100644 index 00000000..b5f44d9e --- /dev/null +++ b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardType.cs @@ -0,0 +1,31 @@ +namespace YGOSharp.OCGWrapper.Enums +{ + public enum CardType + { + Monster = 0x1, + Spell = 0x2, + Trap = 0x4, + Normal = 0x10, + Effect = 0x20, + Fusion = 0x40, + Ritual = 0x80, + TrapMonster = 0x100, + Spirit = 0x200, + Union = 0x400, + Dual = 0x800, + Tuner = 0x1000, + Synchro = 0x2000, + Token = 0x4000, + QuickPlay = 0x10000, + Continuous = 0x20000, + Equip = 0x40000, + Field = 0x80000, + Counter = 0x100000, + Flip = 0x200000, + Toon = 0x400000, + Xyz = 0x800000, + Pendulum = 0x1000000, + SpSummon = 0x2000000, + Link = 0x4000000 + } +} \ No newline at end of file diff --git a/ExecutorBase/YGOSharp.OCGWrapper.Enums/DuelPhase.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/DuelPhase.cs new file mode 100644 index 00000000..f95ce874 --- /dev/null +++ b/ExecutorBase/YGOSharp.OCGWrapper.Enums/DuelPhase.cs @@ -0,0 +1,16 @@ +namespace YGOSharp.OCGWrapper.Enums +{ + public enum DuelPhase + { + Draw = 0x01, + Standby = 0x02, + Main1 = 0x04, + BattleStart = 0x08, + BattleStep = 0x10, + Damage = 0x20, + DamageCal = 0x40, + Battle = 0x80, + Main2 = 0x100, + End = 0x200 + } +} diff --git a/ExecutorBase/YGOSharp.OCGWrapper.Enums/GameMessage.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/GameMessage.cs new file mode 100644 index 00000000..14fc59ca --- /dev/null +++ b/ExecutorBase/YGOSharp.OCGWrapper.Enums/GameMessage.cs @@ -0,0 +1,101 @@ +namespace YGOSharp.OCGWrapper.Enums +{ + public enum GameMessage + { + Retry = 1, + Hint = 2, + Waiting = 3, + Start = 4, + Win = 5, + UpdateData = 6, + UpdateCard = 7, + RequestDeck = 8, + SelectBattleCmd = 10, + SelectIdleCmd = 11, + SelectEffectYn = 12, + SelectYesNo = 13, + SelectOption = 14, + SelectCard = 15, + SelectChain = 16, + SelectPlace = 18, + SelectPosition = 19, + SelectTribute = 20, + SortChain = 21, + SelectCounter = 22, + SelectSum = 23, + SelectDisfield = 24, + SortCard = 25, + SelectUnselect = 26, + ConfirmDecktop = 30, + ConfirmCards = 31, + ShuffleDeck = 32, + ShuffleHand = 33, + RefreshDeck = 34, + SwapGraveDeck = 35, + ShuffleSetCard = 36, + ReverseDeck = 37, + DeckTop = 38, + ShuffleExtra = 39, + NewTurn = 40, + NewPhase = 41, + ConfirmExtratop = 42, + Move = 50, + PosChange = 53, + Set = 54, + Swap = 55, + FieldDisabled = 56, + Summoning = 60, + Summoned = 61, + SpSummoning = 62, + SpSummoned = 63, + FlipSummoning = 64, + FlipSummoned = 65, + Chaining = 70, + Chained = 71, + ChainSolving = 72, + ChainSolved = 73, + ChainEnd = 74, + ChainNegated = 75, + ChainDisabled = 76, + CardSelected = 80, + RandomSelected = 81, + BecomeTarget = 83, + Draw = 90, + Damage = 91, + Recover = 92, + Equip = 93, + LpUpdate = 94, + Unequip = 95, + CardTarget = 96, + CancelTarget = 97, + PayLpCost = 100, + AddCounter = 101, + RemoveCounter = 102, + Attack = 110, + Battle = 111, + AttackDisabled = 112, + DamageStepStart = 113, + DamageStepEnd = 114, + MissedEffect = 120, + BeChainTarget = 121, + CreateRelation = 122, + ReleaseRelation = 123, + TossCoin = 130, + TossDice = 131, + RockPaperScissors = 132, + HandResult = 133, + AnnounceRace = 140, + AnnounceAttrib = 141, + AnnounceCard = 142, + AnnounceNumber = 143, + CardHint = 160, + TagSwap = 161, + ReloadField = 162, + AiName = 163, + ShowHint = 164, + PlayerHint = 165, + MatchKill = 170, + CustomMsg = 180, + DuelWinner = 200 + } +} \ No newline at end of file diff --git a/ExecutorBase/YGOSharp.OCGWrapper.Enums/Query.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/Query.cs new file mode 100644 index 00000000..bf239caf --- /dev/null +++ b/ExecutorBase/YGOSharp.OCGWrapper.Enums/Query.cs @@ -0,0 +1,30 @@ +namespace YGOSharp.OCGWrapper.Enums +{ + public enum Query : uint + { + Code = 0x01, + Position = 0x02, + Alias = 0x04, + Type = 0x08, + Level = 0x10, + Rank = 0x20, + Attribute = 0x40, + Race = 0x80, + Attack = 0x100, + Defence = 0x200, + BaseAttack = 0x400, + BaseDefence = 0x800, + Reason = 0x1000, + ReasonCard = 0x2000, + EquipCard = 0x4000, + TargetCard = 0x8000, + OverlayCard = 0x10000, + Counters = 0x20000, + Owner = 0x40000, + Status = 0x80000, + LScale = 0x200000, + RScale = 0x400000, + Link = 0x800000, + End = 0x80000000 + } +} \ No newline at end of file diff --git a/ExecutorBase/YGOSharp.OCGWrapper/Card.cs b/ExecutorBase/YGOSharp.OCGWrapper/Card.cs new file mode 100644 index 00000000..48b92505 --- /dev/null +++ b/ExecutorBase/YGOSharp.OCGWrapper/Card.cs @@ -0,0 +1,98 @@ +using YGOSharp.OCGWrapper.Enums; +using System.Data; + +namespace YGOSharp.OCGWrapper +{ + public class Card + { + public struct CardData + { + public int Code; + public int Alias; + public long Setcode; + public int Type; + public int Level; + public int Attribute; + public int Race; + public int Attack; + public int Defense; + public int LScale; + public int RScale; + public int LinkMarker; + } + + public int Id { get; private set; } + public int Ot { get; private set; } + public int Alias { get; private set; } + public long Setcode { get; private set; } + public int Type { get; private set; } + + public int Level { get; private set; } + public int LScale { get; private set; } + public int RScale { get; private set; } + public int LinkMarker { get; private set; } + + public int Attribute { get; private set; } + public int Race { get; private set; } + public int Attack { get; private set; } + public int Defense { get; private set; } + + internal CardData Data { get; private set; } + + public static Card Get(int id) + { + return CardsManager.GetCard(id); + } + + public bool HasType(CardType type) + { + return ((Type & (int)type) != 0); + } + + public bool IsExtraCard() + { + return (HasType(CardType.Fusion) || HasType(CardType.Synchro) || HasType(CardType.Xyz) || HasType(CardType.Link)); + } + + internal Card(IDataRecord reader) + { + Id = reader.GetInt32(0); + Ot = reader.GetInt32(1); + Alias = reader.GetInt32(2); + Setcode = reader.GetInt64(3); + Type = reader.GetInt32(4); + + int levelInfo = reader.GetInt32(5); + Level = levelInfo & 0xff; + LScale = (levelInfo >> 24) & 0xff; + RScale = (levelInfo >> 16) & 0xff; + + Race = reader.GetInt32(6); + Attribute = reader.GetInt32(7); + Attack = reader.GetInt32(8); + Defense = reader.GetInt32(9); + + if (HasType(CardType.Link)) + { + LinkMarker = Defense; + Defense = 0; + } + + Data = new CardData() + { + Code = Id, + Alias = Alias, + Setcode = Setcode, + Type = Type, + Level = Level, + Attribute = Attribute, + Race = Race, + Attack = Attack, + Defense = Defense, + LScale = LScale, + RScale = RScale, + LinkMarker = LinkMarker + }; + } + } +} \ No newline at end of file diff --git a/ExecutorBase/YGOSharp.OCGWrapper/CardsManager.cs b/ExecutorBase/YGOSharp.OCGWrapper/CardsManager.cs new file mode 100644 index 00000000..0fef6f72 --- /dev/null +++ b/ExecutorBase/YGOSharp.OCGWrapper/CardsManager.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Data; +using Mono.Data.Sqlite; + +namespace YGOSharp.OCGWrapper +{ + internal static class CardsManager + { + private static IDictionary _cards; + + internal static void Init(string databaseFullPath) + { + _cards = new Dictionary(); + + using (SqliteConnection connection = new SqliteConnection("Data Source=" + databaseFullPath)) + { + connection.Open(); + + using (IDbCommand command = new SqliteCommand("SELECT id, ot, alias, setcode, type, level, race, attribute, atk, def FROM datas", connection)) + { + using (IDataReader reader = command.ExecuteReader()) + { + while (reader.Read()) + { + LoadCard(reader); + } + } + } + } + } + + internal static Card GetCard(int id) + { + if (_cards.ContainsKey(id)) + return _cards[id]; + return null; + } + + private static void LoadCard(IDataRecord reader) + { + Card card = new Card(reader); + _cards.Add(card.Id, card); + } + } +} \ No newline at end of file diff --git a/ExecutorBase/YGOSharp.OCGWrapper/NamedCard.cs b/ExecutorBase/YGOSharp.OCGWrapper/NamedCard.cs new file mode 100644 index 00000000..effc4983 --- /dev/null +++ b/ExecutorBase/YGOSharp.OCGWrapper/NamedCard.cs @@ -0,0 +1,21 @@ +using System.Data; + +namespace YGOSharp.OCGWrapper +{ + public class NamedCard : Card + { + public string Name { get; private set; } + public string Description { get; private set; } + + internal NamedCard(IDataRecord reader) : base(reader) + { + Name = reader.GetString(10); + Description = reader.GetString(11); + } + + public static new NamedCard Get(int id) + { + return NamedCardsManager.GetCard(id); + } + } +} diff --git a/ExecutorBase/YGOSharp.OCGWrapper/NamedCardsManager.cs b/ExecutorBase/YGOSharp.OCGWrapper/NamedCardsManager.cs new file mode 100644 index 00000000..6ed3a562 --- /dev/null +++ b/ExecutorBase/YGOSharp.OCGWrapper/NamedCardsManager.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Data; +using Mono.Data.Sqlite; +using System; +using System.IO; + +namespace YGOSharp.OCGWrapper +{ + public static class NamedCardsManager + { + private static IDictionary _cards = new Dictionary(); + + public static void Init(string databaseFullPath) + { + try + { + if (!File.Exists(databaseFullPath)) + { + throw new Exception("Could not find the cards database."); + } + + using (SqliteConnection connection = new SqliteConnection("Data Source=" + databaseFullPath)) + { + connection.Open(); + + using (IDbCommand command = new SqliteCommand( + "SELECT datas.id, ot, alias, setcode, type, level, race, attribute, atk, def, texts.name, texts.desc" + + " FROM datas INNER JOIN texts ON datas.id = texts.id", + connection)) + { + using (IDataReader reader = command.ExecuteReader()) + { + while (reader.Read()) + { + LoadCard(reader); + } + } + } + } + } + catch (Exception ex) + { + throw new Exception("Could not initialize the cards database. Check the inner exception for more details.", ex); + } + } + + internal static NamedCard GetCard(int id) + { + if (_cards.ContainsKey(id)) + return _cards[id]; + return null; + } + + private static void LoadCard(IDataRecord reader) + { + NamedCard card = new NamedCard(reader); + _cards.Add(card.Id, card); + } + } +} diff --git a/Game/Deck.cs b/Game/AI/Deck.cs similarity index 90% rename from Game/Deck.cs rename to Game/AI/Deck.cs index 6bf18a90..23e39d50 100644 --- a/Game/Deck.cs +++ b/Game/AI/Deck.cs @@ -1,77 +1,77 @@ -using System; -using System.Collections.Generic; -using System.IO; -using YGOSharp.OCGWrapper; - -namespace WindBot.Game -{ - public class Deck - { - public IList Cards { get; private set; } - public IList ExtraCards { get; private set; } - public IList SideCards { get; private set; } - - public Deck() - { - Cards = new List(); - ExtraCards = new List(); - SideCards = new List(); - } - - private void AddNewCard(int cardId, bool mainDeck, bool sideDeck) - { - if (sideDeck) - SideCards.Add(cardId); - else if(mainDeck) - Cards.Add(cardId); - else - ExtraCards.Add(cardId); - } - - public static Deck Load(string name) - { - StreamReader reader = null; - try - { - reader = new StreamReader(new FileStream("Decks/" + name + ".ydk", FileMode.Open, FileAccess.Read)); - - Deck deck = new Deck(); - bool main = true; - bool side = false; - - while (!reader.EndOfStream) - { - string line = reader.ReadLine(); - if (line == null) - continue; - - line = line.Trim(); - if (line.Equals("#extra")) - main = false; - else if (line.StartsWith("#")) - continue; - if (line.Equals("!side")) - { - side = true; - continue; - } - - int id; - if (!int.TryParse(line, out id)) - continue; - - deck.AddNewCard(id, main, side); - } - - reader.Close(); - - return deck; - } - catch (Exception) - { - reader?.Close(); - return null; - } - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.IO; +using YGOSharp.OCGWrapper; + +namespace WindBot.Game +{ + public class Deck + { + public IList Cards { get; private set; } + public IList ExtraCards { get; private set; } + public IList SideCards { get; private set; } + + public Deck() + { + Cards = new List(); + ExtraCards = new List(); + SideCards = new List(); + } + + private void AddNewCard(int cardId, bool mainDeck, bool sideDeck) + { + if (sideDeck) + SideCards.Add(cardId); + else if(mainDeck) + Cards.Add(cardId); + else + ExtraCards.Add(cardId); + } + + public static Deck Load(string name) + { + StreamReader reader = null; + try + { + reader = new StreamReader(new FileStream(Path.Combine(Program.AssetPath, "Decks/", name + ".ydk"), FileMode.Open, FileAccess.Read)); + + Deck deck = new Deck(); + bool main = true; + bool side = false; + + while (!reader.EndOfStream) + { + string line = reader.ReadLine(); + if (line == null) + continue; + + line = line.Trim(); + if (line.Equals("#extra")) + main = false; + else if (line.StartsWith("#")) + continue; + if (line.Equals("!side")) + { + side = true; + continue; + } + + int id; + if (!int.TryParse(line, out id)) + continue; + + deck.AddNewCard(id, main, side); + } + + reader.Close(); + + return deck; + } + catch (Exception) + { + reader?.Close(); + return null; + } + } + } +} diff --git a/Game/AI/Decks/ABCExecutor.cs b/Game/AI/Decks/ABCExecutor.cs new file mode 100644 index 00000000..5eccb999 --- /dev/null +++ b/Game/AI/Decks/ABCExecutor.cs @@ -0,0 +1,1232 @@ +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("ABC", "AI_ABC")] + class ABCExecutor : DefaultExecutor + { + public class CardId + { + public const int UnionDriver = 99249638; + public const int GalaxySoldier = 46659709; + public const int PhotonThrasher = 65367484; + public const int AAssaultCore = 30012506; + public const int BBusterDrake = 77411244; + public const int CCrushWyvern = 3405259; + public const int PhotonOrbital = 89132148; + public const int PhotonVanisher = 43147039; + public const int HeavyMechSupportArmor = 39890958; + public const int AshBlossomAndJoyousSpring = 14558128; + + public const int ReinforcementOfTheArmy = 32807846; + public const int Terraforming = 73628505; + public const int UnauthorizedReactivation = 12524259; + public const int CalledbyTheGrave = 24224830; + public const int CrossOutDesignator = 65681983; + public const int UnionHangar = 66399653; + public const int InfiniteImpermanence = 10045474; + public const int SolemnStrike = 40605147; + + public const int ABCDragonBuster = 1561110; + public const int InvokedMechaba = 75286621; + public const int CyberDragonInfinity = 10443957; + public const int CyberDragonNova = 58069384; + public const int CrystronHalqifibrax = 50588353; + public const int UnionCarrier = 83152482; + public const int IPMasquerena = 65741786; + public const int CrusadiaAvramax = 21887175; + public const int ApollousaBOG = 4280258; + public const int KnightmareUnicorn = 38342335; + public const int KnightmarePhoenix = 2857636; + public const int KnightmareCerberus = 75452921; + } + + public ABCExecutor(GameAI ai, Duel duel) + : base(ai, duel) + { + //#1 Interactions + AddExecutor(ExecutorType.Activate, CardId.ApollousaBOG, ApollousaNegate); + AddExecutor(ExecutorType.Activate, CardId.AshBlossomAndJoyousSpring, DefaultAshBlossomAndJoyousSpring); + AddExecutor(ExecutorType.Activate, CardId.CalledbyTheGrave, DefaultCalledByTheGrave); + AddExecutor(ExecutorType.Activate, CardId.InfiniteImpermanence, DefaultInfiniteImpermanence); + AddExecutor(ExecutorType.Activate, CardId.SolemnStrike, DefaultSolemnStrike); + AddExecutor(ExecutorType.Activate, CardId.CrossOutDesignator, CrossOutNegate); + AddExecutor(ExecutorType.Activate, CardId.InvokedMechaba, CyberDragonInfinityNegate); + AddExecutor(ExecutorType.Activate, CardId.CyberDragonInfinity, CyberDragonInfinityNegate); + AddExecutor(ExecutorType.Activate, CardId.CyberDragonInfinity, CyberDragonInfinityAttach); + AddExecutor(ExecutorType.Activate, CardId.CyberDragonNova, CyberDragonNovaFloat); + AddExecutor(ExecutorType.Activate, CardId.IPMasquerena, IPMasquerenaEffect); + AddExecutor(ExecutorType.Activate, CardId.ABCDragonBuster, ABCBanish); + AddExecutor(ExecutorType.Activate, CardId.ABCDragonBuster, ABCUnionSummon); + + //#2 1st Searches, Equips and Photon summons + AddExecutor(ExecutorType.Activate, CardId.ReinforcementOfTheArmy, ROTAEffect); + AddExecutor(ExecutorType.Activate, CardId.UnionHangar, UnionHangarActivate); + AddExecutor(ExecutorType.Activate, CardId.UnionHangar, UnionHangarEquip); + AddExecutor(ExecutorType.SpSummon, CardId.PhotonThrasher, PhotonThrasherSummon); + AddExecutor(ExecutorType.SpSummon, CardId.PhotonVanisher, PhotonVanisherSummon); + AddExecutor(ExecutorType.Activate, CardId.PhotonOrbital, PhotonOrbitalEquip); + AddExecutor(ExecutorType.Activate, CardId.PhotonOrbital, PhotonOrbitalEffect); + AddExecutor(ExecutorType.Activate, CardId.Terraforming, TerraformingEffect); + AddExecutor(ExecutorType.Activate, CardId.UnauthorizedReactivation, UnauthorizedReactivationEffect); + AddExecutor(ExecutorType.Activate, CardId.UnionDriver, UnionDriverEffect); + + //#3 Monster sets + AddExecutor(ExecutorType.MonsterSet, CardId.CCrushWyvern, MonsterSet); + AddExecutor(ExecutorType.MonsterSet, CardId.BBusterDrake, MonsterSet); + AddExecutor(ExecutorType.MonsterSet, CardId.AAssaultCore, MonsterSet); + + //#4 Prioritized Summons and effects + AddExecutor(ExecutorType.SpSummon, CardId.CyberDragonNova, CyberDragonNovaSummon); + AddExecutor(ExecutorType.SpSummon, CardId.CyberDragonInfinity, CyberDragonInfinitySummon); + AddExecutor(ExecutorType.Summon, CardId.HeavyMechSupportArmor, HMSArmorSummon); + AddExecutor(ExecutorType.Summon, CardId.AAssaultCore, AAssaultCoreSummon); + AddExecutor(ExecutorType.Summon, CardId.BBusterDrake, BBusterDrakeSummon); + AddExecutor(ExecutorType.Summon, CardId.CCrushWyvern, CCrushWyvernSummon); + AddExecutor(ExecutorType.Activate, CardId.AAssaultCore, UnionSpSummon); + AddExecutor(ExecutorType.Activate, CardId.BBusterDrake, UnionSpSummon); + AddExecutor(ExecutorType.Activate, CardId.CCrushWyvern, UnionSpSummon); + + //#5 Knightmare Summons and effects (Turn 2+) + AddExecutor(ExecutorType.SpSummon, CardId.KnightmareUnicorn, KnightmareUnicornSummon); + AddExecutor(ExecutorType.SpSummon, CardId.KnightmarePhoenix, KnightmarePhoenixSummon); + AddExecutor(ExecutorType.SpSummon, CardId.KnightmareCerberus, KnightmareCerberusSummon); + AddExecutor(ExecutorType.Activate, CardId.KnightmareUnicorn, KnightmareUnicornEffect); + AddExecutor(ExecutorType.Activate, CardId.KnightmarePhoenix, KnightmarePhoenixEffect); + AddExecutor(ExecutorType.Activate, CardId.KnightmareCerberus, KnightmareCerberusEffect); + + //#6 Default Plays + AddExecutor(ExecutorType.SpSummon, CardId.UnionCarrier, UnionCarrierSummon); + AddExecutor(ExecutorType.Activate, CardId.UnionCarrier, UnionCarrierEffect); + AddExecutor(ExecutorType.Activate, CardId.GalaxySoldier, GalaxySoldierSpSummon); + AddExecutor(ExecutorType.Activate, CardId.GalaxySoldier, GalaxySoldierEffect); + AddExecutor(ExecutorType.SpSummon, CardId.ApollousaBOG, ApollousaSummon); + AddExecutor(ExecutorType.SpSummon, CardId.CrusadiaAvramax, CrusadiaAvramaxSummon); + AddExecutor(ExecutorType.Activate, CardId.CrusadiaAvramax, CrusadiaAvramaxEffect); + AddExecutor(ExecutorType.SpSummon, CardId.IPMasquerena, IPMasquerenaSummon); + AddExecutor(ExecutorType.SpSummon, CardId.ABCDragonBuster, ABCDragonBusterSummon); + + //#7 Unions Effects + AddExecutor(ExecutorType.Activate, CardId.CCrushWyvern, CCrushWyvernEffect); + AddExecutor(ExecutorType.Activate, CardId.BBusterDrake, BBusterDrakeEffect); + AddExecutor(ExecutorType.Activate, CardId.AAssaultCore, AAssaultCoreEffect); + AddExecutor(ExecutorType.Activate, CardId.HeavyMechSupportArmor, HMSArmorEffect); + AddExecutor(ExecutorType.Activate, CardId.HeavyMechSupportArmor, HMSArmorEquip); + AddExecutor(ExecutorType.Activate, CardId.AAssaultCore, UnionEquip); + AddExecutor(ExecutorType.Activate, CardId.BBusterDrake, UnionEquip); + AddExecutor(ExecutorType.Activate, CardId.CCrushWyvern, UnionEquip); + + AddExecutor(ExecutorType.Repos, MonsterRepos); + + //#8 Spell/Trap sets + AddExecutor(ExecutorType.SpellSet, CardId.CalledbyTheGrave, TrapSet); + AddExecutor(ExecutorType.SpellSet, CardId.InfiniteImpermanence, TrapSet); + AddExecutor(ExecutorType.SpellSet, CardId.CrossOutDesignator, TrapSet); + AddExecutor(ExecutorType.SpellSet, CardId.SolemnStrike, TrapSet); + + + } + private bool UnionHangarActivated = false; + private bool UnionHangarEquipped = false; + private bool UnionDriverUsed = false; + private bool PhotonOrbitalUsed = false; + private bool GalaxySoldierUsed = false; + private bool ABCBanishUsed = false; + private bool ABCUnionSummonUsed = false; + private bool UnionCarrierSummonTurn = false; + private bool NormalSummonUsed = false; + private bool HMSNormalUsed = false; + + public override bool OnSelectHand() + { + // go first + return true; + } + + public override void OnNewTurn() + { + UnionHangarActivated = false; + UnionHangarEquipped = false; + UnionDriverUsed = false; + PhotonOrbitalUsed = false; + GalaxySoldierUsed = false; + ABCBanishUsed = false; + ABCUnionSummonUsed = false; + UnionCarrierSummonTurn = false; + NormalSummonUsed = false; + HMSNormalUsed = false; + } + + private readonly int[] MonsterMassRemoval = + { + 12580477, //Raigeki + 53129443, //Dark Hole + 14532163, //Lightning Storm + 99330325, //Interrupted Kaiju Slumber + 69162969, //Lightning Vortex + 15693423, //Evenly Matched + 53582587, //Torrential Tribute + 8251996, //Ojama Delta Hurricane!! + 44883830, //Des Croaking + }; + + private readonly int[] ABCUnion = + { + CardId.AAssaultCore, + CardId.BBusterDrake, + CardId.CCrushWyvern + }; + + private readonly int[] discards = + { + CardId.AAssaultCore, + CardId.BBusterDrake, + CardId.CCrushWyvern, + CardId.PhotonThrasher, + CardId.UnionDriver, + }; + + private readonly int[] ExtraEquip = + { + CardId.CyberDragonInfinity, + CardId.InvokedMechaba, + CardId.ABCDragonBuster, + }; + + public override IList OnSelectCard(IList cards, int min, int max, long hint, bool cancelable) + { + //Extra Deck millers + int[] extradecksend = + { + 87602890, //Zaborg, The Mega Monarch + 95679145, //Maximus Dragma + 82734805, //Infernoid Tierra + 86062400, //Xyz Avenger + 63737050, //Ryu Okami + + }; + if (Util.GetLastChainCard() != null && Util.GetLastChainCard().IsCode(extradecksend) && Duel.LastChainPlayer != 0) + { + List list = new List(); + if (min >= 1) + list.Add(CardId.CyberDragonNova); + if (min >= 2) + list.Add(CardId.CyberDragonInfinity); + if (min >= 3) + { + list.Add(CardId.ABCDragonBuster); + list.Add(CardId.CrusadiaAvramax); + list.Add(CardId.IPMasquerena); + list.Add(CardId.KnightmareCerberus); + list.Add(CardId.KnightmarePhoenix); + list.Add(CardId.KnightmareUnicorn); + } + int todrop = min; + IList result = new List(); + IList ToRemove = new List(cards); + List record = new List(); + foreach (ClientCard card in ToRemove) + { + if (card?.Id != 0 && card.IsCode(list) && (!record.Contains(card.Id))) + { + record.Add(card.Id); + result.Add(card); + if (--todrop <= 0) break; + } + } + if (todrop <= 0) return result; + } + //Evenly Matched + if (Util.GetLastChainCard() != null && Util.GetLastChainCard().IsCode(15693423) && Duel.LastChainPlayer != 0) + { + List list = new List(); + if (Bot.HasInMonstersZone(CardId.CrusadiaAvramax)) + list.Add(CardId.CrusadiaAvramax); + if (Bot.HasInMonstersZone(CardId.ApollousaBOG) && !Bot.HasInMonstersZone(CardId.CrusadiaAvramax)) + list.Add(CardId.ApollousaBOG); + if (Bot.HasInMonstersZone(CardId.ABCDragonBuster) && !Bot.HasInMonstersZone(CardId.ApollousaBOG) && !Bot.HasInMonstersZone(CardId.CrusadiaAvramax)) + list.Add(CardId.ABCDragonBuster); + if (Bot.HasInMonstersZone(CardId.CrusadiaAvramax) && !Bot.HasInMonstersZone(CardId.ABCDragonBuster) && !Bot.HasInMonstersZone(CardId.ApollousaBOG) && !Bot.HasInMonstersZone(CardId.CrusadiaAvramax)) + list.Add(CardId.CyberDragonInfinity); + int todrop = min; + IList result = new List(); + IList ToRemove = new List(cards); + List record = new List(); + foreach (ClientCard card in ToRemove) + { + if (card?.Id != 0 && !card.IsCode(list)) + { + result.Add(card); + if (--todrop <= 0) break; + } + } + if (todrop <= 0) return result; + } + return null; + } + + public override CardPosition OnSelectPosition(int cardId, IList positions) + { + YGOSharp.OCGWrapper.NamedCard cardData = YGOSharp.OCGWrapper.NamedCard.Get(cardId); + if (cardData != null) + { + if (cardData.Attack <= 1000) + return CardPosition.FaceUpDefence; + if (cardData.HasType(CardType.Union) && Duel.Player == 1) + 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.CrusadiaAvramax) && !attacker.IsDisabled() && defender.IsSpecialSummoned) + attacker.RealPower += defender.Attack; + } + return base.OnPreBattleBetween(attacker, defender); + } + + private bool CrossOutNegate() + { + if (Duel.LastChainPlayer == 1) + { + ClientCard LastChainCard = Util.GetLastChainCard(); + if (LastChainCard.IsCode(CardId.InfiniteImpermanence)) + { + AI.SelectAnnounceID(CardId.InfiniteImpermanence); + return true; + } + if (LastChainCard.IsCode(CardId.AshBlossomAndJoyousSpring)) + { + AI.SelectAnnounceID(CardId.AshBlossomAndJoyousSpring); + return true; + } + if (LastChainCard.IsCode(CardId.CalledbyTheGrave)) + { + AI.SelectAnnounceID(CardId.CalledbyTheGrave); + return true; + } + } + return false; + } + + private bool ABCBanish() + { + if (ActivateDescription == Util.GetStringId(CardId.ABCDragonBuster, 1)) + return false; + if (Duel.LastChainPlayer == 0 || ABCBanishUsed) + return false; + ClientCard target = Util.GetBestEnemyCard(canBeTarget: true); + if (target == null || (target.IsFacedown() && Duel.Phase != DuelPhase.End && Duel.Player == 1) || ((target.HasType(CardType.Spell) || target.HasType(CardType.Trap)) && (!target.HasType(CardType.Equip) && !target.HasType(CardType.Continuous) && !target.HasType(CardType.Field)))) + return false; + AI.SelectOption(0); + if (Bot.HasInHand(ABCUnion)) + AI.SelectCard(ABCUnion); + else AI.SelectCard(Card.Location = CardLocation.Hand); + AI.SelectNextCard(target); + ABCBanishUsed = true; + return true; + } + + private bool ABCUnionSummon() + { + ClientCard LastChainCard = Util.GetLastChainCard(); + if (ActivateDescription == Util.GetStringId(CardId.ABCDragonBuster, 0)) + return false; + if (LastChainCard != null && LastChainCard.IsCode(CardId.ABCDragonBuster) && Duel.LastChainPlayer == 0) + return false; + if ((ABCBanishUsed && Duel.LastChainPlayer != 0) || (Duel.Phase == DuelPhase.End) || (Duel.Phase == DuelPhase.Battle && Bot.GetHandCount() == 0 && Enemy.GetMonsterCount() >= 1)) + { + if (!Bot.HasInExtra(CardId.ABCDragonBuster) && Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(CardId.ABCDragonBuster)) == 1) + return false; + ABCUnionSummonUsed = true; + return true; + } + if (Duel.LastChainPlayer == 0 || !ABCBanishUsed) + { + if (LastChainCard != null && LastChainCard.IsCode(CardId.IPMasquerena) && Util.ChainContainsCard(MonsterMassRemoval)) + { + ABCUnionSummonUsed = true; + return true; + } + if (LastChainCard != null && LastChainCard.IsCode(MonsterMassRemoval) && !LastChainCard.IsCode(15693423) && !Bot.HasInMonstersZone(CardId.IPMasquerena, notDisabled: true)) + { + ABCUnionSummonUsed = true; + return true; + } + if (Util.IsChainTarget(Card) && Duel.LastChainPlayer == 1)//TODO: add a blacklist + { + ABCUnionSummonUsed = true; + return true; + } + return false; + } + return false; + } + + private bool CyberDragonInfinityNegate() + { + if (Duel.LastChainPlayer != 1) + return false; + int[] Blacklist = + { + 74117290, //Dark World Dealings + 74519184, //Hand Destruction + 70368879, //Upstart Goblin + 93946239, //Into the Void + 73628505, //Terraforming + 55010259, //Gold Gadget + 29021114, //Silver Gadget + 34408491, //Beelze of the Diabolic Dragons + 8763963, //Beelzeus of the Diabolic Dragons + }; + ClientCard LastChainCard = Util.GetLastChainCard(); + if (LastChainCard.IsCode(Blacklist)) + return false; + if (LastChainCard.IsCode(423585) && ActivateDescription == Util.GetStringId(423585, 0)) + return false; + return true; + } + + private bool CyberDragonInfinityAttach() + { + if (Duel.LastChainPlayer != -1) + return false; + ClientCard target = Util.GetBestEnemyMonster(true, true); + if (target != null && target.IsAttack()) + { + AI.SelectCard(target); + return true; + } + return false; + } + + private bool CyberDragonNovaFloat() + { + if (Card.Location != CardLocation.Grave) + return false; + return true; + } + + private bool ApollousaNegate() + { + int[] Blacklist = + { + 64734921, //The Agent of Creation - Venus + 34408491, //Beelze of the Diabolic Dragons + 8763963, //Beelzeus of the Diabolic Dragons + 74586817, //PSY-Framelord Omega + 29353756, //ZW - Eagle Claw + }; + int[] HandMZone = + { + 93969023, //Black Metal Dragon + 81471108, //ZW - Tornado Bringer + 45082499, //ZW - Lightning Blade + 2648201, //ZW - Sleipnir Mail + 40941889, //ZW - Asura Strike + 6330307, //DZW - Chimera Clad + 14235211, //Rider of the Storm Winds + 38210374, //Explossum + 38601126, //Robot Buster Destruction Sword + 2602411, //Wizard Buster Destruction Sword + 76218313, //Dragon Buster Destruction Sword + }; + int[] Hand = + { + 94573223, //Inzektor Giga-Mantis + 21977828, //Inzektor Giga-Weevil + 89132148, //Photon Orbital + 76080032, //ZW - Unicorn Spear + 87008374, //ZW - Phoenix Bow + 12927849, //SZW - Fenrir Sword + }; + ClientCard LastChainCard = Util.GetLastChainCard(); + if (LastChainCard.IsCode(57774843) && (Duel.Phase == DuelPhase.Main1 || Duel.Phase == DuelPhase.Main2)) + return false; //Judgment Dragon + if (LastChainCard.IsCode(423585) && ActivateDescription == Util.GetStringId(423585, 0)) + return false; //Summoner Monk + if (LastChainCard.IsCode(43218406) && (LastChainCard.Location == CardLocation.MonsterZone)) + return false; //Water Gizmek + if (LastChainCard.HasSetcode(0x11e) && (LastChainCard.Location == CardLocation.Hand)) + return false; //Danger! + if (LastChainCard.HasSetcode(0x109a) && (LastChainCard.Location == CardLocation.Hand || LastChainCard.Location == CardLocation.MonsterZone) && (Duel.Phase == DuelPhase.Main1 || Duel.Phase == DuelPhase.Main2)) + return false; //Superheavy Samurai Soul + if (LastChainCard.IsCode(HandMZone) && (LastChainCard.Location == CardLocation.Hand || LastChainCard.Location == CardLocation.MonsterZone)) + return false; //Equip effects from hand or monster zone + if (LastChainCard.IsCode(Hand) && LastChainCard.Location == CardLocation.Hand) + return false; //Equip effects from hand + if (LastChainCard.IsCode(Blacklist)) + return false; + return true; + } + + private bool KnightmarePhoenixSummon() + { + if (Duel.Turn == 1) return false; + if (Enemy.GetSpellCount() == 0) return false; + if (Enemy.GetSpellCount() < Enemy.GetMonsterCount() && Bot.HasInExtra(CardId.KnightmareCerberus)) return false; + int[] materials = new[] { + CardId.CCrushWyvern, + CardId.BBusterDrake, + CardId.AAssaultCore, + CardId.PhotonThrasher, + CardId.PhotonVanisher, + CardId.GalaxySoldier, + CardId.UnionDriver, + CardId.HeavyMechSupportArmor, + }; + if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(materials)) >= 2) + { + AI.SelectMaterials(materials); + return true; + } + return false; + } + + private bool KnightmareCerberusSummon() + { + if (Duel.Turn == 1) return false; + if (Enemy.GetMonsterCount() == 0) return false; + if (Enemy.GetMonsterCount() < Enemy.GetSpellCount() && Bot.HasInExtra(CardId.KnightmarePhoenix)) return false; + ClientCard target = Util.GetBestEnemyMonster(true, true); + int[] materials = new[] { + CardId.CCrushWyvern, + CardId.BBusterDrake, + CardId.AAssaultCore, + CardId.PhotonThrasher, + CardId.PhotonVanisher, + CardId.GalaxySoldier, + CardId.UnionDriver, + CardId.HeavyMechSupportArmor, + }; + if (target != null && target.IsSpecialSummoned && target.Sequence < 5) + { + if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(materials)) >= 2) + { + AI.SelectMaterials(materials); + return true; + } + return false; + } + return false; + } + + private bool KnightmareUnicornSummon() + { + if (Duel.Turn == 1) return false; + int[] materials = new[] { + CardId.KnightmareCerberus, + CardId.KnightmarePhoenix, + CardId.CCrushWyvern, + CardId.BBusterDrake, + CardId.AAssaultCore, + CardId.PhotonThrasher, + CardId.PhotonVanisher, + CardId.GalaxySoldier, + CardId.UnionDriver, + CardId.HeavyMechSupportArmor, + }; + if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(materials)) >= 2 && (Bot.HasInMonstersZone(CardId.KnightmareCerberus) || Bot.HasInMonstersZone(CardId.KnightmarePhoenix))) + { + List materials2 = new List(); + List bot_monster = Bot.GetMonsters(); + bot_monster.Sort(CardContainer.CompareCardLevel); + int link_count = 0; + foreach (ClientCard card in bot_monster) + { + if (card.IsFacedown()) continue; + if (!materials2.Contains(card) && card.LinkCount <= 2 && card.IsCode(materials)) + { + materials2.Add(card); + link_count += (card.HasType(CardType.Link)) ? card.LinkCount : 1; + if (link_count >= 3) break; + } + } + if (link_count >= 3) + { + AI.SelectMaterials(materials2); + return true; + } + } + return false; + } + + private bool KnightmarePhoenixEffect() + { + ClientCard target = Util.GetBestEnemySpell(); + if (target == null) + return false; + if (Bot.HasInGraveyard(CardId.AAssaultCore) && Bot.HasInGraveyard(CardId.BBusterDrake) && Bot.HasInHand(CardId.CCrushWyvern)) + AI.SelectCard(CardId.CCrushWyvern); + if (Bot.HasInGraveyard(CardId.BBusterDrake) && Bot.HasInGraveyard(CardId.CCrushWyvern) && Bot.HasInHand(CardId.AAssaultCore)) + AI.SelectCard(CardId.AAssaultCore); + if (Bot.HasInGraveyard(CardId.CCrushWyvern) && Bot.HasInGraveyard(CardId.AAssaultCore) && Bot.HasInHand(CardId.BBusterDrake)) + AI.SelectCard(CardId.BBusterDrake); + if (Bot.HasInHand(discards)) + AI.SelectCard(discards); + else AI.SelectCard(Card.Location = CardLocation.Hand); + AI.SelectNextCard(target); + return true; + } + + private bool KnightmareCerberusEffect() + { + ClientCard target = Util.GetBestEnemyMonster(true, true); + if (target == null && !target.IsSpecialSummoned && target.Sequence >= 5) + return false; + if (Bot.HasInGraveyard(CardId.AAssaultCore) && Bot.HasInGraveyard(CardId.BBusterDrake) && Bot.HasInHand(CardId.CCrushWyvern)) + AI.SelectCard(CardId.CCrushWyvern); + if (Bot.HasInGraveyard(CardId.BBusterDrake) && Bot.HasInGraveyard(CardId.CCrushWyvern) && Bot.HasInHand(CardId.AAssaultCore)) + AI.SelectCard(CardId.AAssaultCore); + if (Bot.HasInGraveyard(CardId.CCrushWyvern) && Bot.HasInGraveyard(CardId.AAssaultCore) && Bot.HasInHand(CardId.BBusterDrake)) + AI.SelectCard(CardId.BBusterDrake); + if (Bot.HasInHand(discards)) + AI.SelectCard(discards); + else AI.SelectCard(Card.Location = CardLocation.Hand); + AI.SelectNextCard(target); + return true; + } + + private bool KnightmareUnicornEffect() + { + ClientCard target = Util.GetBestEnemyCard(canBeTarget: true); + if (target == null) + return false; + if (Bot.HasInGraveyard(CardId.AAssaultCore) && Bot.HasInGraveyard(CardId.BBusterDrake) && Bot.HasInHand(CardId.CCrushWyvern)) + AI.SelectCard(CardId.CCrushWyvern); + if (Bot.HasInGraveyard(CardId.BBusterDrake) && Bot.HasInGraveyard(CardId.CCrushWyvern) && Bot.HasInHand(CardId.AAssaultCore)) + AI.SelectCard(CardId.AAssaultCore); + if (Bot.HasInGraveyard(CardId.CCrushWyvern) && Bot.HasInGraveyard(CardId.AAssaultCore) && Bot.HasInHand(CardId.BBusterDrake)) + AI.SelectCard(CardId.BBusterDrake); + if (Bot.HasInHand(discards)) + AI.SelectCard(discards); + else AI.SelectCard(Card.Location = CardLocation.Hand); + AI.SelectNextCard(target); + return true; + } + + private bool ROTAEffect() + { + if (Bot.HasInHandOrHasInMonstersZone(CardId.PhotonThrasher)) + { + AI.SelectCard(CardId.PhotonVanisher); + return true; + } + else AI.SelectCard(CardId.PhotonThrasher); + return true; + } + + + private bool TerraformingEffect() + { + if (UnionHangarActivated) + return false; + AI.SelectCard(CardId.UnionHangar); + return true; + } + + private bool UnionHangarActivate() + { + if (ActivateDescription != Util.GetStringId(CardId.UnionHangar, 0)) + return false; + if (UnionHangarActivated) + return false; + if (Bot.HasInGraveyard(ABCUnion) && !NormalSummonUsed && Duel.Player == 0) + AI.SelectCard(CardId.HeavyMechSupportArmor); + if (Bot.HasInHand(CardId.BBusterDrake) || Bot.HasInHand(CardId.AAssaultCore)) + AI.SelectCard(CardId.CCrushWyvern); + else + AI.SelectCard(CardId.BBusterDrake); + UnionHangarActivated = true; + return true; + } + + //TODO: add when to equip other abc instead + private bool UnionHangarEquip() + { + if (ActivateDescription == Util.GetStringId(CardId.UnionHangar, 0)) + return false; + if (UnionHangarEquipped) + return false; + if (Duel.Player == 1) + AI.SelectCard(ABCUnion); + if (!ABCUnionSummonUsed && !UnionDriverUsed && Duel.Player == 0) + AI.SelectCard(CardId.UnionDriver); + UnionHangarEquipped = true; + return true; + } + + private bool PhotonThrasherSummon() + { + int[] cards = + { + CardId.AAssaultCore, + CardId.BBusterDrake, + CardId.CCrushWyvern, + CardId.UnionHangar, + CardId.Terraforming, + }; + if (!Bot.HasInHand(cards) && Bot.HasInHand(CardId.GalaxySoldier)) + return false; + return true; + } + + private bool PhotonVanisherSummon() + { + int[] mats = + { + CardId.AAssaultCore, + CardId.BBusterDrake, + CardId.CCrushWyvern, + CardId.PhotonThrasher, + }; + if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(mats)) >= 2 || Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(mats)) == 0 || Bot.HasInMonstersZone(CardId.IPMasquerena)) + return false; + return true; + } + + private bool PhotonOrbitalEquip() + { + if (Card.Location == CardLocation.Hand) + return true; + return false; + } + + private bool HMSArmorSummon() + { + if (!Bot.HasInHandOrInGraveyard(ABCUnion) && !Bot.HasInHand(CardId.GalaxySoldier) && (Bot.HasInMonstersZone(CardId.PhotonThrasher) || Bot.HasInHand(CardId.UnauthorizedReactivation) || Bot.HasInSpellZone(CardId.UnionHangar, notDisabled: true))) + { + NormalSummonUsed = true; + return true; + } + if (Bot.HasInGraveyard(ABCUnion) && Bot.GetMonstersInMainZone().Count() < 4) + { + NormalSummonUsed = true; + return true; + } + return false; + } + + private bool CCrushWyvernSummon() + { + NormalSummonUsed = true; + return true; + } + + private bool BBusterDrakeSummon() + { + if (Bot.HasInHand(CardId.CCrushWyvern)) + return false; + NormalSummonUsed = true; + return true; + } + + private bool AAssaultCoreSummon() + { + if (Bot.HasInHand(CardId.CCrushWyvern) || Bot.HasInHand(CardId.BBusterDrake)) + return false; + NormalSummonUsed = true; + return true; + } + + private bool UnauthorizedReactivationEffect() + { + //TODO: add when to equip other abc instead + if (Duel.LastChainPlayer == 0) + return false; + if (!UnionDriverUsed && !Bot.HasInHandOrInSpellZone(CardId.UnionHangar)) + { + AI.SelectCard(CardLocation.MonsterZone); + AI.SelectNextCard(CardId.UnionDriver); + return true; + } + if (Bot.HasInMonstersZone(CardId.CyberDragonInfinity)) + { + AI.SelectCard(CardId.CyberDragonInfinity); + { + if (Bot.GetRemainingCount(CardId.HeavyMechSupportArmor, 1) != 0) + AI.SelectNextCard(CardId.HeavyMechSupportArmor); + else AI.SelectNextCard(CardId.AAssaultCore); + } + return true; + } + return false; + } + + //NOTE: needs even better logic + private bool UnionDriverEffect() + { + if (UnionDriverUsed || Card.Location != CardLocation.SpellZone) + return false; + if (Bot.HasInHand(CardId.AAssaultCore) || Bot.HasInHand(CardId.BBusterDrake)) + AI.SelectCard(CardId.CCrushWyvern); + if (Bot.HasInMonstersZone(CardId.CCrushWyvern) && Bot.HasInHand(CardId.BBusterDrake) && Bot.HasInHand(CardId.AAssaultCore)) + AI.SelectCard(CardId.CCrushWyvern); + if (Bot.HasInMonstersZone(CardId.CCrushWyvern)) + AI.SelectCard(CardId.BBusterDrake); + if (!Bot.HasInMonstersZoneOrInGraveyard(CardId.AAssaultCore) && (Bot.HasInMonstersZone(CardId.BBusterDrake) && !Bot.HasInHand(CardId.AAssaultCore))) + AI.SelectCard(CardId.AAssaultCore); + if (!Bot.HasInHandOrHasInMonstersZone(CardId.BBusterDrake)) + AI.SelectCard(CardId.BBusterDrake); + UnionDriverUsed = true; + return true; + } + + private bool UnionSpSummon() + { + if (Card.Location == CardLocation.SpellZone) + return true; + return false; + } + + private bool UnionCarrierSummon() + { + if (Bot.HasInMonstersZone(CardId.UnionCarrier, true)) + return false; + + int[] materials = new[] { + CardId.CCrushWyvern, + CardId.BBusterDrake, + CardId.AAssaultCore, + CardId.PhotonThrasher, + CardId.PhotonVanisher, + CardId.HeavyMechSupportArmor, + CardId.UnionDriver, + CardId.GalaxySoldier, + }; + //NOTE: need to allow them if they are Equipped by effects other than union driver + if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(materials)) >= 2) + { + AI.SelectMaterials(materials); + UnionCarrierSummonTurn = true; + return true; + } + return false; + } + + private bool CrusadiaAvramaxSummon() + { + int[] materials = new[] { + CardId.IPMasquerena, + CardId.UnionCarrier, + CardId.KnightmareCerberus, + CardId.KnightmarePhoenix, + CardId.KnightmareUnicorn, + }; + if ((UnionCarrierSummonTurn && Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(materials)) == 2) || Duel.Turn == 1) + return false; + if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(materials)) >= 2) + { + List materials2 = new List(); + List bot_monster = Bot.GetMonsters(); + bot_monster.Sort(CardContainer.CompareCardLevel); + int link_count = 0; + foreach (ClientCard card in bot_monster) + { + if (card.IsFacedown()) continue; + if (!materials2.Contains(card) && card.LinkCount <= 3 && card.IsCode(materials)) + { + materials2.Add(card); + link_count += (card.HasType(CardType.Link)) ? card.LinkCount : 1; + if (link_count >= 4) break; + } + } + if (link_count >= 4) + { + AI.SelectMaterials(materials2); + return true; + } + } + return false; + } + + private bool CrusadiaAvramaxEffect() + { + ClientCard target = Util.GetBestEnemyCard(); + if (target == null) + return false; + AI.SelectNextCard(target); + return true; + } + + private bool ApollousaSummon() + { + if (Duel.Turn == 1) + return false; + int[] materials = new[] { + CardId.CCrushWyvern, + CardId.BBusterDrake, + CardId.AAssaultCore, + CardId.IPMasquerena, + CardId.UnionCarrier, + CardId.PhotonThrasher, + CardId.PhotonVanisher, + CardId.GalaxySoldier, + CardId.UnionDriver, + CardId.HeavyMechSupportArmor, + }; + if ((Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(materials)) >= 3 && Bot.HasInMonstersZone(CardId.IPMasquerena)) || Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(materials)) >= 4)//Needs to check for different names? + { + AI.SelectMaterials(materials); + return true; + } + return false; + } + + private bool IPMasquerenaSummon() + { + if (!UnionCarrierSummonTurn && Duel.Turn == 1) + return false; + int[] materials = new[] { + CardId.CCrushWyvern, + CardId.BBusterDrake, + CardId.AAssaultCore, + CardId.PhotonThrasher, + CardId.PhotonVanisher, + CardId.GalaxySoldier, + CardId.UnionDriver, + CardId.HeavyMechSupportArmor, + }; + if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(materials)) >= 2) + { + AI.SelectMaterials(materials); + return true; + } + return false; + } + + private bool IPMasquerenaEffect() + { + int[] materials1 = new[] { + CardId.CCrushWyvern, + CardId.BBusterDrake, + CardId.AAssaultCore, + CardId.IPMasquerena, + }; + int[] materials2 = new[] { + CardId.IPMasquerena, + CardId.UnionCarrier, + CardId.KnightmareCerberus, + CardId.KnightmarePhoenix, + CardId.KnightmareUnicorn, + }; + if (Duel.LastChainPlayer == 0) + return false; + ClientCard LastChainCard = Util.GetLastChainCard(); + if (LastChainCard != null && LastChainCard.IsCode(MonsterMassRemoval)) + { + if (Bot.HasInMonstersZone(CardId.ABCDragonBuster)) + { + AI.SelectCard(CardId.ApollousaBOG); + AI.SelectMaterials(materials1); + return true; + } + if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(materials2)) >= 2 && !Bot.HasInMonstersZone(CardId.ABCDragonBuster) && !ABCUnionSummonUsed) + { + AI.SelectCard(CardId.CrusadiaAvramax); + AI.SelectMaterials(materials2); + return true; + } + return false; + } + if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(materials1)) >= 3 && ABCUnionSummonUsed) + { + AI.SelectCard(CardId.ApollousaBOG); + AI.SelectMaterials(materials1); + return true; + } + if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(materials2)) >= 2 && ((!Bot.HasInMonstersZone(CardId.ABCDragonBuster) && !ABCUnionSummonUsed) || (Util.IsChainTarget(Card) && Duel.LastChainPlayer == 1))) + { + AI.SelectCard(CardId.CrusadiaAvramax); + AI.SelectMaterials(materials2); + return true; + } + return false; + } + + private bool CyberDragonNovaSummon() + { + int[] materials = new[] { + CardId.GalaxySoldier, + CardId.UnionDriver + }; + if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(materials)) >= 2) + { + AI.SelectMaterials(materials); + return true; + } + return false; + } + + private bool CyberDragonInfinitySummon() + { + AI.SelectMaterials(CardId.CyberDragonNova); + return true; + } + + private bool ABCDragonBusterSummon() + { + if (Bot.Graveyard.GetMatchingCardsCount(card => card.IsCode(ABCUnion)) >= 3) + AI.SelectMaterials(CardLocation.Grave); + else AI.SelectMaterials(CardLocation.MonsterZone); + return true; + } + + private bool AAssaultCoreEffect() + { + if (Card.Location != CardLocation.Grave) + return false; + if (!NormalSummonUsed && Bot.HasInGraveyard(CardId.HeavyMechSupportArmor)) + { + AI.SelectCard(CardId.HeavyMechSupportArmor); + return true; + } + if (Bot.HasInMonstersZone(CardId.IPMasquerena) && (!Bot.HasInHand(CardId.GalaxySoldier) || GalaxySoldierUsed)) + { + if (Bot.Graveyard.GetCardCount(CardId.CCrushWyvern) >= 2) + { + AI.SelectCard(CardId.CCrushWyvern); + return true; + } + if (Bot.Graveyard.GetCardCount(CardId.BBusterDrake) >= 2) + { + AI.SelectCard(CardId.BBusterDrake); + return true; + } + if (Bot.Graveyard.GetCardCount(CardId.AAssaultCore) >= 2) + { + AI.SelectCard(CardId.AAssaultCore); + return true; + } + return false; + } + AI.SelectCard(CardId.CCrushWyvern); + AI.SelectCard(CardId.BBusterDrake); + return true; + } + + private bool BBusterDrakeEffect() + { + if (Card.Location == CardLocation.Grave) + { + if (!NormalSummonUsed) + AI.SelectCard(CardId.HeavyMechSupportArmor); + if (!Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.CCrushWyvern)) + AI.SelectCard(CardId.CCrushWyvern); + if (!Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.AAssaultCore)) + AI.SelectCard(CardId.AAssaultCore); + if (Bot.HasInGraveyard(CardId.CCrushWyvern) && Bot.HasInGraveyard(CardId.AAssaultCore)) + AI.SelectCard(CardId.BBusterDrake); + return true; + } + return false; + } + + private bool CCrushWyvernEffect() + { + if (Card.Location == CardLocation.Grave) + { + if (!Bot.HasInMonstersZoneOrInGraveyard(CardId.BBusterDrake) && Bot.HasInHand(CardId.BBusterDrake)) + AI.SelectCard(CardId.BBusterDrake); + if (!Bot.HasInMonstersZoneOrInGraveyard(CardId.AAssaultCore) && Bot.HasInHand(CardId.AAssaultCore)) + AI.SelectCard(CardId.AAssaultCore); + if (Bot.HasInMonstersZoneOrInGraveyard(CardId.BBusterDrake) && Bot.HasInMonstersZoneOrInGraveyard(CardId.AAssaultCore)) + AI.SelectCard(CardId.CCrushWyvern); + else AI.SelectCard(ABCUnion); + return true; + } + return false; + } + + private bool HMSArmorEffect() + { + if (Card.Location == CardLocation.MonsterZone && !HMSNormalUsed) + { + if ((Bot.HasInHand(CardId.BBusterDrake) || Bot.HasInHand(CardId.AAssaultCore)) && Bot.HasInGraveyard(CardId.CCrushWyvern)) + AI.SelectCard(CardId.CCrushWyvern); + if (Bot.HasInGraveyard(CardId.BBusterDrake)) + AI.SelectCard(CardId.BBusterDrake); + if (Bot.HasInGraveyard(CardId.AAssaultCore)) + AI.SelectCard(CardId.AAssaultCore); + else AI.SelectCard(ABCUnion); + HMSNormalUsed = true; + return true; + } + return false; + } + + private bool HMSArmorEquip() + { + if (Card.Location == CardLocation.MonsterZone && HMSNormalUsed && Bot.HasInMonstersZone(ExtraEquip)) + { + if (Bot.HasInMonstersZone(CardId.CyberDragonInfinity)) + AI.SelectCard(CardId.CyberDragonInfinity); + if (Bot.HasInMonstersZone(CardId.InvokedMechaba)) + AI.SelectCard(CardId.InvokedMechaba); + if (Bot.HasInMonstersZone(CardId.ABCDragonBuster)) + AI.SelectCard(CardId.ABCDragonBuster); + return true; + } + return false; + } + + private bool UnionEquip() + { + if (Card.Location == CardLocation.MonsterZone && Util.IsTurn1OrMain2() && Bot.HasInMonstersZone(ExtraEquip)) + { + if (Bot.HasInMonstersZone(CardId.CyberDragonInfinity)) + AI.SelectCard(CardId.CyberDragonInfinity); + if (Bot.HasInMonstersZone(CardId.InvokedMechaba)) + AI.SelectCard(CardId.InvokedMechaba); + if (Bot.HasInMonstersZone(CardId.ABCDragonBuster)) + AI.SelectCard(CardId.ABCDragonBuster); + return true; + } + return false; + } + + private bool UnionCarrierEffect() + { + if (Bot.HasInMonstersZone(CardId.CyberDragonInfinity)) + { + AI.SelectCard(CardId.CyberDragonInfinity); + { + if (Bot.GetRemainingCount(CardId.HeavyMechSupportArmor, 1) != 0) + AI.SelectNextCard(CardId.HeavyMechSupportArmor); + else AI.SelectNextCard(CardId.AAssaultCore); + } + return true; + } + if (!UnionDriverUsed || !PhotonOrbitalUsed) + { + int[] mats = + { + CardId.AAssaultCore, + CardId.BBusterDrake, + CardId.CCrushWyvern, + CardId.PhotonThrasher, + }; + AI.SelectCard(CardId.UnionCarrier); + if (!UnionDriverUsed) + { + AI.SelectNextCard(CardId.UnionDriver); + return true; + } + if (!PhotonOrbitalUsed) + { + if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(mats)) >= 2 && (Bot.HasInHand(CardId.GalaxySoldier) || GalaxySoldierUsed)) + return false; + AI.SelectNextCard(CardId.PhotonOrbital); + return true; + } + return false; + } + return false; + } + + private bool PhotonOrbitalEffect() + { + if (Card.Location == CardLocation.SpellZone) + { + if (Bot.HasInHand(CardId.GalaxySoldier)) + AI.SelectCard(CardId.PhotonVanisher); + else AI.SelectCard(CardId.GalaxySoldier); + PhotonOrbitalUsed = true; + return true; + } + return false; + } + + private bool GalaxySoldierSpSummon() + { + if (GalaxySoldierUsed && !Bot.HasInMonstersZone(CardId.GalaxySoldier) && Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(discards)) != 1) + return false; + if (Card.Location == CardLocation.Hand) + { + if (Bot.HasInGraveyard(CardId.AAssaultCore) && Bot.HasInGraveyard(CardId.BBusterDrake) && Bot.HasInHand(CardId.CCrushWyvern)) + { + AI.SelectCard(CardId.CCrushWyvern); + return true; + } + if (Bot.HasInGraveyard(CardId.BBusterDrake) && Bot.HasInGraveyard(CardId.CCrushWyvern) && Bot.HasInHand(CardId.AAssaultCore)) + { + AI.SelectCard(CardId.AAssaultCore); + return true; + } + if (Bot.HasInGraveyard(CardId.CCrushWyvern) && Bot.HasInGraveyard(CardId.AAssaultCore) && Bot.HasInHand(CardId.BBusterDrake)) + { + AI.SelectCard(CardId.BBusterDrake); + return true; + } + if (Bot.HasInHand(CardId.AAssaultCore) || Bot.HasInHand(CardId.BBusterDrake) || Bot.HasInHand(CardId.CCrushWyvern) || Bot.HasInHand(CardId.PhotonThrasher) || Bot.HasInHand(CardId.UnionDriver)) + { + AI.SelectCard(discards); + return true; + } + if (!Bot.HasInHandOrHasInMonstersZone(ABCUnion) && (Bot.Hand.GetMatchingCardsCount(card => card.HasAttribute(CardAttribute.Light)) >= 3 || (Bot.Hand.GetMatchingCardsCount(card => card.HasAttribute(CardAttribute.Light)) >= 2 && Bot.HasInMonstersZone(CardId.GalaxySoldier)))) + { + return true; + } + } + return false; + } + + private bool GalaxySoldierEffect() + { + if (Card.Location == CardLocation.MonsterZone && !GalaxySoldierUsed) + { + AI.SelectCard(CardId.GalaxySoldier); + GalaxySoldierUsed = true; + return true; + } + return false; + } + + private bool MonsterSet() + { + if (!Bot.HasInHandOrInSpellZone(CardId.UnionHangar) && !Bot.HasInHand(CardId.UnauthorizedReactivation) && !Bot.HasInHand(CardId.GalaxySoldier) && Bot.GetMonsterCount() == 0 && (Duel.Turn == 1 || (Duel.Turn != 1 && Enemy.GetMonsterCount() >= 1))) + return true; + return false; + } + + private bool TrapSet() + { + if ((Bot.HasInMonstersZone(CardId.ABCDragonBuster) || Bot.HasInMonstersZone(CardId.InvokedMechaba)) && Bot.GetHandCount() == 1) + return false; + if (Util.IsTurn1OrMain2() || Bot.GetMonsterCount() == 0) + { + AI.SelectPlace(Zones.z0 + Zones.z1 + Zones.z3 + Zones.z4); + return true; + } + return false; + } + + private bool MonsterRepos() + { + if (Card.IsFacedown()) + return true; + return DefaultMonsterRepos(); + } + } +} diff --git a/Game/AI/Decks/AltergeistExecutor.cs b/Game/AI/Decks/AltergeistExecutor.cs index cc4f0daf..c39f9d71 100644 --- a/Game/AI/Decks/AltergeistExecutor.cs +++ b/Game/AI/Decks/AltergeistExecutor.cs @@ -211,7 +211,7 @@ public bool EvenlyMatched_Repos() return false; } - public bool isAltergeist(int id) + public bool isAltergeist(long id) { return (id == CardId.Marionetter || id == CardId.Hexstia || id == CardId.Protocol || id == CardId.Multifaker || id == CardId.Meluseek || id == CardId.Kunquery @@ -324,12 +324,7 @@ public int GetTotalATK(IList list) public int SelectSTPlace(ClientCard card=null, bool avoid_Impermanence = false) { - List list = new List(); - list.Add(0); - list.Add(1); - list.Add(2); - list.Add(3); - list.Add(4); + List list = new List { 0, 1, 2, 3, 4 }; int n = list.Count; while (n-- > 1) { @@ -2745,10 +2740,10 @@ public override BattlePhaseAction OnSelectAttackTarget(ClientCard attacker, ILis return null; } - public override IList OnSelectCard(IList cards, int min, int max, int hint, bool cancelable) + public override IList OnSelectCard(IList cards, int min, int max, long hint, bool cancelable) { - int HIINT_TOGRAVE = 504; + long HIINT_TOGRAVE = 504; if (max == 1 && cards[0].Location == CardLocation.Deck && Util.GetLastChainCard() != null && Util.GetLastChainCard().IsCode(23002292) && Bot.GetRemainingCount(CardId.WakingtheDragon,1) > 0) { @@ -2817,7 +2812,7 @@ public override CardPosition OnSelectPosition(int cardId, IList po return 0; } - public override int OnSelectPlace(int cardId, int player, CardLocation location, int available) + public override int OnSelectPlace(long cardId, int player, CardLocation location, int available) { if (player == 0) { diff --git a/Game/AI/Decks/BlueEyesExecutor.cs b/Game/AI/Decks/BlueEyesExecutor.cs index d20e3756..6cfa60b2 100644 --- a/Game/AI/Decks/BlueEyesExecutor.cs +++ b/Game/AI/Decks/BlueEyesExecutor.cs @@ -133,7 +133,7 @@ public override void OnNewTurn() SoulChargeUsed = false; } - public override IList OnSelectCard(IList cards, int min, int max, int hint, bool cancelable) + public override IList OnSelectCard(IList cards, int min, int max, long hint, bool cancelable) { Logger.DebugWriteLine("OnSelectCard " + cards.Count + " " + min + " " + max); if (max == 2 && cards[0].Location == CardLocation.Deck) diff --git a/Game/AI/Decks/DoEveryThingExecutor.cs b/Game/AI/Decks/DoEveryThingExecutor.cs index 3da03b2e..ca0c1780 100644 --- a/Game/AI/Decks/DoEveryThingExecutor.cs +++ b/Game/AI/Decks/DoEveryThingExecutor.cs @@ -25,7 +25,7 @@ public DoEverythingExecutor(GameAI ai, Duel duel) AddExecutor(ExecutorType.SpellSet); } - public override IList OnSelectCard(IList cards, int min, int max, int hint, bool cancelable) + public override IList OnSelectCard(IList cards, int min, int max, long hint, bool cancelable) { if (Duel.Phase == DuelPhase.BattleStart) return null; diff --git a/Game/AI/Decks/DragmaExecutor.cs b/Game/AI/Decks/DragmaExecutor.cs new file mode 100644 index 00000000..9f9dec53 --- /dev/null +++ b/Game/AI/Decks/DragmaExecutor.cs @@ -0,0 +1,933 @@ +using YGOSharp.OCGWrapper.Enums; +using System.Collections.Generic; + +/* Translation Guide + * Dragma = Dogmatika + * Bastard = Titaniklad the Ash Dragon + */ + +namespace WindBot.Game.AI.Decks +{ + [Deck("Dogmatika", "AI_Dogmatika")] + 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 = 60303245; + public const int InvokedGardna = 2220237; + public const int InvokedMechaba = 75286621; + public const int InvokedCaliga = 13529466; + 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 = 74586817; + public const int StaplePegasus = 98506199; + } + + 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, AleisterEffect); + 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.Summon, CardId.InvokedAleister, AleisterSummon); + 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.Summon, CardId.DragmaEcclesia, EcclesiaNormal); + AddExecutor(ExecutorType.Activate, CardId.DragmaEcclesia, EcclesiaEffect); + 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.StapleNtss, NtssPop); + AddExecutor(ExecutorType.Activate, CardId.StapleOmega, OmegaRecur); + AddExecutor(ExecutorType.Activate, CardId.StaplePegasus, PegasusSpin); + + // priority 5 - niche scenarios + 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.ShaddollRuq, 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; + 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) // NOTE: 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; + } + } + + // if caliga is restricting attacks, swing with the strongest first + if (attackers.ContainsCardWithId(CardId.InvokedCaliga)) + { + return Util.GetBestBotMonster(); + } + + 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 AleisterEffect() + { + // search effect + if (Card.Location == CardLocation.MonsterZone) + { + return true; + } + + // 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() + { + // battle phase buff + if (Card.Location == CardLocation.MonsterZone) + { + return true; + } + + // summon for body at end of main + if (Duel.Player == 1 && Duel.MainPhaseEnd) + { + AI.SelectCard(Util.GetBestEnemyMonster(true)); + return true; + } + + // summon to negate + ClientCard chainCard = Util.GetLastChainCard(); + if (Duel.LastChainPlayer == 1 && chainCard != null && chainCard.Location == CardLocation.MonsterZone) + { + 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 || Duel.CurrentChain.ContainsCardWithId(CardId.ShaddollRuq))) + { + return false; + } + + ClientCard enemyMon = Util.GetProblematicEnemyMonster(0, true); + 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() + { + // 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)) + && (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()) // NOTE: Can check for already has counter? + { + AI.SelectCard(chainCard); + return true; + } + + if (Duel.Phase == DuelPhase.End) + { + ClientCard enemyMon = Util.GetBestEnemyMonster(true, true); + 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 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 InvocationEffect() + { + // shuffle effect + if (Card.Location == CardLocation.Grave) + { + return true; + } + + 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.HasInMonstersZoneOrInGraveyard(CardId.InvokedGardna)) + { + AI.SelectCard(CardId.InvokedMechaba); + AI.SelectMaterials(CardId.InvokedGardna); + 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); + 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) + { + AI.SelectCard(CardId.InvokedCaliga); + AI.SelectMaterials(darkCards); + return true; + } + + return false; + } + + // 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 EcclesiaNormal() + { + return true; + } + + private bool EcclesiaEffect() + { + if (Card.Location == CardLocation.Hand) + { + return true; + } + + if (!Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.DragmaMaximus)) + { + AI.SelectCard(CardId.DragmaMaximus); + EcclesiaUsed = true; + return true; + } + + if (!Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.DragmaFleur) && !MaximusUsed && !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 + // TODO: Refine what not to 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)) + { + // let ecclesia search fleur + BastardSentThisTurn = false; + AI.SelectCard(CardId.DragmaFleur); + AI.SelectOption(BastardOptions.search); + return true; + } + + return false; + } + + // priority 4 - misc send targets + private bool NtssPop() + { + ClientCard bestCard = Util.GetBestEnemyCard(false, true); + if (bestCard != null) + { + AI.SelectCard(bestCard); + return true; + } + 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() + { + return true; + } + + 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/Game/AI/Decks/DragunExecutor.cs b/Game/AI/Decks/DragunExecutor.cs index 660cfba1..3466b62d 100644 --- a/Game/AI/Decks/DragunExecutor.cs +++ b/Game/AI/Decks/DragunExecutor.cs @@ -136,7 +136,7 @@ public override CardPosition OnSelectPosition(int cardId, IList po return 0; } - public override int OnSelectPlace(int cardId, int player, CardLocation location, int available) + public override int OnSelectPlace(long cardId, int player, CardLocation location, int available) { if (location == CardLocation.MonsterZone) { diff --git a/Game/AI/Decks/FrogExecutor.cs b/Game/AI/Decks/FrogExecutor.cs index a4c2d6a1..b3130578 100644 --- a/Game/AI/Decks/FrogExecutor.cs +++ b/Game/AI/Decks/FrogExecutor.cs @@ -49,7 +49,7 @@ public FrogExecutor(GameAI ai, Duel duel) AddExecutor(ExecutorType.Activate, CardId.DupeFrog, DupeFrog); AddExecutor(ExecutorType.Activate, CardId.FlipFlopFrog, FlipFlopFrog); AddExecutor(ExecutorType.Activate, CardId.Ronintoadin, Ronintoadin); - AddExecutor(ExecutorType.Activate, CardId.TreebornFrog); + AddExecutor(ExecutorType.Activate, CardId.TreebornFrog, TreebornFrog); AddExecutor(ExecutorType.Activate, CardId.Unifrog); AddExecutor(ExecutorType.Summon, CardId.CryomancerOfTheIceBarrier, SummonFrog); @@ -83,6 +83,19 @@ public FrogExecutor(GameAI ai, Duel duel) private int m_swapFrogSummoned; private int m_flipFlopFrogSummoned; + private int m_treebornFrogCount = 0; + + public override void OnNewTurn() + { + m_treebornFrogCount = 0; + base.OnNewTurn(); + } + + private bool TreebornFrog() + { + m_treebornFrogCount++; + return m_treebornFrogCount <= 5; + } private bool SwapFrogSummon() { diff --git a/Game/AI/Decks/HorusExecutor.cs b/Game/AI/Decks/HorusExecutor.cs index 97fe2f79..0a6fd793 100644 --- a/Game/AI/Decks/HorusExecutor.cs +++ b/Game/AI/Decks/HorusExecutor.cs @@ -28,7 +28,7 @@ public class CardId public const int FoolishBurial = 81439173; public const int MonsterReborn = 83764718; public const int MysticalSpaceTyphoon = 5318639; - public const int BellowOfTheSilverDragon = 80600103; + public const int SilversCry = 87025064; public const int Mountain = 50913601; public const int DragonsRebirth = 20638610; public const int MirrorForce = 44095762; @@ -52,7 +52,7 @@ public HorusExecutor(GameAI ai, Duel duel) : base(ai, duel) AddExecutor(ExecutorType.Activate, CardId.HammerShot, DefaultHammerShot); AddExecutor(ExecutorType.Activate, CardId.Fissure); - AddExecutor(ExecutorType.Activate, CardId.BellowOfTheSilverDragon, BellowOfTheSilverDragon); + AddExecutor(ExecutorType.Activate, CardId.SilversCry, SilversCry); AddExecutor(ExecutorType.Activate, CardId.MonsterReborn, MonsterReborn); AddExecutor(ExecutorType.Summon, CardId.WhiteNightDragon, WhiteNightDragon); @@ -89,7 +89,7 @@ private bool FoolishBurial() return false; } - private bool BellowOfTheSilverDragon() + private bool SilversCry() { if (Duel.Player == 0 && (Duel.Phase == DuelPhase.Draw || Duel.Phase == DuelPhase.Standby)) return false; diff --git a/Game/AI/Decks/Level8Executor.cs b/Game/AI/Decks/Level8Executor.cs index 0cd7215b..0d31c711 100644 --- a/Game/AI/Decks/Level8Executor.cs +++ b/Game/AI/Decks/Level8Executor.cs @@ -237,7 +237,7 @@ public override CardPosition OnSelectPosition(int cardId, IList po return 0; } - public override int OnSelectPlace(int cardId, int player, CardLocation location, int available) + public override int OnSelectPlace(long cardId, int player, CardLocation location, int available) { if (location == CardLocation.SpellZone) { diff --git a/Game/AI/Decks/MathMechExecutor.cs b/Game/AI/Decks/MathMechExecutor.cs new file mode 100644 index 00000000..00a97a32 --- /dev/null +++ b/Game/AI/Decks/MathMechExecutor.cs @@ -0,0 +1,379 @@ +using System; +using YGOSharp.OCGWrapper.Enums; +using System.Collections.Generic; +using System.Diagnostics; +using WindBot; +using WindBot.Game; +using WindBot.Game.AI; +using System.Linq; + +namespace WindBot.Game.AI.Decks +{ + [Deck("MathMech", "AI_Mathmech")] + public class MathmechExecutor : DefaultExecutor + { + public class CardId + { + public const int MathmechNebla = 53577438; + public const int MathmechSigma = 27182739; + public const int MathmechDivision = 89743495; + public const int MathmechAddition = 80965043; + public const int MathmechSubtra = 16360142; + public const int Mathmechdouble = 52354896; + public const int MathmechFinalSigma = 42632209; + public const int Mathmechalem = 85692042; + public const int MathmechMagma = 15248594; + public const int BalancerLord = 08567955; + public const int LightDragon = 61399402; + + // spells + public const int upstartGoblin = 70368879; + public const int raigeki = 12580477; + public const int cynetmining = 57160136; + public const int PotOfDesires= 35261759; + public const int lightningStorm = 14532163; + public const int cosmicCyclone = 08267140; + public const int foolishBurial = 81439173; + public const int OneTimePasscode = 93104632; + public const int mathmechEquation = 14025912; + //traps + public const int threanteningRoar = 36361633; + //tokens + public const int securitytoken = 93104633; + + } + public MathmechExecutor(GameAI ai, Duel duel) + : base(ai, duel) + { + AddExecutor(ExecutorType.Activate, CardId.raigeki ,when_raigeki); + AddExecutor(ExecutorType.Activate, CardId.upstartGoblin); + AddExecutor(ExecutorType.Activate, CardId.OneTimePasscode); + AddExecutor(ExecutorType.SpellSet, CardId.threanteningRoar); + AddExecutor(ExecutorType.Activate,CardId.cosmicCyclone , when_cosmic); + AddExecutor(ExecutorType.Activate,CardId.lightningStorm ,lightstorm_target); + AddExecutor(ExecutorType.Activate,CardId.foolishBurial,foolish_burial_target); + AddExecutor(ExecutorType.Activate,CardId.mathmechEquation,mathmech_equation_target); + AddExecutor(ExecutorType.Activate,CardId.PotOfDesires); + + + AddExecutor(ExecutorType.Summon, CardId.MathmechNebla); + AddExecutor(ExecutorType.Summon,CardId.BalancerLord ); + AddExecutor(ExecutorType.Summon, CardId.Mathmechdouble); + AddExecutor(ExecutorType.Summon, CardId.MathmechSubtra); + AddExecutor(ExecutorType.Summon, CardId.MathmechAddition); + AddExecutor(ExecutorType.Summon, CardId.MathmechDivision); + AddExecutor(ExecutorType.Summon, CardId.MathmechDivision); + AddExecutor(ExecutorType.Activate, CardId.MathmechSigma); + AddExecutor(ExecutorType.Activate,CardId.threanteningRoar); + + //xyz summons + AddExecutor(ExecutorType.SpSummon, CardId.Mathmechalem, when_Mathmechalem); + //xyz effects + AddExecutor(ExecutorType.Activate, CardId.Mathmechalem, mathchalenEffect); + //Synchro + AddExecutor(ExecutorType.SpSummon, CardId.MathmechFinalSigma , FinalSigmaSummon); + + AddExecutor(ExecutorType.Activate, CardId.Mathmechdouble, doubleEffect); + + //normal effects + AddExecutor(ExecutorType.Activate, CardId.MathmechNebla, NeblaEffect); + AddExecutor(ExecutorType.Activate,CardId.MathmechDivision , divisionEffect); + AddExecutor(ExecutorType.Activate,CardId.BalancerLord , active_balancer); + AddExecutor(ExecutorType.Activate, CardId.MathmechSubtra , whom_subtra); + AddExecutor(ExecutorType.Activate, CardId.MathmechAddition , whom_addition); + //spell effects + AddExecutor(ExecutorType.Activate, CardId.cynetmining , how_to_cynet_mine); + AddExecutor(ExecutorType.SpSummon, CardId.MathmechMagma, MagmaSummon); + AddExecutor(ExecutorType.Activate,CardId.MathmechFinalSigma); + AddExecutor(ExecutorType.Activate,CardId.MathmechMagma); + + + //function + + } + + public override bool OnSelectHand() + { + return false; + } + private bool when_cosmic() + { + + if (Enemy.GetSpellCount() > 1) + { + AI.SelectCard(Util.GetBestEnemySpell()); + return true; + } + else + { + return false; + } + } + private bool divisionEffect() + { + if (Enemy.GetMonsterCount() > 0) + { + AI.SelectCard(Util.GetBestEnemyMonster(canBeTarget:true,onlyFaceup:true)); + return true; + } + else + { + return false; + } + } + + private bool when_raigeki() + { + if (Enemy.GetMonsterCount() > 3) + { + return true; + } + else + { + return false; + } + } + + private bool whom_addition() + { + AI.SelectCard(Util.GetBestBotMonster(onlyATK:true)); + return true; + } + + private bool whom_subtra() + { + try + { + AI.SelectCard(Util.GetBestEnemyMonster(onlyFaceup: true, canBeTarget: true)); + return true; + } + catch (Exception e) + { + return true; + } + } + + private bool active_balancer() + { + if (Bot.HasInHand(CardId.MathmechNebla)) + { + AI.SelectCard(CardId.MathmechNebla); + return true; + } + else + { + return true; + } + } + private bool lightstorm_target() + { + if ((Enemy.MonsterZone.ToList().Count > Enemy.SpellZone.ToList().Count ) && Enemy.MonsterZone.ToList().Count>3) + { + AI.SelectPlace(Zones.MonsterZones); + return true; + } + else + { + AI.SelectPlace(Zones.SpellZones); + return true; + } + + } + + private bool mathmech_equation_target() + { + if (Bot.HasInGraveyard(CardId.MathmechNebla)) + { + AI.SelectCard(CardId.MathmechNebla); + return true; + } + else + { + AI.SelectCard((Util.GetBestBotMonster(onlyATK: true))); + return true; + } + } + + private bool foolish_burial_target() + { + AI.SelectCard(CardId.MathmechNebla); + return true; + } + private bool how_to_cynet_mine() + { + AI.SelectCard(Util.GetWorstBotMonster()); + if (!Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.MathmechSigma)) + { + AI.SelectNextCard(CardId.MathmechSigma); + return true; + } + return true; + } + private bool when_Mathmechalem() + { + if (Bot.HasInMonstersZone(CardId.MathmechNebla)){ + return false; + } + else if(Bot.HasInMonstersZone(CardId.MathmechSigma) && Bot.HasInMonstersZone(CardId.Mathmechdouble)) + { + return false; + } + else if (Bot.HasInMonstersZone(CardId.Mathmechalem)) + { + return false; + } + else + { + return true; + } + } + private bool FinalSigmaSummon() + { + if (Duel.Turn < 1) + { + return false; + } + if ((Bot.HasInMonstersZone(CardId.Mathmechdouble) && (( Bot.HasInMonstersZone(CardId.MathmechSigma)) || Bot.HasInMonstersZone(CardId.MathmechNebla)))) + { + AI.SelectPosition(CardPosition.Attack); + try { AI.SelectPlace(Zones.ExtraMonsterZones); } + catch { } + + return true; + } + else + { + return true; + } + + } + private bool NeblaEffect() + { + bool a = Bot.HasInMonstersZone(CardId.MathmechSubtra) || Bot.HasInMonstersZone(CardId.securitytoken) || Bot.HasInMonstersZone(CardId.MathmechSigma) || Bot.HasInMonstersZone(CardId.MathmechAddition) || Bot.HasInMonstersZone(CardId.Mathmechalem) || Bot.HasInMonstersZone(CardId.MathmechDivision); + if (a) + { + List cards = new List(); + cards.Add(CardId.MathmechSigma); + cards.Add(CardId.MathmechSubtra); + cards.Add(CardId.MathmechAddition); + cards.Add(item:CardId.MathmechDivision); + cards.Add(item:CardId.Mathmechalem); + cards.Add(CardId.securitytoken); + int u = 0; + List monsters = Bot.GetMonstersInMainZone(); + for (int i = 0; i < monsters.Count; i++) + { + if (cards.Contains(monsters[i].Id)) + { + u = monsters[i].Id; + break; + } + else + { + u = CardId.securitytoken; + } + } + AI.SelectCard(CardId.securitytoken); + AI.SelectNextCard(CardId.Mathmechdouble); + return true; + + + } + if (Card.Location == CardLocation.Grave) + { + return true; + } + else + { + return false; + } + } + private bool doubleEffect() + { + if (Bot.HasInMonstersZone(CardId.MathmechNebla) || Bot.HasInMonstersZone(CardId.MathmechSigma)) + { + return true; + }; + if (Card.Location == CardLocation.Grave ) + { + return true; + } + else + { + return false; + } + } + + private bool mathchalenEffect() + + { + if (Duel.Turn < 1) + { + return false; + } + if ( (Bot.HasInHandOrInGraveyard(CardId.MathmechNebla) && !Bot.HasInMonstersZone(CardId.MathmechNebla)) && (Card.Location == CardLocation.FieldZone && Card.HasXyzMaterial(0)) ) + { + AI.SelectCard(CardId.Mathmechalem); + AI.SelectNextCard(CardId.MathmechNebla); + return true; + } + + if (Bot.HasInHandOrInGraveyard(CardId.Mathmechdouble) && + (Bot.HasInMonstersZone(CardId.MathmechNebla) || Bot.HasInMonstersZone(CardId.MathmechSigma)) && + Card.Location == CardLocation.FieldZone && Card.HasXyzMaterial(0)) + { + AI.SelectCard(CardId.Mathmechalem); + AI.SelectNextCard(CardId.Mathmechdouble); + return true; + } + if (!Bot.HasInHandOrInGraveyard(CardId.MathmechNebla) && Card.HasXyzMaterial(2)) + { + AI.SelectCard(CardId.MathmechNebla); + AI.SelectThirdCard(CardId.MathmechNebla); + return true; + } + + if (!Bot.HasInHandOrInGraveyard(CardId.MathmechSigma) && Card.HasXyzMaterial(2)) + { + AI.SelectCard(CardId.MathmechSigma); + AI.SelectThirdCard(CardId.MathmechSigma); + return true; + } + else + { + return false; + }; + + + } + + private bool MagmaSummon() + { + if (Bot.HasInMonstersZone(CardId.MathmechNebla)) + { + return false; + } + + if (Bot.HasInMonstersZone(CardId.MathmechSigma) && Bot.HasInMonstersZone(CardId.Mathmechdouble)) + { + return false; + } + else + { + return true; + } + } + + public override int OnSelectPlace(long cardId, int player, CardLocation location, int available) + { + if (cardId == CardId.MathmechFinalSigma) + { + if ((Zones.z5 & available) > 0) return Zones.z5; + if ((Zones.z6 & available) > 0) return Zones.z6; + } + return base.OnSelectPlace(cardId, player, location, available); + } + + } + +} diff --git a/Game/AI/Decks/OrcustExecutor.cs b/Game/AI/Decks/OrcustExecutor.cs index 86f867b1..9d812d43 100644 --- a/Game/AI/Decks/OrcustExecutor.cs +++ b/Game/AI/Decks/OrcustExecutor.cs @@ -1,1135 +1,1135 @@ -using System; -using YGOSharp.OCGWrapper.Enums; -using System.Collections.Generic; -using WindBot; -using WindBot.Game; -using WindBot.Game.AI; -using System.Linq; - -namespace WindBot.Game.AI.Decks -{ - [Deck("Orcust", "AI_Orcust")] - class OrcustExecutor : DefaultExecutor - { - public class CardId - { - public const int OrcustKnightmare = 4055337; - public const int OrcustHarpHorror = 57835716; - public const int OrcustCymbalSkeleton = 21441617; - public const int WorldLegacyWorldWand = 93920420; - public const int ThePhantomKnightsofAncientCloak = 90432163; - public const int ThePhantomKnightsofSilentBoots = 36426778; - - public const int TrickstarCarobein = 98169343; - public const int TrickstarCandina = 61283655; - public const int ArmageddonKnight = 28985331; - public const int ScrapRecycler = 4334811; - public const int DestrudoTheLostDragonsFrisson = 5560911; - public const int JetSynchron = 9742784; - - public const int AshBlossomJoyousSpring = 14558127; - public const int GhostBelleHauntedMansion = 73642296; - public const int MaxxC = 23434538; - - public const int SkyStrikerMobilizeEngage = 63166095; - public const int SkyStrikerMechaEagleBooster = 25733157; - public const int SkyStrikerMechaHornetDrones = 52340444; - public const int SkyStrikerMechaHornetDronesToken = 52340445; - public const int TrickstarLightStage = 35371948; - public const int OrcustratedBabel = 90351981; - - public const int ReinforcementofTheArmy = 32807846; - public const int Terraforming = 73628505; - public const int FoolishBurial = 81439173; - public const int CalledbyTheGrave = 24224830; - - public const int ThePhantomKnightsofShadeBrigandine = 98827725; - public const int PhantomKnightsFogBlade = 25542642; - public const int OrcustratedClimax = 703897; - - public const int BorreloadSavageDragon = 27548199; - public const int ShootingRiserDragon = 68431965; - public const int SheorcustDingirsu = 93854893; - public const int BorrelswordDragon = 85289965; - public const int LongirsuTheOrcustOrchestrator = 76145142; - public const int ThePhantomKnightsofRustyBardiche = 26692769; - public const int KnightmarePhoenix = 2857636; - public const int GalateaTheOrcustAutomaton = 30741503; - public const int CrystronNeedlefiber = 50588353; - public const int SkyStrikerAceKagari = 63288573; - public const int KnightmareMermaid = 3679218; - public const int SalamangreatAlmiraj = 60303245; - } - - public OrcustExecutor(GameAI ai, Duel duel) - : base(ai, duel) - { - AddExecutor(ExecutorType.Activate, CardId.SkyStrikerMechaEagleBooster, EagleBoosterEffect); - AddExecutor(ExecutorType.Activate, CardId.OrcustratedClimax, ClimaxEffect); - - AddExecutor(ExecutorType.Activate, CardId.MaxxC, DefaultMaxxC); - AddExecutor(ExecutorType.Activate, CardId.AshBlossomJoyousSpring, DefaultAshBlossomAndJoyousSpring); - AddExecutor(ExecutorType.Activate, CardId.GhostBelleHauntedMansion, DefaultGhostBelleAndHauntedMansion); - AddExecutor(ExecutorType.Activate, CardId.CalledbyTheGrave, DefaultCalledByTheGrave); - - AddExecutor(ExecutorType.Activate, CardId.Terraforming, TerraformingEffect); - AddExecutor(ExecutorType.Activate, CardId.ReinforcementofTheArmy, ReinforcementofTheArmyEffect); - AddExecutor(ExecutorType.Activate, CardId.FoolishBurial, FoolishBurialEffect); - - AddExecutor(ExecutorType.Activate, CardId.TrickstarLightStage, LightStageEffect); - - AddExecutor(ExecutorType.Activate, CardId.SkyStrikerMobilizeEngage, EngageEffect); - AddExecutor(ExecutorType.Activate, CardId.SkyStrikerMechaHornetDrones, DronesEffectFirst); - AddExecutor(ExecutorType.SpSummon, CardId.SkyStrikerAceKagari); - AddExecutor(ExecutorType.Activate, CardId.SkyStrikerAceKagari); - - AddExecutor(ExecutorType.SpSummon, CardId.KnightmareMermaid, KnightmareMermaidSummon); - AddExecutor(ExecutorType.Activate, CardId.KnightmareMermaid, KnightmareMermaidEffect); - - AddExecutor(ExecutorType.SpSummon, CardId.TrickstarCarobein, CarobeinSummon); - AddExecutor(ExecutorType.Activate, CardId.TrickstarCarobein); - - AddExecutor(ExecutorType.SpellSet, CardId.ThePhantomKnightsofShadeBrigandine); - - AddExecutor(ExecutorType.Summon, CardId.ArmageddonKnight, ArmageddonKnightSummon); - AddExecutor(ExecutorType.Activate, CardId.ArmageddonKnight, ArmageddonKnightEffect); - - AddExecutor(ExecutorType.Summon, CardId.ScrapRecycler, ScrapRecyclerSummon); - AddExecutor(ExecutorType.Activate, CardId.ScrapRecycler, ScrapRecyclerEffect); - - AddExecutor(ExecutorType.Activate, CardId.SkyStrikerMechaHornetDrones, DronesEffect); - - AddExecutor(ExecutorType.Summon, CardId.JetSynchron, JetSynchronSummon); - - AddExecutor(ExecutorType.Activate, CardId.DestrudoTheLostDragonsFrisson, DestrudoSummon); - - AddExecutor(ExecutorType.SpSummon, CardId.CrystronNeedlefiber, NeedlefiberSummonFirst); - AddExecutor(ExecutorType.Activate, CardId.CrystronNeedlefiber, NeedlefiberEffect); - - AddExecutor(ExecutorType.Activate, CardId.ShootingRiserDragon, ShootingRiserDragonEffect); - - AddExecutor(ExecutorType.Summon, CardId.TrickstarCandina, CandinaSummon); - AddExecutor(ExecutorType.Activate, CardId.TrickstarCandina, CandinaEffect); - - AddExecutor(ExecutorType.Summon, CardId.JetSynchron, OneCardComboSummon); - AddExecutor(ExecutorType.Summon, CardId.ThePhantomKnightsofAncientCloak, OneCardComboSummon); - AddExecutor(ExecutorType.Summon, CardId.ThePhantomKnightsofSilentBoots, OneCardComboSummon); - AddExecutor(ExecutorType.SpSummon, CardId.SalamangreatAlmiraj, AlmirajSummon); - - AddExecutor(ExecutorType.Activate, CardId.ThePhantomKnightsofShadeBrigandine, ShadeBrigandineSummonFirst); - - AddExecutor(ExecutorType.SpSummon, CardId.KnightmarePhoenix, KnightmarePhoenixSummon); - AddExecutor(ExecutorType.Activate, CardId.KnightmarePhoenix, KnightmarePhoenixEffect); - - AddExecutor(ExecutorType.SpSummon, CardId.GalateaTheOrcustAutomaton, GalateaSummonFirst); - - AddExecutor(ExecutorType.Activate, CardId.JetSynchron, JetSynchronEffect); - - AddExecutor(ExecutorType.Activate, CardId.OrcustKnightmare, OrcustKnightmareEffect); - - AddExecutor(ExecutorType.Activate, CardId.OrcustHarpHorror, HarpHorrorEffect); - - AddExecutor(ExecutorType.Activate, CardId.WorldLegacyWorldWand, WorldWandEffect); - - AddExecutor(ExecutorType.Activate, CardId.ThePhantomKnightsofAncientCloak, AncientCloakEffect); - - AddExecutor(ExecutorType.SpSummon, CardId.ThePhantomKnightsofRustyBardiche, RustyBardicheSummon); - AddExecutor(ExecutorType.Activate, CardId.ThePhantomKnightsofRustyBardiche, RustyBardicheEffect); - - AddExecutor(ExecutorType.Activate, CardId.OrcustCymbalSkeleton, CymbalSkeletonEffect); - - AddExecutor(ExecutorType.Activate, CardId.GalateaTheOrcustAutomaton, GalateaEffect); - - AddExecutor(ExecutorType.SpSummon, CardId.SheorcustDingirsu, SheorcustDingirsuSummon); - AddExecutor(ExecutorType.Activate, CardId.SheorcustDingirsu, SheorcustDingirsuEffect); - - AddExecutor(ExecutorType.SpSummon, CardId.ThePhantomKnightsofSilentBoots, SilentBootsSummon); - AddExecutor(ExecutorType.Activate, CardId.ThePhantomKnightsofShadeBrigandine, ShadeBrigandineSummonSecond); - - AddExecutor(ExecutorType.SpSummon, CardId.BorreloadSavageDragon); - AddExecutor(ExecutorType.Activate, CardId.BorreloadSavageDragon, BorreloadSavageDragonEffect); - - AddExecutor(ExecutorType.SpSummon, CardId.GalateaTheOrcustAutomaton, GalateaSummonSecond); - - AddExecutor(ExecutorType.Activate, CardId.ThePhantomKnightsofSilentBoots, SilentBootsEffect); - - AddExecutor(ExecutorType.Summon, CardId.GhostBelleHauntedMansion, TunerSummon); - AddExecutor(ExecutorType.Summon, CardId.AshBlossomJoyousSpring, TunerSummon); - AddExecutor(ExecutorType.Summon, CardId.OrcustCymbalSkeleton, OtherSummon); - AddExecutor(ExecutorType.Summon, CardId.OrcustHarpHorror, OtherSummon); - AddExecutor(ExecutorType.Summon, CardId.ThePhantomKnightsofAncientCloak, LinkMaterialSummon); - AddExecutor(ExecutorType.Summon, CardId.MaxxC, LinkMaterialSummon); - AddExecutor(ExecutorType.Summon, CardId.ThePhantomKnightsofSilentBoots, LinkMaterialSummon); - - AddExecutor(ExecutorType.SpSummon, CardId.CrystronNeedlefiber, NeedlefiberSummonSecond); - - AddExecutor(ExecutorType.SpSummon, CardId.BorrelswordDragon, BorrelswordDragonSummon); - AddExecutor(ExecutorType.Activate, CardId.BorrelswordDragon, BorrelswordDragonEffect); - - AddExecutor(ExecutorType.SpellSet, CardId.PhantomKnightsFogBlade); - AddExecutor(ExecutorType.Activate, CardId.PhantomKnightsFogBlade, FogBladeEffect); - AddExecutor(ExecutorType.SpellSet, CardId.OrcustratedClimax); - - AddExecutor(ExecutorType.Activate, CardId.OrcustratedBabel, BabelEffect); - - AddExecutor(ExecutorType.Repos, MonsterRepos); - } - - private bool NormalSummoned = false; - private bool SheorcustDingirsuSummoned = false; - private bool HarpHorrorUsed = false; - private bool CymbalSkeletonUsed = false; - private bool BorrelswordDragonUsed = false; - private ClientCard RustyBardicheTarget = null; - private int ShootingRiserDragonCount = 0; - - private int[] HandCosts = new[] - { - CardId.OrcustCymbalSkeleton, - CardId.OrcustKnightmare, - CardId.DestrudoTheLostDragonsFrisson, - CardId.WorldLegacyWorldWand, - CardId.OrcustHarpHorror, - CardId.ThePhantomKnightsofAncientCloak, - CardId.ThePhantomKnightsofSilentBoots, - CardId.JetSynchron, - CardId.TrickstarLightStage, - CardId.SkyStrikerMobilizeEngage, - CardId.Terraforming, - CardId.ReinforcementofTheArmy, - CardId.MaxxC, - CardId.GhostBelleHauntedMansion - }; - - public override bool OnSelectHand() - { - // go first - return true; - } - - public override void OnNewTurn() - { - NormalSummoned = false; - SheorcustDingirsuSummoned = false; - HarpHorrorUsed = false; - CymbalSkeletonUsed = false; - BorrelswordDragonUsed = false; - RustyBardicheTarget = null; - ShootingRiserDragonCount = 0; - } - - public override void OnChainEnd() - { - RustyBardicheTarget = null; - } - - public override CardPosition OnSelectPosition(int cardId, IList positions) - { - YGOSharp.OCGWrapper.NamedCard cardData = YGOSharp.OCGWrapper.NamedCard.Get(cardId); - if (cardData != null) - { - if (cardData.Attack <= 1000) - return CardPosition.FaceUpDefence; - } - return 0; - } - - public override int OnSelectPlace(int cardId, int player, CardLocation location, int available) - { - if (location == CardLocation.SpellZone) - { - if (cardId == CardId.KnightmarePhoenix || cardId == CardId.CrystronNeedlefiber) - { - ClientCard b = Bot.MonsterZone.GetFirstMatchingCard(card => card.Id == CardId.BorreloadSavageDragon); - int zone = (1 << (b?.Sequence ?? 0)) & available; - if (zone > 0) - return zone; - } - if ((available & Zones.z0) > 0) - return Zones.z0; - if ((available & Zones.z1) > 0) - return Zones.z1; - if ((available & Zones.z2) > 0) - return Zones.z2; - if ((available & Zones.z3) > 0) - return Zones.z3; - if ((available & Zones.z4) > 0) - return Zones.z4; - } - if (location == CardLocation.MonsterZone) - { - if (cardId == CardId.SheorcustDingirsu) - { - ClientCard l = Bot.MonsterZone.GetFirstMatchingCard(card => card.Id == CardId.ThePhantomKnightsofRustyBardiche); - int zones = (l?.GetLinkedZones() ?? 0) & available; - if ((zones & Zones.z4) > 0) - return Zones.z4; - if ((zones & Zones.z3) > 0) - return Zones.z3; - if ((zones & Zones.z2) > 0) - return Zones.z2; - if ((zones & Zones.z1) > 0) - return Zones.z1; - if ((zones & Zones.z0) > 0) - return Zones.z0; - } - if (cardId == CardId.GalateaTheOrcustAutomaton) - { - int zones = Bot.GetLinkedZones() & available; - if ((zones & Zones.z0) > 0) - return Zones.z0; - if ((zones & Zones.z2) > 0) - return Zones.z2; - if ((zones & Zones.z1) > 0) - return Zones.z1; - if ((zones & Zones.z3) > 0) - return Zones.z3; - if ((zones & Zones.z4) > 0) - return Zones.z4; - } - if (cardId == CardId.KnightmarePhoenix) - { - if ((Enemy.MonsterZone[5]?.HasLinkMarker(CardLinkMarker.Top) ?? false) && (available & Zones.z3) > 0) - return Zones.z3; - if ((Enemy.MonsterZone[6]?.HasLinkMarker(CardLinkMarker.Top) ?? false) && (available & Zones.z1) > 0) - return Zones.z1; - } - - if ((available & Zones.z6) > 0) - return Zones.z6; - if ((available & Zones.z5) > 0) - return Zones.z5; - if ((available & Zones.z1) > 0) - return Zones.z1; - if ((available & Zones.z3) > 0) - return Zones.z3; - if ((available & Zones.z0) > 0) - return Zones.z0; - if ((available & Zones.z4) > 0) - return Zones.z4; - if ((available & Zones.z2) > 0) - return Zones.z2; - } - return 0; - } - - public override bool OnPreBattleBetween(ClientCard attacker, ClientCard defender) - { - if (!defender.IsMonsterHasPreventActivationEffectInBattle()) - { - if (attacker.IsCode(CardId.TrickstarCandina) && Bot.HasInHand(CardId.TrickstarCarobein)) - attacker.RealPower = attacker.RealPower + 1800; - - if (attacker.IsCode(CardId.BorrelswordDragon) && !attacker.IsDisabled() && !BorrelswordDragonUsed) - { - attacker.RealPower = attacker.RealPower + defender.GetDefensePower() / 2; - defender.RealPower = defender.RealPower - defender.GetDefensePower() / 2; - } - } - return base.OnPreBattleBetween(attacker, defender); - } - - private bool TerraformingEffect() - { - AI.SelectCard(CardId.TrickstarLightStage); - return true; - } - - private bool ReinforcementofTheArmyEffect() - { - AI.SelectCard(CardId.ArmageddonKnight); - return true; - } - - private bool FoolishBurialEffect() - { - AI.SelectCard(new[] { - CardId.DestrudoTheLostDragonsFrisson, - CardId.JetSynchron, - CardId.OrcustHarpHorror, - CardId.OrcustCymbalSkeleton - }); - return true; - } - - private bool LightStageEffect() - { - if (Card.Location == CardLocation.Hand || Card.IsFacedown()) - { - ClientCard field = Bot.GetFieldSpellCard(); - if ((field?.IsCode(CardId.OrcustratedBabel) ?? false) && Bot.GetMonsterCount() > 1) - return false; - if ((field?.IsCode(CardId.TrickstarLightStage) ?? false) && Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.TrickstarCandina) && Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.TrickstarCarobein)) - return false; - AI.SelectYesNo(true); - if (Bot.HasInHandOrHasInMonstersZone(CardId.TrickstarCandina)) - AI.SelectCard(CardId.TrickstarCarobein); - else - AI.SelectCard(CardId.TrickstarCandina); - return true; - } - ClientCard target = Enemy.SpellZone.GetFirstMatchingCard(card => card.IsFacedown()); - AI.SelectCard(target); - return true; - } - - private bool CarobeinSummon() - { - if (Bot.HasInMonstersZone(CardId.TrickstarCandina)) - { - // TODO: beat mode - return Bot.HasInExtra(CardId.KnightmarePhoenix); - } - else - { - return !NormalSummoned && Bot.Hand.IsExistingMatchingCard(card => card.Level <= 4); - } - } - - private bool EngageEffect() - { - bool needProtect = false; - if (Bot.HasInHand(CardId.ArmageddonKnight)) - needProtect = true; - else if (Bot.HasInHandOrInGraveyard(CardId.DestrudoTheLostDragonsFrisson) && Bot.Hand.IsExistingMatchingCard(card => card.Level <= 4)) - needProtect = true; - else if (Bot.HasInHand(CardId.TrickstarCandina)) - needProtect = true; - if (needProtect) - AI.SelectCard(CardId.SkyStrikerMechaEagleBooster); - else - AI.SelectCard(CardId.SkyStrikerMechaHornetDrones); - AI.SelectYesNo(true); - return true; - } - - private bool DronesEffectFirst() - { - return Bot.GetMonsterCount() == 0; - } - - private bool DronesEffect() - { - return !Bot.HasInHand(CardId.ArmageddonKnight) && !Bot.HasInHand(CardId.TrickstarCandina); - } - - private bool CandinaSummon() - { - NormalSummoned = true; - return true; - } - - private bool CandinaEffect() - { - AI.SelectCard(CardId.TrickstarLightStage); - return true; - } - - private bool ArmageddonKnightSummon() - { - NormalSummoned = true; - return true; - } - - private bool ArmageddonKnightEffect() - { - AI.SelectCard(new[] { - CardId.DestrudoTheLostDragonsFrisson, - CardId.OrcustHarpHorror - }); - return true; - } - - private bool ScrapRecyclerSummon() - { - NormalSummoned = true; - return true; - } - - private bool ScrapRecyclerEffect() - { - AI.SelectCard(new[] { - CardId.JetSynchron, - CardId.OrcustHarpHorror - }); - return true; - } - - private bool JetSynchronSummon() - { - if (Bot.GetMonsterCount() > 0) - { - NormalSummoned = true; - return true; - } - return false; - } - - private bool JetSynchronEffect() - { - AI.SelectCard(HandCosts); - return true; - } - - private bool AlmirajSummon() - { - if (Bot.GetMonsterCount() > 1) - return false; - ClientCard mat = Bot.GetMonsters().First(); - if (mat.IsCode(new[] { - CardId.JetSynchron, - CardId.ThePhantomKnightsofAncientCloak, - CardId.ThePhantomKnightsofSilentBoots - })) - { - AI.SelectMaterials(mat); - return true; - } - return false; - } - - private bool DestrudoSummon() - { - return Bot.GetMonsterCount() < 3 && Bot.HasInExtra(new[] { CardId.CrystronNeedlefiber, CardId.KnightmarePhoenix }); - } - - private bool NeedlefiberSummonFirst() - { - if (!Bot.HasInExtra(CardId.BorreloadSavageDragon)) - return false; - if (!Bot.HasInHand(CardId.JetSynchron) && Bot.GetRemainingCount(CardId.JetSynchron, 1) == 0) - return false; - - int[] matids = new[] { - CardId.DestrudoTheLostDragonsFrisson, - CardId.AshBlossomJoyousSpring, - CardId.GhostBelleHauntedMansion, - CardId.SkyStrikerMechaHornetDronesToken, - CardId.TrickstarCarobein, - CardId.SkyStrikerAceKagari, - CardId.ScrapRecycler, - CardId.ArmageddonKnight, - CardId.TrickstarCandina, - CardId.OrcustHarpHorror, - CardId.OrcustCymbalSkeleton, - CardId.ThePhantomKnightsofAncientCloak, - CardId.ThePhantomKnightsofSilentBoots - }; - if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(matids)) >= 2) - { - AI.SelectMaterials(matids); - return true; - } - return false; - } - - private bool NeedlefiberSummonSecond() - { - IList selected = new List(); - - ClientCard tuner = Bot.MonsterZone.GetFirstMatchingFaceupCard(card => card.IsCode(new[] - { - CardId.DestrudoTheLostDragonsFrisson, - CardId.AshBlossomJoyousSpring, - CardId.GhostBelleHauntedMansion, - CardId.JetSynchron - })); - if (tuner != null) - selected.Add(tuner); - - int[] matids = new[] { - CardId.SkyStrikerMechaHornetDronesToken, - CardId.ThePhantomKnightsofShadeBrigandine, - CardId.SkyStrikerAceKagari, - CardId.ScrapRecycler, - CardId.ArmageddonKnight, - CardId.OrcustHarpHorror, - CardId.OrcustCymbalSkeleton, - CardId.ThePhantomKnightsofAncientCloak, - CardId.ThePhantomKnightsofSilentBoots - }; - - IList mats = Bot.MonsterZone.GetMatchingCards(card => card.Attack <= 1700); - - for (int i = 0; i < matids.Length && selected.Count < 2; i++) - { - ClientCard c = mats.GetFirstMatchingFaceupCard(card => card.IsCode(matids[i])); - if (c != null) - { - selected.Add(c); - if (selected.Count == 2 && Util.GetBotAvailZonesFromExtraDeck(selected) == 0) - selected.Remove(c); - } - } - - if (selected.Count == 2) - { - AI.SelectMaterials(selected); - return true; - } - return false; - } - - private bool NeedlefiberEffect() - { - AI.SelectCard(CardId.JetSynchron); - return true; - } - - private bool ShootingRiserDragonEffect() - { - if (ActivateDescription == -1 || (ActivateDescription == Util.GetStringId(CardId.ShootingRiserDragon, 0))) - { - if (Bot.MonsterZone.IsExistingMatchingCard(card => card.Level == 3 && card.IsFaceup() && !card.IsTuner()) && Bot.GetRemainingCount(CardId.MaxxC, 3) > 0) - { - AI.SelectCard(CardId.MaxxC); - } - else if (Bot.MonsterZone.IsExistingMatchingCard(card => card.Level == 4 && card.IsFaceup() && !card.IsTuner())) - { - AI.SelectCard(new[] { - CardId.ThePhantomKnightsofAncientCloak, - CardId.ThePhantomKnightsofSilentBoots, - CardId.ScrapRecycler, - CardId.OrcustCymbalSkeleton, - CardId.AshBlossomJoyousSpring, - CardId.GhostBelleHauntedMansion - }); - } - else if (Bot.MonsterZone.IsExistingMatchingCard(card => card.Level == 5 && card.IsFaceup() && !card.IsTuner())) - { - AI.SelectCard(new[] { - CardId.OrcustHarpHorror, - CardId.ArmageddonKnight, - CardId.TrickstarCandina - }); - } - else - { - FoolishBurialEffect(); - } - return true; - } - else - { - if (Duel.LastChainPlayer == 0) - return false; - ShootingRiserDragonCount++; - return ShootingRiserDragonCount <= 10; - } - } - - private bool KnightmarePhoenixSummon() - { - if (!KnightmareMermaidSummon()) - return false; - if (!Bot.HasInExtra(CardId.KnightmareMermaid)) - return false; - - int[] firstMats = new[] { - CardId.JetSynchron, - CardId.CrystronNeedlefiber, - CardId.SkyStrikerMechaHornetDronesToken, - CardId.ThePhantomKnightsofShadeBrigandine, - CardId.ScrapRecycler, - CardId.SkyStrikerAceKagari, - CardId.ArmageddonKnight, - CardId.TrickstarCandina, - CardId.TrickstarCarobein - }; - if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(firstMats)) >= 2) - { - AI.SelectMaterials(firstMats); - return true; - } - int[] secondMats = new[] { - CardId.OrcustCymbalSkeleton, - CardId.OrcustHarpHorror, - CardId.DestrudoTheLostDragonsFrisson, - CardId.JetSynchron, - CardId.AshBlossomJoyousSpring, - CardId.GhostBelleHauntedMansion, - CardId.ThePhantomKnightsofSilentBoots, - CardId.ThePhantomKnightsofAncientCloak, - CardId.MaxxC, - CardId.SalamangreatAlmiraj - }; - int[] mats = firstMats.Concat(secondMats).ToArray(); - if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(mats)) >= 2) - { - AI.SelectMaterials(mats); - return true; - } - return false; - } - - private bool KnightmarePhoenixEffect() - { - int costcount = Bot.Hand.GetMatchingCardsCount(card => card.IsCode(HandCosts)); - ClientCard target = Enemy.SpellZone.GetFloodgate(); - ClientCard anytarget = Enemy.SpellZone.GetFirstMatchingCard(card => !card.OwnTargets.Any(cont => cont.IsCode(CardId.TrickstarLightStage))); - if ((costcount > 1 && anytarget != null) || (Bot.GetHandCount() > 1 && target != null)) - { - AI.SelectCard(HandCosts); - if (target == null) - target = anytarget; - AI.SelectNextCard(target); - return true; - } - return false; - } - - private bool KnightmareMermaidSummon() - { - if (Bot.GetHandCount() == 0) - return false; - if (Bot.GetRemainingCount(CardId.OrcustKnightmare, 2) == 0) - return false; - AI.SelectPlace(Zones.ExtraMonsterZones); - return true; - } - - private bool KnightmareMermaidEffect() - { - AI.SelectCard(HandCosts); - return true; - } - - private bool GalateaSummonFirst() - { - // only summon with Mermaid and Orcust Knightmare - IList mats = Bot.MonsterZone.GetMatchingCards(card => card.IsCode(CardId.KnightmareMermaid, CardId.OrcustKnightmare)); - if (mats.Count >= 2) - { - AI.SelectMaterials(mats); - return true; - } - return false; - } - - private bool OrcustKnightmareEffect() - { - if (!Bot.HasInGraveyard(CardId.OrcustHarpHorror)) - { - AI.SelectCard(Util.GetBestBotMonster()); - AI.SelectNextCard(CardId.OrcustHarpHorror); - return true; - } - else if (!Bot.HasInGraveyard(CardId.WorldLegacyWorldWand) && Bot.GetRemainingCount(CardId.WorldLegacyWorldWand, 1) > 0) - { - AI.SelectCard(CardId.GalateaTheOrcustAutomaton); - AI.SelectNextCard(CardId.WorldLegacyWorldWand); - return true; - } - else if (!Bot.HasInGraveyard(CardId.OrcustCymbalSkeleton) && Bot.GetRemainingCount(CardId.OrcustCymbalSkeleton, 1) > 0 && Bot.HasInGraveyard(CardId.SheorcustDingirsu) && !SheorcustDingirsuSummoned) - { - AI.SelectCard(CardId.GalateaTheOrcustAutomaton); - AI.SelectNextCard(CardId.OrcustCymbalSkeleton); - return true; - } - return false; - } - - private bool HarpHorrorEffect() - { - HarpHorrorUsed = true; - AI.SelectCard(CardId.OrcustCymbalSkeleton); - return true; - } - - private bool WorldWandEffect() - { - AI.SelectCard(CardId.OrcustCymbalSkeleton); - return true; - } - - private bool RustyBardicheSummon() - { - //if (Bot.GetRemainingCount(CardId.ThePhantomKnightsofAncientCloak, 1) == 0 && Bot.GetRemainingCount(CardId.ThePhantomKnightsofSilentBoots, 1) == 0) - // return false; - //if (Bot.GetRemainingCount(CardId.ThePhantomKnightsofShadeBrigandine, 1) == 0 && Bot.GetRemainingCount(CardId.PhantomKnightsFogBlade, 2) == 0) - // return false; - IList mats = Bot.MonsterZone.GetMatchingCards(card => card.IsCode(CardId.GalateaTheOrcustAutomaton)); - ClientCard mat2 = Bot.MonsterZone.GetMatchingCards(card => card.IsCode(CardId.OrcustCymbalSkeleton)).FirstOrDefault(); - if (mat2 != null) - mats.Add(mat2); - AI.SelectMaterials(mats); - AI.SelectPlace(Zones.ExtraMonsterZones); - return true; - } - - private bool RustyBardicheEffect() - { - if (ActivateDescription == -1 || ActivateDescription == Util.GetStringId(CardId.ThePhantomKnightsofRustyBardiche, 0)) - { - ClientCard target = GetFogBladeTarget(); - if (target == null) - target = Util.GetBestEnemyCard(false, true); - if (target == null) - return false; - RustyBardicheTarget = target; - AI.SelectCard(target); - return true; - } - else - { - AI.SelectCard(CardId.ThePhantomKnightsofAncientCloak); - if (Bot.HasInMonstersZone(CardId.JetSynchron) && !Bot.MonsterZone.IsExistingMatchingCard(card => card.Level == 4)) - AI.SelectNextCard(CardId.ThePhantomKnightsofShadeBrigandine); - else - AI.SelectNextCard(CardId.PhantomKnightsFogBlade); - return true; - } - } - - private ClientCard GetFogBladeTarget() - { - return Enemy.MonsterZone.GetFirstMatchingCard(card => card.OwnTargets.Any(cont => cont.IsCode(CardId.PhantomKnightsFogBlade))); - } - - private bool CymbalSkeletonEffect() - { - int[] botTurnTargets = new[] { CardId.GalateaTheOrcustAutomaton, CardId.SheorcustDingirsu }; - int[] emenyTurnTargets = new[] { CardId.SheorcustDingirsu, CardId.GalateaTheOrcustAutomaton }; - if (Duel.Player == 0 && Bot.HasInGraveyard(CardId.GalateaTheOrcustAutomaton) && !Bot.HasInMonstersZone(CardId.GalateaTheOrcustAutomaton) && Bot.HasInExtra(CardId.SheorcustDingirsu) && !SheorcustDingirsuSummoned) - { - AI.SelectCard(botTurnTargets); - CymbalSkeletonUsed = true; - return true; - } - else if (Duel.Player == 0 && Bot.HasInGraveyard(CardId.SheorcustDingirsu) && !SheorcustDingirsuSummoned) - { - AI.SelectCard(emenyTurnTargets); - SheorcustDingirsuSummoned = true; - CymbalSkeletonUsed = true; - return true; - } - if (Duel.Player == 1 && Bot.HasInGraveyard(CardId.SheorcustDingirsu) && !SheorcustDingirsuSummoned && - (Util.GetProblematicEnemyCard() != null || Duel.Phase == DuelPhase.End)) - { - AI.SelectCard(emenyTurnTargets); - CymbalSkeletonUsed = true; - SheorcustDingirsuSummoned = true; - return true; - } - return false; - } - - private bool SheorcustDingirsuSummon() - { - SheorcustDingirsuSummoned = true; - return true; - } - - private bool SheorcustDingirsuEffect() - { - if (ActivateDescription == 96) - { - // TODO: more FogBlade lost target +using System; +using YGOSharp.OCGWrapper.Enums; +using System.Collections.Generic; +using WindBot; +using WindBot.Game; +using WindBot.Game.AI; +using System.Linq; + +namespace WindBot.Game.AI.Decks +{ + [Deck("Orcust", "AI_Orcust")] + class OrcustExecutor : DefaultExecutor + { + public class CardId + { + public const int OrcustKnightmare = 4055337; + public const int OrcustHarpHorror = 57835716; + public const int OrcustCymbalSkeleton = 21441617; + public const int WorldLegacyWorldWand = 93920420; + public const int ThePhantomKnightsofAncientCloak = 90432163; + public const int ThePhantomKnightsofSilentBoots = 36426778; + + public const int TrickstarCarobein = 98169343; + public const int TrickstarCandina = 61283655; + public const int ArmageddonKnight = 28985331; + public const int ScrapRecycler = 4334811; + public const int DestrudoTheLostDragonsFrisson = 5560911; + public const int JetSynchron = 9742784; + + public const int AshBlossomJoyousSpring = 14558127; + public const int GhostBelleHauntedMansion = 73642296; + public const int MaxxC = 23434538; + + public const int SkyStrikerMobilizeEngage = 63166095; + public const int SkyStrikerMechaEagleBooster = 25733157; + public const int SkyStrikerMechaHornetDrones = 52340444; + public const int SkyStrikerMechaHornetDronesToken = 52340445; + public const int TrickstarLightStage = 35371948; + public const int OrcustratedBabel = 90351981; + + public const int ReinforcementofTheArmy = 32807846; + public const int Terraforming = 73628505; + public const int FoolishBurial = 81439173; + public const int CalledbyTheGrave = 24224830; + + public const int ThePhantomKnightsofShadeBrigandine = 98827725; + public const int PhantomKnightsFogBlade = 25542642; + public const int OrcustratedClimax = 703897; + + public const int BorreloadSavageDragon = 27548199; + public const int ShootingRiserDragon = 68431965; + public const int SheorcustDingirsu = 93854893; + public const int BorrelswordDragon = 85289965; + public const int LongirsuTheOrcustOrchestrator = 76145142; + public const int ThePhantomKnightsofRustyBardiche = 26692769; + public const int KnightmarePhoenix = 2857636; + public const int GalateaTheOrcustAutomaton = 30741503; + public const int CrystronNeedlefiber = 50588353; + public const int SkyStrikerAceKagari = 63288573; + public const int KnightmareMermaid = 3679218; + public const int SalamangreatAlmiraj = 60303245; + } + + public OrcustExecutor(GameAI ai, Duel duel) + : base(ai, duel) + { + AddExecutor(ExecutorType.Activate, CardId.SkyStrikerMechaEagleBooster, EagleBoosterEffect); + AddExecutor(ExecutorType.Activate, CardId.OrcustratedClimax, ClimaxEffect); + + AddExecutor(ExecutorType.Activate, CardId.MaxxC, DefaultMaxxC); + AddExecutor(ExecutorType.Activate, CardId.AshBlossomJoyousSpring, DefaultAshBlossomAndJoyousSpring); + AddExecutor(ExecutorType.Activate, CardId.GhostBelleHauntedMansion, DefaultGhostBelleAndHauntedMansion); + AddExecutor(ExecutorType.Activate, CardId.CalledbyTheGrave, DefaultCalledByTheGrave); + + AddExecutor(ExecutorType.Activate, CardId.Terraforming, TerraformingEffect); + AddExecutor(ExecutorType.Activate, CardId.ReinforcementofTheArmy, ReinforcementofTheArmyEffect); + AddExecutor(ExecutorType.Activate, CardId.FoolishBurial, FoolishBurialEffect); + + AddExecutor(ExecutorType.Activate, CardId.TrickstarLightStage, LightStageEffect); + + AddExecutor(ExecutorType.Activate, CardId.SkyStrikerMobilizeEngage, EngageEffect); + AddExecutor(ExecutorType.Activate, CardId.SkyStrikerMechaHornetDrones, DronesEffectFirst); + AddExecutor(ExecutorType.SpSummon, CardId.SkyStrikerAceKagari); + AddExecutor(ExecutorType.Activate, CardId.SkyStrikerAceKagari); + + AddExecutor(ExecutorType.SpSummon, CardId.KnightmareMermaid, KnightmareMermaidSummon); + AddExecutor(ExecutorType.Activate, CardId.KnightmareMermaid, KnightmareMermaidEffect); + + AddExecutor(ExecutorType.SpSummon, CardId.TrickstarCarobein, CarobeinSummon); + AddExecutor(ExecutorType.Activate, CardId.TrickstarCarobein); + + AddExecutor(ExecutorType.SpellSet, CardId.ThePhantomKnightsofShadeBrigandine); + + AddExecutor(ExecutorType.Summon, CardId.ArmageddonKnight, ArmageddonKnightSummon); + AddExecutor(ExecutorType.Activate, CardId.ArmageddonKnight, ArmageddonKnightEffect); + + AddExecutor(ExecutorType.Summon, CardId.ScrapRecycler, ScrapRecyclerSummon); + AddExecutor(ExecutorType.Activate, CardId.ScrapRecycler, ScrapRecyclerEffect); + + AddExecutor(ExecutorType.Activate, CardId.SkyStrikerMechaHornetDrones, DronesEffect); + + AddExecutor(ExecutorType.Summon, CardId.JetSynchron, JetSynchronSummon); + + AddExecutor(ExecutorType.Activate, CardId.DestrudoTheLostDragonsFrisson, DestrudoSummon); + + AddExecutor(ExecutorType.SpSummon, CardId.CrystronNeedlefiber, NeedlefiberSummonFirst); + AddExecutor(ExecutorType.Activate, CardId.CrystronNeedlefiber, NeedlefiberEffect); + + AddExecutor(ExecutorType.Activate, CardId.ShootingRiserDragon, ShootingRiserDragonEffect); + + AddExecutor(ExecutorType.Summon, CardId.TrickstarCandina, CandinaSummon); + AddExecutor(ExecutorType.Activate, CardId.TrickstarCandina, CandinaEffect); + + AddExecutor(ExecutorType.Summon, CardId.JetSynchron, OneCardComboSummon); + AddExecutor(ExecutorType.Summon, CardId.ThePhantomKnightsofAncientCloak, OneCardComboSummon); + AddExecutor(ExecutorType.Summon, CardId.ThePhantomKnightsofSilentBoots, OneCardComboSummon); + AddExecutor(ExecutorType.SpSummon, CardId.SalamangreatAlmiraj, AlmirajSummon); + + AddExecutor(ExecutorType.Activate, CardId.ThePhantomKnightsofShadeBrigandine, ShadeBrigandineSummonFirst); + + AddExecutor(ExecutorType.SpSummon, CardId.KnightmarePhoenix, KnightmarePhoenixSummon); + AddExecutor(ExecutorType.Activate, CardId.KnightmarePhoenix, KnightmarePhoenixEffect); + + AddExecutor(ExecutorType.SpSummon, CardId.GalateaTheOrcustAutomaton, GalateaSummonFirst); + + AddExecutor(ExecutorType.Activate, CardId.JetSynchron, JetSynchronEffect); + + AddExecutor(ExecutorType.Activate, CardId.OrcustKnightmare, OrcustKnightmareEffect); + + AddExecutor(ExecutorType.Activate, CardId.OrcustHarpHorror, HarpHorrorEffect); + + AddExecutor(ExecutorType.Activate, CardId.WorldLegacyWorldWand, WorldWandEffect); + + AddExecutor(ExecutorType.Activate, CardId.ThePhantomKnightsofAncientCloak, AncientCloakEffect); + + AddExecutor(ExecutorType.SpSummon, CardId.ThePhantomKnightsofRustyBardiche, RustyBardicheSummon); + AddExecutor(ExecutorType.Activate, CardId.ThePhantomKnightsofRustyBardiche, RustyBardicheEffect); + + AddExecutor(ExecutorType.Activate, CardId.OrcustCymbalSkeleton, CymbalSkeletonEffect); + + AddExecutor(ExecutorType.Activate, CardId.GalateaTheOrcustAutomaton, GalateaEffect); + + AddExecutor(ExecutorType.SpSummon, CardId.SheorcustDingirsu, SheorcustDingirsuSummon); + AddExecutor(ExecutorType.Activate, CardId.SheorcustDingirsu, SheorcustDingirsuEffect); + + AddExecutor(ExecutorType.SpSummon, CardId.ThePhantomKnightsofSilentBoots, SilentBootsSummon); + AddExecutor(ExecutorType.Activate, CardId.ThePhantomKnightsofShadeBrigandine, ShadeBrigandineSummonSecond); + + AddExecutor(ExecutorType.SpSummon, CardId.BorreloadSavageDragon); + AddExecutor(ExecutorType.Activate, CardId.BorreloadSavageDragon, BorreloadSavageDragonEffect); + + AddExecutor(ExecutorType.SpSummon, CardId.GalateaTheOrcustAutomaton, GalateaSummonSecond); + + AddExecutor(ExecutorType.Activate, CardId.ThePhantomKnightsofSilentBoots, SilentBootsEffect); + + AddExecutor(ExecutorType.Summon, CardId.GhostBelleHauntedMansion, TunerSummon); + AddExecutor(ExecutorType.Summon, CardId.AshBlossomJoyousSpring, TunerSummon); + AddExecutor(ExecutorType.Summon, CardId.OrcustCymbalSkeleton, OtherSummon); + AddExecutor(ExecutorType.Summon, CardId.OrcustHarpHorror, OtherSummon); + AddExecutor(ExecutorType.Summon, CardId.ThePhantomKnightsofAncientCloak, LinkMaterialSummon); + AddExecutor(ExecutorType.Summon, CardId.MaxxC, LinkMaterialSummon); + AddExecutor(ExecutorType.Summon, CardId.ThePhantomKnightsofSilentBoots, LinkMaterialSummon); + + AddExecutor(ExecutorType.SpSummon, CardId.CrystronNeedlefiber, NeedlefiberSummonSecond); + + AddExecutor(ExecutorType.SpSummon, CardId.BorrelswordDragon, BorrelswordDragonSummon); + AddExecutor(ExecutorType.Activate, CardId.BorrelswordDragon, BorrelswordDragonEffect); + + AddExecutor(ExecutorType.SpellSet, CardId.PhantomKnightsFogBlade); + AddExecutor(ExecutorType.Activate, CardId.PhantomKnightsFogBlade, FogBladeEffect); + AddExecutor(ExecutorType.SpellSet, CardId.OrcustratedClimax); + + AddExecutor(ExecutorType.Activate, CardId.OrcustratedBabel, BabelEffect); + + AddExecutor(ExecutorType.Repos, MonsterRepos); + } + + private bool NormalSummoned = false; + private bool SheorcustDingirsuSummoned = false; + private bool HarpHorrorUsed = false; + private bool CymbalSkeletonUsed = false; + private bool BorrelswordDragonUsed = false; + private ClientCard RustyBardicheTarget = null; + private int ShootingRiserDragonCount = 0; + + private int[] HandCosts = new[] + { + CardId.OrcustCymbalSkeleton, + CardId.OrcustKnightmare, + CardId.DestrudoTheLostDragonsFrisson, + CardId.WorldLegacyWorldWand, + CardId.OrcustHarpHorror, + CardId.ThePhantomKnightsofAncientCloak, + CardId.ThePhantomKnightsofSilentBoots, + CardId.JetSynchron, + CardId.TrickstarLightStage, + CardId.SkyStrikerMobilizeEngage, + CardId.Terraforming, + CardId.ReinforcementofTheArmy, + CardId.MaxxC, + CardId.GhostBelleHauntedMansion + }; + + public override bool OnSelectHand() + { + // go first + return true; + } + + public override void OnNewTurn() + { + NormalSummoned = false; + SheorcustDingirsuSummoned = false; + HarpHorrorUsed = false; + CymbalSkeletonUsed = false; + BorrelswordDragonUsed = false; + RustyBardicheTarget = null; + ShootingRiserDragonCount = 0; + } + + public override void OnChainEnd() + { + RustyBardicheTarget = null; + } + + public override CardPosition OnSelectPosition(int cardId, IList positions) + { + YGOSharp.OCGWrapper.NamedCard cardData = YGOSharp.OCGWrapper.NamedCard.Get(cardId); + if (cardData != null) + { + if (cardData.Attack <= 1000) + return CardPosition.FaceUpDefence; + } + return 0; + } + + public override int OnSelectPlace(long cardId, int player, CardLocation location, int available) + { + if (location == CardLocation.SpellZone) + { + if (cardId == CardId.KnightmarePhoenix || cardId == CardId.CrystronNeedlefiber) + { + ClientCard b = Bot.MonsterZone.GetFirstMatchingCard(card => card.Id == CardId.BorreloadSavageDragon); + int zone = (1 << (b?.Sequence ?? 0)) & available; + if (zone > 0) + return zone; + } + if ((available & Zones.z0) > 0) + return Zones.z0; + if ((available & Zones.z1) > 0) + return Zones.z1; + if ((available & Zones.z2) > 0) + return Zones.z2; + if ((available & Zones.z3) > 0) + return Zones.z3; + if ((available & Zones.z4) > 0) + return Zones.z4; + } + if (location == CardLocation.MonsterZone) + { + if (cardId == CardId.SheorcustDingirsu) + { + ClientCard l = Bot.MonsterZone.GetFirstMatchingCard(card => card.Id == CardId.ThePhantomKnightsofRustyBardiche); + int zones = (l?.GetLinkedZones() ?? 0) & available; + if ((zones & Zones.z4) > 0) + return Zones.z4; + if ((zones & Zones.z3) > 0) + return Zones.z3; + if ((zones & Zones.z2) > 0) + return Zones.z2; + if ((zones & Zones.z1) > 0) + return Zones.z1; + if ((zones & Zones.z0) > 0) + return Zones.z0; + } + if (cardId == CardId.GalateaTheOrcustAutomaton) + { + int zones = Bot.GetLinkedZones() & available; + if ((zones & Zones.z0) > 0) + return Zones.z0; + if ((zones & Zones.z2) > 0) + return Zones.z2; + if ((zones & Zones.z1) > 0) + return Zones.z1; + if ((zones & Zones.z3) > 0) + return Zones.z3; + if ((zones & Zones.z4) > 0) + return Zones.z4; + } + if (cardId == CardId.KnightmarePhoenix) + { + if ((Enemy.MonsterZone[5]?.HasLinkMarker(CardLinkMarker.Top) ?? false) && (available & Zones.z3) > 0) + return Zones.z3; + if ((Enemy.MonsterZone[6]?.HasLinkMarker(CardLinkMarker.Top) ?? false) && (available & Zones.z1) > 0) + return Zones.z1; + } + + if ((available & Zones.z6) > 0) + return Zones.z6; + if ((available & Zones.z5) > 0) + return Zones.z5; + if ((available & Zones.z1) > 0) + return Zones.z1; + if ((available & Zones.z3) > 0) + return Zones.z3; + if ((available & Zones.z0) > 0) + return Zones.z0; + if ((available & Zones.z4) > 0) + return Zones.z4; + if ((available & Zones.z2) > 0) + return Zones.z2; + } + return 0; + } + + public override bool OnPreBattleBetween(ClientCard attacker, ClientCard defender) + { + if (!defender.IsMonsterHasPreventActivationEffectInBattle()) + { + if (attacker.IsCode(CardId.TrickstarCandina) && Bot.HasInHand(CardId.TrickstarCarobein)) + attacker.RealPower = attacker.RealPower + 1800; + + if (attacker.IsCode(CardId.BorrelswordDragon) && !attacker.IsDisabled() && !BorrelswordDragonUsed) + { + attacker.RealPower = attacker.RealPower + defender.GetDefensePower() / 2; + defender.RealPower = defender.RealPower - defender.GetDefensePower() / 2; + } + } + return base.OnPreBattleBetween(attacker, defender); + } + + private bool TerraformingEffect() + { + AI.SelectCard(CardId.TrickstarLightStage); + return true; + } + + private bool ReinforcementofTheArmyEffect() + { + AI.SelectCard(CardId.ArmageddonKnight); + return true; + } + + private bool FoolishBurialEffect() + { + AI.SelectCard(new[] { + CardId.DestrudoTheLostDragonsFrisson, + CardId.JetSynchron, + CardId.OrcustHarpHorror, + CardId.OrcustCymbalSkeleton + }); + return true; + } + + private bool LightStageEffect() + { + if (Card.Location == CardLocation.Hand || Card.IsFacedown()) + { + ClientCard field = Bot.GetFieldSpellCard(); + if ((field?.IsCode(CardId.OrcustratedBabel) ?? false) && Bot.GetMonsterCount() > 1) + return false; + if ((field?.IsCode(CardId.TrickstarLightStage) ?? false) && Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.TrickstarCandina) && Bot.HasInHandOrInMonstersZoneOrInGraveyard(CardId.TrickstarCarobein)) + return false; + AI.SelectYesNo(true); + if (Bot.HasInHandOrHasInMonstersZone(CardId.TrickstarCandina)) + AI.SelectCard(CardId.TrickstarCarobein); + else + AI.SelectCard(CardId.TrickstarCandina); + return true; + } + ClientCard target = Enemy.SpellZone.GetFirstMatchingCard(card => card.IsFacedown()); + AI.SelectCard(target); + return true; + } + + private bool CarobeinSummon() + { + if (Bot.HasInMonstersZone(CardId.TrickstarCandina)) + { + // TODO: beat mode + return Bot.HasInExtra(CardId.KnightmarePhoenix); + } + else + { + return !NormalSummoned && Bot.Hand.IsExistingMatchingCard(card => card.Level <= 4); + } + } + + private bool EngageEffect() + { + bool needProtect = false; + if (Bot.HasInHand(CardId.ArmageddonKnight)) + needProtect = true; + else if (Bot.HasInHandOrInGraveyard(CardId.DestrudoTheLostDragonsFrisson) && Bot.Hand.IsExistingMatchingCard(card => card.Level <= 4)) + needProtect = true; + else if (Bot.HasInHand(CardId.TrickstarCandina)) + needProtect = true; + if (needProtect) + AI.SelectCard(CardId.SkyStrikerMechaEagleBooster); + else + AI.SelectCard(CardId.SkyStrikerMechaHornetDrones); + AI.SelectYesNo(true); + return true; + } + + private bool DronesEffectFirst() + { + return Bot.GetMonsterCount() == 0; + } + + private bool DronesEffect() + { + return !Bot.HasInHand(CardId.ArmageddonKnight) && !Bot.HasInHand(CardId.TrickstarCandina); + } + + private bool CandinaSummon() + { + NormalSummoned = true; + return true; + } + + private bool CandinaEffect() + { + AI.SelectCard(CardId.TrickstarLightStage); + return true; + } + + private bool ArmageddonKnightSummon() + { + NormalSummoned = true; + return true; + } + + private bool ArmageddonKnightEffect() + { + AI.SelectCard(new[] { + CardId.DestrudoTheLostDragonsFrisson, + CardId.OrcustHarpHorror + }); + return true; + } + + private bool ScrapRecyclerSummon() + { + NormalSummoned = true; + return true; + } + + private bool ScrapRecyclerEffect() + { + AI.SelectCard(new[] { + CardId.JetSynchron, + CardId.OrcustHarpHorror + }); + return true; + } + + private bool JetSynchronSummon() + { + if (Bot.GetMonsterCount() > 0) + { + NormalSummoned = true; + return true; + } + return false; + } + + private bool JetSynchronEffect() + { + AI.SelectCard(HandCosts); + return true; + } + + private bool AlmirajSummon() + { + if (Bot.GetMonsterCount() > 1) + return false; + ClientCard mat = Bot.GetMonsters().First(); + if (mat.IsCode(new[] { + CardId.JetSynchron, + CardId.ThePhantomKnightsofAncientCloak, + CardId.ThePhantomKnightsofSilentBoots + })) + { + AI.SelectMaterials(mat); + return true; + } + return false; + } + + private bool DestrudoSummon() + { + return Bot.GetMonsterCount() < 3 && Bot.HasInExtra(new[] { CardId.CrystronNeedlefiber, CardId.KnightmarePhoenix }); + } + + private bool NeedlefiberSummonFirst() + { + if (!Bot.HasInExtra(CardId.BorreloadSavageDragon)) + return false; + if (!Bot.HasInHand(CardId.JetSynchron) && Bot.GetRemainingCount(CardId.JetSynchron, 1) == 0) + return false; + + int[] matids = new[] { + CardId.DestrudoTheLostDragonsFrisson, + CardId.AshBlossomJoyousSpring, + CardId.GhostBelleHauntedMansion, + CardId.SkyStrikerMechaHornetDronesToken, + CardId.TrickstarCarobein, + CardId.SkyStrikerAceKagari, + CardId.ScrapRecycler, + CardId.ArmageddonKnight, + CardId.TrickstarCandina, + CardId.OrcustHarpHorror, + CardId.OrcustCymbalSkeleton, + CardId.ThePhantomKnightsofAncientCloak, + CardId.ThePhantomKnightsofSilentBoots + }; + if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(matids)) >= 2) + { + AI.SelectMaterials(matids); + return true; + } + return false; + } + + private bool NeedlefiberSummonSecond() + { + IList selected = new List(); + + ClientCard tuner = Bot.MonsterZone.GetFirstMatchingFaceupCard(card => card.IsCode(new[] + { + CardId.DestrudoTheLostDragonsFrisson, + CardId.AshBlossomJoyousSpring, + CardId.GhostBelleHauntedMansion, + CardId.JetSynchron + })); + if (tuner != null) + selected.Add(tuner); + + int[] matids = new[] { + CardId.SkyStrikerMechaHornetDronesToken, + CardId.ThePhantomKnightsofShadeBrigandine, + CardId.SkyStrikerAceKagari, + CardId.ScrapRecycler, + CardId.ArmageddonKnight, + CardId.OrcustHarpHorror, + CardId.OrcustCymbalSkeleton, + CardId.ThePhantomKnightsofAncientCloak, + CardId.ThePhantomKnightsofSilentBoots + }; + + IList mats = Bot.MonsterZone.GetMatchingCards(card => card.Attack <= 1700); + + for (int i = 0; i < matids.Length && selected.Count < 2; i++) + { + ClientCard c = mats.GetFirstMatchingFaceupCard(card => card.IsCode(matids[i])); + if (c != null) + { + selected.Add(c); + if (selected.Count == 2 && Util.GetBotAvailZonesFromExtraDeck(selected) == 0) + selected.Remove(c); + } + } + + if (selected.Count == 2) + { + AI.SelectMaterials(selected); + return true; + } + return false; + } + + private bool NeedlefiberEffect() + { + AI.SelectCard(CardId.JetSynchron); + return true; + } + + private bool ShootingRiserDragonEffect() + { + if (ActivateDescription == -1 || (ActivateDescription == Util.GetStringId(CardId.ShootingRiserDragon, 0))) + { + if (Bot.MonsterZone.IsExistingMatchingCard(card => card.Level == 3 && card.IsFaceup() && !card.IsTuner()) && Bot.GetRemainingCount(CardId.MaxxC, 3) > 0) + { + AI.SelectCard(CardId.MaxxC); + } + else if (Bot.MonsterZone.IsExistingMatchingCard(card => card.Level == 4 && card.IsFaceup() && !card.IsTuner())) + { + AI.SelectCard(new[] { + CardId.ThePhantomKnightsofAncientCloak, + CardId.ThePhantomKnightsofSilentBoots, + CardId.ScrapRecycler, + CardId.OrcustCymbalSkeleton, + CardId.AshBlossomJoyousSpring, + CardId.GhostBelleHauntedMansion + }); + } + else if (Bot.MonsterZone.IsExistingMatchingCard(card => card.Level == 5 && card.IsFaceup() && !card.IsTuner())) + { + AI.SelectCard(new[] { + CardId.OrcustHarpHorror, + CardId.ArmageddonKnight, + CardId.TrickstarCandina + }); + } + else + { + FoolishBurialEffect(); + } + return true; + } + else + { + if (Duel.LastChainPlayer == 0) + return false; + ShootingRiserDragonCount++; + return ShootingRiserDragonCount <= 10; + } + } + + private bool KnightmarePhoenixSummon() + { + if (!KnightmareMermaidSummon()) + return false; + if (!Bot.HasInExtra(CardId.KnightmareMermaid)) + return false; + + int[] firstMats = new[] { + CardId.JetSynchron, + CardId.CrystronNeedlefiber, + CardId.SkyStrikerMechaHornetDronesToken, + CardId.ThePhantomKnightsofShadeBrigandine, + CardId.ScrapRecycler, + CardId.SkyStrikerAceKagari, + CardId.ArmageddonKnight, + CardId.TrickstarCandina, + CardId.TrickstarCarobein + }; + if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(firstMats)) >= 2) + { + AI.SelectMaterials(firstMats); + return true; + } + int[] secondMats = new[] { + CardId.OrcustCymbalSkeleton, + CardId.OrcustHarpHorror, + CardId.DestrudoTheLostDragonsFrisson, + CardId.JetSynchron, + CardId.AshBlossomJoyousSpring, + CardId.GhostBelleHauntedMansion, + CardId.ThePhantomKnightsofSilentBoots, + CardId.ThePhantomKnightsofAncientCloak, + CardId.MaxxC, + CardId.SalamangreatAlmiraj + }; + int[] mats = firstMats.Concat(secondMats).ToArray(); + if (Bot.MonsterZone.GetMatchingCardsCount(card => card.IsCode(mats)) >= 2) + { + AI.SelectMaterials(mats); + return true; + } + return false; + } + + private bool KnightmarePhoenixEffect() + { + int costcount = Bot.Hand.GetMatchingCardsCount(card => card.IsCode(HandCosts)); + ClientCard target = Enemy.SpellZone.GetFloodgate(); + ClientCard anytarget = Enemy.SpellZone.GetFirstMatchingCard(card => !card.OwnTargets.Any(cont => cont.IsCode(CardId.TrickstarLightStage))); + if ((costcount > 1 && anytarget != null) || (Bot.GetHandCount() > 1 && target != null)) + { + AI.SelectCard(HandCosts); + if (target == null) + target = anytarget; + AI.SelectNextCard(target); + return true; + } + return false; + } + + private bool KnightmareMermaidSummon() + { + if (Bot.GetHandCount() == 0) + return false; + if (Bot.GetRemainingCount(CardId.OrcustKnightmare, 2) == 0) + return false; + AI.SelectPlace(Zones.ExtraMonsterZones); + return true; + } + + private bool KnightmareMermaidEffect() + { + AI.SelectCard(HandCosts); + return true; + } + + private bool GalateaSummonFirst() + { + // only summon with Mermaid and Orcust Knightmare + IList mats = Bot.MonsterZone.GetMatchingCards(card => card.IsCode(CardId.KnightmareMermaid, CardId.OrcustKnightmare)); + if (mats.Count >= 2) + { + AI.SelectMaterials(mats); + return true; + } + return false; + } + + private bool OrcustKnightmareEffect() + { + if (!Bot.HasInGraveyard(CardId.OrcustHarpHorror)) + { + AI.SelectCard(Util.GetBestBotMonster()); + AI.SelectNextCard(CardId.OrcustHarpHorror); + return true; + } + else if (!Bot.HasInGraveyard(CardId.WorldLegacyWorldWand) && Bot.GetRemainingCount(CardId.WorldLegacyWorldWand, 1) > 0) + { + AI.SelectCard(CardId.GalateaTheOrcustAutomaton); + AI.SelectNextCard(CardId.WorldLegacyWorldWand); + return true; + } + else if (!Bot.HasInGraveyard(CardId.OrcustCymbalSkeleton) && Bot.GetRemainingCount(CardId.OrcustCymbalSkeleton, 1) > 0 && Bot.HasInGraveyard(CardId.SheorcustDingirsu) && !SheorcustDingirsuSummoned) + { + AI.SelectCard(CardId.GalateaTheOrcustAutomaton); + AI.SelectNextCard(CardId.OrcustCymbalSkeleton); + return true; + } + return false; + } + + private bool HarpHorrorEffect() + { + HarpHorrorUsed = true; + AI.SelectCard(CardId.OrcustCymbalSkeleton); + return true; + } + + private bool WorldWandEffect() + { + AI.SelectCard(CardId.OrcustCymbalSkeleton); + return true; + } + + private bool RustyBardicheSummon() + { + //if (Bot.GetRemainingCount(CardId.ThePhantomKnightsofAncientCloak, 1) == 0 && Bot.GetRemainingCount(CardId.ThePhantomKnightsofSilentBoots, 1) == 0) + // return false; + //if (Bot.GetRemainingCount(CardId.ThePhantomKnightsofShadeBrigandine, 1) == 0 && Bot.GetRemainingCount(CardId.PhantomKnightsFogBlade, 2) == 0) + // return false; + IList mats = Bot.MonsterZone.GetMatchingCards(card => card.IsCode(CardId.GalateaTheOrcustAutomaton)); + ClientCard mat2 = Bot.MonsterZone.GetMatchingCards(card => card.IsCode(CardId.OrcustCymbalSkeleton)).FirstOrDefault(); + if (mat2 != null) + mats.Add(mat2); + AI.SelectMaterials(mats); + AI.SelectPlace(Zones.ExtraMonsterZones); + return true; + } + + private bool RustyBardicheEffect() + { + if (ActivateDescription == -1 || ActivateDescription == Util.GetStringId(CardId.ThePhantomKnightsofRustyBardiche, 0)) + { + ClientCard target = GetFogBladeTarget(); + if (target == null) + target = Util.GetBestEnemyCard(false, true); + if (target == null) + return false; + RustyBardicheTarget = target; + AI.SelectCard(target); + return true; + } + else + { + AI.SelectCard(CardId.ThePhantomKnightsofAncientCloak); + if (Bot.HasInMonstersZone(CardId.JetSynchron) && !Bot.MonsterZone.IsExistingMatchingCard(card => card.Level == 4)) + AI.SelectNextCard(CardId.ThePhantomKnightsofShadeBrigandine); + else + AI.SelectNextCard(CardId.PhantomKnightsFogBlade); + return true; + } + } + + private ClientCard GetFogBladeTarget() + { + return Enemy.MonsterZone.GetFirstMatchingCard(card => card.OwnTargets.Any(cont => cont.IsCode(CardId.PhantomKnightsFogBlade))); + } + + private bool CymbalSkeletonEffect() + { + int[] botTurnTargets = new[] { CardId.GalateaTheOrcustAutomaton, CardId.SheorcustDingirsu }; + int[] emenyTurnTargets = new[] { CardId.SheorcustDingirsu, CardId.GalateaTheOrcustAutomaton }; + if (Duel.Player == 0 && Bot.HasInGraveyard(CardId.GalateaTheOrcustAutomaton) && !Bot.HasInMonstersZone(CardId.GalateaTheOrcustAutomaton) && Bot.HasInExtra(CardId.SheorcustDingirsu) && !SheorcustDingirsuSummoned) + { + AI.SelectCard(botTurnTargets); + CymbalSkeletonUsed = true; + return true; + } + else if (Duel.Player == 0 && Bot.HasInGraveyard(CardId.SheorcustDingirsu) && !SheorcustDingirsuSummoned) + { + AI.SelectCard(emenyTurnTargets); + SheorcustDingirsuSummoned = true; + CymbalSkeletonUsed = true; + return true; + } + if (Duel.Player == 1 && Bot.HasInGraveyard(CardId.SheorcustDingirsu) && !SheorcustDingirsuSummoned && + (Util.GetProblematicEnemyCard() != null || Duel.Phase == DuelPhase.End)) + { + AI.SelectCard(emenyTurnTargets); + CymbalSkeletonUsed = true; + SheorcustDingirsuSummoned = true; + return true; + } + return false; + } + + private bool SheorcustDingirsuSummon() + { + SheorcustDingirsuSummoned = true; + return true; + } + + private bool SheorcustDingirsuEffect() + { + if (ActivateDescription == 96) + { + // TODO: more FogBlade lost target if ((Duel.Phase == DuelPhase.Main1 || Duel.Phase == DuelPhase.Main2) && Duel.CurrentChain.Count == 0) return false; - AI.SelectCard(CardId.OrcustCymbalSkeleton); - return true; - } - ClientCard target; - target = GetFogBladeTarget(); - if (target != null && target != RustyBardicheTarget) - { - AI.SelectOption(0); - AI.SelectCard(target); - return true; - } - target = Util.GetProblematicEnemyMonster(); - if (target != null && target != RustyBardicheTarget) - { - AI.SelectOption(0); - AI.SelectCard(target); - return true; - } - target = Util.GetProblematicEnemySpell(); - if (target != null && target != RustyBardicheTarget) - { - AI.SelectOption(0); - AI.SelectCard(target); - return true; - } - if (Bot.HasInBanished(CardId.OrcustCymbalSkeleton)) - { - AI.SelectOption(1); - AI.SelectCard(CardId.OrcustCymbalSkeleton); - return true; - } - target = Enemy.MonsterZone.GetFirstMatchingCard(card => card != RustyBardicheTarget) ?? Enemy.SpellZone.GetFirstMatchingCard(card => card != RustyBardicheTarget); - if (target != null) - { - AI.SelectOption(0); - AI.SelectCard(target); - return true; - } - AI.SelectOption(1); - //AI.SelectCard(); any card - return true; - } - - private bool AncientCloakEffect() - { - if (Bot.HasInMonstersZone(CardId.SalamangreatAlmiraj) && Bot.HasInExtra(CardId.KnightmarePhoenix)) - AI.SelectCard(CardId.ThePhantomKnightsofShadeBrigandine); - else - AI.SelectCard(CardId.ThePhantomKnightsofSilentBoots); - return true; - } - - private bool SilentBootsSummon() - { - return true; - } - - private bool SilentBootsEffect() - { - if (Bot.HasInMonstersZone(CardId.SalamangreatAlmiraj) && Bot.HasInExtra(CardId.KnightmarePhoenix)) - AI.SelectCard(CardId.ThePhantomKnightsofShadeBrigandine); - else - AI.SelectCard(CardId.PhantomKnightsFogBlade); - return true; - } - - private bool ShadeBrigandineSummonSecond() - { - if (DefaultOnBecomeTarget()) - return true; - return (Bot.HasInMonstersZone(CardId.SalamangreatAlmiraj) && Bot.HasInExtra(CardId.KnightmarePhoenix)) || - (Bot.HasInMonstersZone(CardId.JetSynchron) && Bot.HasInMonstersZone(CardId.ThePhantomKnightsofSilentBoots)); - } - - private bool GalateaSummonSecond() - { - if (!Util.IsTurn1OrMain2()) - return false; - - if (Bot.HasInMonstersZone(CardId.GalateaTheOrcustAutomaton)) - return false; - - IList selected = new List(); - - if (!Bot.HasInGraveyard(CardId.SheorcustDingirsu)) - { - ClientCard sheorcustDingirsu = Bot.MonsterZone.GetFirstMatchingFaceupCard(card => card.IsCode(CardId.SheorcustDingirsu)); - if (sheorcustDingirsu != null) - selected.Add(sheorcustDingirsu); - } - - int[] matids = new[] { - CardId.OrcustKnightmare, - CardId.ThePhantomKnightsofSilentBoots, - CardId.ThePhantomKnightsofAncientCloak, - CardId.OrcustCymbalSkeleton, - CardId.OrcustHarpHorror, - CardId.ScrapRecycler, - CardId.CrystronNeedlefiber, - CardId.SkyStrikerAceKagari, - CardId.KnightmareMermaid, - CardId.ArmageddonKnight - }; - - IList mats = Bot.MonsterZone.GetMatchingCards(card => card.Level > 0 && card.Level <= 7); - - for (int i = 0; i < matids.Length && selected.Count < 2; i++) - { - ClientCard c = mats.GetFirstMatchingFaceupCard(card => card.IsCode(matids[i])); - if (c != null) - { - selected.Add(c); - if (selected.Count == 2 && Util.GetBotAvailZonesFromExtraDeck(selected) == 0) - selected.Remove(c); - } - } - - if (selected.Count == 2) - { - AI.SelectMaterials(selected); - return true; - } - - return false; - } - - private bool GalateaEffect() - { - if (Duel.Player == 0) - { - AI.SelectCard(CardId.OrcustKnightmare); - AI.SelectNextCard(CardId.OrcustratedBabel); - } - if (Duel.Player == 1) - { - AI.SelectCard(CardId.OrcustKnightmare); - AI.SelectNextCard(CardId.OrcustratedClimax); - } - return true; - } - - private bool BorrelswordDragonSummon() - { - if (Util.IsTurn1OrMain2()) - return false; - - List mats = Bot.MonsterZone.GetMatchingCards(card => card.IsFaceup() && card.HasType(CardType.Effect) && card.Attack <= 2000).ToList(); - mats.Sort(CardContainer.CompareCardAttack); - mats.Reverse(); - - int link = 0; - bool doubleused = false; - IList selected = new List(); - foreach (ClientCard card in mats) - { - selected.Add(card); - if (!doubleused && card.LinkCount == 2) - { - doubleused = true; - link += 2; - } - else - link++; - if (link >= 4) - break; - } - - if (link >= 4 && Util.GetBotAvailZonesFromExtraDeck(selected) > 0) - { - AI.SelectMaterials(selected); - return true; - } - return false; - } - - private bool BorrelswordDragonEffect() - { - if (ActivateDescription == -1 || ActivateDescription == Util.GetStringId(CardId.BorrelswordDragon, 1)) - { - BorrelswordDragonUsed = true; - return true; - } - else - { - if (Duel.Player == 0 && (Duel.Turn == 1 || Duel.Phase >= DuelPhase.Main2)) - { - return false; - } - ClientCard target = Bot.MonsterZone.GetFirstMatchingCard(card => card.IsAttack() && !card.HasType(CardType.Link) && card.Attacked && !card.IsShouldNotBeTarget()); - if (target != null) - { - AI.SelectCard(target); - return true; - } - if (!Bot.MonsterZone.IsExistingMatchingCard(card => card.IsAttack() && !card.HasType(CardType.Link))) - { - target = Enemy.MonsterZone.GetFirstMatchingCard(card => card.IsAttack() && !card.HasType(CardType.Link) && !card.IsShouldNotBeTarget()); - if (target != null) - { - AI.SelectCard(target); - return true; - } - } - return false; - } - } - - private bool BabelEffect() - { - if (Card.Location == CardLocation.Grave) - { - IList costCards = Bot.Hand.GetMatchingCards(card => card.IsCode(HandCosts)); - if (costCards.Count > 0) - { - AI.SelectCard(HandCosts); - return true; - } - return false; - } - return Bot.HasInMonstersZoneOrInGraveyard(new[] { - CardId.OrcustCymbalSkeleton, - CardId.OrcustHarpHorror, - CardId.OrcustKnightmare, - CardId.GalateaTheOrcustAutomaton, - CardId.LongirsuTheOrcustOrchestrator, - CardId.SheorcustDingirsu - }); - } - - private bool ShadeBrigandineSummonFirst() - { - return Bot.GetMonsterCount() < 2; - } - - private bool OneCardComboSummon() - { - if (Bot.HasInExtra(CardId.SalamangreatAlmiraj) && Bot.HasInExtra(new[] { CardId.CrystronNeedlefiber, CardId.KnightmarePhoenix }) && Bot.GetMonsterCount() < 3) - { - NormalSummoned = true; - return true; - } - return false; - } - - private bool LinkMaterialSummon() - { - if (Bot.HasInExtra(CardId.KnightmarePhoenix) && Bot.GetMonsterCount() > 0 && Bot.GetMonsterCount() < 3) - { - NormalSummoned = true; - return true; - } - return false; - } - - private bool TunerSummon() - { - if (Bot.HasInExtra(new[] { CardId.CrystronNeedlefiber, CardId.KnightmarePhoenix }) && Bot.GetMonsterCount() > 0 && Bot.GetMonsterCount() < 3) - { - NormalSummoned = true; - return true; - } - return false; - } - - private bool OtherSummon() - { - NormalSummoned = true; - return true; - } - - private bool BorreloadSavageDragonEffect() - { - if (Duel.CurrentChain.Count == 0) - { - AI.SelectCard(new[] { CardId.KnightmarePhoenix, CardId.CrystronNeedlefiber }); - return true; - } - else - { - return true; - } - } - - private bool FogBladeEffect() - { - if (Card.Location == CardLocation.SpellZone) - { - return !Util.HasChainedTrap(0) && DefaultDisableMonster(); - } - else if (Bot.HasInGraveyard(CardId.ThePhantomKnightsofRustyBardiche) || Bot.GetMonsterCount() < 2) - { - AI.SelectCard(CardId.ThePhantomKnightsofRustyBardiche); - return true; - } - return false; - } - - private bool ClimaxEffect() - { - if (Card.Location == CardLocation.SpellZone) - { - return Duel.LastChainPlayer == 1; - } + AI.SelectCard(CardId.OrcustCymbalSkeleton); + return true; + } + ClientCard target; + target = GetFogBladeTarget(); + if (target != null && target != RustyBardicheTarget) + { + AI.SelectOption(0); + AI.SelectCard(target); + return true; + } + target = Util.GetProblematicEnemyMonster(); + if (target != null && target != RustyBardicheTarget) + { + AI.SelectOption(0); + AI.SelectCard(target); + return true; + } + target = Util.GetProblematicEnemySpell(); + if (target != null && target != RustyBardicheTarget) + { + AI.SelectOption(0); + AI.SelectCard(target); + return true; + } + if (Bot.HasInBanished(CardId.OrcustCymbalSkeleton)) + { + AI.SelectOption(1); + AI.SelectCard(CardId.OrcustCymbalSkeleton); + return true; + } + target = Enemy.MonsterZone.GetFirstMatchingCard(card => card != RustyBardicheTarget) ?? Enemy.SpellZone.GetFirstMatchingCard(card => card != RustyBardicheTarget); + if (target != null) + { + AI.SelectOption(0); + AI.SelectCard(target); + return true; + } + AI.SelectOption(1); + //AI.SelectCard(); any card + return true; + } + + private bool AncientCloakEffect() + { + if (Bot.HasInMonstersZone(CardId.SalamangreatAlmiraj) && Bot.HasInExtra(CardId.KnightmarePhoenix)) + AI.SelectCard(CardId.ThePhantomKnightsofShadeBrigandine); + else + AI.SelectCard(CardId.ThePhantomKnightsofSilentBoots); + return true; + } + + private bool SilentBootsSummon() + { + return true; + } + + private bool SilentBootsEffect() + { + if (Bot.HasInMonstersZone(CardId.SalamangreatAlmiraj) && Bot.HasInExtra(CardId.KnightmarePhoenix)) + AI.SelectCard(CardId.ThePhantomKnightsofShadeBrigandine); + else + AI.SelectCard(CardId.PhantomKnightsFogBlade); + return true; + } + + private bool ShadeBrigandineSummonSecond() + { + if (DefaultOnBecomeTarget()) + return true; + return (Bot.HasInMonstersZone(CardId.SalamangreatAlmiraj) && Bot.HasInExtra(CardId.KnightmarePhoenix)) || + (Bot.HasInMonstersZone(CardId.JetSynchron) && Bot.HasInMonstersZone(CardId.ThePhantomKnightsofSilentBoots)); + } + + private bool GalateaSummonSecond() + { + if (!Util.IsTurn1OrMain2()) + return false; + + if (Bot.HasInMonstersZone(CardId.GalateaTheOrcustAutomaton)) + return false; + + IList selected = new List(); + + if (!Bot.HasInGraveyard(CardId.SheorcustDingirsu)) + { + ClientCard sheorcustDingirsu = Bot.MonsterZone.GetFirstMatchingFaceupCard(card => card.IsCode(CardId.SheorcustDingirsu)); + if (sheorcustDingirsu != null) + selected.Add(sheorcustDingirsu); + } + + int[] matids = new[] { + CardId.OrcustKnightmare, + CardId.ThePhantomKnightsofSilentBoots, + CardId.ThePhantomKnightsofAncientCloak, + CardId.OrcustCymbalSkeleton, + CardId.OrcustHarpHorror, + CardId.ScrapRecycler, + CardId.CrystronNeedlefiber, + CardId.SkyStrikerAceKagari, + CardId.KnightmareMermaid, + CardId.ArmageddonKnight + }; + + IList mats = Bot.MonsterZone.GetMatchingCards(card => card.Level > 0 && card.Level <= 7); + + for (int i = 0; i < matids.Length && selected.Count < 2; i++) + { + ClientCard c = mats.GetFirstMatchingFaceupCard(card => card.IsCode(matids[i])); + if (c != null) + { + selected.Add(c); + if (selected.Count == 2 && Util.GetBotAvailZonesFromExtraDeck(selected) == 0) + selected.Remove(c); + } + } + + if (selected.Count == 2) + { + AI.SelectMaterials(selected); + return true; + } + + return false; + } + + private bool GalateaEffect() + { + if (Duel.Player == 0) + { + AI.SelectCard(CardId.OrcustKnightmare); + AI.SelectNextCard(CardId.OrcustratedBabel); + } + if (Duel.Player == 1) + { + AI.SelectCard(CardId.OrcustKnightmare); + AI.SelectNextCard(CardId.OrcustratedClimax); + } + return true; + } + + private bool BorrelswordDragonSummon() + { + if (Util.IsTurn1OrMain2()) + return false; + + List mats = Bot.MonsterZone.GetMatchingCards(card => card.IsFaceup() && card.HasType(CardType.Effect) && card.Attack <= 2000).ToList(); + mats.Sort(CardContainer.CompareCardAttack); + mats.Reverse(); + + int link = 0; + bool doubleused = false; + IList selected = new List(); + foreach (ClientCard card in mats) + { + selected.Add(card); + if (!doubleused && card.LinkCount == 2) + { + doubleused = true; + link += 2; + } + else + link++; + if (link >= 4) + break; + } + + if (link >= 4 && Util.GetBotAvailZonesFromExtraDeck(selected) > 0) + { + AI.SelectMaterials(selected); + return true; + } + return false; + } + + private bool BorrelswordDragonEffect() + { + if (ActivateDescription == -1 || ActivateDescription == Util.GetStringId(CardId.BorrelswordDragon, 1)) + { + BorrelswordDragonUsed = true; + return true; + } + else + { + if (Duel.Player == 0 && (Duel.Turn == 1 || Duel.Phase >= DuelPhase.Main2)) + { + return false; + } + ClientCard target = Bot.MonsterZone.GetFirstMatchingCard(card => card.IsAttack() && !card.HasType(CardType.Link) && card.Attacked && !card.IsShouldNotBeTarget()); + if (target != null) + { + AI.SelectCard(target); + return true; + } + if (!Bot.MonsterZone.IsExistingMatchingCard(card => card.IsAttack() && !card.HasType(CardType.Link))) + { + target = Enemy.MonsterZone.GetFirstMatchingCard(card => card.IsAttack() && !card.HasType(CardType.Link) && !card.IsShouldNotBeTarget()); + if (target != null) + { + AI.SelectCard(target); + return true; + } + } + return false; + } + } + + private bool BabelEffect() + { + if (Card.Location == CardLocation.Grave) + { + IList costCards = Bot.Hand.GetMatchingCards(card => card.IsCode(HandCosts)); + if (costCards.Count > 0) + { + AI.SelectCard(HandCosts); + return true; + } + return false; + } + return Bot.HasInMonstersZoneOrInGraveyard(new[] { + CardId.OrcustCymbalSkeleton, + CardId.OrcustHarpHorror, + CardId.OrcustKnightmare, + CardId.GalateaTheOrcustAutomaton, + CardId.LongirsuTheOrcustOrchestrator, + CardId.SheorcustDingirsu + }); + } + + private bool ShadeBrigandineSummonFirst() + { + return Bot.GetMonsterCount() < 2; + } + + private bool OneCardComboSummon() + { + if (Bot.HasInExtra(CardId.SalamangreatAlmiraj) && Bot.HasInExtra(new[] { CardId.CrystronNeedlefiber, CardId.KnightmarePhoenix }) && Bot.GetMonsterCount() < 3) + { + NormalSummoned = true; + return true; + } + return false; + } + + private bool LinkMaterialSummon() + { + if (Bot.HasInExtra(CardId.KnightmarePhoenix) && Bot.GetMonsterCount() > 0 && Bot.GetMonsterCount() < 3) + { + NormalSummoned = true; + return true; + } + return false; + } + + private bool TunerSummon() + { + if (Bot.HasInExtra(new[] { CardId.CrystronNeedlefiber, CardId.KnightmarePhoenix }) && Bot.GetMonsterCount() > 0 && Bot.GetMonsterCount() < 3) + { + NormalSummoned = true; + return true; + } + return false; + } + + private bool OtherSummon() + { + NormalSummoned = true; + return true; + } + + private bool BorreloadSavageDragonEffect() + { + if (Duel.CurrentChain.Count == 0) + { + AI.SelectCard(new[] { CardId.KnightmarePhoenix, CardId.CrystronNeedlefiber }); + return true; + } + else + { + return true; + } + } + + private bool FogBladeEffect() + { + if (Card.Location == CardLocation.SpellZone) + { + return !Util.HasChainedTrap(0) && DefaultDisableMonster(); + } + else if (Bot.HasInGraveyard(CardId.ThePhantomKnightsofRustyBardiche) || Bot.GetMonsterCount() < 2) + { + AI.SelectCard(CardId.ThePhantomKnightsofRustyBardiche); + return true; + } + return false; + } + + private bool ClimaxEffect() + { + if (Card.Location == CardLocation.SpellZone) + { + return Duel.LastChainPlayer == 1; + } else if (Duel.Phase == DuelPhase.End) { ClientCard target = null; @@ -1146,29 +1146,29 @@ private bool ClimaxEffect() AI.SelectCard(CardId.OrcustHarpHorror); return true; } - } - return false; - } - - private bool EagleBoosterEffect() - { - if (Duel.LastChainPlayer != 1) - return false; - ClientCard target = Bot.GetMonstersInExtraZone().GetFirstMatchingCard( - card => Duel.CurrentChain.Contains(card) || card.IsCode(CardId.KnightmareMermaid)); - if (target != null) - { - AI.SelectCard(target); - return true; - } - return false; - } - - private bool MonsterRepos() - { - if (Card.IsFacedown()) - return true; - return DefaultMonsterRepos(); - } - } -} + } + return false; + } + + private bool EagleBoosterEffect() + { + if (Duel.LastChainPlayer != 1) + return false; + ClientCard target = Bot.GetMonstersInExtraZone().GetFirstMatchingCard( + card => Duel.CurrentChain.Contains(card) || card.IsCode(CardId.KnightmareMermaid)); + if (target != null) + { + AI.SelectCard(target); + return true; + } + return false; + } + + private bool MonsterRepos() + { + if (Card.IsFacedown()) + return true; + return DefaultMonsterRepos(); + } + } +} diff --git a/Game/AI/Decks/PureWindsExecutor.cs b/Game/AI/Decks/PureWindsExecutor.cs new file mode 100644 index 00000000..fa0290e3 --- /dev/null +++ b/Game/AI/Decks/PureWindsExecutor.cs @@ -0,0 +1,1068 @@ +using YGOSharp.OCGWrapper.Enums; +using System.Collections.Generic; +using WindBot; +using WindBot.Game; +using WindBot.Game.AI; +using System.Linq; + +namespace WindBot.Game.AI.Decks +{ + [Deck("PureWinds", "AI_PureWinds")] + // Made by Pluani (AniHelp) and Szefo + class PureWindsExecutor : DefaultExecutor + { + public class CardId + { + public const int SpeedroidTerrortop = 81275020; + public const int WindwitchIceBell = 43722862; + public const int PilicaDescendantOfGusto = 71175527; + public const int SpeedroidTaketomborg = 53932291; + public const int WindaPriestessOfGusto = 54455435; + public const int WindwitchGlassBell = 71007216; + + public const int GustoGulldo = 65277087; + public const int GustoEgul = 91662792; + public const int WindwitchSnowBell = 70117860; + public const int SpeedroidRedEyedDice = 16725505; + public const int Raigeki = 12580477; + public const int MonsterReborn = 83764719; + public const int Reasoning = 58577036; + public const int ElShaddollWinda = 94977269; + + public const int QuillPenOfGulldos = 27980138; + public const int CosmicCyclone = 8267140; + public const int EmergencyTeleport = 67723438; + + public const int ForbiddenChalice = 25789292; + public const int SuperTeamBuddyForceUnite = 8608979; + public const int KingsConsonance = 24590232; + public const int GozenMatch = 53334471; + public const int SolemnStrike = 40605147; + public const int SolemnWarning = 84749824; + + public const int MistWurm = 27315304; + public const int CrystalWingSynchroDragon = 50954680; + public const int ClearWingSynchroDragon = 82044279; + public const int WindwitchWinterBell = 14577226; + + public const int StardustChargeWarrior = 64880894; + public const int DaigustoSphreez = 29552709; + public const int DaigustoGulldos = 84766279; + + public const int HiSpeedroidChanbara = 42110604; + public const int OldEntityHastorr = 70913714; + public const int WynnTheWindCharmerVerdant = 30674956; + public const int GreatFly = 90512490; + public const int KnightmareIblee = 10158145; + public const int ChaosMax = 55410871; + public const int SkillDrain = 82732705; + public const int SoulDrain = 73599290; + public const int Rivalry = 90846359; + public const int OnlyOne = 24207889; + } + + List ReposTargets = new List + { CardId.GustoGulldo, + CardId.WindaPriestessOfGusto, + CardId.GustoEgul, + CardId.PilicaDescendantOfGusto, + CardId.DaigustoGulldos + }; + + List taketomborgSpList = new List + { CardId.WindwitchGlassBell, + CardId.GustoGulldo, + CardId.GustoEgul, + CardId.SpeedroidRedEyedDice, + CardId.WindwitchSnowBell, + CardId.SpeedroidTerrortop + }; + + List level1 = new List + { CardId.GustoEgul, + CardId.SpeedroidRedEyedDice, + CardId.WindwitchSnowBell + }; + + List Pilica = new List + { + CardId.ClearWingSynchroDragon, + CardId.WindwitchWinterBell, + CardId.StardustChargeWarrior + }; + + List level3 = new List + { CardId.PilicaDescendantOfGusto, + CardId.WindwitchIceBell, + CardId.SpeedroidTaketomborg + }; + List KeepSynchro = new List + { CardId.DaigustoSphreez, + CardId.CrystalWingSynchroDragon, + CardId.ClearWingSynchroDragon, + CardId.WindwitchWinterBell, + CardId.GreatFly, + CardId.WynnTheWindCharmerVerdant + }; + List KeepSynchro2 = new List + { + CardId.CrystalWingSynchroDragon, + CardId.DaigustoSphreez, + CardId.ClearWingSynchroDragon, + CardId.WindwitchWinterBell + }; + List reborn = new List + { CardId.ClearWingSynchroDragon, + CardId.DaigustoSphreez, + CardId.WindwitchWinterBell, + CardId.PilicaDescendantOfGusto, + CardId.OldEntityHastorr, + CardId.HiSpeedroidChanbara, + CardId.DaigustoGulldos + }; + List Gulldosulist = new List + { CardId.CrystalWingSynchroDragon, + CardId.MistWurm, + CardId.ClearWingSynchroDragon, + CardId.WindwitchWinterBell, + CardId.ClearWingSynchroDragon, + CardId.StardustChargeWarrior + }; + List Gulldosulist2 = new List + { + CardId.SpeedroidTerrortop, + CardId.PilicaDescendantOfGusto, + CardId.WindaPriestessOfGusto, + CardId.WindwitchIceBell, + CardId.SpeedroidTaketomborg, + CardId.OldEntityHastorr, + CardId.HiSpeedroidChanbara, + CardId.DaigustoGulldos, + CardId.DaigustoSphreez + }; + List EgulsuList = new List + { + CardId.SpeedroidTerrortop, + CardId.PilicaDescendantOfGusto, + CardId.WindaPriestessOfGusto, + CardId.WindwitchIceBell, + CardId.SpeedroidTaketomborg, + CardId.OldEntityHastorr, + CardId.HiSpeedroidChanbara, + CardId.DaigustoGulldos, + CardId.DaigustoSphreez, + CardId.StardustChargeWarrior, + CardId.WindwitchWinterBell, + CardId.ClearWingSynchroDragon + }; + List SynchroList = new List + { + CardId.SpeedroidTerrortop, + CardId.PilicaDescendantOfGusto, + CardId.WindwitchIceBell, + CardId.SpeedroidTaketomborg, + CardId.OldEntityHastorr, + CardId.HiSpeedroidChanbara, + CardId.DaigustoGulldos, + CardId.DaigustoSphreez, + CardId.StardustChargeWarrior, + CardId.WindwitchWinterBell, + CardId.ClearWingSynchroDragon, + CardId.CrystalWingSynchroDragon, + CardId.MistWurm + }; + List SynchroFull = new List + { + CardId.OldEntityHastorr, + CardId.HiSpeedroidChanbara, + CardId.DaigustoGulldos, + CardId.DaigustoSphreez, + CardId.StardustChargeWarrior, + CardId.WindwitchWinterBell, + CardId.ClearWingSynchroDragon, + CardId.CrystalWingSynchroDragon, + CardId.MistWurm + }; + List LinkList = new List + { + CardId.WynnTheWindCharmerVerdant, + CardId.GreatFly + }; + List tuner = new List + { + CardId.GustoGulldo, + CardId.GustoEgul, + CardId.SpeedroidRedEyedDice, + CardId.WindwitchGlassBell, + CardId.WindwitchSnowBell + }; + List gusto = new List + { + CardId.GustoGulldo, + CardId.GustoEgul, + CardId.WindaPriestessOfGusto, + CardId.PilicaDescendantOfGusto, + CardId.DaigustoGulldos, + CardId.DaigustoSphreez + }; + List ET = new List + { + CardId.ClearWingSynchroDragon, + CardId.WindwitchWinterBell + }; + + private bool WindwitchGlassBelleff_used; + private bool Summon_used; + private bool Pilica_eff; + private bool plan_A; + private int SnowBell_count = 0; + //TODO: reset the flags when they should reset ( public override void OnNewTurn() ) + public PureWindsExecutor(GameAI ai, Duel duel) + : base(ai, duel) + { + //counter + AddExecutor(ExecutorType.Activate, CardId.SolemnWarning, base.DefaultSolemnWarning); + AddExecutor(ExecutorType.Activate, CardId.ForbiddenChalice, ForbiddenChaliceeff); + AddExecutor(ExecutorType.Activate, CardId.CrystalWingSynchroDragon, CrystalWingSynchroDragoneff); + AddExecutor(ExecutorType.Activate, CardId.SolemnStrike, base.DefaultSolemnStrike); + AddExecutor(ExecutorType.Activate, CardId.GustoGulldo, GustoGulldoeff); + AddExecutor(ExecutorType.Activate, CardId.GustoEgul, GustoEguleff); + AddExecutor(ExecutorType.Activate, CardId.WindaPriestessOfGusto, WindaPriestessOfGustoeff); + AddExecutor(ExecutorType.Activate, CardId.PilicaDescendantOfGusto, PilicaDescendantOfGustoeff); + AddExecutor(ExecutorType.Activate, CardId.OldEntityHastorr, OldEntityHastorreff); + AddExecutor(ExecutorType.Activate, CardId.WynnTheWindCharmerVerdant, WynnTheWindCharmerVerdanteff); + AddExecutor(ExecutorType.Activate, CardId.GreatFly, GreatFlyeff); + AddExecutor(ExecutorType.Activate, CardId.QuillPenOfGulldos, QuillPenOfGulldoseff); + AddExecutor(ExecutorType.Activate, CardId.CosmicCyclone, CosmicCycloneeff); + AddExecutor(ExecutorType.Activate, CardId.MonsterReborn, Reborneff); + //plan A + AddExecutor(ExecutorType.Activate, CardId.WindwitchIceBell, WindwitchIceBelleff); + AddExecutor(ExecutorType.Activate, CardId.WindwitchGlassBell, WindwitchGlassBelleff); + AddExecutor(ExecutorType.Activate, CardId.WindwitchSnowBell, WindwitchSnowBellsp); + AddExecutor(ExecutorType.Activate, CardId.StardustChargeWarrior); + AddExecutor(ExecutorType.Activate, CardId.WindwitchWinterBell, WindwitchWinterBelleff); + AddExecutor(ExecutorType.Activate, CardId.ClearWingSynchroDragon, ClearWingSynchroDragoneff); + AddExecutor(ExecutorType.Activate, CardId.DaigustoSphreez, DaigustoSphreezeff); + AddExecutor(ExecutorType.Activate, CardId.SpeedroidTerrortop, SpeedroidTerrortopeff); + AddExecutor(ExecutorType.Activate, CardId.SpeedroidTaketomborg, SpeedroidTaketomborgeff); + AddExecutor(ExecutorType.Activate, CardId.SpeedroidRedEyedDice, SpeedroidRedEyedDiceeff); + AddExecutor(ExecutorType.Activate, CardId.MistWurm, MistWurmeff); + AddExecutor(ExecutorType.Activate, CardId.DaigustoGulldos, DaigustoGulldoseff); + AddExecutor(ExecutorType.SpSummon, CardId.WindwitchWinterBell, WindwitchWinterBellsp); + + AddExecutor(ExecutorType.SpSummon, CardId.CrystalWingSynchroDragon, CrystalWingSynchroDragonsp); + // if fail + AddExecutor(ExecutorType.SpSummon, CardId.ClearWingSynchroDragon, ClearWingSynchroDragonsp); + // if fail + AddExecutor(ExecutorType.SpSummon, CardId.DaigustoSphreez, DaigustoSphreezsp); + // plan B + AddExecutor(ExecutorType.SpSummon, CardId.SpeedroidTerrortop); + AddExecutor(ExecutorType.SpSummon, CardId.SpeedroidTaketomborg, SpeedroidTaketomborgsp); + //summon + AddExecutor(ExecutorType.Summon, CardId.PilicaDescendantOfGusto, PilicaDescendantOfGustosu); + AddExecutor(ExecutorType.Summon, CardId.GustoGulldo, GustoGulldosu); + AddExecutor(ExecutorType.Summon, CardId.GustoEgul, GustoEgulsu); + AddExecutor(ExecutorType.Summon, CardId.WindaPriestessOfGusto, WindaPriestessOfGustosu); + AddExecutor(ExecutorType.Summon, CardId.SpeedroidRedEyedDice, SpeedroidRedEyedDicesu); + //other thing + AddExecutor(ExecutorType.SpSummon, CardId.MistWurm); + AddExecutor(ExecutorType.SpSummon, CardId.DaigustoGulldos); + AddExecutor(ExecutorType.SpSummon, CardId.HiSpeedroidChanbara); + AddExecutor(ExecutorType.SpSummon, CardId.StardustChargeWarrior); + AddExecutor(ExecutorType.SpSummon, CardId.OldEntityHastorr); + AddExecutor(ExecutorType.SpSummon, CardId.GreatFly, GreatFlysp); + AddExecutor(ExecutorType.SpSummon, CardId.WynnTheWindCharmerVerdant, WynnTheWindCharmerVerdantsp); + AddExecutor(ExecutorType.Activate, CardId.Raigeki); + AddExecutor(ExecutorType.Activate, CardId.GozenMatch); + AddExecutor(ExecutorType.Activate, CardId.KingsConsonance, KingsConsonanceeff); + //trap set + AddExecutor(ExecutorType.SpellSet, CardId.KingsConsonance); + AddExecutor(ExecutorType.SpellSet, CardId.SolemnStrike); + AddExecutor(ExecutorType.SpellSet, CardId.SolemnWarning); + AddExecutor(ExecutorType.SpellSet, CardId.ForbiddenChalice); + AddExecutor(ExecutorType.SpellSet, CardId.SuperTeamBuddyForceUnite); + AddExecutor(ExecutorType.SpellSet, CardId.GozenMatch); + AddExecutor(ExecutorType.MonsterSet, CardId.GustoGulldo, gulldoset); + AddExecutor(ExecutorType.MonsterSet, CardId.GustoEgul, egulset); + AddExecutor(ExecutorType.MonsterSet, CardId.WindaPriestessOfGusto, windaset); + AddExecutor(ExecutorType.Summon, CardId.WindwitchGlassBell, WindwitchGlassBellsummonfirst); + AddExecutor(ExecutorType.Summon, CardId.WindwitchGlassBell, WindwitchGlassBellsummon); + AddExecutor(ExecutorType.MonsterSet, CardId.SpeedroidRedEyedDice, SpeedroidRedEyedDiceset); + AddExecutor(ExecutorType.MonsterSet, CardId.WindwitchSnowBell, WindwitchSnowBellset); + AddExecutor(ExecutorType.Activate, CardId.EmergencyTeleport, EmergencyTeleporteff); + AddExecutor(ExecutorType.Activate, CardId.Reasoning, Reasoningeff); + AddExecutor(ExecutorType.Activate, CardId.SuperTeamBuddyForceUnite, SuperTeamBuddyForceUniteeff); + + AddExecutor(ExecutorType.Repos, MonsterRepos); + } + + public override void OnNewTurn() + { + WindwitchGlassBelleff_used = false; + Summon_used = false; + Pilica_eff = false; + plan_A = false; + SnowBell_count = 0; + base.OnNewTurn(); + } + private bool windaset() + { + if (Enemy.HasInMonstersZoneOrInGraveyard(CardId.ChaosMax)) + return false; + return true; + } + private bool egulset() + { + if (Enemy.HasInMonstersZoneOrInGraveyard(CardId.ChaosMax)) + return false; + return true; + } + private bool gulldoset() + { + if (Enemy.HasInMonstersZoneOrInGraveyard(CardId.ChaosMax)) + return false; + return true; + } + + private bool Reasoningeff() + { + if ((Bot.HasInMonstersZone(CardId.CrystalWingSynchroDragon) || + Bot.HasInMonstersZone(CardId.MistWurm)) && + (Util.GetBotAvailZonesFromExtraDeck() == 0)) + return false; + if (Bot.HasInMonstersZone(level3) && + Bot.HasInMonstersZone(CardId.WindwitchGlassBell) && + Bot.HasInHand(CardId.WindwitchSnowBell)) + return false; + AI.SelectPosition(CardPosition.FaceUpDefence); + return true; + } + private bool KingsConsonanceeff() + { + AI.SelectCard(CardId.CrystalWingSynchroDragon, + CardId.DaigustoSphreez, + CardId.ClearWingSynchroDragon, + CardId.HiSpeedroidChanbara, + CardId.OldEntityHastorr); + return true; + } + private bool Reborneff() + { + if (Bot.HasInGraveyard(KeepSynchro2)) + { + AI.SelectCard(KeepSynchro2); + return true; + } + if (!Util.IsOneEnemyBetter(true)) return false; + if (!Bot.HasInGraveyard(reborn)) + { + return false; + } + AI.SelectCard(reborn); + return true; + } + + private bool SpeedroidRedEyedDiceset() + { + if (Enemy.HasInMonstersZone(CardId.ChaosMax)) + return false; + if ((Bot.GetMonstersInMainZone().Count + Bot.GetMonstersInExtraZone().Count) == 0) + return true; + return false; + } + + private bool WindwitchSnowBellset() + { + if (Enemy.HasInMonstersZone(CardId.ChaosMax)) + return false; + if ((Bot.GetMonstersInMainZone().Count + Bot.GetMonstersInExtraZone().Count) == 0) + return true; + return false; + } + + private bool GreatFlysp() + { + if (Bot.HasInMonstersZone(KeepSynchro)) + return false; + if (Bot.HasInMonstersZone(CardId.HiSpeedroidChanbara)) + return false; + if (Bot.HasInMonstersZone(CardId.WynnTheWindCharmerVerdant)) + return false; + return true; + } + private bool WynnTheWindCharmerVerdantsp() + { + if (Bot.HasInMonstersZone(KeepSynchro)) + return false; + if (Bot.HasInMonstersZone(CardId.HiSpeedroidChanbara)) + return false; + if (Bot.HasInMonstersZone(CardId.GreatFly)) + return false; + return true; + } + private bool MistWurmeff() + { + AI.SelectCard(Util.GetBestEnemyCard(false, true)); + if (Util.GetBestEnemyCard(false, true) != null) + Logger.DebugWriteLine("*************SelectCard= " + Util.GetBestEnemyCard(false, true).Id); + AI.SelectNextCard(Util.GetBestEnemyCard(false, true)); + if (Util.GetBestEnemyCard(false, true) != null) + Logger.DebugWriteLine("*************SelectCard= " + Util.GetBestEnemyCard(false, true).Id); + AI.SelectThirdCard(Util.GetBestEnemyCard(false, true)); + if (Util.GetBestEnemyCard(false, true) != null) + Logger.DebugWriteLine("*************SelectCard= " + Util.GetBestEnemyCard(false, true).Id); + return true; + } + private bool GustoGulldosu() + { + if (Bot.HasInMonstersZone(Gulldosulist) && + (Util.GetBotAvailZonesFromExtraDeck() == 0)) + { + return false; + } + else if (Bot.HasInMonstersZone(CardId.DaigustoSphreez) || + Bot.HasInHand(CardId.EmergencyTeleport)) + { + Summon_used = true; + return true; + } + else if (Bot.HasInMonstersZone(Gulldosulist2) || Bot.HasInHand(CardId.SpeedroidTaketomborg)) + { + Summon_used = true; + return true; + } + else + { + return false; + } + } + private bool GustoEgulsu() + { + if (Bot.HasInMonstersZone(CardId.DaigustoSphreez) && + !Bot.HasInHand(CardId.GustoGulldo)) + { + Summon_used = true; + return true; + } + else if ((Bot.HasInMonstersZone(CardId.CrystalWingSynchroDragon) || + Bot.HasInMonstersZone(CardId.MistWurm)) && + (Util.GetBotAvailZonesFromExtraDeck() == 0)) + return false; + else if (Bot.HasInMonstersZone(EgulsuList) || Bot.HasInHand(CardId.SpeedroidTaketomborg)) + { + Summon_used = true; + return true; + } + return false; + } + private bool WindaPriestessOfGustosu() + { + if (Bot.HasInMonstersZone(CardId.DaigustoSphreez) && + !Bot.HasInHand(CardId.GustoGulldo) && + !Bot.HasInHand(CardId.GustoEgul)) + { + Summon_used = true; + return true; + } + else if (Bot.HasInMonstersZone(CardId.GustoGulldo) || + Bot.HasInMonstersZone(CardId.WindwitchGlassBell) || + ((Bot.HasInMonstersZone(level3) || Bot.HasInMonstersZone(CardId.WindaPriestessOfGusto)) && + Bot.HasInMonstersZone(tuner)) || + (Bot.HasInMonstersZone(CardId.OldEntityHastorr) && Bot.HasInMonstersZone(level1)) && + (Util.GetBotAvailZonesFromExtraDeck() >= 1)) + { + Summon_used = true; + return true; + } + return false; + } + private bool SpeedroidRedEyedDicesu() + { + if ((Bot.HasInMonstersZone(CardId.CrystalWingSynchroDragon) || + Bot.HasInMonstersZone(CardId.MistWurm) || + Bot.HasInMonstersZone(CardId.DaigustoSphreez)) && + (Util.GetBotAvailZonesFromExtraDeck() == 0)) + return false; + else if (Bot.HasInMonstersZone(EgulsuList)) + { + Summon_used = true; + return true; + } + return false; + } + private bool PilicaDescendantOfGustosu() + { + if ((Bot.HasInMonstersZone(CardId.CrystalWingSynchroDragon) || + Bot.HasInMonstersZone(CardId.MistWurm)) && + (Util.GetBotAvailZonesFromExtraDeck() == 0)) + return false; + else if (Bot.HasInMonstersZone(Pilica) && + !Bot.HasInGraveyard(level1) && + (Util.GetBotAvailZonesFromExtraDeck() == 0)) + return false; + else if (!Bot.HasInMonstersZoneOrInGraveyard(tuner)) + return false; + else { + Summon_used = true; + return true; + } + } + private bool EmergencyTeleporteff() + { + if ((Bot.HasInMonstersZone(CardId.CrystalWingSynchroDragon) || + Bot.HasInMonstersZone(CardId.MistWurm)) && + (Util.GetBotAvailZonesFromExtraDeck() == 0)) + return false; + else if (Bot.HasInMonstersZone(level3) && + Bot.HasInMonstersZone(CardId.WindwitchGlassBell) && + Bot.HasInHand(CardId.WindwitchSnowBell)) + return false; + else if (Bot.HasInMonstersZone(tuner) && Bot.HasInMonstersZone(level3)) + return false; + else if (!Bot.HasInHandOrInMonstersZoneOrInGraveyard(tuner)) + return false; + else if (!Bot.HasInHandOrInMonstersZoneOrInGraveyard(level1) && Bot.HasInMonstersZone(ET)) + return false; + if (Pilica_eff == true) + return false; + AI.SelectCard(CardId.PilicaDescendantOfGusto); + AI.SelectPosition(CardPosition.FaceUpDefence); + return true; + } + private bool SpeedroidRedEyedDiceeff() + { + if (Bot.HasInMonstersZone(CardId.SpeedroidTerrortop)) + { + AI.SelectCard(CardId.SpeedroidTerrortop); + AI.SelectNumber(6); + return true; + } + else if (Bot.HasInMonstersZone(CardId.SpeedroidTaketomborg)) + { + AI.SelectCard(CardId.SpeedroidTaketomborg); + AI.SelectNumber(6); + return true; + } + else + { + return false; + } + } + + private bool DaigustoGulldoseff() + { + AI.SelectCard(); + AI.SelectNextCard(Util.GetBestEnemyMonster()); + return true; + } + private bool SpeedroidTaketomborgeff() + { + if ((Bot.GetRemainingCount(CardId.SpeedroidRedEyedDice, 1) >= 1) && + Bot.HasInMonstersZone(CardId.SpeedroidTerrortop)) + { + AI.SelectCard(CardId.SpeedroidRedEyedDice); + return true; + } + return false; + } + + private bool QuillPenOfGulldoseff() + { + var gyTargets = Bot.Graveyard.Where(x => x.Attribute == (int)CardAttribute.Wind).Select(x => x.Id).ToArray(); + if (gyTargets.Count() >= 2) + { + AI.SelectCard(gyTargets); + if (Bot.HasInSpellZone(CardId.OldEntityHastorr)) + { + AI.SelectNextCard(CardId.OldEntityHastorr); + } + else if (Util.GetProblematicEnemyCard() != null) + { + + AI.SelectNextCard(Util.GetProblematicEnemyCard()); + } + else + { + return false; + } + return true; + } + return false; + } + + private bool WindwitchIceBelleff() + { + if (Enemy.HasInMonstersZone(CardId.ElShaddollWinda)) return false; + if (WindwitchGlassBelleff_used && !Bot.HasInHand(CardId.WindwitchSnowBell)) return false; + //AI.SelectPlace(Zones.z2, 1); + if (Bot.GetRemainingCount(CardId.WindwitchGlassBell, 3) >= 1) + { + AI.SelectCard(CardId.WindwitchGlassBell); + } + else if (Bot.HasInHand(CardId.WindwitchGlassBell)) + { + AI.SelectCard(CardId.WindwitchSnowBell); + } + AI.GetSelectedPosition(); + if (Card.Location == CardLocation.Hand) + { + AI.SelectPosition(CardPosition.FaceUpDefence); + AI.SelectPosition(CardPosition.FaceUpDefence); + } + return true; + } + private bool SpeedroidTaketomborgsp() + { + if (Util.GetBotAvailZonesFromExtraDeck() == 0) + return false; + if (Bot.HasInMonstersZone(CardId.DaigustoSphreez)) + return false; + if (Bot.HasInMonstersZone(taketomborgSpList)) + { + AI.SelectPosition(CardPosition.FaceUpDefence); + return true; + } + return false; + + } + private bool WindwitchGlassBelleff() + { + if ((Bot.HasInHandOrHasInMonstersZone(CardId.WindwitchIceBell) || + Bot.HasInHandOrHasInMonstersZone(CardId.SpeedroidTaketomborg) || + Bot.HasInMonstersZone(CardId.PilicaDescendantOfGusto)) && + !Bot.HasInHand(CardId.WindwitchSnowBell)) + { + AI.SelectCard(CardId.WindwitchSnowBell); + WindwitchGlassBelleff_used = true; + return true; + } + AI.SelectCard(CardId.WindwitchIceBell); + WindwitchGlassBelleff_used = true; + return true; + } + + private bool OldEntityHastorreff() + { + AI.SelectCard(Util.GetBestEnemyMonster()); + return true; + } + + private bool WynnTheWindCharmerVerdanteff() + { + AI.SelectCard(CardId.PilicaDescendantOfGusto, CardId.WindwitchIceBell, CardId.SpeedroidTerrortop, CardId.GustoGulldo, CardId.GustoEgul, CardId.WindaPriestessOfGusto); + return true; + } + private bool SpeedroidTerrortopeff() + { + AI.SelectCard(CardId.SpeedroidTaketomborg, CardId.SpeedroidRedEyedDice); + return true; + } + private bool GreatFlyeff() + { + AI.SelectCard(CardId.PilicaDescendantOfGusto, CardId.WindwitchIceBell, CardId.SpeedroidTerrortop, CardId.GustoGulldo, CardId.GustoEgul, CardId.WindaPriestessOfGusto); + return true; + } + + private bool PilicaDescendantOfGustoeff() + { + AI.SelectCard(CardId.GustoGulldo, CardId.WindwitchGlassBell, CardId.WindwitchSnowBell, CardId.GustoEgul, CardId.SpeedroidRedEyedDice); + Pilica_eff = true; + return true; + } + + private bool SuperTeamBuddyForceUniteeff() + { + foreach (ClientCard card in Duel.CurrentChain) + if (card.IsCode(CardId.SuperTeamBuddyForceUnite)) + return false; + if (Bot.HasInGraveyard(CardId.PilicaDescendantOfGusto) && Bot.HasInMonstersZone(CardId.DaigustoSphreez)) + { + AI.SelectCard(CardId.SuperTeamBuddyForceUnite, CardId.DaigustoSphreez, CardId.PilicaDescendantOfGusto); + AI.SelectPosition(CardPosition.Attack); + return true; + } + + if (Bot.HasInGraveyard(CardId.WindaPriestessOfGusto) && Bot.HasInMonstersZone(CardId.DaigustoSphreez)) + { + AI.SelectCard(CardId.SuperTeamBuddyForceUnite, CardId.DaigustoSphreez, CardId.WindaPriestessOfGusto); + AI.SelectPosition(CardPosition.Attack); + return true; + } + + if (Bot.HasInGraveyard(CardId.DaigustoSphreez) && Bot.HasInMonstersZone(CardId.PilicaDescendantOfGusto)) + { + AI.SelectCard(CardId.SuperTeamBuddyForceUnite, CardId.PilicaDescendantOfGusto, CardId.DaigustoSphreez); + AI.SelectPosition(CardPosition.Attack); + return true; + } + + if (Bot.HasInGraveyard(CardId.DaigustoSphreez) && Bot.HasInMonstersZone(CardId.WindaPriestessOfGusto)) + { + AI.SelectCard(CardId.SuperTeamBuddyForceUnite, CardId.WindaPriestessOfGusto, CardId.DaigustoSphreez); + AI.SelectPosition(CardPosition.Attack); + return true; + } + if (Bot.HasInGraveyard(CardId.DaigustoGulldos) && Bot.HasInMonstersZone(CardId.WindaPriestessOfGusto)) + { + AI.SelectCard(CardId.SuperTeamBuddyForceUnite, CardId.WindaPriestessOfGusto, CardId.DaigustoGulldos); + AI.SelectPosition(CardPosition.Attack); + return true; + } + if (Bot.HasInGraveyard(CardId.DaigustoGulldos) && Bot.HasInMonstersZone(CardId.PilicaDescendantOfGusto)) + { + AI.SelectCard(CardId.SuperTeamBuddyForceUnite, CardId.DaigustoGulldos, CardId.PilicaDescendantOfGusto); + AI.SelectPosition(CardPosition.Attack); + return true; + } + if (Bot.HasInGraveyard(CardId.DaigustoSphreez) && Bot.HasInMonstersZone(CardId.DaigustoGulldos)) + { + AI.SelectCard(CardId.DaigustoGulldos, CardId.DaigustoSphreez); + AI.SelectPosition(CardPosition.Attack); + return true; + } + if (Bot.HasInGraveyard(CardId.CrystalWingSynchroDragon)) + { + AI.SelectCard(CardId.CrystalWingSynchroDragon); + AI.SelectPosition(CardPosition.Attack); + return true; + } + if (Bot.HasInGraveyard(CardId.CrystalWingSynchroDragon)) + { + AI.SelectCard(CardId.ClearWingSynchroDragon); + AI.SelectPosition(CardPosition.Attack); + return true; + } + if (Bot.HasInGraveyard(SynchroList)) + { + AI.SelectCard(SynchroList); + AI.SelectPosition(CardPosition.Attack); + return true; + } + if (Bot.HasInGraveyard(CardId.PilicaDescendantOfGusto) && Bot.HasInMonstersZone(CardId.WindaPriestessOfGusto)) + { + AI.SelectCard(CardId.WindaPriestessOfGusto, CardId.PilicaDescendantOfGusto); + AI.SelectPosition(CardPosition.FaceUpDefence); + return true; + } + if (Util.GetBotAvailZonesFromExtraDeck() >= 1) + { + if ((Bot.HasInMonstersZone(CardId.SpeedroidTerrortop) || + Bot.HasInMonstersZone(CardId.SpeedroidRedEyedDice) || + Bot.HasInMonstersZone(CardId.HiSpeedroidChanbara)) && + !Bot.HasInHand(CardId.SpeedroidTaketomborg)) + { + AI.SelectCard(CardId.SpeedroidRedEyedDice, CardId.SpeedroidTerrortop, CardId.SpeedroidTaketomborg); + return true; + } + if ((Bot.HasInMonstersZone(CardId.SpeedroidTerrortop) || + Bot.HasInMonstersZone(CardId.SpeedroidRedEyedDice) || + Bot.HasInMonstersZone(CardId.HiSpeedroidChanbara)) && + Bot.HasInHand(CardId.SpeedroidTaketomborg)) + return false; + } + if (Bot.HasInGraveyard(CardId.SuperTeamBuddyForceUnite)) + { + AI.SelectCard(CardId.SuperTeamBuddyForceUnite); + return true; + } + return false; + } + + private bool WindwitchSnowBellsp() + { + if (SnowBell_count >= 5) return false; + if ((Bot.HasInMonstersZone(CardId.CrystalWingSynchroDragon) || + Bot.HasInMonstersZone(CardId.DaigustoSphreez) || + Bot.HasInMonstersZone(CardId.MistWurm)) && + (Util.GetBotAvailZonesFromExtraDeck() == 0)) + return false; + if (Bot.HasInMonstersZone(level3) && + Bot.HasInMonstersZone(CardId.WindwitchGlassBell) && + Bot.HasInMonstersZone(level1)) + return false; + if ((Bot.HasInMonstersZone(CardId.ClearWingSynchroDragon) || + Bot.HasInMonstersZone(CardId.WindwitchWinterBell)) && + Bot.HasInMonstersZone(CardId.WindwitchSnowBell) && + (Util.GetBotAvailZonesFromExtraDeck() == 0)) + return false; + AI.SelectPosition(CardPosition.FaceUpDefence); + SnowBell_count++; + return true; + } + private bool DaigustoSphreezsp() + { + //AI.SelectPlace(Zones.z5, Zones.ExtraMonsterZones); + AI.SelectCard(CardId.WindwitchSnowBell, CardId.PilicaDescendantOfGusto, CardId.WindaPriestessOfGusto); + AI.SelectCard(CardId.SpeedroidRedEyedDice, CardId.PilicaDescendantOfGusto, CardId.WindaPriestessOfGusto); + AI.SelectCard(CardId.GustoGulldo, CardId.PilicaDescendantOfGusto); + AI.SelectCard(CardId.WindwitchSnowBell, CardId.DaigustoGulldos); + AI.SelectCard(CardId.SpeedroidRedEyedDice, CardId.DaigustoGulldos); + AI.SelectPosition(CardPosition.Attack); + return true; + } + private bool DaigustoSphreezeff() + { + if (Summon_used == true) + { + AI.SelectCard(CardId.PilicaDescendantOfGusto, CardId.GustoGulldo, CardId.GustoEgul, CardId.WindaPriestessOfGusto); + return true; + } + AI.SelectCard(CardId.GustoGulldo, CardId.PilicaDescendantOfGusto, CardId.GustoEgul, CardId.WindaPriestessOfGusto); + return true; + } + private bool WindwitchWinterBelleff() + { + AI.SelectCard(CardId.WindwitchGlassBell); + return true; + } + + private bool WindwitchWinterBellsp() + { + if (Bot.HasInHandOrInSpellZone(CardId.SuperTeamBuddyForceUnite) || + Bot.HasInHandOrInSpellZone(CardId.MonsterReborn)) + return false; + if (Bot.HasInMonstersZone(CardId.WindwitchIceBell) && + Bot.HasInMonstersZone(CardId.WindwitchGlassBell) && + Bot.HasInMonstersZone(CardId.WindwitchSnowBell)) + { + //AI.SelectPlace(Zones.z5, Zones.ExtraMonsterZones); + AI.GetSelectedPosition(); + AI.SelectPosition(CardPosition.FaceUpAttack); + AI.SelectCard(CardId.WindwitchIceBell, CardId.WindwitchGlassBell); + return true; + } + + return false; + } + + private bool ClearWingSynchroDragonsp() + { + if (Bot.HasInMonstersZone(CardId.DaigustoSphreez)) + return false; + AI.SelectPosition(CardPosition.Attack); + return true; + } + + private bool ClearWingSynchroDragoneff() + { + if (Duel.LastChainPlayer == 1) + { + return true; + } + return false; + } + private bool CrystalWingSynchroDragonsp() + { + if (Bot.HasInMonstersZone(CardId.WindwitchSnowBell) && Bot.HasInMonstersZone(CardId.WindwitchWinterBell)) + { + //AI.SelectPlace(Zones.z5, Zones.ExtraMonsterZones); + plan_A = true; + } + else if (Bot.HasInMonstersZone(CardId.WindwitchSnowBell) && Bot.HasInMonstersZone(CardId.ClearWingSynchroDragon)) + { + //AI.SelectPlace(Zones.z5, Zones.ExtraMonsterZones); + plan_A = true; + } + return true; + } + private bool ForbiddenChaliceeff() + { + if (Duel.LastChainPlayer == 1) + { + var target = Util.GetProblematicEnemyMonster(0, true); + if (target != null && !target.IsShouldNotBeSpellTrapTarget() && Duel.CurrentChain.Contains(target)) + { + AI.SelectCard(target); + return true; + } + } + return false; + } + private bool CosmicCycloneeff() + { + foreach (ClientCard card in Duel.CurrentChain) + if (card.IsCode(_CardId.CosmicCyclone)) + return false; + if ((Enemy.HasInSpellZone(CardId.SkillDrain) || + Enemy.HasInSpellZone(CardId.Rivalry) || + Enemy.HasInSpellZone(CardId.OnlyOne)) && + (Bot.LifePoints > 1000)) + { + AI.SelectCard(CardId.SkillDrain, CardId.SoulDrain, CardId.Rivalry, CardId.OnlyOne); + return true; + } + if (Bot.HasInSpellZone(CardId.OldEntityHastorr) && (Bot.LifePoints > 1000)) + { + AI.SelectCard(CardId.OldEntityHastorr); + return true; + } + return (Bot.LifePoints > 1000) && DefaultMysticalSpaceTyphoon(); + } + private bool CrystalWingSynchroDragoneff() + { + if (Duel.LastChainPlayer == 1) + { + return true; + } + return false; + } + private bool GustoGulldoeff() + { + if (Bot.HasInMonstersZone(CardId.DaigustoSphreez)) + { + AI.SelectCard(CardId.GustoEgul, CardId.WindaPriestessOfGusto); + AI.SelectPosition(CardPosition.Attack); + return true; + } + AI.SelectCard(CardId.GustoEgul, CardId.WindaPriestessOfGusto); + AI.SelectPosition(CardPosition.FaceUpDefence); + return true; + } + private bool GustoEguleff() + { + if (Bot.HasInMonstersZone(CardId.DaigustoSphreez)) + { + AI.SelectCard(CardId.WindaPriestessOfGusto, CardId.PilicaDescendantOfGusto); + AI.SelectPosition(CardPosition.Attack); + return true; + } + AI.SelectCard(CardId.WindaPriestessOfGusto, CardId.PilicaDescendantOfGusto); + AI.SelectPosition(CardPosition.FaceUpDefence); + return true; + } + private bool WindaPriestessOfGustoeff() + { + if (Bot.HasInMonstersZone(CardId.DaigustoSphreez)) + { + AI.SelectCard(CardId.GustoGulldo, CardId.GustoEgul); + AI.SelectPosition(CardPosition.Attack); + } + AI.SelectCard(CardId.GustoGulldo, CardId.GustoEgul); + AI.SelectPosition(CardPosition.FaceUpDefence); + return true; + } + private bool WindwitchGlassBellsummonfirst() + { + if (Bot.HasInHand(CardId.PilicaDescendantOfGusto) && + (Bot.HasInGraveyard(CardId.GustoGulldo) || + Bot.HasInGraveyard(CardId.GustoEgul) || + Bot.HasInGraveyard(CardId.WindwitchGlassBell) || + Bot.HasInGraveyard(CardId.SpeedroidRedEyedDice))) + { + return false; + } + if (Bot.HasInMonstersZone(CardId.DaigustoSphreez)) + return false; + else if (!Bot.HasInHand(CardId.WindwitchIceBell)) + { + Summon_used = true; + return true; + } + return false; + } + private bool WindwitchGlassBellsummon() + { + if (!plan_A && (Bot.HasInGraveyard(CardId.WindwitchGlassBell) || Bot.HasInMonstersZone(CardId.WindwitchGlassBell))) + return false; + //AI.SelectPlace(Zones.z2, 1); + if (Bot.HasInMonstersZone(CardId.WindwitchIceBell) && + !Bot.HasInMonstersZone(CardId.WindwitchGlassBell)) + { + Summon_used = true; + return true; + } + if (WindwitchGlassBelleff_used) return false; + return false; + } + + public bool MonsterRepos() + { + if (Card.IsCode(CardId.CrystalWingSynchroDragon) || Card.IsCode(CardId.DaigustoSphreez)) + return (!Card.HasPosition(CardPosition.Attack)); + if (Card.IsCode(SynchroFull)) + { + if (Card.IsFacedown() || Card.IsDefense()) + return true; + } + if (Bot.HasInMonstersZone(CardId.DaigustoSphreez)) + { + if (Card.IsCode(gusto)) + { + if (Card.IsFacedown() || Card.IsDefense()) + { + return true; + } + else + { + return false; + } + } + } + + if ((Util.GetBotAvailZonesFromExtraDeck() >= 1) && + ((Bot.GetMonsterCount() - Bot.GetMonstersInExtraZone().Count) >= 2)) + { + if (Bot.HasInMonstersZone(tuner) && + (Bot.HasInMonstersZone(level3) || + Bot.HasInMonstersZone(CardId.WindwitchGlassBell))) + { + if (Card.IsFacedown()) + return true; + } + if (Bot.HasInMonstersZone(CardId.WindaPriestessOfGusto) && + (Bot.HasInMonstersZone(CardId.GustoGulldo) || + Bot.HasInMonstersZone(CardId.WindwitchGlassBell))) + { + if (Card.IsFacedown()) + return true; + } + if (((Bot.GetMonsterCount() - Bot.GetMonstersInExtraZone().Count) >= 3) && + Bot.HasInMonstersZone(level1) && + (Bot.HasInMonstersZone(CardId.WindaPriestessOfGusto) || + Bot.HasInMonstersZone(level3))) + { + if (Card.IsFacedown()) + return true; + } + if (((Bot.GetMonsterCount() - Bot.GetMonstersInExtraZone().Count) >= 2) && + (Bot.HasInMonstersZone(CardId.GustoGulldo) || Bot.HasInMonstersZone(CardId.WindwitchGlassBell)) && + Bot.HasInMonstersZone(CardId.WindaPriestessOfGusto)) + { + if (Card.IsFacedown()) + return true; + } + } + + if (Card.IsFacedown()) + return false; + return base.DefaultMonsterRepos(); + } + public override bool OnSelectHand() + { + // go first + return true; + } + public override bool OnPreBattleBetween(ClientCard attacker, ClientCard defender) + { + if (attacker.IsCode(CardId.CrystalWingSynchroDragon)) + { + if (defender.Level >= 5) + attacker.RealPower = attacker.Attack + defender.Attack; + return true; + } + else if (attacker.IsCode(CardId.DaigustoSphreez)) + { + attacker.RealPower = attacker.Attack + defender.Attack + defender.Defense; + return true; + } + else if (Bot.HasInMonstersZone(CardId.DaigustoSphreez) && + attacker.IsCode(CardId.DaigustoSphreez, CardId.GustoGulldo, CardId.GustoEgul, CardId.WindaPriestessOfGusto, CardId.PilicaDescendantOfGusto, CardId.DaigustoGulldos)) + { + attacker.RealPower = attacker.Attack + defender.Attack + defender.Defense; + return true; + } + return base.OnPreBattleBetween(attacker, defender); + } + + } +} \ No newline at end of file diff --git a/Game/AI/Decks/SalamangreatExecutor.cs b/Game/AI/Decks/SalamangreatExecutor.cs index 1bed44da..5d8ff387 100644 --- a/Game/AI/Decks/SalamangreatExecutor.cs +++ b/Game/AI/Decks/SalamangreatExecutor.cs @@ -1167,7 +1167,7 @@ public override void OnChainEnd() base.OnChainEnd(); } - public override int OnSelectPlace(int cardId, int player, CardLocation location, int available) + public override int OnSelectPlace(long cardId, int player, CardLocation location, int available) { if (player == 0) { diff --git a/Game/AI/Decks/SkyStrikerExecutor.cs b/Game/AI/Decks/SkyStrikerExecutor.cs index e5ca4e99..6f14c311 100644 --- a/Game/AI/Decks/SkyStrikerExecutor.cs +++ b/Game/AI/Decks/SkyStrikerExecutor.cs @@ -58,8 +58,8 @@ public class CardId public SkyStrikerExecutor(GameAI ai, Duel duel) : base(ai, duel) { - AddExecutor(ExecutorType.Activate, CardId.AshBlossom, DefaultTrap); - AddExecutor(ExecutorType.Activate, CardId.GhostRabbit, DefaultTrap); + AddExecutor(ExecutorType.Activate, CardId.AshBlossom, DefaultAshBlossomAndJoyousSpring); + AddExecutor(ExecutorType.Activate, CardId.GhostRabbit, DefaultGhostOgreAndSnowRabbit); AddExecutor(ExecutorType.Activate, CardId.EffectVeiler, DefaultBreakthroughSkill); AddExecutor(ExecutorType.Activate, CardId.SolemnWarning, DefaultSolemnWarning); AddExecutor(ExecutorType.Activate, CardId.SolemnJudgment, DefaultSolemnJudgment); diff --git a/Game/AI/Decks/TimeThiefExecutor.cs b/Game/AI/Decks/TimeThiefExecutor.cs new file mode 100644 index 00000000..efd28a58 --- /dev/null +++ b/Game/AI/Decks/TimeThiefExecutor.cs @@ -0,0 +1,285 @@ +using System; +using YGOSharp.OCGWrapper.Enums; +using System.Collections.Generic; +using System.Diagnostics; +using WindBot; +using WindBot.Game; +using WindBot.Game.AI; +using System.Linq; +using System.Reflection; + +namespace WindBot.Game.AI.Decks +{ + [Deck("TimeThief", "AI_Timethief")] + public class TimeThiefExecutor : DefaultExecutor + { + public class Monsters + { + //monsters + public const int TimeThiefWinder = 56308388; + public const int TimeThiefBezelShip = 82496079; + public const int TimeThiefCronocorder = 74578720; + public const int TimeThiefRegulator = 19891131; + public const int PhotonTrasher = 65367484; + public const int PerformTrickClown = 67696066; + } + + public class Spells + { + // spells + public const int UpstartGoblin = 70368879; + public const int Raigeki = 12580477; + public const int FoolishBurial = 81439173; + public const int TimeThiefStartup = 10877309; + public const int TimeThiefHack = 81670445; + } + public class Traps + { + //traps + public const int XyzReborn = 26708437; + public const int XyzExtreme = 57319935; + public const int TimeThiefRetrograte = 76587747; + public const int PhantomKnightsShade = 98827725; + public const int TimeThiefFlyBack = 18678554; + } + public class XYZs + { + //xyz + public const int TimeThiefRedoer = 55285840; + public const int TimeThiefPerpetua = 59208943; + public const int CrazyBox = 42421606; + } + + + + public TimeThiefExecutor(GameAI ai, Duel duel) + : base(ai, duel) + { + // executors + //Spell activate + AddExecutor(ExecutorType.Activate,Spells.UpstartGoblin); + AddExecutor(ExecutorType.Activate,Spells.FoolishBurial,FoolishBurialTarget); + AddExecutor(ExecutorType.Activate,Spells.TimeThiefStartup,TimeThiefStartupEffect); + AddExecutor(ExecutorType.Activate,Spells.TimeThiefHack); + + // trap executors set + AddExecutor(ExecutorType.SpellSet,Traps.XyzExtreme); + AddExecutor(ExecutorType.SpellSet,Traps.XyzReborn); + AddExecutor(ExecutorType.SpellSet,Traps.PhantomKnightsShade); + AddExecutor(ExecutorType.SpellSet,Traps.TimeThiefRetrograte); + AddExecutor(ExecutorType.SpellSet,Traps.TimeThiefFlyBack); + + //normal summons + AddExecutor(ExecutorType.Summon,Monsters.TimeThiefRegulator ); + AddExecutor(ExecutorType.SpSummon, Monsters.PhotonTrasher, SummonToDef ); + AddExecutor(ExecutorType.Summon,Monsters.TimeThiefWinder ); + AddExecutor(ExecutorType.Summon,Monsters.TimeThiefBezelShip ); + AddExecutor(ExecutorType.Summon,Monsters.PerformTrickClown ); + AddExecutor(ExecutorType.Summon,Monsters.TimeThiefCronocorder ); + //xyz summons + AddExecutor(ExecutorType.SpSummon,XYZs.TimeThiefRedoer); + AddExecutor(ExecutorType.SpSummon,XYZs.TimeThiefPerpetua); + // activate trap + AddExecutor(ExecutorType.Activate,Traps.PhantomKnightsShade); + AddExecutor(ExecutorType.Activate,Traps.XyzExtreme , XyzExtremeEffect); + AddExecutor(ExecutorType.Activate,Traps.XyzReborn , XyzRebornEffect); + AddExecutor(ExecutorType.Activate,Traps.TimeThiefRetrograte , RetrograteEffect); + AddExecutor(ExecutorType.Activate,Traps.TimeThiefFlyBack ); + + //xyz effects + AddExecutor(ExecutorType.Activate,XYZs.TimeThiefRedoer,RedoerEffect); + AddExecutor(ExecutorType.Activate,XYZs.TimeThiefPerpetua , PerpertuaEffect); + + //monster effects + AddExecutor(ExecutorType.Activate,Monsters.TimeThiefRegulator , RegulatorEffect); + AddExecutor(ExecutorType.Activate,Monsters.TimeThiefWinder); + AddExecutor(ExecutorType.Activate,Monsters.TimeThiefCronocorder); + AddExecutor(ExecutorType.Activate,Monsters.PerformTrickClown, TrickClownEffect); + AddExecutor(ExecutorType.Activate,Monsters.TimeThiefBezelShip); + } + + private bool SummonToDef() + { + AI.SelectPosition(CardPosition.Defence); + return true; + } + + + private bool RegulatorEffect() + { + if (Card.Location == CardLocation.MonsterZone) + { + AI.SelectCard(Monsters.TimeThiefCronocorder); + AI.SelectCard(Monsters.TimeThiefWinder); + return true; + } + + if (Card.Location == CardLocation.Grave) + { + return true; + } + + return false; + } + + private bool PerpertuaEffect() + { + if (Bot.HasInGraveyard(XYZs.TimeThiefRedoer)) + { + AI.SelectCard(XYZs.TimeThiefRedoer); + return true; + } + + if (Bot.HasInMonstersZone(XYZs.TimeThiefRedoer)) + { + AI.SelectCard(Monsters.TimeThiefBezelShip); + AI.SelectNextCard(XYZs.TimeThiefRedoer); + return true; + } + + return false; + } + + private int _totalAttack; + private int _totalBotAttack; + private bool RedoerEffect() + { + + List enemy = Enemy.GetMonstersInMainZone(); + List units = Card.Overlays; + if (Duel.Phase == DuelPhase.Standby && (AI.Executor.Util.GetStringId(XYZs.TimeThiefRedoer,0) == + ActivateDescription)) + { + + return true; + } + + try + { + if (Bot.HasInSpellZone(Traps.XyzReborn)) + { + return false; + } + + if (Bot.HasInSpellZone(Traps.XyzExtreme)) + { + return false; + } + + for (int i = 0; i < enemy.Count; i++) + { + _totalAttack += enemy[i].Attack; + } + + foreach (var t in Bot.GetMonsters()) + { + _totalBotAttack += t.Attack; + } + + if (_totalAttack > Bot.LifePoints + _totalBotAttack) + { + return false; + } + + + + foreach (var t in enemy) + { + if (t.Attack < 2400 || !t.IsAttack()) continue; + try + { + AI.SelectCard(t.Id); + AI.SelectCard(t.Id); + } + catch{} + + return true; + } + } + catch{} + + if (Bot.UnderAttack) + { + //AI.SelectCard(Util.GetBestEnemyMonster()); + return true; + } + + return false; + + } + + private bool RetrograteEffect() + { + if (Card.Owner== 1) + { + return true; + } + return false; + + } + + private bool XyzRebornEffect() + { + if (Bot.HasInGraveyard(XYZs.TimeThiefRedoer)) + { + AI.SelectCard(XYZs.TimeThiefRedoer); + return true; + } + return true; + + } + //function + private bool XyzExtremeEffect() + { + AI.SelectCard(XYZs.CrazyBox); + return true; + } + private bool TimeThiefStartupEffect() + { + if (Card.Location == CardLocation.Hand) + { + if (Bot.HasInHand(Monsters.TimeThiefRegulator) && !(Bot.GetMonsterCount() > 0)) + { + AI.SelectCard(Monsters.TimeThiefRegulator); + return true; + } + if(Bot.HasInHand(Monsters.TimeThiefWinder) && Bot.GetMonsterCount()>1) + { + AI.SelectCard(Monsters.TimeThiefWinder); + return true; + } + return true; + + } + if (Card.Location == CardLocation.Grave) + { + AI.SelectCard(Monsters.TimeThiefCronocorder); + AI.SelectCard(Spells.TimeThiefHack); + AI.SelectCard(Traps.TimeThiefFlyBack); + return true; + } + + return false; + + } + private bool FoolishBurialTarget() + { + AI.SelectCard(Monsters.PerformTrickClown); + return true; + } + + private bool TrickClownEffect() + { + if (Bot.LifePoints <= 1000) + { + return false; + } + AI.SelectPosition(CardPosition.FaceUpDefence); + return true; + } + + + + } + +} diff --git a/Game/AI/Decks/TrickstarExecutor.cs b/Game/AI/Decks/TrickstarExecutor.cs index b5d23b7a..e30e284d 100644 --- a/Game/AI/Decks/TrickstarExecutor.cs +++ b/Game/AI/Decks/TrickstarExecutor.cs @@ -193,12 +193,7 @@ public bool Five_Rainbow() public int SelectSTPlace() { - List list = new List(); - list.Add(0); - list.Add(1); - list.Add(2); - list.Add(3); - list.Add(4); + List list = new List { 0, 1, 2, 3, 4 }; int n = list.Count; while (n-- > 1) { diff --git a/Game/AI/Decks/WitchcraftExecutor.cs b/Game/AI/Decks/WitchcraftExecutor.cs new file mode 100644 index 00000000..8bfb00af --- /dev/null +++ b/Game/AI/Decks/WitchcraftExecutor.cs @@ -0,0 +1,2928 @@ +using YGOSharp.OCGWrapper; +using YGOSharp.OCGWrapper.Enums; +using System.Collections.Generic; +using WindBot; +using WindBot.Game; +using WindBot.Game.AI; +using System.Linq; + + +namespace WindBot.Game.AI.Decks +{ + [Deck("Witchcraft", "AI_Witchcraft")] + + class WitchcraftExecutor : DefaultExecutor + { + public class CardId + { + public const int PSYDriver = 49036338; + public const int GolemAruru = 71074418; + public const int MadameVerre = 21522601; + public const int Haine = 84523092; + public const int Schmietta = 21744288; + public const int Pittore = 95245544; + public const int AshBlossom_JoyousSpring = 14558127; + public const int PSYGamma = 38814750; + public const int MaxxC = 23434538; + public const int Potterie = 59851535; + public const int Genni = 64756282; + public const int Collaboration = 10805153; + public const int ThatGrassLooksGreener = 11110587; + public const int LightningStorm = 14532163; + public const int PotofExtravagance = 49238328; + public const int DarkRulerNoMore = 54693926; + public const int Creation = 57916305; + public const int Reasoning = 58577036; + public const int MetalfoesFusion = 73594093; + public const int Holiday = 83301414; + public const int CalledbytheGrave = 24224830; + public const int Draping = 56894757; + public const int CrossoutDesignator = 65681983; + public const int Unveiling = 70226289; + public const int MagiciansLeftHand = 13758665; + public const int Scroll = 19673561; + public const int MagiciansRestage = 40252269; + public const int WitchcrafterBystreet = 83289866; + public const int MagicianRightHand = 87769556; + public const int InfiniteImpermanence = 10045474; + public const int Masterpiece = 55072170; + public const int Patronus = 94553671; + public const int BorreloadSavageDragon = 27548199; + public const int DracoBerserkeroftheTenyi = 5041348; + public const int PSYOmega = 74586817; + public const int TGWonderMagician = 98558751; + public const int BorrelswordDragon = 85289965; + public const int KnightmareUnicorn = 38342335; + public const int KnightmarePhoenix = 2857636; + public const int PSYLambda = 8802510; + public const int CrystronHalqifibrax = 50588353; + public const int SalamangreatAlmiraj = 60303245; + public const int RelinquishedAnima = 94259633; + + public const int NaturalExterio = 99916754; + public const int NaturalBeast = 33198837; + public const int ImperialOrder = 61740673; + public const int SwordsmanLV7 = 37267041; + public const int RoyalDecreel = 51452091; + public const int Anti_Spell = 58921041; + public const int Numbe41BagooskatheTerriblyTiredTapir = 90590303; + public const int PerformapalFive_RainbowMagician = 19619755; + + 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 WitchcraftExecutor(GameAI ai, Duel duel) + : base(ai, duel) + { + // do first + AddExecutor(ExecutorType.Activate, CardId.PotofExtravagance, PotofExtravaganceActivate); + AddExecutor(ExecutorType.SpellSet, SpellSetForFiveRainbow); + + // clear + AddExecutor(ExecutorType.Activate, CardId.DarkRulerNoMore, DarkRulerNoMoreActivate); + AddExecutor(ExecutorType.Activate, CardId.LightningStorm, LightningStormActivate); + AddExecutor(ExecutorType.Activate, CardId.RelinquishedAnima); + + // counter & quick effect + AddExecutor(ExecutorType.Activate, CardId.Schmietta, DeckSSWitchcraft); + AddExecutor(ExecutorType.Activate, CardId.Pittore, DeckSSWitchcraft); + AddExecutor(ExecutorType.Activate, CardId.Potterie, DeckSSWitchcraft); + AddExecutor(ExecutorType.Activate, CardId.Genni, DeckSSWitchcraft); + AddExecutor(ExecutorType.Activate, CardId.PSYGamma, PSYGammaActivate); + AddExecutor(ExecutorType.Activate, CardId.MaxxC, MaxxCActivate); + AddExecutor(ExecutorType.Activate, CardId.GolemAruru, GolemAruruActivate); + AddExecutor(ExecutorType.Activate, CardId.BorreloadSavageDragon, BorreloadSavageDragonActivate); + AddExecutor(ExecutorType.Activate, CardId.InfiniteImpermanence, InfiniteImpermanenceActivate); + AddExecutor(ExecutorType.Activate, CardId.AshBlossom_JoyousSpring, AshBlossom_JoyousSpringActivate); + AddExecutor(ExecutorType.Activate, CardId.CalledbytheGrave, CalledbytheGraveActivate); + AddExecutor(ExecutorType.Activate, CardId.CrossoutDesignator, CrossoutDesignatorActivate); + AddExecutor(ExecutorType.Activate, CardId.MagicianRightHand, SpellsActivate); + AddExecutor(ExecutorType.Activate, CardId.MagiciansLeftHand, SpellsActivate); + AddExecutor(ExecutorType.Activate, CardId.Unveiling, UnveilingActivate); + AddExecutor(ExecutorType.Activate, CardId.Draping, DrapingActivate); + AddExecutor(ExecutorType.Activate, CardId.PSYOmega, PSYOmegaActivate); + AddExecutor(ExecutorType.Activate, CardId.DracoBerserkeroftheTenyi, DracoBerserkeroftheTenyiActivate); + AddExecutor(ExecutorType.Activate, CardId.MadameVerre, MadameVerreActivate); + AddExecutor(ExecutorType.Activate, CardId.Haine, HaineActivate); + AddExecutor(ExecutorType.Activate, CardId.SalamangreatAlmiraj, SalamangreatAlmirajActivate); + + // PSY auto + AddExecutor(ExecutorType.Activate, CardId.PSYLambda); + AddExecutor(ExecutorType.SpSummon, CardId.PSYLambda, PSYLambdaSummon); + AddExecutor(ExecutorType.SpSummon, CardId.PSYOmega, Lv8Summon); + AddExecutor(ExecutorType.SpSummon, CardId.BorreloadSavageDragon, BorreloadSavageDragonSummon); + AddExecutor(ExecutorType.SpSummon, CardId.DracoBerserkeroftheTenyi, Lv8Summon); + AddExecutor(ExecutorType.SpSummon, CardId.BorreloadSavageDragon, Lv8Summon); + + // auto + AddExecutor(ExecutorType.Activate, CardId.WitchcrafterBystreet, WitchcraftRecycle); + AddExecutor(ExecutorType.Activate, WitchcraftRecycle); + AddExecutor(ExecutorType.Activate, CardId.MetalfoesFusion); + AddExecutor(ExecutorType.Activate, CardId.TGWonderMagician, TGWonderMagicianActivate); + AddExecutor(ExecutorType.Activate, CardId.KnightmareUnicorn, KnightmareUnicornActivate); + AddExecutor(ExecutorType.Activate, CardId.KnightmarePhoenix, KnightmarePhoenixActivate); + AddExecutor(ExecutorType.Activate, CardId.CrystronHalqifibrax, CrystronHalqifibraxActivate); + + // activate with counter + AddExecutor(ExecutorType.Activate, CardId.ThatGrassLooksGreener, SpellsActivatewithCounter); + AddExecutor(ExecutorType.Activate, CardId.Reasoning, SpellsActivatewithCounter); + + // witchcraft summon + AddExecutor(ExecutorType.Activate, CardId.Masterpiece, MasterpieceActivate); + AddExecutor(ExecutorType.Activate, CardId.Patronus, PatronusActivate); + AddExecutor(ExecutorType.Activate, CardId.MagiciansRestage, MagiciansRestageActivate); + AddExecutor(ExecutorType.Activate, CardId.Holiday, HolidayActivate); + + // summon + AddExecutor(ExecutorType.Summon, CardId.Schmietta, WitchcraftSummon); + AddExecutor(ExecutorType.Summon, CardId.Pittore, WitchcraftSummon); + AddExecutor(ExecutorType.Summon, CardId.Potterie, WitchcraftSummon); + AddExecutor(ExecutorType.Summon, CardId.Genni, WitchcraftSummon); + AddExecutor(ExecutorType.Activate, CardId.Creation, CreationActivate); + + // witchcraft resources + AddExecutor(ExecutorType.Activate, CardId.Pittore, PittoreActivate); + AddExecutor(ExecutorType.Activate, CardId.Schmietta, SchmiettaActivate); + AddExecutor(ExecutorType.Activate, CardId.Genni, GenniActivate); + AddExecutor(ExecutorType.Activate, CardId.Potterie, PotterieActivate); + + // extra calling + AddExecutor(ExecutorType.SpSummon, CardId.KnightmarePhoenix, KnightmarePhoenixSummon); + AddExecutor(ExecutorType.SpSummon, CardId.RelinquishedAnima, RelinquishedAnimaSummon); + AddExecutor(ExecutorType.SpSummon, CardId.CrystronHalqifibrax, CrystronHalqifibraxSummon); + AddExecutor(ExecutorType.SpSummon, CardId.BorrelswordDragon, BorrelswordDragonSummon); + AddExecutor(ExecutorType.SpSummon, CardId.KnightmareUnicorn, KnightmareUnicornSummon); + AddExecutor(ExecutorType.SpSummon, CardId.SalamangreatAlmiraj, SalamangreatAlmirajSummon); + AddExecutor(ExecutorType.Summon, SummonForLink); + + // activate spells normally + AddExecutor(ExecutorType.Activate, CardId.ThatGrassLooksGreener, SpellsActivateNoCost); + AddExecutor(ExecutorType.Activate, CardId.Reasoning, SpellsActivateNoCost); + AddExecutor(ExecutorType.Activate, CardId.MagicianRightHand, SpellsActivateNoCost); + AddExecutor(ExecutorType.Activate, CardId.MagiciansLeftHand, SpellsActivateNoCost); + + //AddExecutor(ExecutorType.SummonOrSet); + + // rest + AddExecutor(ExecutorType.Summon, WitchcraftSummonForRecycle); + AddExecutor(ExecutorType.Repos, MonsterRepos); + AddExecutor(ExecutorType.Activate, CardId.WitchcrafterBystreet, WitchcrafterBystreetActivate); + AddExecutor(ExecutorType.Activate, CardId.Scroll, ScrollActivate); + AddExecutor(ExecutorType.SpellSet, SpellSet); + } + + int Witchcraft_setcode = 0x128; + int TimeLord_setcode = 0x4a; + int[] important_witchcraft = { CardId.Haine, CardId.MadameVerre }; + Dictionary witchcraft_level = new Dictionary { + {CardId.GolemAruru, 8}, {CardId.MadameVerre, 7}, {CardId.Haine, 7}, {CardId.Schmietta, 4}, + {CardId.Pittore, 3}, {CardId.Potterie, 2}, {CardId.Genni, 1} + }; + + List Impermanence_list = new List(); + List FirstCheckSS = new List(); + List UseSSEffect = new List(); + List ActivatedCards = new List(); + Dictionary CalledbytheGraveCount = new Dictionary(); + int CrossoutDesignatorTarget = 0; + bool MadameVerreGainedATK = false; + bool summoned = false; + bool enemy_activate_MaxxC = false; + bool enemy_activate_DimensionShifter = false; + bool MagiciansLeftHand_used = false; + bool MagicianRightHand_used = false; + ClientCard MagiciansLeftHand_negate = null; + ClientCard MagicianRightHand_negate = null; + int PSYOmega_count = 0; + + // go first + public override bool OnSelectHand() + { + return true; + } + + // reset the negated card in case of activated again + public override void OnChainEnd() + { + if (MagiciansLeftHand_negate != null) + { + MagiciansLeftHand_used = true; + MagiciansLeftHand_negate = null; + } + if (MagicianRightHand_negate != null) + { + MagicianRightHand_used = true; + MagicianRightHand_negate = null; + } + base.OnChainEnd(); + } + + // check whether enemy activate important card + public override void OnChaining(int player, ClientCard card) + { + if (card == null) return; + // MagiciansLeftHand / MagicianRightHand + if (!MagicianRightHand_used && card.IsSpell() && card.Controller == 1) + { + if (Bot.MonsterZone.GetFirstMatchingCard(c => c.HasRace(CardRace.SpellCaster)) != null + && Bot.HasInSpellZone(CardId.MagicianRightHand, true)) + { + Logger.DebugWriteLine("MagicianRightHand negate: " + card.Name ?? "???"); + MagicianRightHand_negate = card; + } + } + if (!MagiciansLeftHand_used && card.IsTrap() && card.Controller == 1) + { + if (Bot.MonsterZone.GetFirstMatchingCard(c => c.HasRace(CardRace.SpellCaster)) != null + && Bot.HasInSpellZone(CardId.MagiciansLeftHand, true)) + { + Logger.DebugWriteLine("MagiciansLeftHand negate: " + card.Name ?? "???"); + MagiciansLeftHand_negate = card; + } + } + + if (player == 1 && card.Id == CardId.MaxxC && CheckCalledbytheGrave(CardId.MaxxC) == 0) + { + enemy_activate_MaxxC = true; + } + if (player == 1 && card.Id == CardId.DimensionShifter && CheckCalledbytheGrave(CardId.DimensionShifter) == 0) + { + enemy_activate_DimensionShifter = true; + } + if (player == 1 && card.Id == CardId.InfiniteImpermanence && CrossoutDesignatorTarget != CardId.InfiniteImpermanence) + { + for (int i = 0; i < 5; ++i) + { + if (Enemy.SpellZone[i] == card) + { + Impermanence_list.Add(4-i); + break; + } + } + } + base.OnChaining(player, card); + } + + // new turn reset + public override void OnNewTurn() + { + CrossoutDesignatorTarget = 0; + MadameVerreGainedATK = false; + summoned = false; + enemy_activate_MaxxC = false; + enemy_activate_DimensionShifter = false; + MagiciansLeftHand_used = false; + MagicianRightHand_used = false; + MagiciansLeftHand_negate = null; + MagicianRightHand_negate = null; + Impermanence_list.Clear(); + FirstCheckSS.Clear(); + UseSSEffect.Clear(); + ActivatedCards.Clear(); + int PSYOmega_count = 0; + // CalledbytheGrave refrest + List key_list = CalledbytheGraveCount.Keys.ToList(); + foreach (int dic in key_list) + { + if (CalledbytheGraveCount[dic] > 1) + { + CalledbytheGraveCount[dic] -= 1; + } + } + } + + // power fix + public override bool OnPreBattleBetween(ClientCard attacker, ClientCard defender) + { + if (!defender.IsMonsterHasPreventActivationEffectInBattle()) + { + if (!MadameVerreGainedATK && Bot.HasInMonstersZone(CardId.MadameVerre, true, false, true) && attacker.HasSetcode(Witchcraft_setcode)) + { + attacker.RealPower += CheckPlusAttackforMadameVerre(); + } + } + return base.OnPreBattleBetween(attacker, defender); + } + + // overwrite OnSelectCard to act normally in SelectUnselect + public override IList OnSelectCard(IList cards, int min, int max, long hint, bool cancelable) + { + // Patronus HINTMSG_ATOHAND + if (hint == 506) + { + bool flag = true; + foreach(ClientCard card in cards) + { + if (!card.HasSetcode(Witchcraft_setcode) || card.Location != CardLocation.Removed || !card.IsSpell()) + { + flag = false; + break; + } + } + if (flag) + { + Logger.DebugWriteLine("** Patronus recycle."); + // select all + IList selected = new List(); + for (int i = 1; i <= max; ++i) + { + selected.Add(cards[cards.Count - i]); + Logger.DebugWriteLine("** Select " + cards[cards.Count - i].Name ?? "???"); + } + return selected; + } + } + // MaxxC HINTMSG_SPSUMMON + if (hint == 509 && enemy_activate_MaxxC) + { + // check whether SS from deck while using effect + bool flag = true; + List levels = new List(); + List check_cardid = new List { CardId.Haine, CardId.MadameVerre, CardId.GolemAruru }; + List checked_card = new List { null, null, null }; + foreach (ClientCard card in cards) + { + if (card != null && card.Location == CardLocation.Deck && card.Controller == 0 && card.HasSetcode(Witchcraft_setcode)) + { + for (int i = 0; i < 3; ++i) + { + if (card.Id == check_cardid[i]) + { + checked_card[i] = card; + } + } + // Patronus also special summon from deck + if (!levels.Contains(card.Level)) + { + levels.Add(card.Level); + } + } + else + { + flag = false; + break; + } + } + + // only special summon advance monster + if (flag && levels.Count > 1) + { + Logger.DebugWriteLine("SS with MaxxC."); + IList result = new List(); + // check MadameVerre + int extra_attack = CheckPlusAttackforMadameVerre(true, true, true); + int bot_best = Util.GetBestAttack(Bot); + if (CheckProblematicCards() != null && Util.IsAllEnemyBetterThanValue(bot_best + extra_attack, true) == false) + { + if (!Bot.HasInMonstersZone(CardId.MadameVerre) && checked_card[1] != null) + { + result.Add(checked_card[1]); + return result; + } + } + for (int i = 0; i < 3; ++i) + { + if (checked_card[i] != null) + { + result.Add(checked_card[i]); + return result; + } + } + } + } + // MadameVerre HINTMSG_CONFIRM + if (hint == 526) + { + Logger.DebugWriteLine("** min-max: " + min.ToString() + " / " + max.ToString()); + foreach (ClientCard card in cards) + { + Logger.DebugWriteLine(card.Name ?? "???"); + } + + // select all + IList selected = new List(); + for (int i = 1; i <= max; ++i) + { + selected.Add(cards[cards.Count - i]); + Logger.DebugWriteLine("** Select " + cards[cards.Count - i].Name ?? "???"); + } + return selected; + } + + return base.OnSelectCard(cards, min, max, hint, cancelable); + } + + // position select + public override CardPosition OnSelectPosition(int cardId, IList positions) + { + NamedCard Data = NamedCard.Get(cardId); + if (Data == null) + { + return base.OnSelectPosition(cardId, positions); + } + if (!Enemy.HasInMonstersZone(_CardId.BlueEyesChaosMAXDragon) + && (Duel.Player == 1 && (cardId == CardId.MadameVerre || + Util.GetOneEnemyBetterThanValue(Data.Attack + 1) != null)) + || cardId == CardId.MaxxC || cardId == CardId.AshBlossom_JoyousSpring) + { + return CardPosition.FaceUpDefence; + } + if (cardId == CardId.MadameVerre && Util.IsTurn1OrMain2()) + { + return CardPosition.FaceUpDefence; + } + return base.OnSelectPosition(cardId, positions); + } + + // shuffle List + public List CardListShuffle(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; + } + + // check negated time count of id + public int CheckCalledbytheGrave(int id) + { + if (!CalledbytheGraveCount.ContainsKey(id)) + { + return 0; + } + return CalledbytheGraveCount[id]; + } + + // check enemy's dangerous card in grave + public List CheckDangerousCardinEnemyGrave(bool onlyMonster = false) + { + List result = Enemy.Graveyard.GetMatchingCards(card => + (!onlyMonster || card.IsMonster()) && card.HasSetcode(0x11b)).ToList(); + return result; + } + + // check whether negate maxxc and InfiniteImpermanence + public void CheckDeactiveFlag() + { + if (Util.GetLastChainCard() != null && Util.GetLastChainCard().Id == CardId.MaxxC && Duel.LastChainPlayer == 1) + { + enemy_activate_MaxxC = false; + } + if (Util.GetLastChainCard() != null && Util.GetLastChainCard().Id == CardId.DimensionShifter && Duel.LastChainPlayer == 1) + { + enemy_activate_DimensionShifter = false; + } + } + + /// + /// Check count of discardable spells for witchcraft monsters. + /// + /// Card that prepared to use and can't discard. + public int CheckDiscardableSpellCount(ClientCard except = null) + { + int discardable_hands = 0; + int count_witchcraftspell = Bot.Hand.GetMatchingCardsCount(card => (card.IsSpell() && (card.HasSetcode(Witchcraft_setcode)) && card != except)); + int count_remainhands = CheckRemainInDeck(CardId.MagiciansLeftHand, CardId.MagicianRightHand); + int count_MagiciansRestage = Bot.Hand.GetMatchingCardsCount(card => card.Id == CardId.MagiciansRestage && card != except); + int count_MetalfoesFusion = Bot.Hand.GetCardCount(CardId.MetalfoesFusion); + int count_WitchcrafterBystreet = Bot.SpellZone.GetMatchingCardsCount(card => card.IsFaceup() && card.Id == CardId.WitchcrafterBystreet && !card.IsDisabled()); + if (count_MagiciansRestage > 0) + { + discardable_hands += (count_MagiciansRestage > count_remainhands ? count_remainhands : count_MagiciansRestage); + } + if (!ActivatedCards.Contains(CardId.WitchcrafterBystreet) && (count_WitchcrafterBystreet >= 2 || (count_WitchcrafterBystreet >= 1 && Duel.Phase > DuelPhase.Battle))) + { + discardable_hands += 1; + } + discardable_hands += count_witchcraftspell + count_MetalfoesFusion; + return discardable_hands; + } + + /// + /// Check whether last chain card should be disabled. + /// + public bool CheckLastChainNegated() + { + ClientCard lastcard = Util.GetLastChainCard(); + if (lastcard == null || lastcard.Controller != 1) return false; + if (lastcard.IsMonster() && lastcard.HasSetcode(TimeLord_setcode) && Duel.Phase == DuelPhase.Standby) return false; + return lastcard == MagiciansLeftHand_negate || lastcard == MagicianRightHand_negate; + } + + /// + /// Check whether match link condition. + /// + /// Min Link count + /// Min material count + /// materails list + /// whether need tuner + /// + public bool CheckLinkMaterialsMatch(int LinkCount, int MaterialCount, List list, bool need_tune = false) + { + // material count check + if (list.Count < MaterialCount) return false; + + // link marker check + int linkcount = 0; + foreach(ClientCard card in list) + { + linkcount += (card.HasType(CardType.Link) ? card.LinkCount : 1); + } + if (linkcount != LinkCount) + { + foreach (ClientCard card in list) + { + linkcount += 1; + } + if (linkcount != LinkCount) return false; + } + + // tuner check + if (need_tune && list.GetFirstMatchingCard(card => card.IsTuner()) == null) return false; + return true; + } + + /// + /// Check link summon materials. If not enough, return an empty list. + /// + /// Link monster's link count. + /// Link monster's least material count. + /// Whether materials need tuner(use for CrystronHalqifibrax) + /// Extra monster use for material check. + public List CheckLinkMaterials(int LinkCount, int MaterialCount, bool need_tuner = false, List extra = null) + { + List psy_cardids = new List { CardId.PSYGamma, CardId.PSYDriver }; + List result = Bot.MonsterZone.GetMatchingCards(card => card.IsFaceup() && psy_cardids.Contains(card.Id)).ToList(); + if (CheckLinkMaterialsMatch(LinkCount, MaterialCount, result, need_tuner)) return result; + + List bot_monsters = Enemy.MonsterZone.GetMatchingCards(c => c.IsFaceup()).ToList(); + if (extra != null) bot_monsters = bot_monsters.Union(extra).ToList(); + bot_monsters.Sort(CardContainer.CompareCardAttack); + + int remaindiscard = CheckDiscardableSpellCount(); + int enemybest = Util.GetBestAttack(Enemy); + foreach (ClientCard card in bot_monsters) + { + if ((card.HasSetcode(Witchcraft_setcode) && (card.Level >= 5 || remaindiscard >= 2)) + || (card.Attack >= enemybest) + || (card.HasType(CardType.Link) && card.LinkMarker > 2)) + { + continue; + } + result.Add(card); + if (CheckLinkMaterialsMatch(LinkCount, MaterialCount, result, need_tuner)) return result; + } + if (!CheckLinkMaterialsMatch(LinkCount, MaterialCount, result, need_tuner)) result.Clear(); + + return result; + } + + /// + /// Check how many attack MadameVerre can provide + /// + /// whether ignore the activate of MadameVerre + /// check prerecycle spells in grave + /// force check whether have MadameVerre + public int CheckPlusAttackforMadameVerre(bool ignore_activated = false, bool check_recycle = false, bool force = false) + { + // not MadameVerre on field + if (!force && Bot.MonsterZone.GetFirstMatchingCard(card => card.Id == CardId.MadameVerre && !card.IsDisabled()) == null) return 0; + if (!ignore_activated && MadameVerreGainedATK) return 0; + + HashSet spells_id = new HashSet(); + foreach(ClientCard card in Bot.Hand) + { + if (card.IsSpell()) + { + spells_id.Add(card.Id); + } + } + if (check_recycle && Bot.MonsterZone.GetFirstMatchingCard(card => card.IsFaceup() && card.HasSetcode(Witchcraft_setcode)) != null) + { + List spell_checklist = new List { CardId.Holiday, CardId.Creation, CardId.Draping, CardId.Unveiling, CardId.Collaboration }; + foreach (int cardid in spell_checklist) + { + if (Bot.HasInGraveyard(cardid) && !ActivatedCards.Contains(cardid)) + { + spells_id.Add(Card.Id); + } + } + } + int max_hand = spells_id.Count() >= 6 ? 6 : spells_id.Count(); + return max_hand * 1000; + + } + + /// + /// Check problematic cards on enemy's field. + /// + /// whether can be targeted + /// only check danger monsters + public ClientCard CheckProblematicCards(bool canBeTarget = false, bool OnlyDanger = false) + { + ClientCard card = Enemy.MonsterZone.GetFloodgate(canBeTarget); + if (card != null) + return card; + + card = Enemy.MonsterZone.GetDangerousMonster(canBeTarget); + if (card != null + && (Duel.Player == 0 || (Duel.Phase > DuelPhase.Main1 && Duel.Phase < DuelPhase.Main2))) + return card; + + card = Enemy.MonsterZone.GetInvincibleMonster(canBeTarget); + if (card != null + && (Duel.Player == 0 || (Duel.Phase > DuelPhase.Main1 && Duel.Phase < DuelPhase.Main2))) + return card; + + List enemy_monsters = Enemy.MonsterZone.GetMatchingCards(c => c.IsFaceup()).ToList(); + enemy_monsters.Sort(CardContainer.CompareCardAttack); + enemy_monsters.Reverse(); + foreach (ClientCard target in enemy_monsters) + { + if (target.HasType(CardType.Fusion) || target.HasType(CardType.Ritual) || target.HasType(CardType.Synchro) || target.HasType(CardType.Xyz) || (target.HasType(CardType.Link) && target.LinkCount >= 2)) + { + if (!canBeTarget || !(target.IsShouldNotBeTarget() || target.IsShouldNotBeMonsterTarget())) return target; + } + } + + if (OnlyDanger) return null; + + int highest_self = Util.GetBestPower(Bot); + if (!MadameVerreGainedATK && Bot.HasInMonstersZone(CardId.MadameVerre, true, false, true)) + { + highest_self += CheckPlusAttackforMadameVerre(); + } + return Util.GetProblematicEnemyCard(highest_self, canBeTarget); + } + + /// + /// Check how many spells can be recylced to hand. + /// + public int CheckRecyclableCount(bool tohand = false, bool ignore_monster = false) + { + if (!ignore_monster && Bot.MonsterZone.GetFirstMatchingCard(card => card.IsFaceup() && card.HasSetcode(Witchcraft_setcode)) == null) return 0; + int result = 0; + List spell_checklist = new List { CardId.Holiday, CardId.Creation, CardId.Draping, CardId.Unveiling, CardId.Collaboration }; + if (!tohand) + { + spell_checklist.Add(CardId.WitchcrafterBystreet); + spell_checklist.Add(CardId.Scroll); + } + foreach (int cardid in spell_checklist) + { + if (Bot.HasInGraveyard(cardid) && !ActivatedCards.Contains(cardid)) + { + result++; + } + } + return result; + } + + /// + /// Check remain cards in deck + /// + /// Card's ID + public int CheckRemainInDeck(int id) + { + switch (id) + { + case CardId.PSYDriver: + return Bot.GetRemainingCount(CardId.PSYDriver, 1); + case CardId.GolemAruru: + return Bot.GetRemainingCount(CardId.GolemAruru, 1); + case CardId.MadameVerre: + return Bot.GetRemainingCount(CardId.MadameVerre, 1); + case CardId.Haine: + return Bot.GetRemainingCount(CardId.Haine, 2); + case CardId.Schmietta: + return Bot.GetRemainingCount(CardId.Schmietta, 3); + case CardId.Pittore: + return Bot.GetRemainingCount(CardId.Pittore, 3); + case CardId.AshBlossom_JoyousSpring: + return Bot.GetRemainingCount(CardId.AshBlossom_JoyousSpring, 1); + case CardId.PSYGamma: + return Bot.GetRemainingCount(CardId.PSYGamma, 3); + case CardId.MaxxC: + return Bot.GetRemainingCount(CardId.MaxxC, 1); + case CardId.Potterie: + return Bot.GetRemainingCount(CardId.Potterie, 1); + case CardId.Genni: + return Bot.GetRemainingCount(CardId.Genni, 2); + case CardId.Collaboration: + return Bot.GetRemainingCount(CardId.Collaboration, 1); + case CardId.ThatGrassLooksGreener: + return Bot.GetRemainingCount(CardId.ThatGrassLooksGreener, 2); + case CardId.LightningStorm: + return Bot.GetRemainingCount(CardId.LightningStorm, 2); + case CardId.PotofExtravagance: + return Bot.GetRemainingCount(CardId.PotofExtravagance, 3); + case CardId.DarkRulerNoMore: + return Bot.GetRemainingCount(CardId.DarkRulerNoMore, 2); + case CardId.Creation: + return Bot.GetRemainingCount(CardId.Creation, 3); + case CardId.Reasoning: + return Bot.GetRemainingCount(CardId.Reasoning, 3); + case CardId.MetalfoesFusion: + return Bot.GetRemainingCount(CardId.MetalfoesFusion, 1); + case CardId.Holiday: + return Bot.GetRemainingCount(CardId.Holiday, 3); + case CardId.CalledbytheGrave: + return Bot.GetRemainingCount(CardId.CalledbytheGrave, 3); + case CardId.Draping: + return Bot.GetRemainingCount(CardId.Draping, 1); + case CardId.CrossoutDesignator: + return Bot.GetRemainingCount(CardId.CrossoutDesignator, 2); + case CardId.Unveiling: + return Bot.GetRemainingCount(CardId.Unveiling, 1); + case CardId.MagiciansLeftHand: + return Bot.GetRemainingCount(CardId.MagiciansLeftHand, 1); + case CardId.Scroll: + return Bot.GetRemainingCount(CardId.Scroll, 1); + case CardId.MagiciansRestage: + return Bot.GetRemainingCount(CardId.MagiciansRestage, 2); + case CardId.WitchcrafterBystreet: + return Bot.GetRemainingCount(CardId.WitchcrafterBystreet, 3); + case CardId.MagicianRightHand: + return Bot.GetRemainingCount(CardId.MagicianRightHand, 1); + case CardId.InfiniteImpermanence: + return Bot.GetRemainingCount(CardId.InfiniteImpermanence, 3); + case CardId.Masterpiece: + return Bot.GetRemainingCount(CardId.Masterpiece, 1); + case CardId.Patronus: + return Bot.GetRemainingCount(CardId.Patronus, 2); + default: + return 0; + } + } + + /// + /// Check remain cards in deck + /// + /// Card's ID list + public int CheckRemainInDeck(params int[] ids) + { + int result = 0; + foreach (int cardid in ids) + { + result += CheckRemainInDeck(cardid); + } + return result; + } + + /// + /// Check whether cards will be removed. If so, do not send cards to grave. + /// + public bool CheckWhetherWillbeRemoved() + { + if (enemy_activate_DimensionShifter) return true; + List check_card = new List { CardId.BanisheroftheRadiance, CardId.BanisheroftheLight, CardId.MacroCosmos, CardId.DimensionalFissure }; + foreach(int cardid in check_card) + { + List fields = new List { Bot, Enemy }; + foreach (ClientField cf in fields) + { + if (cf.HasInMonstersZone(cardid, true) || cf.HasInSpellZone(cardid, true)) + { + return true; + } + } + } + return false; + } + + /// + /// Whether spell or trap will be negate. If so, return true. + /// + /// is counter trap + /// check target + /// + public bool SpellNegatable(bool isCounter = false, ClientCard target = null) + { + // target default set + if (target == null) target = Card; + if (target.Id == CrossoutDesignatorTarget) return true; + // won't negate if not on field + if (target.Location != CardLocation.SpellZone && target.Location != CardLocation.Hand) return false; + + // negate judge + 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()) + { + if (Enemy.HasInSpellZone(CardId.RoyalDecreel, true) || Bot.HasInSpellZone(CardId.RoyalDecreel, true)) return true; + } + // how to get here? + return false; + } + + /// + /// Check whether'll be negated + /// + public bool NegatedCheck(bool disablecheck = true){ + if (Card.IsSpell() || Card.IsTrap()){ + if (SpellNegatable()) return true; + } + if (CheckCalledbytheGrave(Card.Id) > 0 || Card.Id == CrossoutDesignatorTarget){ + return true; + } + if (Card.IsMonster() && Card.Location == CardLocation.MonsterZone && Card.IsDefense()) + { + if (Enemy.MonsterZone.GetFirstMatchingFaceupCard(card => card.Id == CardId.Numbe41BagooskatheTerriblyTiredTapir && card.IsDefense() && !card.IsDisabled()) != null + || Bot.MonsterZone.GetFirstMatchingFaceupCard(card => card.Id == CardId.Numbe41BagooskatheTerriblyTiredTapir && card.IsDefense() && !card.IsDisabled()) != null) + { + return true; + } + } + if (disablecheck){ + return Card.IsDisabled(); + } + return false; + } + + /// + /// Select spell/trap's place randomly to avoid InfiniteImpermanence and so on. + /// + /// Card to set(default current card) + /// Whether need to avoid InfiniteImpermanence + /// Whether need to avoid set in this place + public void SelectSTPlace(ClientCard card = null, bool avoid_Impermanence = false, List avoid_list=null) + { + List list = new List { 0, 1, 2, 3, 4 }; + int n = list.Count; + while (n-- > 1) + { + int index = Program.Rand.Next(n + 1); + int temp = list[index]; + list[index] = list[n]; + list[n] = temp; + } + foreach (int seq in list) + { + int zone = (int)System.Math.Pow(2, seq); + if (Bot.SpellZone[seq] == null) + { + if (card != null && card.Location == CardLocation.Hand && avoid_Impermanence && Impermanence_list.Contains(seq)) continue; + if (avoid_list != null && avoid_list.Contains(seq)) continue; + AI.SelectPlace(zone); + return; + }; + } + AI.SelectPlace(0); + } + + // Spell&trap's set + public bool SpellSet(){ + if (Duel.Phase == DuelPhase.Main1 && Bot.HasAttackingMonster() && Duel.Turn > 1) return false; + if (Card.Id == CardId.CrossoutDesignator && Duel.Turn >= 5) return false; + + // set condition + int[] activate_with_condition = { CardId.Masterpiece, CardId.Draping }; + if (activate_with_condition.Contains(Card.Id)) + { + if (Bot.MonsterZone.GetFirstMatchingCard(card => card.HasSetcode(Witchcraft_setcode)) == null) + { + return false; + } + } + if (Card.Id == CardId.Unveiling) + { + return false; + } + if (Card.Id == CardId.Patronus) + { + int count = Bot.Banished.GetMatchingCardsCount(card => card.HasSetcode(Witchcraft_setcode)); + if (count == 0) + { + count += Bot.Graveyard.GetMatchingCardsCount(card => card.HasSetcode(Witchcraft_setcode)); + } + if (count == 0) + { + return false; + } + } + + // prepare spells to discard + if (Card.IsSpell()){ + int spells_todiscard = CheckRecyclableCount() + Bot.Hand.GetMatchingCardsCount(card => card.IsSpell()); + int will_discard = 0; + if (Bot.HasInMonstersZone(CardId.Haine)) will_discard ++; + if (Bot.HasInMonstersZone(CardId.MadameVerre)) will_discard ++; + + if (will_discard >= spells_todiscard){ + return false; + } + } + + // select place + if ((Card.IsTrap() || Card.HasType(CardType.QuickPlay))) + { + List avoid_list = new List(); + int Impermanence_set = 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); + Impermanence_set += (int)System.Math.Pow(2, 4 - i); + } + } + if (Bot.HasInHand(CardId.InfiniteImpermanence)) + { + if (Card.IsCode(CardId.InfiniteImpermanence)) + { + AI.SelectPlace(Impermanence_set); + return true; + } else + { + SelectSTPlace(Card, false, avoid_list); + return true; + } + } else + { + SelectSTPlace(); + } + return true; + } + // anti-spell relevant + else if (Enemy.HasInSpellZone(CardId.Anti_Spell, true) || Bot.HasInSpellZone(CardId.Anti_Spell, true)) + { + if (Card.IsSpell() && Card.Id != CardId.MetalfoesFusion) + { + SelectSTPlace(); + return true; + } + } + return false; + } + + // Spell&trap's set for Performapal Five-Rainbow Magician + public bool SpellSetForFiveRainbow() + { + // check + bool have_FiveRainbow = false; + List list = new List(); + ClientCard l = null; + ClientCard r = null; + if (Duel.IsNewRule || Duel.IsNewRule2020) + { + list.Add(Enemy.SpellZone[0]); + list.Add(Enemy.SpellZone[4]); + } + else + { + list.Add(Enemy.SpellZone[6]); + list.Add(Enemy.SpellZone[7]); + } + foreach(ClientCard card in list) + { + if (card != null && card.Id == CardId.PerformapalFive_RainbowMagician) + { + have_FiveRainbow = true; + break; + } + } + + if (!have_FiveRainbow) return false; + if (Bot.GetMonsterCount() == 0 || Bot.SpellZone.GetFirstMatchingCard(card => card.IsFacedown()) != null) return false; + if (Card.IsSpell()) + { + SelectSTPlace(null, true); + return true; + } + + return false; + } + + // use for repos + public bool MonsterRepos() + { + int self_attack = Card.Attack + 1; + int extra_attack = CheckPlusAttackforMadameVerre(true, true); + Logger.DebugWriteLine("self_attack of " + (Card.Name ?? "X") + ": " + self_attack.ToString()); + if (Card.HasSetcode(Witchcraft_setcode)) + { + self_attack += extra_attack; + } + + if (Card.IsFaceup() && Card.IsDefense() && self_attack <= 1) + return false; + + int best_attack = 0; + foreach (ClientCard card in Bot.GetMonsters()) + { + int attack = card.Attack; + if (card.HasSetcode(Witchcraft_setcode)) + { + attack += extra_attack; + } + if (attack >= best_attack) + { + best_attack = attack; + } + } + + bool enemyBetter = Util.IsAllEnemyBetterThanValue(best_attack, true); + + if (Card.IsAttack() && enemyBetter) + return true; + if (Card.IsDefense() && !enemyBetter && self_attack >= Card.Defense) + return true; + return false; + } + + /// + /// Select spell cost for Witchcraft. + /// + public void SelectDiscardSpell() + { + int count_remainhands = CheckRemainInDeck(CardId.MagiciansLeftHand, CardId.MagicianRightHand); + int count_witchcraftspell = Bot.Hand.GetMatchingCardsCount(card => (card.IsSpell() && (card.HasSetcode(Witchcraft_setcode)))); + int WitchcrafterBystreet_count = Bot.SpellZone.GetMatchingCardsCount(card => card.IsFaceup() && card.Id == CardId.WitchcrafterBystreet); + if (Bot.HasInHand(CardId.MagiciansRestage) && count_remainhands > 0) + { + AI.SelectCard(CardId.MagiciansRestage); + } + else if (Bot.HasInHand(CardId.MetalfoesFusion)) + { + AI.SelectCard(CardId.MetalfoesFusion); + } + else if (!ActivatedCards.Contains(CardId.Scroll) && Bot.SpellZone.GetCardCount(CardId.Scroll) > 0) + { + AI.SelectCard(Bot.SpellZone.GetFirstMatchingFaceupCard(card => card.Id == CardId.Scroll)); + ActivatedCards.Add(CardId.Scroll); + } + else if (!ActivatedCards.Contains(CardId.WitchcrafterBystreet) && WitchcrafterBystreet_count >= 2) + { + AI.SelectCard(Bot.SpellZone.GetFirstMatchingFaceupCard(card => card.Id == CardId.WitchcrafterBystreet)); + ActivatedCards.Add(CardId.WitchcrafterBystreet); + } + else if (count_witchcraftspell > 0) + { + List cost_list = new List{ CardId.Scroll, CardId.WitchcrafterBystreet, CardId.Collaboration, CardId.Unveiling, CardId.Draping }; + if (Duel.Player == 1) + { + cost_list.Add(CardId.Creation); + cost_list.Add(CardId.Holiday); + } else + { + cost_list.Add(CardId.Holiday); + cost_list.Add(CardId.Creation); + } + foreach (int cardid in cost_list) + { + IList targets = Bot.Hand.GetMatchingCards(card => card.Id == cardid); + if (targets.Count() > 0) + { + AI.SelectCard(targets); + return; + } + } + AI.SelectCard(CardId.Scroll, CardId.WitchcrafterBystreet); + } + else if (Bot.HasInHand(CardId.PotofExtravagance) && Bot.ExtraDeck.Count < 6) + { + AI.SelectCard(CardId.PotofExtravagance); + } + else if (WitchcrafterBystreet_count >= 1) + { + AI.SelectCard(Bot.SpellZone.GetFirstMatchingFaceupCard(card => card.Id == CardId.WitchcrafterBystreet)); + ActivatedCards.Add(CardId.WitchcrafterBystreet); + } + else + { + AI.SelectCard(CardId.ThatGrassLooksGreener, CardId.LightningStorm, CardId.PotofExtravagance, CardId.MagiciansLeftHand, CardId.MagicianRightHand, CardId.CrossoutDesignator, CardId.CalledbytheGrave); + } + } + + /// + /// For normal spells activate + /// + public bool SpellsActivate() + { + if (SpellNegatable()) return false; + if (CheckDiscardableSpellCount() <= 1) return false; + if ((Card.Id == CardId.ThatGrassLooksGreener || Card.Id == CardId.Reasoning) && CheckWhetherWillbeRemoved()) return false; + if (Card.Id == CardId.MagiciansLeftHand || Card.Id == CardId.MagicianRightHand) + { + if (Bot.MonsterZone.GetFirstMatchingCard(card => card.HasRace(CardRace.SpellCaster)) == null + && (summoned || Bot.Hand.GetFirstMatchingCard(card => card.HasRace(CardRace.SpellCaster) && card.Level <= 4) == null)) + { + return false; + } + } + SelectSTPlace(Card, true); + return true; + } + + /// + /// For normal spells activate without cost + /// + public bool SpellsActivateNoCost() + { + if (SpellNegatable()) return false; + if ((Card.Id == CardId.ThatGrassLooksGreener || Card.Id == CardId.Reasoning) && CheckWhetherWillbeRemoved()) return false; + if (Card.Id == CardId.MagiciansLeftHand || Card.Id == CardId.MagicianRightHand) + { + if (Bot.MonsterZone.GetFirstMatchingCard(card => card.HasRace(CardRace.SpellCaster)) == null + && (summoned || Bot.Hand.GetFirstMatchingCard(card => card.HasRace(CardRace.SpellCaster) && card.Level <= 4) == null)) + { + return false; + } + } + SelectSTPlace(Card, true); + return true; + } + + /// + /// Check wheter have enough counter to care for important spells. if not, delay it. + /// + public bool SpellsActivatewithCounter() + { + if (SpellNegatable()) return false; + if ((Card.Id == CardId.ThatGrassLooksGreener || Card.Id == CardId.Reasoning) && CheckWhetherWillbeRemoved()) return false; + int[] counter_cards = { CardId.PSYGamma, CardId.CalledbytheGrave, CardId.CrossoutDesignator }; + int count = Bot.Hand.GetMatchingCardsCount(card => counter_cards.Contains(card.Id)); + count += Bot.SpellZone.GetMatchingCardsCount(card => counter_cards.Contains(card.Id)); + if (count > 0 || Bot.Hand.GetCardCount(Card.Id) >= 2) + { + SelectSTPlace(Card, true); + return true; + } + return Program.Rand.Next(2) > 0; + } + + /// + /// Summon Witchcraft for special summoning from deck. + /// + public bool WitchcraftSummon() + { + if (UseSSEffect.Contains(Card.Id)) return false; + int count_spell = Bot.Hand.GetMatchingCardsCount(card => (card.IsSpell())); + int count_target = CheckRemainInDeck(CardId.MadameVerre, CardId.Haine, CardId.GolemAruru); + if (count_spell > 0 && count_target > 0) + { + summoned = true; + return true; + } + return false; + } + + /// + /// Summon Witchcraft for recycling spells + /// + public bool WitchcraftSummonForRecycle() + { + if (!Card.HasSetcode(Witchcraft_setcode) || Card.Level > 4) + { + return false; + } + if (CheckRecyclableCount(false, true) > 0 && Bot.MonsterZone.GetFirstMatchingFaceupCard(card => card.HasSetcode(Witchcraft_setcode)) == null) + { + summoned = true; + return true; + } + return false; + } + + /// + /// Check whether summon monster for link summon. + /// + public bool SummonForLink() + { + // reject advance summon + if (Card.Level >= 5) return false; + summoned = true; + + if (BorrelswordDragonSummonCheck(Card).Count >= 3) + { + Logger.DebugWriteLine("Summon for BorrelswordDragon."); + List list = BorrelswordDragonSummonCheck(Card); + foreach( ClientCard c in list) + { + Logger.DebugWriteLine(c.Name ?? "???"); + } + return true; + } + if (KnightmareUnicornSummonCheck(Card).Count >= 2) + { + Logger.DebugWriteLine("Summon for KnightmareUnicorn."); + return true; + } + if (KnightmarePhoenixSummonCheck(Card).Count >= 2) + { + Logger.DebugWriteLine("Summon for KnightmarePhoenix."); + return true; + } + if (RelinquishedAnimaSummonCheck(Card) != -1) + { + Logger.DebugWriteLine("Summon for RelinquishedAnima."); + return true; + } + if (SalamangreatAlmirajSummonCheck(Card)) + { + Logger.DebugWriteLine("Summon for SalamangreatAlmiraj."); + return true; + } + + summoned = false; + return false; + } + + /// + /// Special Witchcraft from deck for all monsters, except spells/traps. + /// + /// max level can be special summoned. + public bool DeckSSWitchcraft() + { + if (Card.Location != CardLocation.MonsterZone) return false; + if (Duel.LastChainPlayer == 0) return false; + if (NegatedCheck(false)) return false; + if (Duel.Player == 0 && !FirstCheckSS.Contains(Card.Id)) + { + // activate when ask twice + FirstCheckSS.Add(Card.Id); + return false; + } + + // get discardable count + int discardable_hands = CheckDiscardableSpellCount(); + + // not must SS + if (discardable_hands == 0 && Bot.MonsterZone.GetFirstMatchingCard(card => card.HasSetcode(Witchcraft_setcode) && card.Level >= 6) != null) + { + return false; + } + + SelectDiscardSpell(); + + // check whether should call MadameVerre for destroying monster + bool lesssummon = false; + int extra_attack = CheckPlusAttackforMadameVerre(true, false, true); + int best_power = Util.GetBestAttack(Bot); + if (CheckRemainInDeck(CardId.Haine) > 0 && best_power < 2400) best_power = 2400; + Logger.DebugWriteLine("less summon check: " + (best_power + extra_attack - 1000).ToString() + " to " + (best_power + extra_attack).ToString()); + if (Util.GetOneEnemyBetterThanValue(best_power) != null + && Util.GetOneEnemyBetterThanValue(best_power + extra_attack) == null + && Util.GetOneEnemyBetterThanValue(best_power + extra_attack - 1000) != null) + { + lesssummon = true; + } + + // SS lower 4 + if (!enemy_activate_MaxxC && !lesssummon && discardable_hands >= 2 && Duel.Player == 0) + { + int[] SS_priority = { CardId.Schmietta, CardId.Pittore, CardId.Genni, CardId.Potterie }; + foreach (int cardid in SS_priority) + { + if (!UseSSEffect.Contains(cardid) && Card.Id != cardid && CheckRemainInDeck(cardid) > 0 + && Bot.MonsterZone.GetFirstMatchingCard(card => card.Id == cardid && card.IsFaceup()) == null) + { + UseSSEffect.Add(Card.Id); + AI.SelectNextCard(cardid); + return true; + } + } + } + + // check whether continue to ss + bool should_attack = Util.GetOneEnemyBetterThanValue(Card.Attack) == null; + if ((should_attack ^ Card.IsDefense()) && Duel.Player == 1) return false; + if (CheckRemainInDeck(CardId.Haine, CardId.MadameVerre, CardId.GolemAruru) == 0) return false; + + // SS higer level + if (Bot.HasInMonstersZone(CardId.Haine) || (lesssummon && !Bot.HasInMonstersZone(CardId.MadameVerre, true))) + { + AI.SelectNextCard(CardId.MadameVerre, CardId.Haine, CardId.GolemAruru); + } + else + { + AI.SelectNextCard(CardId.Haine, CardId.MadameVerre, CardId.GolemAruru); + } + UseSSEffect.Add(Card.Id); + return true; + } + + // recycle witchcraft spells in grave + public bool WitchcraftRecycle() + { + if (Card.IsSpell() && Card.HasSetcode(Witchcraft_setcode) && Card.Location == CardLocation.Grave) { + ActivatedCards.Add(Card.Id); + if (Card.HasType(CardType.Continuous)) + { + SelectSTPlace(Card); + } + return true; + } + return false; + } + + // activate of GolemAruru + public bool GolemAruruActivate() + { + if (ActivateDescription == Util.GetStringId(CardId.GolemAruru, 2)) + { + return true; + } + if (NegatedCheck()) return false; + ClientCard targetcard = CheckProblematicCards(true); + if (targetcard != null) + { + AI.SelectCard(targetcard); + return true; + } + AI.SelectCard(CardId.Holiday, CardId.Creation, CardId.Draping, CardId.Scroll, CardId.WitchcrafterBystreet, CardId.Unveiling, CardId.Collaboration ); + return true; + } + + // activate of MadameVerre + public bool MadameVerreActivate() + { + if (NegatedCheck(true)) return false; + // negate + if (ActivateDescription == Util.GetStringId(CardId.MadameVerre, 1)) + { + if (Card.IsDisabled()) return false; + if (CheckLastChainNegated()) return false; + + // negate before activate + if (Enemy.MonsterZone.GetFirstMatchingCard(card => card.IsMonsterShouldBeDisabledBeforeItUseEffect() && !card.IsDisabled()) != null) + { + SelectDiscardSpell(); + return true; + } + + // chain check + ClientCard LastChainCard = Util.GetLastChainCard(); + if ((LastChainCard != null && LastChainCard.Controller == 1 && LastChainCard.Location == CardLocation.MonsterZone)) + { + // negate monsters' activate + SelectDiscardSpell(); + return true; + } + + // negate battle related effect + if (Duel.Phase > DuelPhase.Main1 && Duel.Phase < DuelPhase.Main2) + { + if (Enemy.MonsterZone.GetFirstMatchingCard(card => + card.IsMonsterDangerous() || (Duel.Player == 0) && card.IsMonsterInvincible()) != null) + { + SelectDiscardSpell(); + return true; + } + } + + return false; + } + // gain ATK + else + { + ClientCard self_card = Bot.BattlingMonster; + ClientCard enemy_card = Enemy.BattlingMonster; + if (self_card != null && enemy_card != null) + { + int power_cangain = CheckPlusAttackforMadameVerre(); + int diff = enemy_card.GetDefensePower() - self_card.GetDefensePower(); + Logger.DebugWriteLine("power: " + power_cangain.ToString()); + Logger.DebugWriteLine("diff: " + diff.ToString()); + if (diff > 0) + { + // avoid useless effect + if (self_card.IsDefense() && power_cangain < diff) + { + return false; + } + AI.SelectCard(Bot.Hand.GetMatchingCards(card => card.IsSpell())); + MadameVerreGainedATK = true; + return true; + } + else if (Enemy.GetMonsterCount() == 1 || (enemy_card.IsAttack() && Enemy.LifePoints <= diff + power_cangain)) + { + AI.SelectCard(Bot.Hand.GetMatchingCards(card => card.IsSpell())); + MadameVerreGainedATK = true; + return true; + } + } + } + return false; + } + + // activate of Haine + public bool HaineActivate() + { + if (NegatedCheck(true) || Duel.LastChainPlayer == 0) return false; + // danger check + ClientCard targetcard = Enemy.MonsterZone.GetFloodgate(true); + if (targetcard == null) + { + Logger.DebugWriteLine("*** Haine 2nd check."); + targetcard = Enemy.SpellZone.FirstOrDefault(card => card?.Data != null && card.IsFloodgate() && card.IsFaceup() && (!card.IsShouldNotBeTarget() || !Duel.ChainTargets.Contains(card))); + // GetFloodgate(true); + } + if (targetcard == null) + { + Logger.DebugWriteLine("*** Haine 3rd check."); + targetcard = CheckProblematicCards(true, (Duel.Phase <= DuelPhase.Main1 || Duel.Phase >= DuelPhase.Main2)); + if (targetcard != null && targetcard.HasSetcode(TimeLord_setcode) && !targetcard.IsDisabled()) targetcard = null; + } + if (targetcard == null && Duel.LastChainPlayer == 1) + { + Logger.DebugWriteLine("*** Haine 4th check."); + ClientCard lastcard = Util.GetLastChainCard(); + if (lastcard != null && !lastcard.IsDisabled() && !CheckLastChainNegated() + && (lastcard.HasType(CardType.Continuous) || lastcard.HasType(CardType.Equip) || lastcard.HasType(CardType.Field)) + && (lastcard.Location == CardLocation.SpellZone || lastcard.Location == CardLocation.FieldZone)) + { + targetcard = lastcard; + } + } + if (targetcard != null) + { + Logger.DebugWriteLine("*** Haine target: "+ targetcard.Name ?? "???"); + SelectDiscardSpell(); + AI.SelectNextCard(targetcard); + return true; + } + + // pendulum check + if (!CheckLastChainNegated()) + { + ClientCard l = null; + ClientCard r = null; + if (Duel.IsNewRule || Duel.IsNewRule2020) + { + l = Enemy.SpellZone[0]; + r = Enemy.SpellZone[4]; + } + else + { + l = Enemy.SpellZone[6]; + r = Enemy.SpellZone[7]; + } + if (l != null && r != null && l.LScale != r.RScale) + { + Logger.DebugWriteLine("*** Haine pendulum destroy"); + SelectDiscardSpell(); + AI.SelectNextCard(Program.Rand.Next(2) == 1 ? l : r); + return true; + } + } + + + // end check + if (Duel.Player == 0 && Duel.Phase == DuelPhase.End) + { + Logger.DebugWriteLine("*** Haine self check"); + int selected_cost = 0; + // spare spell check + int[] checklist = { CardId.Collaboration, CardId.Unveiling, CardId.Scroll, CardId.Holiday, CardId.Creation, CardId.Draping }; + foreach (int cardid in checklist) + { + if (!ActivatedCards.Contains(cardid) && Bot.HasInHand(cardid)) + { + selected_cost = cardid; + break; + } + } + + if (selected_cost == 0) return false; + IList target_1 = Enemy.SpellZone.GetMatchingCards(card => card.IsFaceup()); + IList target_2 = Enemy.MonsterZone.GetMatchingCards(card => card.IsFaceup()); + List targets = target_1.Union(target_2).ToList(); + if (targets.Count == 0) + { + return false; + } + // shuffle and select randomly + targets = CardListShuffle(targets); + AI.SelectCard(selected_cost); + AI.SelectNextCard(targets); + return true; + } + return false; + } + + // activate of Schmietta + public bool SchmiettaActivate() + { + if (Card.Location != CardLocation.Grave) return false; + if (NegatedCheck(false) || CheckWhetherWillbeRemoved()) return false; + // spell check + bool can_recycle = Bot.MonsterZone.GetFirstMatchingCard( + card => card.IsFaceup() && card.HasSetcode(Witchcraft_setcode) && card.Id != CardId.GolemAruru + ) != null; + if (can_recycle) + { + int[] spell_checklist = { CardId.WitchcrafterBystreet, CardId.Holiday, CardId.Creation, CardId.Draping, CardId.Scroll, CardId.Unveiling, CardId.Collaboration }; + foreach (int cardid in spell_checklist) + { + if (CheckRemainInDeck(cardid) > 0 && !Bot.HasInHandOrInSpellZone(cardid) && !Bot.HasInGraveyard(cardid) && !ActivatedCards.Contains(cardid)) + { + AI.SelectCard(cardid); + ActivatedCards.Add(CardId.Schmietta); + return true; + } + } + } + + bool can_find_Holiday = Bot.HasInHandOrInSpellZone(CardId.Holiday) || (can_recycle && Bot.HasInGraveyard(CardId.Holiday) && !(ActivatedCards.Contains(CardId.Holiday))); + // monster check + if (Bot.HasInHand(important_witchcraft) && !Bot.HasInGraveyard(CardId.Pittore) + && !ActivatedCards.Contains(CardId.Pittore) && CheckRemainInDeck(CardId.Pittore) > 0 && can_find_Holiday){ + AI.SelectCard(CardId.Pittore); + ActivatedCards.Add(CardId.Schmietta); + return true; + } + + // ss check + if (Bot.HasInHand(CardId.Holiday) && !ActivatedCards.Contains(CardId.Holiday) && !Bot.HasInGraveyard(important_witchcraft)) + { + AI.SelectCard(important_witchcraft); + ActivatedCards.Add(CardId.Schmietta); + return true; + } + + // copy check + if (!ActivatedCards.Contains(CardId.Genni)) + { + int has_Genni = Bot.HasInGraveyard(CardId.Genni) ? 1 : 0; + int has_Holiday = Bot.HasInGraveyard(CardId.Holiday) ? 1 : 0; + int has_important = Bot.HasInGraveyard(important_witchcraft) ? 1 : 0; + // lack one of them + if (has_Genni + has_Holiday + has_important == 2) + { + if (has_Genni == 0) + { + AI.SelectCard(CardId.Genni); + ActivatedCards.Add(CardId.Schmietta); + return true; + } + if (has_Holiday == 0) + { + AI.SelectCard(CardId.Holiday); + ActivatedCards.Add(CardId.Schmietta); + return true; + } + if (has_important == 0) + { + AI.SelectCard(important_witchcraft); + ActivatedCards.Add(CardId.Schmietta); + return true; + } + } + } + + // Pittore check + if (!ActivatedCards.Contains(CardId.Pittore) && !Bot.HasInGraveyard(CardId.Pittore)) + { + if (PittoreActivate()) + { + AI.SelectCard(CardId.Pittore); + ActivatedCards.Add(CardId.Schmietta); + return true; + } + } + + // trap check + if (CheckRemainInDeck(CardId.Masterpiece) >= 2){ + AI.SelectCard(CardId.Masterpiece); + ActivatedCards.Add(CardId.Schmietta); + return true; + } + + return false; + } + + // activate of Pittore + public bool PittoreActivate() + { + if (Card.Location != CardLocation.Grave) return false; + if (NegatedCheck(false) || CheckWhetherWillbeRemoved()) return false; + if (Bot.Hand.GetFirstMatchingCard(card => card.HasSetcode(Witchcraft_setcode)) == null) return false; + + // discard advance + if (Bot.Hand.GetFirstMatchingCard(card => card.Id == CardId.MadameVerre || card.Id == CardId.Haine) != null) + { + AI.SelectCard(CardId.MadameVerre, CardId.Haine); + ActivatedCards.Add(CardId.Pittore); + return true; + } + + // spell check + int[] spell_checklist = { CardId.Scroll, CardId.Unveiling, CardId.Collaboration, CardId.Draping, CardId.WitchcrafterBystreet, CardId.Holiday, CardId.Creation }; + foreach (int cardid in spell_checklist) + { + if (Bot.HasInHand(cardid) && !ActivatedCards.Contains(cardid)){ + AI.SelectCard(cardid); + ActivatedCards.Add(CardId.Pittore); + return true; + } + } + + // monster check + if ((Bot.HasInHand(CardId.Schmietta) && !ActivatedCards.Contains(CardId.Schmietta)) + ||Bot.Hand.GetMatchingCardsCount(card => card.HasSetcode(Witchcraft_setcode) && card.Level <= 4) >= 2){ + int[] monster_checklist = { CardId.Schmietta, CardId.Pittore, CardId.Genni, CardId.Potterie}; + foreach (int cardid in spell_checklist) + { + if (Bot.HasInHand(cardid)){ + AI.SelectCard(cardid); + ActivatedCards.Add(CardId.Pittore); + return true; + } + } + } + + return false; + } + + // activate of AshBlossom_JoyousSpring + public bool AshBlossom_JoyousSpringActivate() + { + if (NegatedCheck(true) || CheckLastChainNegated()) return false; + CheckDeactiveFlag(); + return DefaultAshBlossomAndJoyousSpring(); + } + + // activate of PSYGamma + public bool PSYGammaActivate() + { + if (NegatedCheck(true)) return false; + CheckDeactiveFlag(); + return true; + } + + // activate of MaxxC + public bool MaxxCActivate() + { + if (NegatedCheck(true)) return false; + return DefaultMaxxC(); + } + + // activate of Potterie + public bool PotterieActivate() + { + if (Card.Location != CardLocation.Grave) return false; + if (NegatedCheck(true)) return false; + + // Holiday check + if (!ActivatedCards.Contains(CardId.Holiday) && Bot.HasInGraveyard(CardId.Holiday)){ + if (Bot.HasInGraveyard(important_witchcraft)){ + AI.SelectCard(CardId.Holiday); + ActivatedCards.Add(CardId.Potterie); + return true; + } + } + + // safe check + if (CheckProblematicCards() == null){ + int[] checklist = {CardId.Patronus, CardId.GolemAruru}; + foreach (int cardid in checklist){ + if (Bot.HasInGraveyard(cardid)){ + AI.SelectCard(cardid); + ActivatedCards.Add(CardId.Potterie); + return true; + } + } + } + return false; + } + + // activate of Genni + public bool GenniActivate() + { + if (Card.Location != CardLocation.Grave) return false; + if (NegatedCheck(true)) return false; + + // Holiday check + int HolidayCount = Bot.Graveyard.GetMatchingCardsCount(card => card.Id == CardId.Holiday); + int SS_id = HolidayCheck(Card); + if (HolidayCount > 0 && SS_id > 0){ + AI.SelectCard(CardId.Holiday); + AI.SelectNextCard(SS_id); + ActivatedCards.Add(CardId.Genni); + return true; + } + + // Draping check + if (Bot.HasInGraveyard(CardId.Draping)){ + if (Enemy.GetMonsterCount() == 0 && Duel.Phase == DuelPhase.Main1){ + int total_attack = 0; + foreach (ClientCard card in Bot.GetMonsters()){ + total_attack += card.Attack; + } + // otk confirm + if (total_attack >= Enemy.LifePoints){ + int bot_count = Bot.MonsterZone.GetMatchingCardsCount(card => card.IsFaceup() && card.HasSetcode(Witchcraft_setcode)); + IList enemy_cards = Enemy.GetSpells(); + if (bot_count >= enemy_cards.Count()){ + AI.SelectCard(CardId.Draping); + AI.SelectNextCard(enemy_cards); + ActivatedCards.Add(CardId.Genni); + return true; + } + } + } + } + + return false; + } + + // activate of Collaboration + public bool CollaborationActivate() + { + if (Card.Location == CardLocation.Grave) return false; + if (NegatedCheck(true)) return false; + ClientCard target = Util.GetBestBotMonster(true); + if (Util.GetOneEnemyBetterThanMyBest() == null){ + if (Enemy.SpellZone.GetFirstMatchingCard(card => card.IsFacedown()) != null + || Enemy.MonsterZone.GetMatchingCardsCount(card => card.GetDefensePower() < target.Attack) >= 2){ + AI.SelectCard(target); + SelectSTPlace(null, true); + ActivatedCards.Add(CardId.Collaboration); + return true; + } + } + return false; + } + + // activate of LightningStorm + public bool LightningStormActivate() + { + int bestPower = 0; + foreach (ClientCard hand in Bot.Hand) + { + if (hand.IsMonster() && hand.Level <= 4 && hand.Attack > bestPower) bestPower = hand.Attack; + } + + int opt = -1; + // destroy monster + if (Enemy.MonsterZone.GetFirstMatchingCard(card => card.IsFloodgate() && card.IsAttack()) != null + || Enemy.MonsterZone.GetMatchingCardsCount(card => card.IsAttack() && card.Attack >= bestPower) >= 2) opt = 0; + // destroy spell/trap + else if (Enemy.GetSpellCount() >= 2 || Util.GetProblematicEnemySpell() != null) opt = 1; + + if (opt == -1) return false; + + // only one selection + if (Enemy.MonsterZone.GetFirstMatchingCard(card => card.IsAttack()) == null + || Enemy.GetSpellCount() == 0) + { + AI.SelectOption(0); + SelectSTPlace(null, true); + return true; + } + AI.SelectOption(opt); + SelectSTPlace(null, true); + return true; + } + + // activate of PotofExtravagance + public bool PotofExtravaganceActivate() + { + // won't activate if it'll be negate + if (SpellNegatable()) return false; + + SelectSTPlace(Card, true); + AI.SelectOption(1); + return true; + } + + // activate of DarkRulerNoMore + public bool DarkRulerNoMoreActivate() + { + if (SpellNegatable()) return false; + if (Enemy.MonsterZone.GetFirstMatchingCard(card => card.IsFloodgate() && !card.IsDisabled()) != null) + { + SelectSTPlace(null, true); + return true; + } + return false; + } + + // activate of Creation + public bool CreationActivate() + { + if (Card.Location == CardLocation.Grave) return false; + if (NegatedCheck(true)) return false; + + // discard cost ensure + int least_cost = (Bot.HasInMonstersZone(CardId.Haine) ? 1 : 0) + (Bot.HasInMonstersZone(CardId.MadameVerre) ? 1 : 0); + int discardable = Bot.Hand.GetMatchingCardsCount(card => card != Card && card.IsSpell()) + CheckRecyclableCount() -1; + if (discardable < least_cost) return false; + + // search monster to summon + bool need_lower = (!summoned || ( + Bot.MonsterZone.GetFirstMatchingCard(card => card.HasSetcode(Witchcraft_setcode)) == null + && Bot.Hand.GetFirstMatchingCard(card => card.IsMonster() && card.HasSetcode(Witchcraft_setcode) && card.Level <= 4) == null)); + if (need_lower) + { + AI.SelectCard(CardId.Schmietta, CardId.Pittore, CardId.Genni, CardId.Potterie, CardId.GolemAruru); + SelectSTPlace(null, true); + ActivatedCards.Add(CardId.Creation); + return true; + } + // search GolemAruru + else + { + if (Bot.HasInHand(CardId.GolemAruru)) return false; + if (Bot.MonsterZone.GetFirstMatchingCard(card => card.IsFaceup() && card.HasSetcode(Witchcraft_setcode)) == null) + { + AI.SelectCard(CardId.GolemAruru, CardId.Schmietta, CardId.Pittore, CardId.Genni, CardId.Potterie); + SelectSTPlace(null, true); + ActivatedCards.Add(CardId.Creation); + return true; + } else + { + AI.SelectCard(CardId.Schmietta, CardId.Pittore, CardId.Genni, CardId.Potterie, CardId.GolemAruru); + SelectSTPlace(null, true); + ActivatedCards.Add(CardId.Creation); + return true; + } + } + } + + /// + /// Check Holiday's target. If nothing should be SS, return 0. + /// + /// + /// + public int HolidayCheck(ClientCard except_card = null){ + // SS important first + List check_list = new List { CardId.Haine, CardId.MadameVerre, CardId.GolemAruru}; + foreach (int cardid in check_list) + { + if (Bot.HasInGraveyard(cardid) && Bot.MonsterZone.GetFirstMatchingCard(card => card.IsFaceup() && card.Id == cardid) == null) + { + Logger.DebugWriteLine("*** Holiday check 1st: " + cardid.ToString()); + return cardid; + } + } + check_list.Clear(); + if (CheckProblematicCards() == null) + { + if (Bot.HasInGraveyard(CardId.GolemAruru) && Bot.MonsterZone.GetFirstMatchingCard(card => card.IsFaceup() && card.HasSetcode(Witchcraft_setcode)) != null) + { + Logger.DebugWriteLine("*** Holiday check 2nd: GolemAruru"); + return CardId.GolemAruru; + } + check_list.Add(CardId.Schmietta); + check_list.Add(CardId.Pittore); + check_list.Add(CardId.Genni); + check_list.Add(CardId.Potterie); + foreach (int cardid in check_list) + { + if (!UseSSEffect.Contains(cardid) && Bot.Graveyard.GetFirstMatchingCard(card => card.Id == cardid && card != except_card) != null && CheckDiscardableSpellCount(Card) > 0) + { + Logger.DebugWriteLine("*** Holiday check 3rd: " + cardid.ToString()); + return cardid; + } + } + } + else + { + check_list.Add(CardId.Haine); + check_list.Add(CardId.MadameVerre); + check_list.Add(CardId.GolemAruru); + foreach (int cardid in check_list) + { + if (Bot.Graveyard.GetFirstMatchingCard(card => card.Id == cardid && card != except_card) != null) + { + return cardid; + } + } + } + return 0; + } + + // activate of Holiday + public bool HolidayActivate() + { + if (Card.Location == CardLocation.Grave) return false; + if (NegatedCheck(true)) return false; + int target = HolidayCheck(); + if (target != 0) + { + AI.SelectCard(target); + SelectSTPlace(null, true); + ActivatedCards.Add(CardId.Holiday); + return true; + } + return false; + } + + // activate of CalledbytheGrave + public bool CalledbytheGraveActivate() + { + if (NegatedCheck(true)) return false; + if (Duel.LastChainPlayer == 1) + { + // negate + if (Util.GetLastChainCard().IsMonster()) + { + int code = Util.GetLastChainCard().Id; + if (code == 0) return false; + if (CheckCalledbytheGrave(code) > 0 || CrossoutDesignatorTarget == code) return false; + if (Enemy.Graveyard.GetFirstMatchingCard(card => card.IsMonster() && card.IsOriginalCode(code)) != null) + { + if (!(Card.Location == CardLocation.SpellZone)) + { + SelectSTPlace(null, true); + } + AI.SelectCard(code); + CalledbytheGraveCount[code] = 2; + CheckDeactiveFlag(); + return true; + } + } + + // banish target + foreach (ClientCard cards in Enemy.Graveyard) + { + if (Duel.ChainTargets.Contains(cards)) + { + int code = cards.Id; + AI.SelectCard(cards); + CalledbytheGraveCount[code] = 2; + return true; + } + } + + // become targets + if (Duel.ChainTargets.Contains(Card)) + { + List enemy_monsters = Enemy.Graveyard.GetMatchingCards(card => card.IsMonster()).ToList(); + if (enemy_monsters.Count > 0) + { + enemy_monsters.Sort(CardContainer.CompareCardAttack); + enemy_monsters.Reverse(); + int code = enemy_monsters[0].Id; + AI.SelectCard(code); + CalledbytheGraveCount[code] = 2; + return true; + } + } + } + + // avoid danger monster in grave + if (Duel.LastChainPlayer == 1) return false; + List targets = CheckDangerousCardinEnemyGrave(true); + if (targets.Count() > 0) { + int code = targets[0].Id; + if (!(Card.Location == CardLocation.SpellZone)) + { + SelectSTPlace(null, true); + } + AI.SelectCard(code); + CalledbytheGraveCount[code] = 2; + return true; + } + + return false; + } + + // activate of Draping + public bool DrapingActivate() + { + if (Card.Location == CardLocation.Grave) return false; + if (NegatedCheck(true)) return false; + + IList dangerours_spells = Enemy.SpellZone.GetMatchingCards(card => card.IsFloodgate() && !card.IsDisabled() && card.IsSpell()); + IList dangerours_traps = Enemy.SpellZone.GetMatchingCards(card => card.IsFloodgate() && !card.IsDisabled() && card.IsTrap()); + List faceup_spells = CardListShuffle(Enemy.SpellZone.GetMatchingCards(card => card.IsFaceup() && card.IsSpell()).ToList()); + List faceup_traps = CardListShuffle(Enemy.SpellZone.GetMatchingCards(card => card.IsFaceup() && card.IsTrap()).ToList()); + List setcards = CardListShuffle(Enemy.SpellZone.GetMatchingCards(card => card.IsFacedown()).ToList()); + if (Duel.Player == 0 || Duel.Phase == DuelPhase.End) + { + IList targets_1 = dangerours_spells.Union(dangerours_traps).Union(faceup_spells).Union(faceup_traps).Union(setcards).ToList(); + if (targets_1.Count() == 0) return false; + AI.SelectCard(targets_1); + SelectSTPlace(null, true); + ActivatedCards.Add(CardId.Draping); + return true; + } + IList targets_2 = dangerours_traps.Union(faceup_traps).ToList(); + if (targets_2.Count() == 0) return false; + targets_2 = targets_2.Union(dangerours_spells).Union(faceup_spells).Union(setcards).ToList(); + AI.SelectCard(targets_2); + SelectSTPlace(null, true); + ActivatedCards.Add(CardId.Draping); + return true; + } + + // activate of CrossoutDesignator + public bool CrossoutDesignatorActivate() + { + if (NegatedCheck(true) || CheckLastChainNegated()) return false; + // negate + if (Duel.LastChainPlayer == 1 && Util.GetLastChainCard() != null) + { + int code = Util.GetLastChainCard().Id; + int alias = Util.GetLastChainCard().Alias; + if (alias != 0 && alias - code < 10) code = alias; + if (code == 0) return false; + if (CheckCalledbytheGrave(code) > 0 || CrossoutDesignatorTarget == code) return false; + if (CheckRemainInDeck(code) > 0) + { + if (!(Card.Location == CardLocation.SpellZone)) + { + SelectSTPlace(null, true); + } + AI.SelectAnnounceID(code); + CrossoutDesignatorTarget = code; + CheckDeactiveFlag(); + return true; + } + } + return false; + } + + // activate of Unveiling + public bool UnveilingActivate() + { + if (Card.Location == CardLocation.Grave) return false; + if (NegatedCheck(true)) return false; + + // LightningStorm check + if (Bot.HasInHandOrInSpellZone(CardId.LightningStorm)) + { + int faceup_count = Bot.SpellZone.GetMatchingCardsCount(card => card.IsFaceup()); + faceup_count += Bot.MonsterZone.GetMatchingCardsCount(card => card.IsFaceup()); + if (faceup_count == 0 && LightningStormActivate()) + { + return false; + } + } + + if (Bot.HasInHand(important_witchcraft)) + { + AI.SelectCard(important_witchcraft); + SelectSTPlace(null, true); + ActivatedCards.Add(CardId.Unveiling); + return true; + } + return false; + } + + // activate of Scroll + public bool ScrollActivate() + { + if (SpellNegatable() || Card.Location == CardLocation.Grave || Duel.Phase == DuelPhase.Main2) + { + return false; + } + if (Bot.MonsterZone.GetFirstMatchingCard(card => card.HasRace(CardRace.SpellCaster)) == null) + { + return false; + } + SelectSTPlace(null, true); + return true; + } + + // activate of MagiciansRestage + public bool MagiciansRestageActivate() + { + // search + if (Card.Location == CardLocation.Grave) + { + if (Enemy.SpellZone.GetFirstMatchingCard(card => card.IsFacedown()) != null) + { + AI.SelectCard(CardId.MagiciansLeftHand, CardId.MagicianRightHand); + } + else + { + AI.SelectCard(CardId.MagicianRightHand, CardId.MagiciansLeftHand); + } + return true; + } + + if (SpellNegatable()) + { + return false; + } + + // find target + if (CheckDiscardableSpellCount(Card) < 1) return false; + int target = 0; + int[] target_list = { CardId.Genni, CardId.Pittore, CardId.Potterie }; + foreach (int cardid in target_list) + { + if (!UseSSEffect.Contains(cardid) && Bot.HasInGraveyard(cardid)) + { + target = cardid; + break; + } + } + if (target == 0) return false; + if (Card.Location == CardLocation.Hand) + { + SelectSTPlace(null, true); + return true; + } + AI.SelectCard(target); + return true; + } + + // activate of WitchcrafterBystreet + public bool WitchcrafterBystreetActivate() + { + if (SpellNegatable() || Card.Location == CardLocation.Grave) + { + return false; + } + if (Bot.HasInSpellZone(CardId.WitchcrafterBystreet, true) || Bot.MonsterZone.GetFirstMatchingCard(card => card.HasSetcode(Witchcraft_setcode) && card.IsFaceup()) == null) + { + return false; + } + SelectSTPlace(null, true); + return true; + } + + // activate of Impermanence + public bool InfiniteImpermanenceActivate() + { + if (SpellNegatable()) return false; + if (CrossoutDesignatorTarget == CardId.InfiniteImpermanence) return false; + if (CheckLastChainNegated()) return false; + // negate before monster's effect's used + foreach (ClientCard m in Enemy.GetMonsters()) + { + if (!m.IsDisabled() && Duel.LastChainPlayer != 0 && + ((m.IsMonsterShouldBeDisabledBeforeItUseEffect() || m.IsFloodgate()) + || (Duel.Phase > DuelPhase.Main1 && Duel.Phase < DuelPhase.Main2 && + (m.IsMonsterDangerous() || m.IsMonsterInvincible() + || (m.IsMonsterHasPreventActivationEffectInBattle() && Bot.HasInMonstersZone(CardId.MadameVerre))) + ))) + { + if (Card.Location == CardLocation.SpellZone) + { + for (int i = 0; i < 5; ++i) + { + if (Bot.SpellZone[i] == Card) + { + Impermanence_list.Add(i); + break; + } + } + } + if (Card.Location == CardLocation.Hand) + { + SelectSTPlace(Card, true); + } + AI.SelectCard(m); + return true; + } + } + + ClientCard LastChainCard = Util.GetLastChainCard(); + + // negate spells + 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))) + { + List enemy_monsters = Enemy.GetMonsters(); + enemy_monsters.Sort(CardContainer.CompareCardAttack); + enemy_monsters.Reverse(); + foreach (ClientCard card in enemy_monsters) + { + if (card.IsFaceup() && !card.IsShouldNotBeTarget() && !card.IsShouldNotBeSpellTrapTarget()) + { + AI.SelectCard(card); + Impermanence_list.Add(this_seq); + return true; + } + } + } + } + + // negate monsters + if ((LastChainCard == null || LastChainCard.Controller != 1 || LastChainCard.Location != CardLocation.MonsterZone + || CheckLastChainNegated() || LastChainCard.IsShouldNotBeTarget() || LastChainCard.IsShouldNotBeSpellTrapTarget())) + return false; + if (Card.Location == CardLocation.SpellZone) + { + for (int i = 0; i < 5; ++i) + { + if (Bot.SpellZone[i] == Card) + { + Impermanence_list.Add(i); + break; + } + } + } + if (Card.Location == CardLocation.Hand) + { + SelectSTPlace(Card, true); + } + if (LastChainCard != null) AI.SelectCard(LastChainCard); + else + { + List enemy_monsters = Enemy.GetMonsters(); + enemy_monsters.Sort(CardContainer.CompareCardAttack); + enemy_monsters.Reverse(); + foreach (ClientCard card in enemy_monsters) + { + if (card.IsFaceup() && !card.IsShouldNotBeTarget() && !card.IsShouldNotBeSpellTrapTarget()) + { + AI.SelectCard(card); + return true; + } + } + } + return true; + } + + // activate of Masterpiece + public bool MasterpieceActivate() + { + // search effect + if (Card.Location == CardLocation.SpellZone) + { + if (NegatedCheck(true)) return false; + // select randomly (TODO) + IList target_1 = Bot.Graveyard.GetMatchingCards(card => card.IsSpell() && CheckRemainInDeck(card.Id) > 0); + IList target_2 = Enemy.Graveyard.GetMatchingCards(card => card.IsSpell() && CheckRemainInDeck(card.Id) > 0); + List targets = CardListShuffle(target_1.Union(target_2).ToList()); + AI.SelectCard(targets); + return true; + } + else + // ss effect + { + // LightningStorm check + if (Bot.HasInHandOrInSpellZone(CardId.LightningStorm)) + { + int faceup_count = Bot.SpellZone.GetMatchingCardsCount(card => card.IsFaceup()); + faceup_count += Bot.MonsterZone.GetMatchingCardsCount(card => card.IsFaceup()); + if (faceup_count == 0 && LightningStormActivate()) + { + return false; + } + } + + List tobanish_spells = CardListShuffle(Bot.Graveyard.GetMatchingCards(card => card.IsSpell() && !card.HasSetcode(Witchcraft_setcode) && card.Id != CardId.MetalfoesFusion).ToList()); + if (Bot.HasInGraveyard(CardId.Patronus)) + { + List witchcraft_spells = CardListShuffle(Bot.Graveyard.GetMatchingCards(card => card.IsSpell() && card.HasSetcode(Witchcraft_setcode)).ToList()); + tobanish_spells = witchcraft_spells.Union(tobanish_spells).ToList(); + } + int max_level = tobanish_spells.Count(); + + //check discardable count + int discardable_hands = CheckDiscardableSpellCount(); + List will_discard_list = new List { CardId.Haine, CardId.MadameVerre, CardId.Schmietta, CardId.Pittore, CardId.Potterie, CardId.Genni }; + foreach (int cardid in will_discard_list) + { + if (Bot.HasInMonstersZone(cardid)) + { + discardable_hands--; + } + } + + // SS lower 4 + if (discardable_hands >= 1 && Duel.Player == 0) + { + int[] SS_priority = { CardId.Schmietta, CardId.Pittore, CardId.Genni, CardId.Potterie }; + foreach (int cardid in SS_priority) + { + int level = witchcraft_level[cardid]; + if (!UseSSEffect.Contains(cardid) & CheckRemainInDeck(cardid) > 0 && level <= max_level) + { + AI.SelectNumber(level); + AI.SelectCard(tobanish_spells); + AI.SelectNextCard(cardid); + return true; + } + } + } + + // SS higer level + List ss_priority = new List(); + if (Bot.HasInMonstersZone(CardId.Haine)) + { + ss_priority.Add(CardId.MadameVerre); + ss_priority.Add(CardId.Haine); + } + else + { + ss_priority.Add(CardId.Haine); + ss_priority.Add(CardId.MadameVerre); + } + ss_priority.Add(CardId.GolemAruru); + foreach (int cardid in ss_priority) + { + int level = witchcraft_level[cardid]; + if (CheckRemainInDeck(cardid) > 0 && level <= max_level) + { + AI.SelectNumber(level); + AI.SelectCard(tobanish_spells); + AI.SelectNextCard(cardid); + return true; + } + } + + } + return false; + } + + // activate of Patronus + public bool PatronusActivate() + { + // activate immediately + if (ActivateDescription == 94) + { + return true; + } + // search + if (Card.Location == CardLocation.SpellZone) + { + if (NegatedCheck(true) || Duel.LastChainPlayer == 0) return false; + // find lack of spells + int lack_spells = 0; + int[] spell_checklist = { CardId.WitchcrafterBystreet, CardId.Holiday, CardId.Creation, CardId.Draping, CardId.Scroll, CardId.Unveiling, CardId.Collaboration }; + foreach (int cardid in spell_checklist) + { + if (!Bot.HasInHandOrInSpellZone(cardid) && !Bot.HasInGraveyard(cardid)) + { + lack_spells = cardid; + break; + } + } + + // banish check + List banish_checklist = new List{ CardId.Haine, CardId.MadameVerre, CardId.GolemAruru, CardId.Schmietta, CardId.Pittore}; + if (lack_spells == 0) + { + List new_list = new List { CardId.Pittore, CardId.Genni, CardId.Schmietta, CardId.Potterie }; + banish_checklist = banish_checklist.Union(new_list).ToList(); + } + else + { + List new_list = new List { CardId.Schmietta, CardId.Pittore, CardId.Genni, CardId.Potterie }; + banish_checklist = banish_checklist.Union(new_list).ToList(); + } + foreach(int cardid in banish_checklist) + { + ClientCard target = Bot.Banished.GetFirstMatchingCard(card => card.Id == cardid); + if (target != null) + { + AI.SelectCard(target); + AI.SelectNextCard(lack_spells); + return true; + } + } + } + + // recycle + if (Card.Location == CardLocation.Grave) + { + if (Bot.HasInHandOrInSpellZoneOrInGraveyard(CardId.Masterpiece)) + { + return false; + } + IList targets = Bot.Banished.GetMatchingCards(card => card.IsSpell() && card.HasSetcode(Witchcraft_setcode)); + AI.SelectCard(targets); + return true; + } + return false; + } + + // summmon process of Level 8 Synchro Monster + public bool Lv8Summon() + { + if (Bot.HasInMonstersZone(CardId.PSYGamma) && Bot.HasInMonstersZone(CardId.PSYDriver)) + { + List targets = new List { CardId.PSYDriver, CardId.PSYGamma }; + AI.SelectMaterials(targets); + return true; + } + return false; + } + + // summon process of BorreloadSavageDragon + public bool BorreloadSavageDragonSummon() + { + if (Bot.Graveyard.GetFirstMatchingCard(card => card.HasType(CardType.Link)) == null) + { + return false; + } + return Lv8Summon(); + } + + // equip target comparer for BorreloadSavageDragon + public static int BorreloadSavageDragonEquipCompare(ClientCard cardA, ClientCard cardB) + { + if (cardA.LinkCount > cardB.LinkCount) + return -1; + if (cardA.LinkCount < cardB.LinkCount) + return -1; + if (cardA.Attack > cardB.Attack) + return 1; + if (cardA.Attack < cardB.Attack) + return -1; + return 0; + } + + // activate of BorreloadSavageDragon + public bool BorreloadSavageDragonActivate() + { + // equip + if (ActivateDescription == Util.GetStringId(CardId.BorreloadSavageDragon, 0)) + { + List links = Bot.Graveyard.GetMatchingCards(card => card.HasType(CardType.Link)).ToList(); + links.Sort(BorreloadSavageDragonEquipCompare); + AI.SelectCard(links); + return true; + } + // negate + if (NegatedCheck(true) || Duel.LastChainPlayer != 1) return false; + if (Util.GetLastChainCard().HasSetcode(0x11e) && Util.GetLastChainCard().Location == CardLocation.Hand) return false; + CheckDeactiveFlag(); + return false; + } + + // activate of DracoBerserkeroftheTenyi(TODO) + public bool DracoBerserkeroftheTenyiActivate() + { + Logger.DebugWriteLine("DracoBerserkeroftheTenyi's Effect: " + ActivateDescription.ToString()); + return true; + } + + // activate of PSYOmega + public bool PSYOmegaActivate() + { + // recycle + if (Duel.Phase == DuelPhase.Standby) + { + if (Bot.Banished.Count == 0) + { + return false; + } + List targets = CardListShuffle(Bot.Banished.GetMatchingCards(card => card.HasSetcode(Witchcraft_setcode)).ToList()); + AI.SelectCard(targets); + return true; + } + // banish hands + if (Card.Location == CardLocation.MonsterZone) + { + if (Duel.Player == 1 || Bot.HasInMonstersZone(CardId.PSYLambda) || (Util.IsChainTarget(Card)) ) + { + return true; + } else + { + return Util.IsAllEnemyBetterThanValue(Card.Attack, true); + } + } + // recycle from grave + if (Card.Location == CardLocation.Grave) + { + if (PSYOmega_count >= 5){ + return false; + } + List enemy_danger = CheckDangerousCardinEnemyGrave(); + if (enemy_danger.Count > 0) + { + AI.SelectCard(enemy_danger); + PSYOmega_count ++; + return true; + } + if (!Bot.HasInHandOrInSpellZoneOrInGraveyard(CardId.Holiday) && Bot.HasInGraveyard(important_witchcraft)) + { + AI.SelectCard(important_witchcraft); + PSYOmega_count ++; + return true; + } + if (CheckProblematicCards() == null) + { + AI.SelectCard(CardId.CalledbytheGrave, CardId.CrossoutDesignator, + CardId.MaxxC, CardId.AshBlossom_JoyousSpring, + CardId.MagicianRightHand, CardId.MagiciansLeftHand, CardId.MagiciansRestage, CardId.Patronus, + CardId.LightningStorm, CardId.Reasoning); + PSYOmega_count ++; + return true; + } + } + return false; + } + + // activate of TGWonderMagician + public bool TGWonderMagicianActivate() + { + if (Card.Location != CardLocation.MonsterZone) return true; + Logger.DebugWriteLine("TGWonderMagician: " + ActivateDescription.ToString()); + List problem_cards = Enemy.SpellZone.GetMatchingCards(card => card.IsFloodgate()).ToList(); + List faceup_cards = Enemy.SpellZone.GetMatchingCards(card => card.IsFaceup()).ToList(); + List facedown_cards = Enemy.SpellZone.GetMatchingCards(card => card.IsFacedown()).ToList(); + List result = problem_cards.Union(faceup_cards).ToList().Union(facedown_cards).ToList(); + AI.SelectCard(result); + return true; + } + + // check whether summon BorrelswordDragon + public List BorrelswordDragonSummonCheck(ClientCard included = null) + { + List empty_list = new List(); + List extra_list = new List(); + if (included != null) extra_list.Add(included); + List materials = CheckLinkMaterials(4, 3, false, extra_list); + if (materials.Count < 3) return empty_list; + + // need BorrelswordDragon? + // for problem monster + ClientCard flag = Util.GetOneEnemyBetterThanMyBest(); + if (flag != null) + { + return materials; + } + // for higher attack + int total_attack = 0; + foreach (ClientCard card in materials) + { + total_attack += card.Attack; + } + if (total_attack >= 3000) return empty_list; + + return materials; + } + + // summon process of BorrelswordDragon + public bool BorrelswordDragonSummon() + { + List materials = BorrelswordDragonSummonCheck(); + if (materials.Count < 3) return false; + AI.SelectMaterials(materials); + return true; + } + + // activate of BorrelswordDragon + public bool BorrelswordDragonActivate() + { + if (ActivateDescription == -1) + { + ClientCard enemy_monster = Enemy.BattlingMonster; + if (enemy_monster != null && enemy_monster.HasPosition(CardPosition.Attack)) + { + return (Card.Attack - enemy_monster.Attack < Enemy.LifePoints); + } + return true; + }; + ClientCard BestEnemy = Util.GetBestEnemyMonster(true); + ClientCard WorstBot = Bot.GetMonsters().GetLowestAttackMonster(); + if (BestEnemy == null || BestEnemy.HasPosition(CardPosition.FaceDown)) return false; + if (WorstBot == null || WorstBot.HasPosition(CardPosition.FaceDown)) return false; + if (BestEnemy.Attack >= WorstBot.RealPower) + { + AI.SelectCard(BestEnemy); + return true; + } + return false; + } + + // check whether summon KnightmareUnicorn + public List KnightmareUnicornSummonCheck(ClientCard included = null) + { + List empty_list = new List(); + List extra_list = new List(); + if (included != null) extra_list.Add(included); + List materials = CheckLinkMaterials(3, 2, false, extra_list); + if (materials.Count < 2) return empty_list; + + // need KnightmareUnicorn? + // for clear spells + ClientCard flag = CheckProblematicCards(true, true); + if (flag != null) + { + if (Bot.Hand.GetMatchingCardsCount(card => card != Card) == 0) + { + return empty_list; + } + else + { + return materials; + } + } + // for higher attack + int total_attack = 0; + foreach(ClientCard card in materials) + { + total_attack += card.Attack; + } + if (total_attack >= 2200) return empty_list; + + return materials; + } + + // summon process of KnightmareUnicorn + public bool KnightmareUnicornSummon() + { + List materials = KnightmareUnicornSummonCheck(); + if (materials.Count < 2) return false; + AI.SelectMaterials(materials); + return true; + } + + // activate of KnightmareUnicorn + public bool KnightmareUnicornActivate() + { + ClientCard card = CheckProblematicCards(true); + if (card == null) return false; + // avoid cards that cannot target. + IList enemy_list = new List(); + if (!card.IsShouldNotBeMonsterTarget() && !card.IsShouldNotBeTarget()) enemy_list.Add(card); + foreach (ClientCard enemy in Enemy.GetMonstersInExtraZone()) + { + if (enemy != null && !enemy_list.Contains(enemy) && !enemy.IsShouldNotBeMonsterTarget() && !enemy.IsShouldNotBeTarget()) enemy_list.Add(enemy); + } + foreach (ClientCard enemy in Enemy.GetMonstersInMainZone()) + { + if (enemy != null && !enemy_list.Contains(enemy) && !enemy.IsShouldNotBeMonsterTarget() && !enemy.IsShouldNotBeTarget()) enemy_list.Add(enemy); + } + foreach (ClientCard enemy in Enemy.GetSpells()) + { + if (enemy != null && !enemy_list.Contains(enemy) && !enemy.IsShouldNotBeMonsterTarget() && !enemy.IsShouldNotBeTarget()) enemy_list.Add(enemy); + } + if (enemy_list.Count > 0) + { + SelectDiscardSpell(); + AI.SelectNextCard(enemy_list); + return true; + } + return false; + } + + // check whether summon KnightmarePhoenix + public List KnightmarePhoenixSummonCheck(ClientCard included = null) + { + List empty_list = new List(); + List extra_list = new List(); + if (included != null) extra_list.Add(included); + List materials = CheckLinkMaterials(2, 2, true, extra_list); + if (materials.Count < 2) return empty_list; + + // need KnightmarePhoenix? + // for clear spells + ClientCard flag = Util.GetProblematicEnemySpell(); + if (flag != null) + { + if (Bot.Hand.GetMatchingCardsCount(card => card != Card) == 0) + { + return empty_list; + } else + { + return materials; + } + } + // for higher attack + if (materials[0].Attack + materials[1].Attack >= 1900) return empty_list; + + return materials; + } + + // summon process of KnightmarePhoenix + public bool KnightmarePhoenixSummon() + { + List materials = KnightmarePhoenixSummonCheck(); + if (materials.Count < 2) return false; + AI.SelectMaterials(materials); + return true; + } + + // activate of KnightmarePhoenix + public bool KnightmarePhoenixActivate() + { + List targets = new List(); + targets.Add(Util.GetProblematicEnemySpell()); + List spells = Enemy.GetSpells(); + List faceups = new List(); + List facedowns = new List(); + CardListShuffle(spells); + foreach (ClientCard card in spells) + { + if (card.HasPosition(CardPosition.FaceUp) && !(card.IsShouldNotBeTarget() || card.IsShouldNotBeMonsterTarget())) faceups.Add(card); + else if (card.HasPosition(CardPosition.FaceDown)) facedowns.Add(card); + } + targets = targets.Union(faceups).Union(facedowns).ToList(); + if (targets.Count == 0) return false; + SelectDiscardSpell(); + AI.SelectNextCard(targets); + return true; + } + + // check whether summon CrystronHalqifibrax + public List CrystronHalqifibraxSummonCheck(ClientCard included = null) + { + List empty_list = new List(); + List extra_list = new List(); + if (included != null) extra_list.Add(included); + List materials = CheckLinkMaterials(2, 2, true, extra_list); + if (materials.Count < 2) return empty_list; + + // need CrystronHalqifibrax? + if (CheckRemainInDeck(CardId.PSYGamma, CardId.AshBlossom_JoyousSpring) == 0) return empty_list; + + + return empty_list; + } + + // summon process of CrystronHalqifibrax + public bool CrystronHalqifibraxSummon() + { + List materials = CrystronHalqifibraxSummonCheck(); + if (materials.Count < 2) return false; + AI.SelectMaterials(materials); + return true; + } + + // activate of CrystronHalqifibrax + public bool CrystronHalqifibraxActivate() + { + if (Duel.Player == 0) + { + return true; + } + else if (Util.IsChainTarget(Card) || Util.GetProblematicEnemySpell() != null) return true; + else if (Duel.Player == 1 && Duel.Phase == DuelPhase.BattleStart && Util.IsOneEnemyBetterThanValue(1500, true)) + { + if (Util.IsOneEnemyBetterThanValue(1900, true)) + { + AI.SelectPosition(CardPosition.FaceUpDefence); + } + else + { + AI.SelectPosition(CardPosition.FaceUpAttack); + } + return true; + } + return false; + } + + // check whether summon SalamangreatAlmiraj + public bool SalamangreatAlmirajSummonCheck(ClientCard included = null) + { + // use witchcraft first + if (CheckDiscardableSpellCount() >= 2) return false; + List materials = Bot.GetMonsters(); + if (included != null) materials.Add(included); + if (materials.GetCardCount(CardId.Pittore) + materials.GetCardCount(CardId.Genni) == 0) return false; + if (Bot.HasInHand(important_witchcraft)) return true; + + return false; + } + + // summmon process of SalamangreatAlmiraj + public bool SalamangreatAlmirajSummon() + { + if (!SalamangreatAlmirajSummonCheck()) return false; + List material = new List { CardId.Pittore, CardId.Genni }; + AI.SelectMaterials(material); + return true; + } + + // activate of SalamangreatAlmiraj + public bool SalamangreatAlmirajActivate() + { + if (Card.Location == CardLocation.Grave) return true; + if (Duel.Player == 1) + { + AI.SelectCard(Util.GetBestBotMonster()); + return true; + } + return false; + } + + // summmon process of PSYLambda + public bool PSYLambdaSummon() + { + if (Bot.HasInMonstersZone(CardId.PSYGamma) && Bot.HasInMonstersZone(CardId.PSYDriver)) + { + if (Bot.HasInHand(CardId.PSYGamma) || Bot.HasInMonstersZone(CardId.PSYOmega)) { + List targets = new List{CardId.PSYDriver, CardId.PSYGamma}; + AI.SelectMaterials(targets); + return true; + } + } + return false; + } + + /// + /// return place to summon RelinquishedAnima. + /// if no need to summon, return -1 + /// + /// Cards included into account + public int RelinquishedAnimaSummonCheck(ClientCard included = null) + { + // use witchcraft first + if (CheckDiscardableSpellCount() >= 2) return -1; + List materials = Bot.GetMonsters(); + if (included != null) materials.Add(included); + + int place = -1; + int attack = Util.GetBestAttack(Bot); + // select place + + List checklist = new List { Enemy.MonsterZone[6], Enemy.MonsterZone[5] }; + List placelist = new List { 1, 3 }; + for (int i = 0; i < 2; ++i) + { + ClientCard card = checklist[i]; + int _place = placelist[i]; + if (card != null && card.HasLinkMarker((int)CardLinkMarker.Top) && card.Attack > attack && + !card.IsShouldNotBeMonsterTarget() && !card.IsShouldNotBeTarget()) + { + ClientCard self_card = Bot.MonsterZone[_place]; + if (self_card == null || self_card.Level == 1) + { + place = _place; + attack = card.Attack; + } + } + } + checklist = new List { Enemy.MonsterZone[3], Enemy.MonsterZone[1] }; + placelist = new List { 5, 6 }; + for (int i = 0; i < 2; ++i) + { + ClientCard card = checklist[i]; + int _place = placelist[i]; + if (card != null && card.Attack > attack && + !card.IsShouldNotBeMonsterTarget() && !card.IsShouldNotBeTarget()) + { + ClientCard enemy_card = Enemy.MonsterZone[11 - _place]; + if (enemy_card != null) continue; + ClientCard self_card = Bot.MonsterZone[_place]; + if (self_card == null || self_card.Level == 1) + { + place = _place; + attack = card.Attack; + } + } + } + + return place; + } + + // summmon process of RelinquishedAnima + public bool RelinquishedAnimaSummon() + { + int place = RelinquishedAnimaSummonCheck(); + Logger.DebugWriteLine("RelinquishedAnima summon check: " + place.ToString()); + if (place != -1) + { + int zone = (int)System.Math.Pow(2, place); + AI.SelectPlace(zone); + if (Bot.MonsterZone[place] != null && Bot.MonsterZone[place].Level == 1) + { + AI.SelectMaterials(Bot.MonsterZone[place]); + } else + { + AI.SelectMaterials(CardId.Genni); + } + return true; + } + return false; + } + + // default Chicken game + public bool ChickenGame() + { + if (SpellNegatable()) return false; + if (Bot.LifePoints <= 1000) + return false; + if (Bot.LifePoints - 1000 <= Enemy.LifePoints && ActivateDescription == Util.GetStringId(_CardId.ChickenGame, 0)) + { + return true; + } + if (Bot.LifePoints - 1000 > Enemy.LifePoints && ActivateDescription == Util.GetStringId(_CardId.ChickenGame, 1)) + { + return true; + } + return false; + } + } +} diff --git a/Game/AI/DecksManager.cs b/Game/DecksManager.cs similarity index 60% rename from Game/AI/DecksManager.cs rename to Game/DecksManager.cs index 09c15ead..eac947da 100644 --- a/Game/AI/DecksManager.cs +++ b/Game/DecksManager.cs @@ -1,81 +1,114 @@ -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace WindBot.Game.AI -{ - public static class DecksManager - { - private class DeckInstance - { - public string Deck { get; private set; } - public Type Type { get; private set; } - public string Level { get; private set; } - - public DeckInstance(string deck, Type type, string level) - { - Deck = deck; - Type = type; - Level = level; - } - } - - private static Dictionary _decks; - private static List _list; - private static Random _rand; - - public static void Init() - { - _decks = new Dictionary(); - _rand = new Random(); - - Assembly asm = Assembly.GetExecutingAssembly(); - Type[] types = asm.GetTypes(); - - foreach (Type type in types) - { - MemberInfo info = type; - object[] attributes = info.GetCustomAttributes(false); - foreach (object attribute in attributes) - { - if (attribute is DeckAttribute) - { - DeckAttribute deck = (DeckAttribute)attribute; - _decks.Add(deck.Name, new DeckInstance(deck.File, type, deck.Level)); - } - } - } - - _list = new List(); - _list.AddRange(_decks.Values); - - Logger.WriteLine("Decks initialized, " + _decks.Count + " found."); - } - - public static Executor Instantiate(GameAI ai, Duel duel) - { - DeckInstance infos; - - string deck = ai.Game.Deck; - - if (deck != null && _decks.ContainsKey(deck)) - { - infos = _decks[deck]; - Logger.WriteLine("Deck found, loading " + infos.Deck); - } - else - { - do - { - infos = _list[_rand.Next(_list.Count)]; - } - while (infos.Level != "Normal"); - Logger.WriteLine("Deck not found, loading random: " + infos.Deck); - } - - Executor executor = (Executor)Activator.CreateInstance(infos.Type, ai, duel); - executor.Deck = infos.Deck; - return executor; - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using WindBot.Game.AI; + +namespace WindBot.Game +{ + public static class DecksManager + { + private class DeckInstance + { + public string Deck { get; private set; } + public Type Type { get; private set; } + public string Level { get; private set; } + + public DeckInstance(string deck, Type type, string level) + { + Deck = deck; + Type = type; + Level = level; + } + } + + private static Dictionary _decks; + private static List _list; + private static Random _rand; + + public static void Init() + { + _decks = new Dictionary(); + _rand = new Random(); + + Assembly asm = Assembly.GetExecutingAssembly(); + Type[] types = asm.GetTypes(); + + foreach (Type type in types) + { + MemberInfo info = type; + object[] attributes = info.GetCustomAttributes(false); + foreach (object attribute in attributes) + { + if (attribute is DeckAttribute) + { + DeckAttribute deck = (DeckAttribute)attribute; + _decks.Add(deck.Name, new DeckInstance(deck.File, type, deck.Level)); + } + } + } + try + { + string[] files = Directory.GetFiles("Executors", "*.dll", SearchOption.TopDirectoryOnly); + foreach (string file in files) + { + Assembly assembly = Assembly.LoadFrom(file); + Type[] types2 = assembly.GetTypes(); + foreach (Type type in types2) + { + try + { + MemberInfo info = type; + object[] attributes = info.GetCustomAttributes(false); + foreach (object attribute in attributes) + { + if (attribute is DeckAttribute) + { + DeckAttribute deck = (DeckAttribute)attribute; + _decks.Add(deck.Name, new DeckInstance(deck.File, type, deck.Level)); + } + } + } + catch (Exception ex) + { + Logger.WriteErrorLine("Executor loading (" + file + ") error: " + ex); + } + } + } + } + catch (Exception ex) + { + Logger.WriteErrorLine(ex.ToString()); + } + + _list = new List(); + _list.AddRange(_decks.Values); + + Logger.WriteLine("Decks initialized, " + _decks.Count + " found."); + } + + public static Executor Instantiate(GameAI ai, Duel duel, string deck) + { + DeckInstance infos; + + if (deck != null && _decks.ContainsKey(deck)) + { + infos = _decks[deck]; + Logger.WriteLine("Deck found, loading " + infos.Deck); + } + else + { + do + { + infos = _list[_rand.Next(_list.Count)]; + } + while (infos.Level != "Normal"); + Logger.WriteLine("Deck not found, loading random: " + infos.Deck); + } + + Executor executor = (Executor)Activator.CreateInstance(infos.Type, ai, duel); + executor.Deck = infos.Deck; + return executor; + } + } +} diff --git a/Game/GameBehavior.cs b/Game/GameBehavior.cs index 48bcc184..2bbd28b0 100644 --- a/Game/GameBehavior.cs +++ b/Game/GameBehavior.cs @@ -1,1603 +1,1629 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text.RegularExpressions; -using WindBot.Game.AI; -using YGOSharp.Network; -using YGOSharp.Network.Enums; -using YGOSharp.Network.Utils; -using YGOSharp.OCGWrapper; -using YGOSharp.OCGWrapper.Enums; - -namespace WindBot.Game -{ - public class GameBehavior - { - public GameClient Game { get; private set; } - public YGOClient Connection { get; private set; } - public Deck Deck { get; private set; } - - private GameAI _ai; - - private IDictionary> _packets; - private IDictionary> _messages; - - private Room _room; - private Duel _duel; - private int _hand; - private bool _debug; - private int _select_hint; - private GameMessage _lastMessage; - public class LocationInfo - { - public int controler; - public int location; - public int sequence; - public int position; - public LocationInfo() - { - controler = 0; - location = 0; - sequence = 0; - position = 0; - } - public LocationInfo(BinaryReader packet, bool isfirst = false) - { - Read(packet, isfirst); - } - public void Read(BinaryReader packet, bool isfirst = false) - { - controler = packet.ReadByte(); - if(!isfirst) - controler = 1 - controler; - location = packet.ReadByte(); - sequence = packet.ReadInt32(); - position = packet.ReadInt32(); - } - } - - public GameBehavior(GameClient game) - { - Game = game; - Connection = game.Connection; - _hand = game.Hand; - _debug = game.Debug; - _packets = new Dictionary>(); - _messages = new Dictionary>(); - RegisterPackets(); - - _room = new Room(); - _duel = new Duel(); - - _ai = new GameAI(Game, _duel); - _ai.Executor = DecksManager.Instantiate(_ai, _duel); - Deck = Deck.Load(_ai.Executor.Deck); - - _select_hint = 0; - } - - public int GetLocalPlayer(int player) - { - return _duel.IsFirst ? player : 1 - player; - } - - public void OnPacket(BinaryReader packet) - { - StocMessage id = (StocMessage)packet.ReadByte(); - if (id == StocMessage.GameMsg) - { - GameMessage msg = (GameMessage)packet.ReadByte(); - if (_messages.ContainsKey(msg)) - _messages[msg](packet); - _lastMessage = msg; - return; - } - if (_packets.ContainsKey(id)) - _packets[id](packet); - } - - private void RegisterPackets() - { - _packets.Add(StocMessage.JoinGame, OnJoinGame); - _packets.Add(StocMessage.TypeChange, OnTypeChange); - _packets.Add(StocMessage.HsPlayerEnter, OnPlayerEnter); - _packets.Add(StocMessage.HsPlayerChange, OnPlayerChange); - _packets.Add(StocMessage.SelectHand, OnSelectHand); - _packets.Add(StocMessage.SelectTp, OnSelectTp); - _packets.Add(StocMessage.TimeLimit, OnTimeLimit); - _packets.Add(StocMessage.Replay, OnReplay); - _packets.Add(StocMessage.DuelEnd, OnDuelEnd); - _packets.Add(StocMessage.Chat, OnChat); - _packets.Add(StocMessage.ChangeSide, OnChangeSide); - _packets.Add(StocMessage.ErrorMsg, OnErrorMsg); - - _messages.Add(GameMessage.Retry, OnRetry); - _messages.Add(GameMessage.Start, OnStart); - _messages.Add(GameMessage.Hint, OnHint); - _messages.Add(GameMessage.Win, OnWin); - _messages.Add(GameMessage.Draw, OnDraw); - _messages.Add(GameMessage.ShuffleDeck, OnShuffleDeck); - _messages.Add(GameMessage.ShuffleHand, OnShuffleHand); - _messages.Add(GameMessage.ShuffleExtra, OnShuffleExtra); - _messages.Add(GameMessage.ShuffleSetCard, OnShuffleSetCard); - _messages.Add(GameMessage.TagSwap, OnTagSwap); - _messages.Add(GameMessage.NewTurn, OnNewTurn); - _messages.Add(GameMessage.NewPhase, OnNewPhase); - _messages.Add(GameMessage.Damage, OnDamage); - _messages.Add(GameMessage.PayLpCost, OnDamage); - _messages.Add(GameMessage.Recover, OnRecover); - _messages.Add(GameMessage.LpUpdate, OnLpUpdate); - _messages.Add(GameMessage.Move, OnMove); - _messages.Add(GameMessage.Swap, OnSwap); - _messages.Add(GameMessage.Attack, OnAttack); - _messages.Add(GameMessage.Battle, OnBattle); - _messages.Add(GameMessage.AttackDisabled, OnAttackDisabled); - _messages.Add(GameMessage.PosChange, OnPosChange); - _messages.Add(GameMessage.Chaining, OnChaining); - _messages.Add(GameMessage.ChainEnd, OnChainEnd); - _messages.Add(GameMessage.SortCard, OnCardSorting); - _messages.Add(GameMessage.SortChain, OnChainSorting); - _messages.Add(GameMessage.UpdateCard, OnUpdateCard); - _messages.Add(GameMessage.UpdateData, OnUpdateData); - _messages.Add(GameMessage.BecomeTarget, OnBecomeTarget); - _messages.Add(GameMessage.SelectBattleCmd, OnSelectBattleCmd); - _messages.Add(GameMessage.SelectCard, OnSelectCard); - _messages.Add(GameMessage.SelectUnselect, OnSelectUnselectCard); - _messages.Add(GameMessage.SelectChain, OnSelectChain); - _messages.Add(GameMessage.SelectCounter, OnSelectCounter); - _messages.Add(GameMessage.SelectDisfield, OnSelectDisfield); - _messages.Add(GameMessage.SelectEffectYn, OnSelectEffectYn); - _messages.Add(GameMessage.SelectIdleCmd, OnSelectIdleCmd); - _messages.Add(GameMessage.SelectOption, OnSelectOption); - _messages.Add(GameMessage.SelectPlace, OnSelectPlace); - _messages.Add(GameMessage.SelectPosition, OnSelectPosition); - _messages.Add(GameMessage.SelectSum, OnSelectSum); - _messages.Add(GameMessage.SelectTribute, OnSelectTribute); - _messages.Add(GameMessage.SelectYesNo, OnSelectYesNo); - _messages.Add(GameMessage.AnnounceAttrib, OnAnnounceAttrib); - _messages.Add(GameMessage.AnnounceCard, OnAnnounceCard); - _messages.Add(GameMessage.AnnounceNumber, OnAnnounceNumber); - _messages.Add(GameMessage.AnnounceRace, OnAnnounceRace); - _messages.Add(GameMessage.RockPaperScissors, OnRockPaperScissors); - _messages.Add(GameMessage.Equip, OnEquip); - _messages.Add(GameMessage.Unequip, OnUnEquip); - _messages.Add(GameMessage.CardTarget, OnCardTarget); - _messages.Add(GameMessage.CancelTarget, OnCancelTarget); - _messages.Add(GameMessage.Summoning, OnSummoning); - _messages.Add(GameMessage.Summoned, OnSummoned); - _messages.Add(GameMessage.SpSummoning, OnSpSummoning); - _messages.Add(GameMessage.SpSummoned, OnSpSummoned); - _messages.Add(GameMessage.FlipSummoning, OnSummoning); - _messages.Add(GameMessage.FlipSummoned, OnSummoned); - } - - private void OnJoinGame(BinaryReader packet) - { - /*int lflist = (int)*/ packet.ReadUInt32(); - /*int rule = */ packet.ReadByte(); - /*int mode = */ packet.ReadByte(); - int duel_rule = packet.ReadByte(); - /*bool nocheck deck =*/ packet.ReadByte(); - /*bool noshuffle deck =*/ packet.ReadByte(); - /*align*/ packet.ReadBytes(3); - /*int start_lp =(int)*/ packet.ReadUInt32(); - /*int start_hand =*/ packet.ReadByte(); - /*int draw_count =*/ packet.ReadByte(); - /*int time_limit =*/ packet.ReadUInt16(); - /*align =*/ packet.ReadBytes(4); - const ulong SERVER_HANDSHAKE = 4903489263569811227; - ulong handshake = packet.ReadUInt64(); - if (handshake != SERVER_HANDSHAKE) - { - Connection.Close(); - return; - } - int team1 = packet.ReadInt32(); - int team2 = packet.ReadInt32(); - /*int best_of =*/ packet.ReadInt32(); - int duel_flag = packet.ReadInt32(); - /*int forbidden_types =*/ packet.ReadInt32(); - /*int extra_rules =*/ packet.ReadInt32(); - _room.Players = team1 + team2; - const int DUEL_EMZONE = 0x2000; - const int DUEL_FSX_MMZONE = 0x4000; - _ai.Duel.IsNewRule = (duel_flag & DUEL_EMZONE) != 0; - _ai.Duel.IsNewRule2020 = (duel_flag & DUEL_FSX_MMZONE) != 0; - BinaryWriter deck = GamePacketFactory.Create(CtosMessage.UpdateDeck); - deck.Write(Deck.Cards.Count + Deck.ExtraCards.Count); - deck.Write(Deck.SideCards.Count); - foreach (int card in Deck.Cards) - deck.Write(card); - foreach (int card in Deck.ExtraCards) - deck.Write(card); - foreach (int card in Deck.SideCards) - deck.Write(card); - Connection.Send(deck); - _ai.OnJoinGame(); - } - - private void OnChangeSide(BinaryReader packet) - { - BinaryWriter deck = GamePacketFactory.Create(CtosMessage.UpdateDeck); - deck.Write(Deck.Cards.Count + Deck.ExtraCards.Count); - deck.Write(Deck.SideCards.Count); - foreach (int card in Deck.Cards) - deck.Write(card); - foreach (int card in Deck.ExtraCards) - deck.Write(card); - foreach (int card in Deck.SideCards) - deck.Write(card); - Connection.Send(deck); - _ai.OnJoinGame(); - } - - private void OnTypeChange(BinaryReader packet) - { - int type = packet.ReadByte(); - int pos = type & 0xF; - if (pos < 0 || pos > 3) - { - Connection.Close(); - return; - } - _room.Position = pos; - _room.IsHost = ((type >> 4) & 0xF) != 0; - _room.IsReady[pos] = true; - Connection.Send(CtosMessage.HsReady); - } - - private void OnPlayerEnter(BinaryReader packet) - { - string name = packet.ReadUnicode(20); - int pos = packet.ReadByte(); - if (pos < 8) - _room.Names[pos] = name; - } - - private void OnPlayerChange(BinaryReader packet) - { - int change = packet.ReadByte(); - int pos = (change >> 4) & 0xF; - int state = change & 0xF; - if (pos > 3) - return; - if (state < 8) - { - string oldname = _room.Names[pos]; - _room.Names[pos] = null; - _room.Names[state] = oldname; - _room.IsReady[pos] = false; - _room.IsReady[state] = false; - } - else if (state == (int)PlayerChange.Ready) - _room.IsReady[pos] = true; - else if (state == (int)PlayerChange.NotReady) - _room.IsReady[pos] = false; - else if (state == (int)PlayerChange.Leave || state == (int)PlayerChange.Observe) - { - _room.IsReady[pos] = false; - _room.Names[pos] = null; - } - - if (_room.IsHost && _room.IsReady[0] && _room.IsReady[1]) - Connection.Send(CtosMessage.HsStart); - } - - private void OnSelectHand(BinaryReader packet) - { - int result; - if (_hand > 0) - result = _hand; - else - result = _ai.OnRockPaperScissors(); - Connection.Send(CtosMessage.HandResult, (byte)result); - } - - private void OnSelectTp(BinaryReader packet) - { - bool start = _ai.OnSelectHand(); - Connection.Send(CtosMessage.TpResult, (byte)(start ? 1 : 0)); - } - - private void OnTimeLimit(BinaryReader packet) - { - int player = GetLocalPlayer(packet.ReadByte()); - if (player == 0) - Connection.Send(CtosMessage.TimeConfirm); - } - - private void OnReplay(BinaryReader packet) - { - /*byte[] replay =*/ packet.ReadToEnd(); - - /* - const string directory = "Replays"; - if (!Directory.Exists(directory)) - Directory.CreateDirectory(directory); - - string otherName = _room.Position == 0 ? _room.Names[1] : _room.Names[0]; - string file = DateTime.Now.ToString("yyyy-MM-dd.HH-mm.") + otherName + ".yrp"; - string fullname = Path.Combine(directory, file); - - if (Regex.IsMatch(file, @"^[\w\-. ]+$")) - File.WriteAllBytes(fullname, replay); - */ - - //Connection.Close(); - } - - private void OnDuelEnd(BinaryReader packet) - { - Connection.Close(); - } - - private void OnChat(BinaryReader packet) - { - int player = packet.ReadInt16(); - string message = packet.ReadUnicode(256); - string myName = (player != 0) ? _room.Names[1] : _room.Names[0]; - string otherName = (player == 0) ? _room.Names[1] : _room.Names[0]; - if (player < 4) - Logger.DebugWriteLine(otherName + " say to " + myName + ": " + message); - } - - private void OnErrorMsg(BinaryReader packet) - { - int msg = packet.ReadByte(); - // align - packet.ReadByte(); - packet.ReadByte(); - packet.ReadByte(); - int pcode = packet.ReadInt32(); - if (msg == 2) //ERRMSG_DECKERROR - { - int code = pcode & 0xFFFFFFF; - int flag = pcode >> 28; - if (flag <= 5) //DECKERROR_CARDCOUNT - { - NamedCard card = NamedCard.Get(code); - if (card != null) - _ai.OnDeckError(card.Name); - else - _ai.OnDeckError("Unknown Card"); - } - else - _ai.OnDeckError("DECK"); - } - Connection.Close(); - } - - private void OnRetry(BinaryReader packet) - { - _ai.OnRetry(); - Connection.Close(); - throw new Exception("Got MSG_RETRY. Last message is " + _lastMessage); - } - - private void OnHint(BinaryReader packet) - { - int type = packet.ReadByte(); - int player = packet.ReadByte(); - int data = packet.ReadInt32(); - if (type == 1) // HINT_EVENT - { - if (data == 24) // battling - { - _duel.Fields[0].UnderAttack = false; - _duel.Fields[1].UnderAttack = false; - } - } - if (type == 3) // HINT_SELECTMSG - { - _select_hint = data; - } - } - - private void OnStart(BinaryReader packet) - { - int type = packet.ReadByte(); - _duel.IsFirst = (type & 0xF) == 0; - _duel.Turn = 0; - /*int duel_rule = packet.ReadByte(); - _ai.Duel.IsNewRule = (duel_rule == 4); - _ai.Duel.IsNewRule2020 = (duel_rule >= 5);*/ - _duel.Fields[GetLocalPlayer(0)].LifePoints = packet.ReadInt32(); - _duel.Fields[GetLocalPlayer(1)].LifePoints = packet.ReadInt32(); - int deck = packet.ReadInt16(); - int extra = packet.ReadInt16(); - _duel.Fields[GetLocalPlayer(0)].Init(deck, extra); - deck = packet.ReadInt16(); - extra = packet.ReadInt16(); - _duel.Fields[GetLocalPlayer(1)].Init(deck, extra); - - Logger.DebugWriteLine("Duel started: " + _room.Names[0] + " versus " + _room.Names[1]); - _ai.OnStart(); - } - - private void OnWin(BinaryReader packet) - { - int result = GetLocalPlayer(packet.ReadByte()); - - string otherName = _room.Position == 0 ? _room.Names[1] : _room.Names[0]; - string textResult = (result == 2 ? "Draw" : result == 0 ? "Win" : "Lose"); - Logger.DebugWriteLine("Duel finished against " + otherName + ", result: " + textResult); - } - - private void OnDraw(BinaryReader packet) - { - int player = GetLocalPlayer(packet.ReadByte()); - int count = packet.ReadInt32(); - if (_debug) - Logger.WriteLine("(" + player.ToString() + " draw " + count.ToString() + " card)"); - - for (int i = 0; i < count; ++i) - { - _duel.Fields[player].Deck.RemoveAt(_duel.Fields[player].Deck.Count - 1); - _duel.Fields[player].Hand.Add(new ClientCard(0, CardLocation.Hand, -1)); - } - _ai.OnDraw(player); - } - - private void OnShuffleDeck(BinaryReader packet) - { - int player = GetLocalPlayer(packet.ReadByte()); - foreach (ClientCard card in _duel.Fields[player].Deck) - card.SetId(0); - } - - private void OnShuffleHand(BinaryReader packet) - { - int player = GetLocalPlayer(packet.ReadByte()); - packet.ReadInt32(); - foreach (ClientCard card in _duel.Fields[player].Hand) - card.SetId(packet.ReadInt32()); - } - - private void OnShuffleExtra(BinaryReader packet) - { - int player = GetLocalPlayer(packet.ReadByte()); - packet.ReadInt32(); - foreach (ClientCard card in _duel.Fields[player].ExtraDeck) - { - if (!card.IsFaceup()) - card.SetId(packet.ReadInt32()); - } - } - - private void OnShuffleSetCard(BinaryReader packet) - { - int location = packet.ReadByte(); - int count = packet.ReadByte(); - ClientCard[] list = new ClientCard[5]; - for (int i = 0; i < count; ++i) - { - LocationInfo loc = new LocationInfo(packet, _duel.IsFirst); - ClientCard card = _duel.GetCard(loc.controler, (CardLocation)loc.location, loc.sequence); - if (card == null) continue; - list[i] = card; - card.SetId(0); - } - for (int i = 0; i < count; ++i) - { - LocationInfo loc = new LocationInfo(packet, _duel.IsFirst); - ClientCard card = _duel.GetCard(loc.controler, (CardLocation)loc.location, loc.sequence); - if (card == null) continue; - ClientCard[] zone = (loc.location == (int)CardLocation.MonsterZone) ? _duel.Fields[loc.controler].MonsterZone : _duel.Fields[loc.controler].SpellZone; - zone[loc.sequence] = list[i]; - } - } - - private void OnTagSwap(BinaryReader packet) - { - int player = GetLocalPlayer(packet.ReadByte()); - int mcount = packet.ReadInt32(); - int ecount = packet.ReadInt32(); - /*int pcount = */ packet.ReadInt32(); - int hcount = packet.ReadInt32(); - /*int topcode =*/ packet.ReadInt32(); - _duel.Fields[player].Deck.Clear(); - for (int i = 0; i < mcount; ++i) - { - _duel.Fields[player].Deck.Add(new ClientCard(0, CardLocation.Deck, -1)); - } - _duel.Fields[player].ExtraDeck.Clear(); - for (int i = 0; i < ecount; ++i) - { - int code = packet.ReadInt32(); - _duel.Fields[player].ExtraDeck.Add(new ClientCard(code, CardLocation.Extra, -1)); - packet.ReadInt32(); // position - } - _duel.Fields[player].Hand.Clear(); - for (int i = 0; i < hcount; ++i) - { - int code = packet.ReadInt32(); - _duel.Fields[player].Hand.Add(new ClientCard(code, CardLocation.Hand,-1)); - packet.ReadInt32(); // position - } - } - - private void OnNewTurn(BinaryReader packet) - { - _duel.Turn++; - _duel.Player = GetLocalPlayer(packet.ReadByte()); - _ai.OnNewTurn(); - } - - private void OnNewPhase(BinaryReader packet) - { - _duel.Phase = (DuelPhase)packet.ReadInt16(); - if (_debug && _duel.Phase == DuelPhase.Standby) - { - Logger.WriteLine("*********Bot Hand*********"); - foreach (ClientCard card in _duel.Fields[0].Hand) - { - Logger.WriteLine(card.Name); - } - Logger.WriteLine("*********Bot Spell*********"); - foreach (ClientCard card in _duel.Fields[0].SpellZone) - { - Logger.WriteLine(card?.Name); - } - Logger.WriteLine("*********Bot Monster*********"); - foreach (ClientCard card in _duel.Fields[0].MonsterZone) - { - Logger.WriteLine(card?.Name); - } - Logger.WriteLine("*********Finish*********"); - } - if (_debug) - Logger.WriteLine("(Go to " + (_duel.Phase.ToString()) + ")"); - _duel.LastSummonPlayer = -1; - _duel.SummoningCards.Clear(); - _duel.LastSummonedCards.Clear(); - _duel.Fields[0].BattlingMonster = null; - _duel.Fields[1].BattlingMonster = null; - _duel.Fields[0].UnderAttack = false; - _duel.Fields[1].UnderAttack = false; - List monsters = _duel.Fields[0].GetMonsters(); - foreach (ClientCard monster in monsters) - { - monster.Attacked = false; - } - _select_hint = 0; - _ai.OnNewPhase(); - } - - private void OnDamage(BinaryReader packet) - { - int player = GetLocalPlayer(packet.ReadByte()); - int final = _duel.Fields[player].LifePoints - packet.ReadInt32(); - if (final < 0) final = 0; - if (_debug) - Logger.WriteLine("(" + player.ToString() + " got damage , LifePoint left= " + final.ToString() + ")"); - _duel.Fields[player].LifePoints = final; - } - - private void OnRecover(BinaryReader packet) - { - int player = GetLocalPlayer(packet.ReadByte()); - _duel.Fields[player].LifePoints += packet.ReadInt32(); - } - - private void OnLpUpdate(BinaryReader packet) - { - int player = GetLocalPlayer(packet.ReadByte()); - _duel.Fields[player].LifePoints = packet.ReadInt32(); - } - - private void OnMove(BinaryReader packet) - { - // TODO: update equip cards and target cards - int cardId = packet.ReadInt32(); - LocationInfo previous = new LocationInfo(packet, _duel.IsFirst); - LocationInfo current = new LocationInfo(packet, _duel.IsFirst); - packet.ReadInt32(); // reason - - ClientCard card = _duel.GetCard(previous.controler, (CardLocation)previous.location, previous.sequence); - if ((previous.location & (int)CardLocation.Overlay) != 0) - { - previous.location = previous.location & 0x7f; - card = _duel.GetCard(previous.controler, (CardLocation)previous.location, previous.sequence); - if (card != null) - { - if (_debug) - Logger.WriteLine("(" + previous.controler.ToString() + " 's " + (card.Name ?? "UnKnowCard") + " deattach " + (NamedCard.Get(cardId)?.Name) + ")"); - card.Overlays.Remove(cardId); - } - previous.location = 0; // the card is removed when it go to overlay, so here we treat it as a new card - } - else - _duel.RemoveCard((CardLocation)previous.location, card, previous.controler, previous.sequence); - - if ((current.location & (int)CardLocation.Overlay) != 0) - { - current.location = current.location & 0x7f; - card = _duel.GetCard(current.controler, (CardLocation)current.location, current.sequence); - if (card != null) - { - if (_debug) - Logger.WriteLine("(" + previous.controler.ToString() + " 's " + (card.Name ?? "UnKnowCard") + " overlay " + (NamedCard.Get(cardId)?.Name) + ")"); - card.Overlays.Add(cardId); - } - } - else - { - if (previous.location == 0) - { - if (_debug) - Logger.WriteLine("(" + previous.controler.ToString() + " 's " + (NamedCard.Get(cardId)?.Name) - + " appear in " + (CardLocation)current.location + ")"); - _duel.AddCard((CardLocation)current.location, cardId, current.controler, current.sequence, current.position); - } - else - { - _duel.AddCard((CardLocation)current.location, card, current.controler, current.sequence, current.position, cardId); - if (card != null && previous.location != current.location) - card.IsSpecialSummoned = false; - if (_debug && card != null) - Logger.WriteLine("(" + previous.controler.ToString() + " 's " + (card.Name ?? "UnKnowCard") - + " from " + - (CardLocation)previous.location + " move to " + (CardLocation)current.location + ")"); - } - } - } - - private void OnSwap(BinaryReader packet) - { - int cardId1 = packet.ReadInt32(); - LocationInfo info1 = new LocationInfo(packet, _duel.IsFirst); - int cardId2 = packet.ReadInt32(); - LocationInfo info2 = new LocationInfo(packet, _duel.IsFirst); - ClientCard card1 = _duel.GetCard(info1.controler, (CardLocation)info1.location, info1.sequence); - ClientCard card2 = _duel.GetCard(info2.controler, (CardLocation)info2.location, info2.sequence); - if (card1 == null || card2 == null) return; - _duel.RemoveCard((CardLocation)info1.location, card1, info1.controler, info1.sequence); - _duel.RemoveCard((CardLocation)info2.location, card2, info2.controler, info2.sequence); - _duel.AddCard((CardLocation)info2.location, card1, info2.controler, info2.sequence, card1.Position, cardId1); - _duel.AddCard((CardLocation)info1.location, card2, info1.controler, info1.sequence, card2.Position, cardId2); - } - - private void OnAttack(BinaryReader packet) - { - LocationInfo info1 = new LocationInfo(packet, _duel.IsFirst); - LocationInfo info2 = new LocationInfo(packet, _duel.IsFirst); - - ClientCard attackcard = _duel.GetCard(info1.controler, (CardLocation)info1.location, info1.sequence); - ClientCard defendcard = _duel.GetCard(info2.controler, (CardLocation)info2.location, info2.sequence); - if (_debug) - { - if (defendcard == null) Logger.WriteLine("(" + (attackcard.Name ?? "UnKnowCard") + " direct attack!!)"); - else Logger.WriteLine("(" + info1.controler.ToString() + " 's " + (attackcard.Name ?? "UnKnowCard") + " attack " + info2.controler.ToString() + " 's " + (defendcard.Name ?? "UnKnowCard") + ")"); - } - _duel.Fields[attackcard.Controller].BattlingMonster = attackcard; - _duel.Fields[1 - attackcard.Controller].BattlingMonster = defendcard; - _duel.Fields[1 - attackcard.Controller].UnderAttack = true; - - if (info2.location == 0 && info1.controler != 0) - { - _ai.OnDirectAttack(attackcard); - } - } - - private void OnBattle(BinaryReader packet) - { - _duel.Fields[0].UnderAttack = false; - _duel.Fields[1].UnderAttack = false; - } - - private void OnAttackDisabled(BinaryReader packet) - { - _duel.Fields[0].UnderAttack = false; - _duel.Fields[1].UnderAttack = false; - } - - private void OnPosChange(BinaryReader packet) - { - packet.ReadInt32(); // card id - int pc = GetLocalPlayer(packet.ReadByte()); - int pl = packet.ReadByte(); - int ps = packet.ReadSByte(); - int pp = packet.ReadSByte(); - int cp = packet.ReadSByte(); - ClientCard card = _duel.GetCard(pc, (CardLocation)pl, ps); - if (card != null) - { - card.Position = cp; - if ((pp & (int) CardPosition.FaceUp) > 0 && (cp & (int) CardPosition.FaceDown) > 0) - card.ClearCardTargets(); - if (_debug) - Logger.WriteLine("(" + (card.Name ?? "UnKnowCard") + " change position to " + (CardPosition)cp + ")"); - } - } - - private void OnChaining(BinaryReader packet) - { - packet.ReadInt32(); // card id - LocationInfo info = new LocationInfo(packet, _duel.IsFirst); - ClientCard card = _duel.GetCard(info.controler, info.location, info.sequence, info.position); - int cc = GetLocalPlayer(packet.ReadByte()); - if (_debug) - if (card != null) Logger.WriteLine("(" + cc.ToString() + " 's " + (card.Name ?? "UnKnowCard") + " activate effect)"); - _ai.OnChaining(card, cc); - //_duel.ChainTargets.Clear(); - _duel.ChainTargetOnly.Clear(); - _duel.LastSummonPlayer = -1; - _duel.CurrentChain.Add(card); - _duel.LastChainPlayer = cc; - - } - - private void OnChainEnd(BinaryReader packet) - { - _ai.OnChainEnd(); - _duel.LastChainPlayer = -1; - _duel.CurrentChain.Clear(); - _duel.ChainTargets.Clear(); - _duel.ChainTargetOnly.Clear(); - } - - private void OnCardSorting(BinaryReader packet) - { - /*int player =*/ GetLocalPlayer(packet.ReadByte()); - IList originalCards = new List(); - IList cards = new List(); - int count = packet.ReadInt32(); - for (int i = 0; i < count; ++i) - { - int id = packet.ReadInt32(); - int controler = GetLocalPlayer(packet.ReadByte()); - CardLocation loc = (CardLocation)packet.ReadInt32(); - int seq = packet.ReadInt32(); - ClientCard card; - if (((int)loc & (int)CardLocation.Overlay) != 0) - card = new ClientCard(id, CardLocation.Overlay, -1); - else - card = _duel.GetCard(controler, loc, seq); - if (card == null) continue; - if (id != 0) - card.SetId(id); - originalCards.Add(card); - cards.Add(card); - } - - IList selected = _ai.OnCardSorting(cards); - byte[] result = new byte[count]; - for (int i = 0; i < count; ++i) - { - int id = 0; - for (int j = 0; j < count; ++j) - { - if (selected[j] == null) continue; - if (selected[j].Equals(originalCards[i])) - { - id = j; - break; - } - } - result[i] = (byte)id; - } - - BinaryWriter reply = GamePacketFactory.Create(CtosMessage.Response); - reply.Write(result); - Connection.Send(reply); - } - - private void OnChainSorting(BinaryReader packet) - { - /*BinaryWriter writer =*/ GamePacketFactory.Create(CtosMessage.Response); - Connection.Send(CtosMessage.Response, -1); - } - - private void OnUpdateCard(BinaryReader packet) - { - int player = GetLocalPlayer(packet.ReadByte()); - int loc = packet.ReadByte(); - int seq = packet.ReadByte(); - - ClientCard card = _duel.GetCard(player, (CardLocation)loc, seq); - - card?.Update(packet, _duel); - } - - private void OnUpdateData(BinaryReader packet) - { - int player = GetLocalPlayer(packet.ReadByte()); - CardLocation loc = (CardLocation)packet.ReadByte(); - IList cards = null; - switch (loc) - { - case CardLocation.Hand: - cards = _duel.Fields[player].Hand; - break; - case CardLocation.MonsterZone: - cards = _duel.Fields[player].MonsterZone; - break; - case CardLocation.SpellZone: - cards = _duel.Fields[player].SpellZone; - break; - case CardLocation.Grave: - cards = _duel.Fields[player].Graveyard; - break; - case CardLocation.Removed: - cards = _duel.Fields[player].Banished; - break; - case CardLocation.Deck: - cards = _duel.Fields[player].Deck; - break; - case CardLocation.Extra: - cards = _duel.Fields[player].ExtraDeck; - break; - } - if (cards != null) - { - /*int size = */packet.ReadInt32(); - foreach (ClientCard card in cards) - { - if (card != null) - { - long pos = packet.BaseStream.Position; - long len = card.Update(packet, _duel); - packet.BaseStream.Position = pos + len; - } - } - } - } - - private void OnBecomeTarget(BinaryReader packet) - { - int count = packet.ReadInt32(); - for (int i = 0; i < count; ++i) - { - LocationInfo info = new LocationInfo(packet, _duel.IsFirst); - ClientCard card = _duel.GetCard(info.controler, (CardLocation)info.location, info.sequence); - if (card == null) continue; - if (_debug) - Logger.WriteLine("(" + (CardLocation)info.location + " 's " + (card.Name ?? "UnKnowCard") + " become target)"); - _duel.ChainTargets.Add(card); - _duel.ChainTargetOnly.Add(card); - } - } - - private void OnSelectBattleCmd(BinaryReader packet) - { - packet.ReadByte(); // player - _duel.BattlePhase = new BattlePhase(); - BattlePhase battle = _duel.BattlePhase; - - int count = packet.ReadInt32(); - for (int i = 0; i < count; ++i) - { - packet.ReadInt32(); // card id - int con = GetLocalPlayer(packet.ReadByte()); - CardLocation loc = (CardLocation)packet.ReadByte(); - int seq = packet.ReadInt32(); - long desc = packet.ReadInt64(); - packet.ReadByte(); // operation type - - ClientCard card = _duel.GetCard(con, loc, seq); - if (card != null) - { - card.ActionIndex[0] = i; - battle.ActivableCards.Add(card); - battle.ActivableDescs.Add(desc); - } - } - - count = packet.ReadInt32(); - for (int i = 0; i < count; ++i) - { - packet.ReadInt32(); // card id - int con = GetLocalPlayer(packet.ReadByte()); - CardLocation loc = (CardLocation)packet.ReadByte(); - int seq = packet.ReadByte(); - int diratt = packet.ReadByte(); - - ClientCard card = _duel.GetCard(con, loc, seq); - if (card != null) - { - card.ActionIndex[1] = i; - if (diratt > 0) - card.CanDirectAttack = true; - else - card.CanDirectAttack = false; - battle.AttackableCards.Add(card); - card.Attacked = false; - } - } - List monsters = _duel.Fields[0].GetMonsters(); - foreach (ClientCard monster in monsters) - { - if (!battle.AttackableCards.Contains(monster)) - monster.Attacked = true; - } - - battle.CanMainPhaseTwo = packet.ReadByte() != 0; - battle.CanEndPhase = packet.ReadByte() != 0; - - Connection.Send(CtosMessage.Response, _ai.OnSelectBattleCmd(battle).ToValue()); - } - - private void InternalOnSelectCard(BinaryReader packet, Func, int, int, int, bool, IList> func, bool tribute = false) - { - packet.ReadByte(); // player - bool cancelable = packet.ReadByte() != 0; - int min = packet.ReadInt32(); - int max = packet.ReadInt32(); - - IList cards = new List(); - int count = packet.ReadInt32(); - for (int i = 0; i < count; ++i) - { - int id = packet.ReadInt32(); - LocationInfo info = tribute ? new LocationInfo(packet, _duel.IsFirst) : new LocationInfo(); - if (tribute) - { - info.controler = packet.ReadByte(); - info.location = packet.ReadByte(); - info.sequence = packet.ReadInt32(); - packet.ReadByte(); - } - ClientCard card; - if (((int)info.location & (int)CardLocation.Overlay) != 0) - card = new ClientCard(id, CardLocation.Overlay, -1); - else if (info.location == 0) - card = new ClientCard(id, 0, 0); - else - card = _duel.GetCard(info.controler, (CardLocation)info.location, info.sequence); - if (card == null) continue; - if (card.Id == 0) - card.SetId(id); - cards.Add(card); - } - - IList selected = func(cards, min, max, _select_hint, cancelable); - _select_hint = 0; - - if (selected.Count == 0 && cancelable) - { - Connection.Send(CtosMessage.Response, -1); - return; - } - - BinaryWriter reply = GamePacketFactory.Create(CtosMessage.Response); - - reply.Write((int)0); - reply.Write((int)selected.Count); - - for (int i = 0; i < selected.Count; ++i) - { - int id = 0; - for (int j = 0; j < count; ++j) - { - if (cards[j] == null) continue; - if (cards[j].Equals(selected[i])) - { - id = j; - break; - } - } - reply.Write((int)id); - } - Connection.Send(reply); - } - - private void InternalOnSelectUnselectCard(BinaryReader packet, Func, int, int, int, bool, IList> func) - { - packet.ReadByte(); // player - bool finishable = packet.ReadByte() != 0; - bool cancelable = packet.ReadByte() != 0 || finishable; - int min = packet.ReadInt32(); - int max = packet.ReadInt32(); - - IList cards = new List(); - int count = packet.ReadInt32(); - for (int i = 0; i < count; ++i) - { - int id = packet.ReadInt32(); - LocationInfo info = new LocationInfo(packet, _duel.IsFirst); - ClientCard card; - if (((int)info.location & (int)CardLocation.Overlay) != 0) - card = new ClientCard(id, CardLocation.Overlay, -1); - else if (info.location == 0) - card = new ClientCard(id, 0, 0); - else - card = _duel.GetCard(info.controler, (CardLocation)info.location, info.sequence); - if (card == null) continue; - if (card.Id == 0) - card.SetId(id); - cards.Add(card); - } - int count2 = packet.ReadInt32(); - for (int i = 0; i < count2; ++i) - { - int id = packet.ReadInt32(); - LocationInfo info = new LocationInfo(packet, _duel.IsFirst); - } - - IList selected = func(cards, (finishable ? 0 : 1), 1, _select_hint, cancelable); - - if (selected.Count == 0 && cancelable) - { - Connection.Send(CtosMessage.Response, -1); - return; - } - - BinaryWriter reply = GamePacketFactory.Create(CtosMessage.Response); - reply.Write(selected.Count); - for (int i = 0; i < selected.Count; ++i) - { - int id = 0; - for (int j = 0; j < count; ++j) - { - if (cards[j] == null) continue; - if (cards[j].Equals(selected[i])) - { - id = j; - break; - } - } - reply.Write(id); - } - - Connection.Send(reply); - } - - private void OnSelectCard(BinaryReader packet) - { - InternalOnSelectCard(packet, _ai.OnSelectCard); - } - - private void OnSelectUnselectCard(BinaryReader packet) - { - InternalOnSelectUnselectCard(packet, _ai.OnSelectCard); - } - - private void OnSelectChain(BinaryReader packet) - { - packet.ReadByte(); // player - packet.ReadByte(); // specount - bool forced = packet.ReadByte() != 0; - packet.ReadInt32(); // hint1 - packet.ReadInt32(); // hint2 - int count = packet.ReadInt32(); - - IList cards = new List(); - IList descs = new List(); - - for (int i = 0; i < count; ++i) - { - packet.ReadInt32(); // card id - LocationInfo info = new LocationInfo(packet, _duel.IsFirst); - - long desc = packet.ReadInt64(); - if (desc == 221) // trigger effect - { - desc = 0; - } - cards.Add(_duel.GetCard(info.controler, info.location, info.sequence, info.position)); - descs.Add(desc); - packet.ReadByte(); // operation type - } - - if (cards.Count == 0) - { - Connection.Send(CtosMessage.Response, -1); - return; - } - - if (cards.Count == 1 && forced) - { - Connection.Send(CtosMessage.Response, 0); - return; - } - - Connection.Send(CtosMessage.Response, _ai.OnSelectChain(cards, descs, forced)); - } - - private void OnSelectCounter(BinaryReader packet) - { - packet.ReadByte(); // player - int type = packet.ReadInt16(); - int quantity = packet.ReadInt32(); - - IList cards = new List(); - IList counters = new List(); - int count = packet.ReadByte(); - for (int i = 0; i < count; ++i) - { - packet.ReadInt32(); // card id - int player = GetLocalPlayer(packet.ReadByte()); - CardLocation loc = (CardLocation) packet.ReadByte(); - int seq = packet.ReadByte(); - int num = packet.ReadInt16(); - cards.Add(_duel.GetCard(player, loc, seq)); - counters.Add(num); - } - - IList used = _ai.OnSelectCounter(type, quantity, cards, counters); - byte[] result = new byte[used.Count * 2]; - for (int i = 0; i < used.Count; ++i) - { - result[i * 2] = (byte)(used[i] & 0xff); - result[i * 2 + 1] = (byte)(used[i] >> 8); - } - BinaryWriter reply = GamePacketFactory.Create(CtosMessage.Response); - reply.Write(result); - Connection.Send(reply); - } - - private void OnSelectDisfield(BinaryReader packet) - { - OnSelectPlace(packet); - } - - private void OnSelectEffectYn(BinaryReader packet) - { - packet.ReadByte(); // player - - int cardId = packet.ReadInt32(); - int player = GetLocalPlayer(packet.ReadByte()); - CardLocation loc = (CardLocation)packet.ReadByte(); - int seq = packet.ReadByte(); - packet.ReadByte(); - long desc = packet.ReadInt64(); - - if (desc == 0 || desc == 221) - { - // 0: phase trigger effect - // 221: trigger effect - // for compatibility - desc = -1; - } - - ClientCard card = _duel.GetCard(player, loc, seq); - if (card == null) - { - Connection.Send(CtosMessage.Response, 0); - return; - } - - if (card.Id == 0) - card.SetId(cardId); - - int reply = _ai.OnSelectEffectYn(card, desc) ? (1) : (0); - Connection.Send(CtosMessage.Response, reply); - } - - private void OnSelectIdleCmd(BinaryReader packet) - { - packet.ReadByte(); // player - - _duel.MainPhase = new MainPhase(); - MainPhase main = _duel.MainPhase; - int count; - for (int k = 0; k < 5; k++) - { - count = packet.ReadInt32(); - for (int i = 0; i < count; ++i) - { - packet.ReadInt32(); // card id - int con = GetLocalPlayer(packet.ReadByte()); - CardLocation loc = (CardLocation)packet.ReadByte(); - int seq = k == 2 ? packet.ReadByte() : packet.ReadInt32(); - ClientCard card = _duel.GetCard(con, loc, seq); - if (card == null) continue; - card.ActionIndex[k] = i; - switch (k) - { - case 0: - main.SummonableCards.Add(card); - break; - case 1: - main.SpecialSummonableCards.Add(card); - break; - case 2: - main.ReposableCards.Add(card); - break; - case 3: - main.MonsterSetableCards.Add(card); - break; - case 4: - main.SpellSetableCards.Add(card); - break; - } - } - } - count = packet.ReadInt32(); - for (int i = 0; i < count; ++i) - { - packet.ReadInt32(); // card id - int con = GetLocalPlayer(packet.ReadByte()); - CardLocation loc = (CardLocation)packet.ReadByte(); - int seq = packet.ReadInt32(); - long desc = packet.ReadInt64(); - packet.ReadByte(); // operation type - - ClientCard card = _duel.GetCard(con, loc, seq); - if (card == null) continue; - card.ActionIndex[5] = i; - if (card.ActionActivateIndex.ContainsKey(desc)) - card.ActionActivateIndex.Remove(desc); - card.ActionActivateIndex.Add(desc, i); - main.ActivableCards.Add(card); - main.ActivableDescs.Add(desc); - } - - main.CanBattlePhase = packet.ReadByte() != 0; - main.CanEndPhase = packet.ReadByte() != 0; - packet.ReadByte(); // CanShuffle - - Connection.Send(CtosMessage.Response, _ai.OnSelectIdleCmd(main).ToValue()); - } - - private void OnSelectOption(BinaryReader packet) - { - IList options = new List(); - packet.ReadByte(); // player - int count = packet.ReadByte(); - for (int i = 0; i < count; ++i) - options.Add(packet.ReadInt64()); - Connection.Send(CtosMessage.Response, _ai.OnSelectOption(options)); - } - - private void OnSelectPlace(BinaryReader packet) - { - packet.ReadByte(); // player - packet.ReadByte(); // min - int field = ~packet.ReadInt32(); - - int player; - CardLocation location; - int filter; - - if ((field & 0x7f) != 0) - { - player = 0; - location = CardLocation.MonsterZone; - filter = field & Zones.MonsterZones; - } - else if ((field & 0x1f00) != 0) - { - player = 0; - location = CardLocation.SpellZone; - filter = (field >> 8) & Zones.SpellZones; - } - else if ((field & 0xc000) != 0) - { - player = 0; - location = CardLocation.PendulumZone; - filter = (field >> 14) & Zones.PendulumZones; - } - else if ((field & 0x7f0000) != 0) - { - player = 1; - location = CardLocation.MonsterZone; - filter = (field >> 16) & Zones.MonsterZones; - } - else if ((field & 0x1f000000) != 0) - { - player = 1; - location = CardLocation.SpellZone; - filter = (field >> 24) & Zones.SpellZones; - } - else - { - player = 1; - location = CardLocation.PendulumZone; - filter = (field >> 30) & Zones.PendulumZones; - } - - int selected = _ai.OnSelectPlace(_select_hint, player, location, filter); - _select_hint = 0; - - byte[] resp = new byte[3]; - resp[0] = (byte)GetLocalPlayer(player); - - if (location != CardLocation.PendulumZone) - { - resp[1] = (byte)location; - if ((selected & filter) > 0) - filter &= selected; - - if ((filter & Zones.z2) != 0) resp[2] = 2; - else if ((filter & Zones.z1) != 0) resp[2] = 1; - else if ((filter & Zones.z3) != 0) resp[2] = 3; - else if ((filter & Zones.z0) != 0) resp[2] = 0; - else if ((filter & Zones.z4) != 0) resp[2] = 4; - else if ((filter & Zones.z6) != 0) resp[2] = 6; - else if ((filter & Zones.z5) != 0) resp[2] = 5; - } - else - { - resp[1] = (byte)CardLocation.SpellZone; - if ((selected & filter) > 0) - filter &= selected; - - if ((filter & Zones.z0) != 0) resp[2] = 6; - if ((filter & Zones.z1) != 0) resp[2] = 7; - } - - BinaryWriter reply = GamePacketFactory.Create(CtosMessage.Response); - reply.Write(resp); - Connection.Send(reply); - } - - private void OnSelectPosition(BinaryReader packet) - { - packet.ReadByte(); // player - int cardId = packet.ReadInt32(); - int pos = packet.ReadByte(); - if (pos == 0x1 || pos == 0x2 || pos == 0x4 || pos == 0x8) - { - Connection.Send(CtosMessage.Response, pos); - return; - } - IList positions = new List(); - if ((pos & (int)CardPosition.FaceUpAttack) != 0) - positions.Add(CardPosition.FaceUpAttack); - if ((pos & (int)CardPosition.FaceDownAttack) != 0) - positions.Add(CardPosition.FaceDownAttack); - if ((pos & (int)CardPosition.FaceUpDefence) != 0) - positions.Add(CardPosition.FaceUpDefence); - if ((pos & (int)CardPosition.FaceDownDefence) != 0) - positions.Add(CardPosition.FaceDownDefence); - Connection.Send(CtosMessage.Response, (int)_ai.OnSelectPosition(cardId, positions)); - } - - private void OnSelectSum(BinaryReader packet) - { - packet.ReadByte(); // player - bool mode = packet.ReadByte() == 0; - int sumval = packet.ReadInt32(); - int min = packet.ReadInt32(); - int max = packet.ReadInt32(); - - if (max <= 0) - max = 99; - - IList mandatoryCards = new List(); - IList cards = new List(); - - for (int j = 0; j < 2; ++j) - { - int count = packet.ReadInt32(); - for (int i = 0; i < count; ++i) - { - int cardId = packet.ReadInt32(); - int player = GetLocalPlayer(packet.ReadByte()); - CardLocation loc = (CardLocation)packet.ReadByte(); - int seq = packet.ReadInt32(); - ClientCard card = _duel.GetCard(player, loc, seq); - if (cardId != 0 && card.Id != cardId) - card.SetId(cardId); - card.SelectSeq = i; - int OpParam = packet.ReadInt32(); - int OpParam1 = OpParam & 0xffff; - int OpParam2 = OpParam >> 16; - if (OpParam2 > 0 && OpParam1 > OpParam2) - { - card.OpParam1 = OpParam2; - card.OpParam2 = OpParam1; - } - else - { - card.OpParam1 = OpParam1; - card.OpParam2 = OpParam2; - } - if (j == 0) - mandatoryCards.Add(card); - else - cards.Add(card); - } - } - - for (int k = 0; k < mandatoryCards.Count; ++k) - { - sumval -= mandatoryCards[k].OpParam1; - } - - IList selected = _ai.OnSelectSum(cards, sumval, min, max, _select_hint, mode); - _select_hint = 0; - byte[] result = new byte[mandatoryCards.Count + selected.Count + 16]; - result[0] = 0; - result[1] = 1; - result[2] = 0; - result[3] = 0; - - - int tot_count = mandatoryCards.Count + selected.Count; - - result[4] = (byte)(tot_count & 0xff); - result[5] = (byte)((tot_count >> 4) & 0xff); - result[6] = (byte)((tot_count >> 8) & 0xff); - result[7] = (byte)((tot_count >> 16) & 0xff); - - int index = 8; - - while (index <= mandatoryCards.Count) - { - result[index++] = 0; - } - int l = 0; - while (l < selected.Count) - { - result[index++] = (byte)selected[l].SelectSeq; - ++l; - } - - BinaryWriter reply = GamePacketFactory.Create(CtosMessage.Response); - reply.Write(result); - Connection.Send(reply); - } - - private void OnSelectTribute(BinaryReader packet) - { - InternalOnSelectCard(packet, _ai.OnSelectTribute, true); - } - - private void OnSelectYesNo(BinaryReader packet) - { - packet.ReadByte(); // player - long desc = packet.ReadInt64(); - int reply; - if (desc == 30) - reply = _ai.OnSelectBattleReplay() ? 1 : 0; - else - reply = _ai.OnSelectYesNo(desc) ? 1 : 0; - Connection.Send(CtosMessage.Response, reply); - } - - private void OnAnnounceAttrib(BinaryReader packet) - { - IList attributes = new List(); - packet.ReadByte(); // player - int count = packet.ReadByte(); - int available = packet.ReadInt32(); - int filter = 0x1; - for (int i = 0; i < 7; ++i) - { - if ((available & filter) != 0) - attributes.Add((CardAttribute) filter); - filter <<= 1; - } - attributes = _ai.OnAnnounceAttrib(count, attributes); - int reply = 0; - for (int i = 0; i < count; ++i) - reply += (int)attributes[i]; - Connection.Send(CtosMessage.Response, reply); - } - - private void OnAnnounceCard(BinaryReader packet) - { - // not fully implemented - Connection.Send(CtosMessage.Response, _ai.OnAnnounceCard()); - } - - private void OnAnnounceNumber(BinaryReader packet) - { - IList numbers = new List(); - packet.ReadByte(); // player - int count = packet.ReadByte(); - for (int i = 0; i < count; ++i) - numbers.Add(packet.ReadInt32()); - Connection.Send(CtosMessage.Response, _ai.OnAnnounceNumber(numbers)); - } - - private void OnAnnounceRace(BinaryReader packet) - { - IList races = new List(); - packet.ReadByte(); // player - int count = packet.ReadByte(); - int available = packet.ReadInt32(); - int filter = 0x1; - for (int i = 0; i < 23; ++i) - { - if ((available & filter) != 0) - races.Add((CardRace)filter); - filter <<= 1; - } - races = _ai.OnAnnounceRace(count, races); - int reply = 0; - for (int i = 0; i < count; ++i) - reply += (int)races[i]; - Connection.Send(CtosMessage.Response, reply); - } - - private void OnRockPaperScissors(BinaryReader packet) - { - packet.ReadByte(); // player - int result; - if (_hand > 0) - result = _hand; - else - result = _ai.OnRockPaperScissors(); - Connection.Send(CtosMessage.Response, result); - } - - private void OnEquip(BinaryReader packet) - { - LocationInfo info1 = new LocationInfo(packet, _duel.IsFirst); - LocationInfo info2 = new LocationInfo(packet, _duel.IsFirst); - ClientCard equipCard = _duel.GetCard(info1.controler, (CardLocation)info1.location, info1.sequence); - ClientCard targetCard = _duel.GetCard(info2.controler, (CardLocation)info2.location, info2.sequence); - if (equipCard == null || targetCard == null) return; - equipCard.EquipTarget?.EquipCards.Remove(equipCard); - equipCard.EquipTarget = targetCard; - targetCard.EquipCards.Add(equipCard); - } - - private void OnUnEquip(BinaryReader packet) - { - LocationInfo info = new LocationInfo(packet, _duel.IsFirst); - ClientCard equipCard = _duel.GetCard(info.controler, (CardLocation)info.location, info.sequence); - if (equipCard == null) return; - if (equipCard.EquipTarget != null) - { - equipCard.EquipTarget.EquipCards.Remove(equipCard); - equipCard.EquipTarget = null; - } - } - - private void OnCardTarget(BinaryReader packet) - { - LocationInfo info1 = new LocationInfo(packet, _duel.IsFirst); - LocationInfo info2 = new LocationInfo(packet, _duel.IsFirst); - ClientCard ownerCard = _duel.GetCard(info1.controler, (CardLocation)info1.location, info1.sequence); - ClientCard targetCard = _duel.GetCard(info2.controler, (CardLocation)info2.location, info2.sequence); - if (ownerCard == null || targetCard == null) return; - ownerCard.TargetCards.Add(targetCard); - targetCard.OwnTargets.Add(ownerCard); - } - - private void OnCancelTarget(BinaryReader packet) - { - LocationInfo info1 = new LocationInfo(packet, _duel.IsFirst); - LocationInfo info2 = new LocationInfo(packet, _duel.IsFirst); - ClientCard ownerCard = _duel.GetCard(info1.controler, (CardLocation)info1.location, info1.sequence); - ClientCard targetCard = _duel.GetCard(info2.controler, (CardLocation)info2.location, info2.sequence); - if (ownerCard == null || targetCard == null) return; - ownerCard.TargetCards.Remove(targetCard); - targetCard.OwnTargets.Remove(ownerCard); - } - - private void OnSummoning(BinaryReader packet) - { - _duel.LastSummonedCards.Clear(); - int code = packet.ReadInt32(); - LocationInfo info = new LocationInfo(packet, _duel.IsFirst); - ClientCard card = _duel.GetCard(info.controler, (CardLocation)info.location, info.sequence); - _duel.SummoningCards.Add(card); - _duel.LastSummonPlayer = info.controler; - } - - private void OnSummoned(BinaryReader packet) - { - foreach (ClientCard card in _duel.SummoningCards) - { - _duel.LastSummonedCards.Add(card); - } - _duel.SummoningCards.Clear(); - } - - private void OnSpSummoning(BinaryReader packet) - { - _duel.LastSummonedCards.Clear(); - _ai.CleanSelectMaterials(); - int code = packet.ReadInt32(); - LocationInfo info = new LocationInfo(packet, _duel.IsFirst); - ClientCard card = _duel.GetCard(info.controler, (CardLocation)info.location, info.sequence); - _duel.SummoningCards.Add(card); - _duel.LastSummonPlayer = info.controler; - } - - private void OnSpSummoned(BinaryReader packet) - { - foreach (ClientCard card in _duel.SummoningCards) - { - card.IsSpecialSummoned = true; - _duel.LastSummonedCards.Add(card); - } - _duel.SummoningCards.Clear(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using WindBot.Game.AI; +using YGOSharp.Network; +using YGOSharp.Network.Enums; +using YGOSharp.Network.Utils; +using YGOSharp.OCGWrapper; +using YGOSharp.OCGWrapper.Enums; + +namespace WindBot.Game +{ + public class GameBehavior + { + public GameClient Game { get; private set; } + public YGOClient Connection { get; private set; } + public Deck Deck { get; private set; } + + private GameAI _ai; + + private IDictionary> _packets; + private IDictionary> _messages; + + private Room _room; + private Duel _duel; + private int _hand; + private bool _debug; + private long _select_hint; + private GameMessage _lastMessage; + public class LocationInfo + { + public int controler; + public int location; + public int sequence; + public int position; + public LocationInfo() + { + controler = 0; + location = 0; + sequence = 0; + position = 0; + } + public LocationInfo(BinaryReader packet, bool isfirst = false) + { + Read(packet, isfirst); + } + public void Read(BinaryReader packet, bool isfirst = false) + { + controler = packet.ReadByte(); + if(!isfirst) + controler = 1 - controler; + location = packet.ReadByte(); + sequence = packet.ReadInt32(); + position = packet.ReadInt32(); + } + } + + public GameBehavior(GameClient game) + { + Game = game; + Connection = game.Connection; + _hand = game.Hand; + _debug = game.Debug; + _packets = new Dictionary>(); + _messages = new Dictionary>(); + RegisterPackets(); + + _room = new Room(); + _duel = new Duel(); + + _ai = new GameAI(_duel, Game.Dialog, Game.Chat, Game.Log, Program.AssetPath); + _ai.Executor = DecksManager.Instantiate(_ai, _duel, Game.Deck); + Deck = Deck.Load(_ai.Executor.Deck); + + _select_hint = 0; + } + + public int GetLocalPlayer(int player) + { + return _duel.IsFirst ? player : 1 - player; + } + + public void OnPacket(BinaryReader packet) + { + StocMessage id = (StocMessage)packet.ReadByte(); + if (id == StocMessage.GameMsg) + { + GameMessage msg = (GameMessage)packet.ReadByte(); + if (_messages.ContainsKey(msg)) + _messages[msg](packet); + _lastMessage = msg; + return; + } + if (_packets.ContainsKey(id)) + _packets[id](packet); + } + + private void RegisterPackets() + { + _packets.Add(StocMessage.JoinGame, OnJoinGame); + _packets.Add(StocMessage.TypeChange, OnTypeChange); + _packets.Add(StocMessage.HsPlayerEnter, OnPlayerEnter); + _packets.Add(StocMessage.HsPlayerChange, OnPlayerChange); + _packets.Add(StocMessage.SelectHand, OnSelectHand); + _packets.Add(StocMessage.SelectTp, OnSelectTp); + _packets.Add(StocMessage.TimeLimit, OnTimeLimit); + _packets.Add(StocMessage.Replay, OnReplay); + _packets.Add(StocMessage.DuelEnd, OnDuelEnd); + _packets.Add(StocMessage.Chat, OnChat); + _packets.Add(StocMessage.ChangeSide, OnChangeSide); + _packets.Add(StocMessage.ErrorMsg, OnErrorMsg); + _packets.Add(StocMessage.Rematch, OnRematch); + + _messages.Add(GameMessage.Retry, OnRetry); + _messages.Add(GameMessage.Start, OnStart); + _messages.Add(GameMessage.Hint, OnHint); + _messages.Add(GameMessage.Win, OnWin); + _messages.Add(GameMessage.Draw, OnDraw); + _messages.Add(GameMessage.ShuffleDeck, OnShuffleDeck); + _messages.Add(GameMessage.ShuffleHand, OnShuffleHand); + _messages.Add(GameMessage.ShuffleExtra, OnShuffleExtra); + _messages.Add(GameMessage.ShuffleSetCard, OnShuffleSetCard); + _messages.Add(GameMessage.TagSwap, OnTagSwap); + _messages.Add(GameMessage.NewTurn, OnNewTurn); + _messages.Add(GameMessage.NewPhase, OnNewPhase); + _messages.Add(GameMessage.Damage, OnDamage); + _messages.Add(GameMessage.PayLpCost, OnDamage); + _messages.Add(GameMessage.Recover, OnRecover); + _messages.Add(GameMessage.LpUpdate, OnLpUpdate); + _messages.Add(GameMessage.Move, OnMove); + _messages.Add(GameMessage.Swap, OnSwap); + _messages.Add(GameMessage.Attack, OnAttack); + _messages.Add(GameMessage.Battle, OnBattle); + _messages.Add(GameMessage.AttackDisabled, OnAttackDisabled); + _messages.Add(GameMessage.PosChange, OnPosChange); + _messages.Add(GameMessage.Chaining, OnChaining); + _messages.Add(GameMessage.ChainEnd, OnChainEnd); + _messages.Add(GameMessage.SortCard, OnCardSorting); + _messages.Add(GameMessage.SortChain, OnChainSorting); + _messages.Add(GameMessage.UpdateCard, OnUpdateCard); + _messages.Add(GameMessage.UpdateData, OnUpdateData); + _messages.Add(GameMessage.BecomeTarget, OnBecomeTarget); + _messages.Add(GameMessage.SelectBattleCmd, OnSelectBattleCmd); + _messages.Add(GameMessage.SelectCard, OnSelectCard); + _messages.Add(GameMessage.SelectUnselect, OnSelectUnselectCard); + _messages.Add(GameMessage.SelectChain, OnSelectChain); + _messages.Add(GameMessage.SelectCounter, OnSelectCounter); + _messages.Add(GameMessage.SelectDisfield, OnSelectDisfield); + _messages.Add(GameMessage.SelectEffectYn, OnSelectEffectYn); + _messages.Add(GameMessage.SelectIdleCmd, OnSelectIdleCmd); + _messages.Add(GameMessage.SelectOption, OnSelectOption); + _messages.Add(GameMessage.SelectPlace, OnSelectPlace); + _messages.Add(GameMessage.SelectPosition, OnSelectPosition); + _messages.Add(GameMessage.SelectSum, OnSelectSum); + _messages.Add(GameMessage.SelectTribute, OnSelectTribute); + _messages.Add(GameMessage.SelectYesNo, OnSelectYesNo); + _messages.Add(GameMessage.AnnounceAttrib, OnAnnounceAttrib); + _messages.Add(GameMessage.AnnounceCard, OnAnnounceCard); + _messages.Add(GameMessage.AnnounceNumber, OnAnnounceNumber); + _messages.Add(GameMessage.AnnounceRace, OnAnnounceRace); + _messages.Add(GameMessage.RockPaperScissors, OnRockPaperScissors); + _messages.Add(GameMessage.Equip, OnEquip); + _messages.Add(GameMessage.Unequip, OnUnEquip); + _messages.Add(GameMessage.CardTarget, OnCardTarget); + _messages.Add(GameMessage.CancelTarget, OnCancelTarget); + _messages.Add(GameMessage.Summoning, OnSummoning); + _messages.Add(GameMessage.Summoned, OnSummoned); + _messages.Add(GameMessage.SpSummoning, OnSpSummoning); + _messages.Add(GameMessage.SpSummoned, OnSpSummoned); + _messages.Add(GameMessage.FlipSummoning, OnSummoning); + _messages.Add(GameMessage.FlipSummoned, OnSummoned); + } + + private void OnJoinGame(BinaryReader packet) + { + /*int lflist = (int)*/ packet.ReadUInt32(); + /*int rule = */ packet.ReadByte(); + /*int mode = */ packet.ReadByte(); + int duel_rule = packet.ReadByte(); + /*bool nocheck deck =*/ packet.ReadByte(); + /*bool noshuffle deck =*/ packet.ReadByte(); + /*align*/ packet.ReadBytes(3); + /*int start_lp =(int)*/ packet.ReadUInt32(); + /*int start_hand =*/ packet.ReadByte(); + /*int draw_count =*/ packet.ReadByte(); + /*int time_limit =*/ packet.ReadUInt16(); + /*align =*/ packet.ReadBytes(4); + const uint SERVER_HANDSHAKE = 4043399681u; + uint handshake = packet.ReadUInt32(); + if (handshake != SERVER_HANDSHAKE) + { + Connection.Close(); + return; + } + /*int version =*/ packet.ReadUInt32(); + int team1 = packet.ReadInt32(); + int team2 = packet.ReadInt32(); + /*int best_of =*/ packet.ReadInt32(); + int duel_flag = packet.ReadInt32(); + /*int forbidden_types =*/ packet.ReadInt32(); + /*int extra_rules =*/ packet.ReadInt32(); + _room.Players = team1 + team2; + const int DUEL_EMZONE = 0x2000; + const int DUEL_FSX_MMZONE = 0x4000; + _ai.Duel.IsNewRule = (duel_flag & DUEL_EMZONE) != 0; + _ai.Duel.IsNewRule2020 = (duel_flag & DUEL_FSX_MMZONE) != 0; + BinaryWriter deck = GamePacketFactory.Create(CtosMessage.UpdateDeck); + deck.Write(Deck.Cards.Count + Deck.ExtraCards.Count); + deck.Write(Deck.SideCards.Count); + foreach (int card in Deck.Cards) + deck.Write(card); + foreach (int card in Deck.ExtraCards) + deck.Write(card); + foreach (int card in Deck.SideCards) + deck.Write(card); + Connection.Send(deck); + _ai.OnJoinGame(); + } + + private void OnChangeSide(BinaryReader packet) + { + BinaryWriter deck = GamePacketFactory.Create(CtosMessage.UpdateDeck); + deck.Write(Deck.Cards.Count + Deck.ExtraCards.Count); + deck.Write(Deck.SideCards.Count); + foreach (int card in Deck.Cards) + deck.Write(card); + foreach (int card in Deck.ExtraCards) + deck.Write(card); + foreach (int card in Deck.SideCards) + deck.Write(card); + Connection.Send(deck); + _ai.OnJoinGame(); + } + + private void OnRematch(BinaryReader packet) + { + Connection.Send(CtosMessage.RematchResponse, (byte)(1)); + } + + private void OnTypeChange(BinaryReader packet) + { + int type = packet.ReadByte(); + int pos = type & 0xF; + if (pos < 0 || pos >= _room.Players) + { + Connection.Close(); + return; + } + _room.Position = pos; + _room.IsHost = ((type >> 4) & 0xF) != 0; + _room.IsReady[pos] = true; + Connection.Send(CtosMessage.HsReady); + } + + private void OnPlayerEnter(BinaryReader packet) + { + string name = packet.ReadUnicode(20); + int pos = packet.ReadByte(); + if (pos < 8) + _room.Names[pos] = name; + } + + private void OnPlayerChange(BinaryReader packet) + { + int change = packet.ReadByte(); + int pos = (change >> 4) & 0xF; + int state = change & 0xF; + if (pos >= _room.Players) + return; + if (state < 8) + { + string oldname = _room.Names[pos]; + _room.Names[pos] = null; + _room.Names[state] = oldname; + _room.IsReady[pos] = false; + _room.IsReady[state] = false; + } + else if (state == (int)PlayerChange.Ready) + _room.IsReady[pos] = true; + else if (state == (int)PlayerChange.NotReady) + _room.IsReady[pos] = false; + else if (state == (int)PlayerChange.Leave || state == (int)PlayerChange.Observe) + { + _room.IsReady[pos] = false; + _room.Names[pos] = null; + } + + if (_room.IsHost && _room.IsReady[0] && _room.IsReady[1]) + Connection.Send(CtosMessage.HsStart); + } + + private void OnSelectHand(BinaryReader packet) + { + int result; + if (_hand > 0) + result = _hand; + else + result = _ai.OnRockPaperScissors(); + Connection.Send(CtosMessage.HandResult, (byte)result); + } + + private void OnSelectTp(BinaryReader packet) + { + bool start = _ai.OnSelectHand(); + Connection.Send(CtosMessage.TpResult, (byte)(start ? 1 : 0)); + } + + private void OnTimeLimit(BinaryReader packet) + { + int player = GetLocalPlayer(packet.ReadByte()); + if (player == 0) + Connection.Send(CtosMessage.TimeConfirm); + } + + private void OnReplay(BinaryReader packet) + { + /*byte[] replay =*/ packet.ReadToEnd(); + + /* + const string directory = "Replays"; + if (!Directory.Exists(directory)) + Directory.CreateDirectory(directory); + + string otherName = _room.Position == 0 ? _room.Names[1] : _room.Names[0]; + string file = DateTime.Now.ToString("yyyy-MM-dd.HH-mm.") + otherName + ".yrp"; + string fullname = Path.Combine(directory, file); + + if (Regex.IsMatch(file, @"^[\w\-. ]+$")) + File.WriteAllBytes(fullname, replay); + */ + + //Connection.Close(); + } + + private void OnDuelEnd(BinaryReader packet) + { + Connection.Close(); + } + + private void OnChat(BinaryReader packet) + { + int player = packet.ReadInt16(); + string message = packet.ReadUnicode(256); + string myName = (player != 0) ? _room.Names[1] : _room.Names[0]; + string otherName = (player == 0) ? _room.Names[1] : _room.Names[0]; + if (player < 4) + Logger.DebugWriteLine(otherName + " say to " + myName + ": " + message); + } + + private void OnErrorMsg(BinaryReader packet) + { + int msg = packet.ReadByte(); + // align + packet.ReadByte(); + packet.ReadByte(); + packet.ReadByte(); + if (msg == 2) //ERRMSG_DECKERROR + { + int flag = packet.ReadInt32(); + packet.ReadInt32(); + packet.ReadInt32(); + packet.ReadInt32(); + int code = packet.ReadInt32(); + if (flag <= 5) //DECKERROR_CARDCOUNT + { + NamedCard card = NamedCard.Get(code); + if (card != null) + _ai.OnDeckError(card.Name); + else + _ai.OnDeckError("Unknown Card"); + } + else + _ai.OnDeckError("DECK"); + } + Connection.Close(); + } + + private void OnRetry(BinaryReader packet) + { + string otherName = _room.Position == 0 ? _room.Names[1] : _room.Names[0]; + Logger.DebugWriteLine("Duel finished against " + otherName + " because of MsgRetry"); + } + + private void OnHint(BinaryReader packet) + { + int type = packet.ReadByte(); + int player = packet.ReadByte(); + long data = packet.ReadInt64(); + if (type == 1) // HINT_EVENT + { + if (data == 24) // battling + { + _duel.Fields[0].UnderAttack = false; + _duel.Fields[1].UnderAttack = false; + } else if (data == 23) //Main Phase end + { + _duel.MainPhaseEnd = true; + } + } + if (type == 3) // HINT_SELECTMSG + { + _select_hint = data; + } + } + + private void OnStart(BinaryReader packet) + { + int type = packet.ReadByte(); + _duel.IsFirst = (type & 0xF) == 0; + _duel.Turn = 0; + /*int duel_rule = packet.ReadByte(); + _ai.Duel.IsNewRule = (duel_rule == 4); + _ai.Duel.IsNewRule2020 = (duel_rule >= 5);*/ + _duel.Fields[GetLocalPlayer(0)].LifePoints = packet.ReadInt32(); + _duel.Fields[GetLocalPlayer(1)].LifePoints = packet.ReadInt32(); + int deck = packet.ReadInt16(); + int extra = packet.ReadInt16(); + _duel.Fields[GetLocalPlayer(0)].Init(deck, extra); + deck = packet.ReadInt16(); + extra = packet.ReadInt16(); + _duel.Fields[GetLocalPlayer(1)].Init(deck, extra); + + Logger.DebugWriteLine("Duel started: " + _room.Names[0] + " versus " + _room.Names[1]); + _ai.OnStart(); + } + + private void OnWin(BinaryReader packet) + { + int result = GetLocalPlayer(packet.ReadByte()); + + string otherName = _room.Position == 0 ? _room.Names[1] : _room.Names[0]; + string textResult = (result == 2 ? "Draw" : result == 0 ? "Win" : "Lose"); + Logger.DebugWriteLine("Duel finished against " + otherName + ", result: " + textResult); + } + + private void OnDraw(BinaryReader packet) + { + int player = GetLocalPlayer(packet.ReadByte()); + int count = packet.ReadInt32(); + if (_debug) + Logger.WriteLine("(" + player.ToString() + " draw " + count.ToString() + " card)"); + + for (int i = 0; i < count; ++i) + { + _duel.Fields[player].Deck.RemoveAt(_duel.Fields[player].Deck.Count - 1); + _duel.Fields[player].Hand.Add(new ClientCard(0, CardLocation.Hand, -1, player)); + } + _ai.OnDraw(player); + } + + private void OnShuffleDeck(BinaryReader packet) + { + int player = GetLocalPlayer(packet.ReadByte()); + foreach (ClientCard card in _duel.Fields[player].Deck) + card.SetId(0); + } + + private void OnShuffleHand(BinaryReader packet) + { + int player = GetLocalPlayer(packet.ReadByte()); + packet.ReadInt32(); + foreach (ClientCard card in _duel.Fields[player].Hand) + card.SetId(packet.ReadInt32()); + } + + private void OnShuffleExtra(BinaryReader packet) + { + int player = GetLocalPlayer(packet.ReadByte()); + packet.ReadInt32(); + foreach (ClientCard card in _duel.Fields[player].ExtraDeck) + { + if (!card.IsFaceup()) + card.SetId(packet.ReadInt32()); + } + } + + private void OnShuffleSetCard(BinaryReader packet) + { + int location = packet.ReadByte(); + int count = packet.ReadByte(); + ClientCard[] list = new ClientCard[5]; + for (int i = 0; i < count; ++i) + { + LocationInfo loc = new LocationInfo(packet, _duel.IsFirst); + ClientCard card = _duel.GetCard(loc.controler, (CardLocation)loc.location, loc.sequence); + if (card == null) continue; + list[i] = card; + card.SetId(0); + } + for (int i = 0; i < count; ++i) + { + LocationInfo loc = new LocationInfo(packet, _duel.IsFirst); + ClientCard card = _duel.GetCard(loc.controler, (CardLocation)loc.location, loc.sequence); + if (card == null) continue; + ClientCard[] zone = (loc.location == (int)CardLocation.MonsterZone) ? _duel.Fields[loc.controler].MonsterZone : _duel.Fields[loc.controler].SpellZone; + zone[loc.sequence] = list[i]; + } + } + + private void OnTagSwap(BinaryReader packet) + { + int player = GetLocalPlayer(packet.ReadByte()); + int mcount = packet.ReadInt32(); + int ecount = packet.ReadInt32(); + /*int pcount = */ packet.ReadInt32(); + int hcount = packet.ReadInt32(); + /*int topcode =*/ packet.ReadInt32(); + _duel.Fields[player].Deck.Clear(); + for (int i = 0; i < mcount; ++i) + { + _duel.Fields[player].Deck.Add(new ClientCard(0, CardLocation.Deck, -1, player)); + } + _duel.Fields[player].ExtraDeck.Clear(); + for (int i = 0; i < ecount; ++i) + { + int code = packet.ReadInt32(); + _duel.Fields[player].ExtraDeck.Add(new ClientCard(code, CardLocation.Extra, -1, player)); + packet.ReadInt32(); // position + } + _duel.Fields[player].Hand.Clear(); + for (int i = 0; i < hcount; ++i) + { + int code = packet.ReadInt32(); + _duel.Fields[player].Hand.Add(new ClientCard(code, CardLocation.Hand,-1, player)); + packet.ReadInt32(); // position + } + } + + private void OnNewTurn(BinaryReader packet) + { + _duel.Turn++; + _duel.Player = GetLocalPlayer(packet.ReadByte()); + _ai.OnNewTurn(); + } + + private void OnNewPhase(BinaryReader packet) + { + _duel.Phase = (DuelPhase)packet.ReadInt16(); + if (_debug && _duel.Phase == DuelPhase.Standby) + { + Logger.WriteLine("*********Bot Hand*********"); + foreach (ClientCard card in _duel.Fields[0].Hand) + { + Logger.WriteLine(card.Name); + } + Logger.WriteLine("*********Bot Spell*********"); + foreach (ClientCard card in _duel.Fields[0].SpellZone) + { + Logger.WriteLine(card?.Name); + } + Logger.WriteLine("*********Bot Monster*********"); + foreach (ClientCard card in _duel.Fields[0].MonsterZone) + { + Logger.WriteLine(card?.Name); + } + Logger.WriteLine("*********Finish*********"); + } + if (_debug) + Logger.WriteLine("(Go to " + (_duel.Phase.ToString()) + ")"); + _duel.LastSummonPlayer = -1; + _duel.SummoningCards.Clear(); + _duel.LastSummonedCards.Clear(); + _duel.Fields[0].BattlingMonster = null; + _duel.Fields[1].BattlingMonster = null; + _duel.Fields[0].UnderAttack = false; + _duel.Fields[1].UnderAttack = false; + List monsters = _duel.Fields[0].GetMonsters(); + foreach (ClientCard monster in monsters) + { + monster.Attacked = false; + } + _duel.MainPhaseEnd = false; + _select_hint = 0; + _ai.OnNewPhase(); + } + + private void OnDamage(BinaryReader packet) + { + int player = GetLocalPlayer(packet.ReadByte()); + int final = _duel.Fields[player].LifePoints - packet.ReadInt32(); + if (final < 0) final = 0; + if (_debug) + Logger.WriteLine("(" + player.ToString() + " got damage , LifePoint left = " + final.ToString() + ")"); + _duel.Fields[player].LifePoints = final; + } + + private void OnRecover(BinaryReader packet) + { + int player = GetLocalPlayer(packet.ReadByte()); + int final = _duel.Fields[player].LifePoints + packet.ReadInt32(); + if (_debug) + Logger.WriteLine("(" + player.ToString() + " got healed , LifePoint left = " + final.ToString() + ")"); + _duel.Fields[player].LifePoints = final; + } + + private void OnLpUpdate(BinaryReader packet) + { + int player = GetLocalPlayer(packet.ReadByte()); + _duel.Fields[player].LifePoints = packet.ReadInt32(); + } + + private void OnMove(BinaryReader packet) + { + // TODO: update equip cards and target cards + int cardId = packet.ReadInt32(); + LocationInfo previous = new LocationInfo(packet, _duel.IsFirst); + LocationInfo current = new LocationInfo(packet, _duel.IsFirst); + packet.ReadInt32(); // reason + + ClientCard card = _duel.GetCard(previous.controler, (CardLocation)previous.location, previous.sequence); + if ((previous.location & (int)CardLocation.Overlay) != 0) + { + previous.location = previous.location & 0x7f; + card = _duel.GetCard(previous.controler, (CardLocation)previous.location, previous.sequence); + if (card != null) + { + if (_debug) + Logger.WriteLine("(" + previous.controler.ToString() + " 's " + (card.Name ?? "UnKnowCard") + " deattach " + (NamedCard.Get(cardId)?.Name) + ")"); + card.Overlays.Remove(cardId); + } + previous.location = 0; // the card is removed when it go to overlay, so here we treat it as a new card + } + else + _duel.RemoveCard((CardLocation)previous.location, card, previous.controler, previous.sequence); + + if ((current.location & (int)CardLocation.Overlay) != 0) + { + current.location = current.location & 0x7f; + card = _duel.GetCard(current.controler, (CardLocation)current.location, current.sequence); + if (card != null) + { + if (_debug) + Logger.WriteLine("(" + previous.controler.ToString() + " 's " + (card.Name ?? "UnKnowCard") + " overlay " + (NamedCard.Get(cardId)?.Name) + ")"); + card.Overlays.Add(cardId); + } + } + else + { + if (previous.location == 0) + { + if (_debug) + Logger.WriteLine("(" + previous.controler.ToString() + " 's " + (NamedCard.Get(cardId)?.Name) + + " appear in " + (CardLocation)current.location + ")"); + _duel.AddCard((CardLocation)current.location, cardId, current.controler, current.sequence, current.position); + } + else + { + _duel.AddCard((CardLocation)current.location, card, current.controler, current.sequence, current.position, cardId); + if (card != null && previous.location != current.location) + card.IsSpecialSummoned = false; + if (_debug && card != null) + Logger.WriteLine("(" + previous.controler.ToString() + " 's " + (card.Name ?? "UnKnowCard") + + " from " + + (CardLocation)previous.location + " move to " + (CardLocation)current.location + ")"); + } + } + } + + private void OnSwap(BinaryReader packet) + { + int cardId1 = packet.ReadInt32(); + LocationInfo info1 = new LocationInfo(packet, _duel.IsFirst); + int cardId2 = packet.ReadInt32(); + LocationInfo info2 = new LocationInfo(packet, _duel.IsFirst); + ClientCard card1 = _duel.GetCard(info1.controler, (CardLocation)info1.location, info1.sequence); + ClientCard card2 = _duel.GetCard(info2.controler, (CardLocation)info2.location, info2.sequence); + if (card1 == null || card2 == null) return; + _duel.RemoveCard((CardLocation)info1.location, card1, info1.controler, info1.sequence); + _duel.RemoveCard((CardLocation)info2.location, card2, info2.controler, info2.sequence); + _duel.AddCard((CardLocation)info2.location, card1, info2.controler, info2.sequence, card1.Position, cardId1); + _duel.AddCard((CardLocation)info1.location, card2, info1.controler, info1.sequence, card2.Position, cardId2); + } + + private void OnAttack(BinaryReader packet) + { + LocationInfo info1 = new LocationInfo(packet, _duel.IsFirst); + LocationInfo info2 = new LocationInfo(packet, _duel.IsFirst); + + ClientCard attackcard = _duel.GetCard(info1.controler, (CardLocation)info1.location, info1.sequence); + ClientCard defendcard = _duel.GetCard(info2.controler, (CardLocation)info2.location, info2.sequence); + if (_debug) + { + if (defendcard == null) Logger.WriteLine("(" + (attackcard.Name ?? "UnKnowCard") + " direct attack!!)"); + else Logger.WriteLine("(" + info1.controler.ToString() + " 's " + (attackcard.Name ?? "UnKnowCard") + " attack " + info2.controler.ToString() + " 's " + (defendcard.Name ?? "UnKnowCard") + ")"); + } + _duel.Fields[attackcard.Controller].BattlingMonster = attackcard; + _duel.Fields[1 - attackcard.Controller].BattlingMonster = defendcard; + _duel.Fields[1 - attackcard.Controller].UnderAttack = true; + + if (info2.location == 0 && info1.controler != 0) + { + _ai.OnDirectAttack(attackcard); + } + } + + private void OnBattle(BinaryReader packet) + { + _duel.Fields[0].UnderAttack = false; + _duel.Fields[1].UnderAttack = false; + } + + private void OnAttackDisabled(BinaryReader packet) + { + _duel.Fields[0].UnderAttack = false; + _duel.Fields[1].UnderAttack = false; + } + + private void OnPosChange(BinaryReader packet) + { + packet.ReadInt32(); // card id + int pc = GetLocalPlayer(packet.ReadByte()); + int pl = packet.ReadByte(); + int ps = packet.ReadSByte(); + int pp = packet.ReadSByte(); + int cp = packet.ReadSByte(); + ClientCard card = _duel.GetCard(pc, (CardLocation)pl, ps); + if (card != null) + { + card.Position = cp; + if ((pp & (int) CardPosition.FaceUp) > 0 && (cp & (int) CardPosition.FaceDown) > 0) + card.ClearCardTargets(); + if (_debug) + Logger.WriteLine("(" + (card.Name ?? "UnKnowCard") + " change position to " + (CardPosition)cp + ")"); + } + } + + private void OnChaining(BinaryReader packet) + { + int cardId = packet.ReadInt32(); + LocationInfo info = new LocationInfo(packet, _duel.IsFirst); + ClientCard card = _duel.GetCard(info.controler, info.location, info.sequence, info.position); + if (card.Id == 0) + card.SetId(cardId); + int cc = GetLocalPlayer(packet.ReadByte()); + if (_debug) + if (card != null) Logger.WriteLine("(" + cc.ToString() + " 's " + (card.Name ?? "UnKnowCard") + " activate effect)"); + _ai.OnChaining(card, cc); + //_duel.ChainTargets.Clear(); + _duel.ChainTargetOnly.Clear(); + _duel.LastSummonPlayer = -1; + _duel.CurrentChain.Add(card); + _duel.LastChainPlayer = cc; + + } + + private void OnChainEnd(BinaryReader packet) + { + _duel.MainPhaseEnd = false; + _ai.OnChainEnd(); + _duel.LastChainPlayer = -1; + _duel.CurrentChain.Clear(); + _duel.ChainTargets.Clear(); + _duel.ChainTargetOnly.Clear(); + } + + private void OnCardSorting(BinaryReader packet) + { + int player = GetLocalPlayer(packet.ReadByte()); + IList originalCards = new List(); + IList cards = new List(); + int count = packet.ReadInt32(); + for (int i = 0; i < count; ++i) + { + int id = packet.ReadInt32(); + int controler = GetLocalPlayer(packet.ReadByte()); + CardLocation loc = (CardLocation)packet.ReadInt32(); + int seq = packet.ReadInt32(); + ClientCard card; + if (((int)loc & (int)CardLocation.Overlay) != 0) + card = new ClientCard(id, CardLocation.Overlay, -1, player); + else + card = _duel.GetCard(controler, loc, seq); + if (card == null) continue; + if (id != 0) + card.SetId(id); + originalCards.Add(card); + cards.Add(card); + } + + IList selected = _ai.OnCardSorting(cards); + byte[] result = new byte[count]; + for (int i = 0; i < count; ++i) + { + int id = 0; + for (int j = 0; j < count; ++j) + { + if (selected[j] == null) continue; + if (selected[j].Equals(originalCards[i])) + { + id = j; + break; + } + } + result[i] = (byte)id; + } + + BinaryWriter reply = GamePacketFactory.Create(CtosMessage.Response); + reply.Write(result); + Connection.Send(reply); + } + + private void OnChainSorting(BinaryReader packet) + { + /*BinaryWriter writer =*/ GamePacketFactory.Create(CtosMessage.Response); + Connection.Send(CtosMessage.Response, -1); + } + + private void OnUpdateCard(BinaryReader packet) + { + int player = GetLocalPlayer(packet.ReadByte()); + int loc = packet.ReadByte(); + int seq = packet.ReadByte(); + + ClientCard card = _duel.GetCard(player, (CardLocation)loc, seq); + + card?.Update(packet, _duel); + } + + private void OnUpdateData(BinaryReader packet) + { + int player = GetLocalPlayer(packet.ReadByte()); + CardLocation loc = (CardLocation)packet.ReadByte(); + IList cards = null; + switch (loc) + { + case CardLocation.Hand: + cards = _duel.Fields[player].Hand; + break; + case CardLocation.MonsterZone: + cards = _duel.Fields[player].MonsterZone; + break; + case CardLocation.SpellZone: + cards = _duel.Fields[player].SpellZone; + break; + case CardLocation.Grave: + cards = _duel.Fields[player].Graveyard; + break; + case CardLocation.Removed: + cards = _duel.Fields[player].Banished; + break; + case CardLocation.Deck: + cards = _duel.Fields[player].Deck; + break; + case CardLocation.Extra: + cards = _duel.Fields[player].ExtraDeck; + break; + } + if (cards != null) + { + /*int size = */packet.ReadInt32(); + foreach (ClientCard card in cards) + { + if (card != null) + { + long pos = packet.BaseStream.Position; + long len = card.Update(packet, _duel); + packet.BaseStream.Position = pos + len; + } + else + { + packet.BaseStream.Position += 2; + } + } + } + } + + private void OnBecomeTarget(BinaryReader packet) + { + int count = packet.ReadInt32(); + for (int i = 0; i < count; ++i) + { + LocationInfo info = new LocationInfo(packet, _duel.IsFirst); + ClientCard card = _duel.GetCard(info.controler, (CardLocation)info.location, info.sequence); + if (card == null) continue; + if (_debug) + Logger.WriteLine("(" + (CardLocation)info.location + " 's " + (card.Name ?? "UnKnowCard") + " become target)"); + _duel.ChainTargets.Add(card); + _duel.ChainTargetOnly.Add(card); + } + } + + private void OnSelectBattleCmd(BinaryReader packet) + { + packet.ReadByte(); // player + _duel.BattlePhase = new BattlePhase(); + BattlePhase battle = _duel.BattlePhase; + + int count = packet.ReadInt32(); + for (int i = 0; i < count; ++i) + { + packet.ReadInt32(); // card id + int con = GetLocalPlayer(packet.ReadByte()); + CardLocation loc = (CardLocation)packet.ReadByte(); + int seq = packet.ReadInt32(); + long desc = packet.ReadInt64(); + packet.ReadByte(); // operation type + + ClientCard card = _duel.GetCard(con, loc, seq); + if (card != null) + { + card.ActionIndex[0] = i; + battle.ActivableCards.Add(card); + battle.ActivableDescs.Add(desc); + } + } + + count = packet.ReadInt32(); + for (int i = 0; i < count; ++i) + { + packet.ReadInt32(); // card id + int con = GetLocalPlayer(packet.ReadByte()); + CardLocation loc = (CardLocation)packet.ReadByte(); + int seq = packet.ReadByte(); + int diratt = packet.ReadByte(); + + ClientCard card = _duel.GetCard(con, loc, seq); + if (card != null) + { + card.ActionIndex[1] = i; + if (diratt > 0) + card.CanDirectAttack = true; + else + card.CanDirectAttack = false; + battle.AttackableCards.Add(card); + card.Attacked = false; + } + } + List monsters = _duel.Fields[0].GetMonsters(); + foreach (ClientCard monster in monsters) + { + if (!battle.AttackableCards.Contains(monster)) + monster.Attacked = true; + } + + battle.CanMainPhaseTwo = packet.ReadByte() != 0; + battle.CanEndPhase = packet.ReadByte() != 0; + + Connection.Send(CtosMessage.Response, _ai.OnSelectBattleCmd(battle).ToValue()); + } + + private void InternalOnSelectCard(BinaryReader packet, Func, int, int, long, bool, IList> func, bool tribute = false) + { + int player = packet.ReadByte(); + bool cancelable = packet.ReadByte() != 0; + int min = packet.ReadInt32(); + int max = packet.ReadInt32(); + + IList cards = new List(); + int count = packet.ReadInt32(); + for (int i = 0; i < count; ++i) + { + int id = packet.ReadInt32(); + LocationInfo info = !tribute ? new LocationInfo(packet, _duel.IsFirst) : new LocationInfo(); + if (tribute) + { + info.controler = packet.ReadByte(); + if (!_duel.IsFirst) + info.controler = 1 - info.controler; + info.location = packet.ReadByte(); + info.sequence = packet.ReadInt32(); + packet.ReadByte(); + } + ClientCard card; + if (((int)info.location & (int)CardLocation.Overlay) != 0) + card = new ClientCard(id, CardLocation.Overlay, -1, player); + else if (info.location == 0) + card = new ClientCard(id, 0, 0, player); + else + card = _duel.GetCard(info.controler, (CardLocation)info.location, info.sequence); + if (card == null) continue; + if (card.Id == 0) + card.SetId(id); + cards.Add(card); + } + + IList selected = func(cards, min, max, _select_hint, cancelable); + _select_hint = 0; + + if (selected.Count == 0 && cancelable) + { + Connection.Send(CtosMessage.Response, -1); + return; + } + + BinaryWriter reply = GamePacketFactory.Create(CtosMessage.Response); + + reply.Write((int)0); + reply.Write((int)selected.Count); + + for (int i = 0; i < selected.Count; ++i) + { + int id = 0; + for (int j = 0; j < count; ++j) + { + if (cards[j] == null) continue; + if (cards[j].Equals(selected[i])) + { + id = j; + break; + } + } + reply.Write((int)id); + } + Connection.Send(reply); + } + + private void InternalOnSelectUnselectCard(BinaryReader packet, Func, int, int, long, bool, IList> func) + { + int player = packet.ReadByte(); + bool finishable = packet.ReadByte() != 0; + bool cancelable = packet.ReadByte() != 0 || finishable; + int min = packet.ReadInt32(); + int max = packet.ReadInt32(); + + IList cards = new List(); + int count = packet.ReadInt32(); + for (int i = 0; i < count; ++i) + { + int id = packet.ReadInt32(); + LocationInfo info = new LocationInfo(packet, _duel.IsFirst); + ClientCard card; + if (((int)info.location & (int)CardLocation.Overlay) != 0) + card = new ClientCard(id, CardLocation.Overlay, -1, player); + else if (info.location == 0) + card = new ClientCard(id, 0, 0, player); + else + card = _duel.GetCard(info.controler, (CardLocation)info.location, info.sequence); + if (card == null) continue; + if (card.Id == 0) + card.SetId(id); + cards.Add(card); + } + int count2 = packet.ReadInt32(); + for (int i = 0; i < count2; ++i) + { + int id = packet.ReadInt32(); + LocationInfo info = new LocationInfo(packet, _duel.IsFirst); + } + + IList selected = func(cards, (finishable ? 0 : 1), 1, _select_hint, cancelable); + + if (selected.Count == 0 && cancelable) + { + Connection.Send(CtosMessage.Response, -1); + return; + } + + BinaryWriter reply = GamePacketFactory.Create(CtosMessage.Response); + reply.Write(selected.Count); + for (int i = 0; i < selected.Count; ++i) + { + int id = 0; + for (int j = 0; j < count; ++j) + { + if (cards[j] == null) continue; + if (cards[j].Equals(selected[i])) + { + id = j; + break; + } + } + reply.Write(id); + } + + Connection.Send(reply); + } + + private void OnSelectCard(BinaryReader packet) + { + InternalOnSelectCard(packet, _ai.OnSelectCard); + } + + private void OnSelectUnselectCard(BinaryReader packet) + { + InternalOnSelectUnselectCard(packet, _ai.OnSelectCard); + } + + private void OnSelectChain(BinaryReader packet) + { + packet.ReadByte(); // player + packet.ReadByte(); // specount + bool forced = packet.ReadByte() != 0; + packet.ReadInt32(); // hint1 + packet.ReadInt32(); // hint2 + int count = packet.ReadInt32(); + + IList cards = new List(); + IList descs = new List(); + + for (int i = 0; i < count; ++i) + { + packet.ReadInt32(); // card id + LocationInfo info = new LocationInfo(packet, _duel.IsFirst); + + long desc = packet.ReadInt64(); + if (desc == 221) // trigger effect + { + desc = 0; + } + cards.Add(_duel.GetCard(info.controler, info.location, info.sequence, info.position)); + descs.Add(desc); + packet.ReadByte(); // operation type + } + + if (cards.Count == 0) + { + Connection.Send(CtosMessage.Response, -1); + return; + } + + if (cards.Count == 1 && forced) + { + Connection.Send(CtosMessage.Response, 0); + return; + } + + Connection.Send(CtosMessage.Response, _ai.OnSelectChain(cards, descs, forced)); + } + + private void OnSelectCounter(BinaryReader packet) + { + packet.ReadByte(); // player + int type = packet.ReadInt16(); + int quantity = packet.ReadInt32(); + + IList cards = new List(); + IList counters = new List(); + int count = packet.ReadByte(); + for (int i = 0; i < count; ++i) + { + packet.ReadInt32(); // card id + int player = GetLocalPlayer(packet.ReadByte()); + CardLocation loc = (CardLocation) packet.ReadByte(); + int seq = packet.ReadByte(); + int num = packet.ReadInt16(); + cards.Add(_duel.GetCard(player, loc, seq)); + counters.Add(num); + } + + IList used = _ai.OnSelectCounter(type, quantity, cards, counters); + byte[] result = new byte[used.Count * 2]; + for (int i = 0; i < used.Count; ++i) + { + result[i * 2] = (byte)(used[i] & 0xff); + result[i * 2 + 1] = (byte)(used[i] >> 8); + } + BinaryWriter reply = GamePacketFactory.Create(CtosMessage.Response); + reply.Write(result); + Connection.Send(reply); + } + + private void OnSelectDisfield(BinaryReader packet) + { + OnSelectPlace(packet); + } + + private void OnSelectEffectYn(BinaryReader packet) + { + packet.ReadByte(); // player + + int cardId = packet.ReadInt32(); + int player = GetLocalPlayer(packet.ReadByte()); + CardLocation loc = (CardLocation)packet.ReadByte(); + int seq = packet.ReadByte(); + packet.ReadByte(); + long desc = packet.ReadInt64(); + + if (desc == 0 || desc == 221) + { + // 0: phase trigger effect + // 221: trigger effect + // for compatibility + desc = -1; + } + + ClientCard card = _duel.GetCard(player, loc, seq); + if (card == null) + { + Connection.Send(CtosMessage.Response, 0); + return; + } + + if (card.Id == 0) + card.SetId(cardId); + + int reply = _ai.OnSelectEffectYn(card, desc) ? (1) : (0); + Connection.Send(CtosMessage.Response, reply); + } + + private void OnSelectIdleCmd(BinaryReader packet) + { + packet.ReadByte(); // player + + _duel.MainPhase = new MainPhase(); + MainPhase main = _duel.MainPhase; + int count; + for (int k = 0; k < 5; k++) + { + count = packet.ReadInt32(); + for (int i = 0; i < count; ++i) + { + packet.ReadInt32(); // card id + int con = GetLocalPlayer(packet.ReadByte()); + CardLocation loc = (CardLocation)packet.ReadByte(); + int seq = k == 2 ? packet.ReadByte() : packet.ReadInt32(); + ClientCard card = _duel.GetCard(con, loc, seq); + if (card == null) continue; + card.ActionIndex[k] = i; + switch (k) + { + case 0: + main.SummonableCards.Add(card); + break; + case 1: + main.SpecialSummonableCards.Add(card); + break; + case 2: + main.ReposableCards.Add(card); + break; + case 3: + main.MonsterSetableCards.Add(card); + break; + case 4: + main.SpellSetableCards.Add(card); + break; + } + } + } + count = packet.ReadInt32(); + for (int i = 0; i < count; ++i) + { + packet.ReadInt32(); // card id + int con = GetLocalPlayer(packet.ReadByte()); + CardLocation loc = (CardLocation)packet.ReadByte(); + int seq = packet.ReadInt32(); + long desc = packet.ReadInt64(); + packet.ReadByte(); // operation type + + ClientCard card = _duel.GetCard(con, loc, seq); + if (card == null) continue; + card.ActionIndex[5] = i; + if (card.ActionActivateIndex.ContainsKey(desc)) + card.ActionActivateIndex.Remove(desc); + card.ActionActivateIndex.Add(desc, i); + main.ActivableCards.Add(card); + main.ActivableDescs.Add(desc); + } + + main.CanBattlePhase = packet.ReadByte() != 0; + main.CanEndPhase = packet.ReadByte() != 0; + packet.ReadByte(); // CanShuffle + + Connection.Send(CtosMessage.Response, _ai.OnSelectIdleCmd(main).ToValue()); + } + + private void OnSelectOption(BinaryReader packet) + { + IList options = new List(); + packet.ReadByte(); // player + int count = packet.ReadByte(); + for (int i = 0; i < count; ++i) + options.Add(packet.ReadInt64()); + Connection.Send(CtosMessage.Response, _ai.OnSelectOption(options)); + } + + private void OnSelectPlace(BinaryReader packet) + { + packet.ReadByte(); // player + packet.ReadByte(); // min + int field = ~packet.ReadInt32(); + + int player; + CardLocation location; + int filter; + + if ((field & 0x7f) != 0) + { + player = 0; + location = CardLocation.MonsterZone; + filter = field & Zones.MonsterZones; + } + else if ((field & 0x1f00) != 0) + { + player = 0; + location = CardLocation.SpellZone; + filter = (field >> 8) & Zones.SpellZones; + } + else if ((field & 0xc000) != 0) + { + player = 0; + location = CardLocation.PendulumZone; + filter = (field >> 14) & Zones.PendulumZones; + } + else if ((field & 0x7f0000) != 0) + { + player = 1; + location = CardLocation.MonsterZone; + filter = (field >> 16) & Zones.MonsterZones; + } + else if ((field & 0x1f000000) != 0) + { + player = 1; + location = CardLocation.SpellZone; + filter = (field >> 24) & Zones.SpellZones; + } + else + { + player = 1; + location = CardLocation.PendulumZone; + filter = (field >> 30) & Zones.PendulumZones; + } + + int selected = _ai.OnSelectPlace(_select_hint, player, location, filter); + _select_hint = 0; + + byte[] resp = new byte[3]; + resp[0] = (byte)GetLocalPlayer(player); + + if (location != CardLocation.PendulumZone) + { + resp[1] = (byte)location; + if ((selected & filter) > 0) + filter &= selected; + + if ((filter & Zones.z2) != 0) resp[2] = 2; + else if ((filter & Zones.z1) != 0) resp[2] = 1; + else if ((filter & Zones.z3) != 0) resp[2] = 3; + else if ((filter & Zones.z0) != 0) resp[2] = 0; + else if ((filter & Zones.z4) != 0) resp[2] = 4; + else if ((filter & Zones.z6) != 0) resp[2] = 6; + else if ((filter & Zones.z5) != 0) resp[2] = 5; + } + else + { + resp[1] = (byte)CardLocation.SpellZone; + if ((selected & filter) > 0) + filter &= selected; + + if ((filter & Zones.z0) != 0) resp[2] = 6; + if ((filter & Zones.z1) != 0) resp[2] = 7; + } + + BinaryWriter reply = GamePacketFactory.Create(CtosMessage.Response); + reply.Write(resp); + Connection.Send(reply); + } + + private void OnSelectPosition(BinaryReader packet) + { + packet.ReadByte(); // player + int cardId = packet.ReadInt32(); + int pos = packet.ReadByte(); + if (pos == 0x1 || pos == 0x2 || pos == 0x4 || pos == 0x8) + { + Connection.Send(CtosMessage.Response, pos); + return; + } + IList positions = new List(); + if ((pos & (int)CardPosition.FaceUpAttack) != 0) + positions.Add(CardPosition.FaceUpAttack); + if ((pos & (int)CardPosition.FaceDownAttack) != 0) + positions.Add(CardPosition.FaceDownAttack); + if ((pos & (int)CardPosition.FaceUpDefence) != 0) + positions.Add(CardPosition.FaceUpDefence); + if ((pos & (int)CardPosition.FaceDownDefence) != 0) + positions.Add(CardPosition.FaceDownDefence); + Connection.Send(CtosMessage.Response, (int)_ai.OnSelectPosition(cardId, positions)); + } + + private void OnSelectSum(BinaryReader packet) + { + packet.ReadByte(); // player + bool mode = packet.ReadByte() == 0; + int sumval = packet.ReadInt32(); + int min = packet.ReadInt32(); + int max = packet.ReadInt32(); + + if (max <= 0) + max = 99; + + IList mandatoryCards = new List(); + IList cards = new List(); + + for (int j = 0; j < 2; ++j) + { + int count = packet.ReadInt32(); + for (int i = 0; i < count; ++i) + { + int cardId = packet.ReadInt32(); + int player = GetLocalPlayer(packet.ReadByte()); + CardLocation loc = (CardLocation)packet.ReadByte(); + int seq = packet.ReadInt32(); + ClientCard card = _duel.GetCard(player, loc, seq); + if (cardId != 0 && card.Id != cardId) + card.SetId(cardId); + card.SelectSeq = i; + int OpParam = packet.ReadInt32(); + int OpParam1 = OpParam & 0xffff; + int OpParam2 = OpParam >> 16; + if (OpParam2 > 0 && OpParam1 > OpParam2) + { + card.OpParam1 = OpParam2; + card.OpParam2 = OpParam1; + } + else + { + card.OpParam1 = OpParam1; + card.OpParam2 = OpParam2; + } + if (j == 0) + mandatoryCards.Add(card); + else + cards.Add(card); + } + } + + for (int k = 0; k < mandatoryCards.Count; ++k) + { + sumval -= mandatoryCards[k].OpParam1; + } + + IList selected = _ai.OnSelectSum(cards, sumval, min, max, _select_hint, mode); + _select_hint = 0; + byte[] result = new byte[mandatoryCards.Count + selected.Count + 16]; + result[0] = 0; + result[1] = 1; + result[2] = 0; + result[3] = 0; + + + int tot_count = mandatoryCards.Count + selected.Count; + + result[4] = (byte)(tot_count & 0xff); + result[5] = (byte)((tot_count >> 4) & 0xff); + result[6] = (byte)((tot_count >> 8) & 0xff); + result[7] = (byte)((tot_count >> 16) & 0xff); + + int index = 8; + + while (index <= mandatoryCards.Count) + { + result[index++] = 0; + } + int l = 0; + while (l < selected.Count) + { + result[index++] = (byte)selected[l].SelectSeq; + ++l; + } + + BinaryWriter reply = GamePacketFactory.Create(CtosMessage.Response); + reply.Write(result); + Connection.Send(reply); + } + + private void OnSelectTribute(BinaryReader packet) + { + InternalOnSelectCard(packet, _ai.OnSelectTribute, true); + } + + private void OnSelectYesNo(BinaryReader packet) + { + packet.ReadByte(); // player + long desc = packet.ReadInt64(); + int reply; + if (desc == 30) + reply = _ai.OnSelectBattleReplay() ? 1 : 0; + else if (desc == 1989) + reply = 1; + else + reply = _ai.OnSelectYesNo(desc) ? 1 : 0; + Connection.Send(CtosMessage.Response, reply); + } + + private void OnAnnounceAttrib(BinaryReader packet) + { + IList attributes = new List(); + packet.ReadByte(); // player + int count = packet.ReadByte(); + int available = packet.ReadInt32(); + int filter = 0x1; + for (int i = 0; i < 7; ++i) + { + if ((available & filter) != 0) + attributes.Add((CardAttribute) filter); + filter <<= 1; + } + attributes = _ai.OnAnnounceAttrib(count, attributes); + int reply = 0; + for (int i = 0; i < count; ++i) + reply += (int)attributes[i]; + Connection.Send(CtosMessage.Response, reply); + } + + private void OnAnnounceCard(BinaryReader packet) + { + // not fully implemented + Connection.Send(CtosMessage.Response, _ai.OnAnnounceCard()); + } + + private void OnAnnounceNumber(BinaryReader packet) + { + IList numbers = new List(); + packet.ReadByte(); // player + int count = packet.ReadByte(); + for (int i = 0; i < count; ++i) + numbers.Add(packet.ReadInt32()); + Connection.Send(CtosMessage.Response, _ai.OnAnnounceNumber(numbers)); + } + + private void OnAnnounceRace(BinaryReader packet) + { + IList races = new List(); + packet.ReadByte(); // player + int count = packet.ReadByte(); + int available = packet.ReadInt32(); + int filter = 0x1; + for (int i = 0; i < 23; ++i) + { + if ((available & filter) != 0) + races.Add((CardRace)filter); + filter <<= 1; + } + races = _ai.OnAnnounceRace(count, races); + int reply = 0; + for (int i = 0; i < count; ++i) + reply += (int)races[i]; + Connection.Send(CtosMessage.Response, reply); + } + + private void OnRockPaperScissors(BinaryReader packet) + { + packet.ReadByte(); // player + int result; + if (_hand > 0) + result = _hand; + else + result = _ai.OnRockPaperScissors(); + Connection.Send(CtosMessage.Response, result); + } + + private void OnEquip(BinaryReader packet) + { + LocationInfo info1 = new LocationInfo(packet, _duel.IsFirst); + LocationInfo info2 = new LocationInfo(packet, _duel.IsFirst); + ClientCard equipCard = _duel.GetCard(info1.controler, (CardLocation)info1.location, info1.sequence); + ClientCard targetCard = _duel.GetCard(info2.controler, (CardLocation)info2.location, info2.sequence); + if (equipCard == null || targetCard == null) return; + equipCard.EquipTarget?.EquipCards.Remove(equipCard); + equipCard.EquipTarget = targetCard; + targetCard.EquipCards.Add(equipCard); + } + + private void OnUnEquip(BinaryReader packet) + { + LocationInfo info = new LocationInfo(packet, _duel.IsFirst); + ClientCard equipCard = _duel.GetCard(info.controler, (CardLocation)info.location, info.sequence); + if (equipCard == null) return; + if (equipCard.EquipTarget != null) + { + equipCard.EquipTarget.EquipCards.Remove(equipCard); + equipCard.EquipTarget = null; + } + } + + private void OnCardTarget(BinaryReader packet) + { + LocationInfo info1 = new LocationInfo(packet, _duel.IsFirst); + LocationInfo info2 = new LocationInfo(packet, _duel.IsFirst); + ClientCard ownerCard = _duel.GetCard(info1.controler, (CardLocation)info1.location, info1.sequence); + ClientCard targetCard = _duel.GetCard(info2.controler, (CardLocation)info2.location, info2.sequence); + if (ownerCard == null || targetCard == null) return; + ownerCard.TargetCards.Add(targetCard); + targetCard.OwnTargets.Add(ownerCard); + } + + private void OnCancelTarget(BinaryReader packet) + { + LocationInfo info1 = new LocationInfo(packet, _duel.IsFirst); + LocationInfo info2 = new LocationInfo(packet, _duel.IsFirst); + ClientCard ownerCard = _duel.GetCard(info1.controler, (CardLocation)info1.location, info1.sequence); + ClientCard targetCard = _duel.GetCard(info2.controler, (CardLocation)info2.location, info2.sequence); + if (ownerCard == null || targetCard == null) return; + ownerCard.TargetCards.Remove(targetCard); + targetCard.OwnTargets.Remove(ownerCard); + } + + private void OnSummoning(BinaryReader packet) + { + _duel.LastSummonedCards.Clear(); + int code = packet.ReadInt32(); + LocationInfo info = new LocationInfo(packet, _duel.IsFirst); + ClientCard card = _duel.GetCard(info.controler, (CardLocation)info.location, info.sequence); + _duel.SummoningCards.Add(card); + _duel.LastSummonPlayer = info.controler; + } + + private void OnSummoned(BinaryReader packet) + { + foreach (ClientCard card in _duel.SummoningCards) + { + _duel.LastSummonedCards.Add(card); + } + _duel.SummoningCards.Clear(); + } + + private void OnSpSummoning(BinaryReader packet) + { + _duel.LastSummonedCards.Clear(); + _ai.CleanSelectMaterials(); + int code = packet.ReadInt32(); + LocationInfo info = new LocationInfo(packet, _duel.IsFirst); + ClientCard card = _duel.GetCard(info.controler, (CardLocation)info.location, info.sequence); + _duel.SummoningCards.Add(card); + _duel.LastSummonPlayer = info.controler; + } + + private void OnSpSummoned(BinaryReader packet) + { + foreach (ClientCard card in _duel.SummoningCards) + { + card.IsSpecialSummoned = true; + _duel.LastSummonedCards.Add(card); + } + _duel.SummoningCards.Clear(); + } + } +} diff --git a/Game/GameClient.cs b/Game/GameClient.cs index ee299203..6bef63df 100644 --- a/Game/GameClient.cs +++ b/Game/GameClient.cs @@ -18,7 +18,7 @@ public class GameClient public bool _chat; private string _serverHost; private int _serverPort; - private short _proVersion; + private int _proVersion; private string _roomInfo; @@ -35,7 +35,7 @@ public GameClient(WindBotInfo Info) _serverHost = Info.Host; _serverPort = Info.Port; _roomInfo = Info.HostInfo; - _proVersion = (short)Info.Version; + _proVersion = Info.Version; } public void Start() @@ -57,9 +57,10 @@ private void OnConnected() byte[] junk = { 0xCC, 0xCC, 0x00, 0x00, 0x00, 0x00 }; packet = GamePacketFactory.Create(CtosMessage.JoinGame); - packet.Write(_proVersion); + packet.Write((short)_proVersion); packet.Write(junk); - packet.WriteUnicode(_roomInfo, 30); + packet.WriteUnicode(_roomInfo, 20); + packet.Write(_proVersion); Connection.Send(packet); } @@ -68,14 +69,31 @@ public void Tick() Connection.Update(); } - public void Chat(string message) + public void Chat(string message, bool forced) { + if (!forced && !_chat) + return; byte[] content = Encoding.Unicode.GetBytes(message + "\0"); BinaryWriter chat = GamePacketFactory.Create(CtosMessage.Chat); chat.Write(content); Connection.Send(chat); } + public void Log(string message, int type) + { + if(type == 0) + { + Logger.WriteLine(message); + } else if (type == 1) + { + Logger.DebugWriteLine(message); + } + else if (type == 2) + { + Logger.WriteErrorLine(message); + } + } + private void OnPacketReceived(BinaryReader reader) { _behavior.OnPacket(reader); diff --git a/LICENSE b/LICENSE index d4118327..896d34ed 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,9 @@ -WindBot Ignite, a C# AI for EDOPro by Project Ignis +WindBot Ignite, a C# AI for Project Ignis: EDOPro --------------------------------------------------- Copyright (C) 2019-2020 Edoardo Lolletti (edo9300) + Kevin Lu, Gareth Jones +See version history at https://github.com/ProjectIgnis/windbot +for a full list of contributors. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/Logger.cs b/Logger.cs index 277b5c5e..086a3667 100644 --- a/Logger.cs +++ b/Logger.cs @@ -1,25 +1,40 @@ -using System; - -namespace WindBot -{ - public static class Logger - { - public static void WriteLine(string message) - { - Console.WriteLine("[" + DateTime.Now.ToString("yy-MM-dd HH:mm:ss") + "] " + message); - } - public static void DebugWriteLine(string message) - { -#if DEBUG - Console.WriteLine("[" + DateTime.Now.ToString("yy-MM-dd HH:mm:ss") + "] " + message); -#endif - } - public static void WriteErrorLine(string message) - { - Console.BackgroundColor = ConsoleColor.Red; - Console.ForegroundColor = ConsoleColor.White; - Console.Error.WriteLine("[" + DateTime.Now.ToString("yy-MM-dd HH:mm:ss") + "] " + message); - Console.ResetColor(); - } - } +using System; +#if LIBWINDBOT +using Android.Util; +#endif + +namespace WindBot +{ + public static class Logger + { + public static void WriteLine(string message) + { +#if !LIBWINDBOT + Console.WriteLine("[" + DateTime.Now.ToString("yy-MM-dd HH:mm:ss") + "] " + message); +#else + Log.Info("Edoprowindbot", "[" + DateTime.Now.ToString("yy-MM-dd HH:mm:ss") + "] " + message); +#endif + } + public static void DebugWriteLine(string message) + { +#if DEBUG +#if !LIBWINDBOT + Console.WriteLine("[" + DateTime.Now.ToString("yy-MM-dd HH:mm:ss") + "] " + message); +#else + Log.Debug("Edoprowindbot", "[" + DateTime.Now.ToString("yy-MM-dd HH:mm:ss") + "] " + message); +#endif +#endif + } + public static void WriteErrorLine(string message) + { +#if !LIBWINDBOT + Console.BackgroundColor = ConsoleColor.Red; + Console.ForegroundColor = ConsoleColor.White; + Console.Error.WriteLine("[" + DateTime.Now.ToString("yy-MM-dd HH:mm:ss") + "] " + message); + Console.ResetColor(); +#else + Log.Error("Edoprowindbot", "[" + DateTime.Now.ToString("yy-MM-dd HH:mm:ss") + "] " + message); +#endif + } + } } \ No newline at end of file diff --git a/Program.cs b/Program.cs index 9245c83d..40550550 100644 --- a/Program.cs +++ b/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Threading; using System.Net; @@ -11,6 +11,8 @@ namespace WindBot { public class Program { + public static string AssetPath; + internal static Random Rand; internal static void Main(string[] args) @@ -19,7 +21,9 @@ internal static void Main(string[] args) Config.Load(args); - Logger.WriteLine(Config.GetString("Deck")); + Logger.WriteLine(Config.GetString("Deck")); + + AssetPath = Config.GetString("AssetPath", ""); string databasePath = Config.GetString("DbPath", "cards.cdb"); @@ -50,11 +54,18 @@ public static void InitDatas(string databasePath) { Rand = new Random(); DecksManager.Init(); - string[] dbPaths = { + + string[] dbPaths; + //If databasePath is an absolute path like "‪C:/ProjectIgnis/expansions/cards.cdb", + //then Path.GetFullPath("../‪C:/ProjectIgnis/expansions/cards.cdb" would give an error, + //due to containing a colon that's not part of a volume identifier. + if (Path.IsPathRooted(databasePath)) dbPaths = new string[] { databasePath }; + else dbPaths = new string[]{ Path.GetFullPath(databasePath), Path.GetFullPath("../" + databasePath), Path.GetFullPath("../expansions/" + databasePath) }; + foreach (var absPath in dbPaths) { if (File.Exists(absPath)) diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index b92b294b..70b218be 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -8,9 +8,9 @@ [assembly: AssemblyTitle("WindBot")] [assembly: AssemblyDescription("A C# bot for YGOPro.")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("IceYGO")] -[assembly: AssemblyProduct("WindBot")] -[assembly: AssemblyCopyright("Copyright © IceYGO 2015-2017")] +[assembly: AssemblyCompany("Project Ignis")] +[assembly: AssemblyProduct("WindBot Ignite")] +[assembly: AssemblyCopyright("Copyright (C) 2020 edo9300 and others")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Properties/AssemblyInfoLib.cs b/Properties/AssemblyInfoLib.cs new file mode 100644 index 00000000..7828aa41 --- /dev/null +++ b/Properties/AssemblyInfoLib.cs @@ -0,0 +1,30 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Android.App; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("libWindbot")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("libWindBot Ignite")] +[assembly: AssemblyCopyright("Copyright (C) 2020 edo9300 and others")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/README-old.md b/README-old.md new file mode 100644 index 00000000..f549e519 --- /dev/null +++ b/README-old.md @@ -0,0 +1,273 @@ +# WindBot + +A C# bot for YGOPro, compatible with the [YGOSharp](https://github.com/IceYGO/ygosharp) and [SRVPro](https://github.com/moecube/srvpro) server. + +### How to use: + +* Compile `WindBot.sln` using Visual Studio or Mono. + +* Put `cards.cdb` next to the compiled `WindBot.exe`. + +* Run YGOPro, create a host. + +* Run WindBot and observe. + +### Supported commandlines + +`Name` +The nickname for the bot. + +`Deck` +The deck to be used by the bot. Available decks are listed below. Keep empty to use random deck. + +`Dialog` +The dialog texts to be used by the bot. See Dialogs folder for list. + +`Host` +The IP of the host to be connected to. + +`Port` +The port of the host to be connected to. + +`HostInfo` +The host info (password) to be used. + +`Version` +The version of YGOPro. + +`Hand` +If you are testing deck, you may want to make sure the bot go first or second. `Hand=1` will make the bot always show Scissors, 2 for Rock, 3 for Paper. + +`Chat` +False to turn the chat of bot off. + +`Debug` +Print verbose log of card movement info. False at default. (May be updated in future) + +`ServerMode` and `ServerPort` +WindBot can run as a "server", provide a http interface to create bot. + +### Available decks + +**Easy**: + +* Burn + +* Frog + +* Horus + +* MokeyMokey + +* MokeyMokeyKing + +* OldSchool + +**Normal**: + +* Altergeist + +* Blue-Eyes + +* BlueEyesMaxDragon + +* ChainBurn + +* DarkMagician + +* Dragun + +* Dragunity + +* GrenMajuThunderBoarder + +* Level VIII + +* LightswornShaddoldinosour + +* Orcust + +* Phantasm + +* Qliphort + +* Rainbow + +* Rank V + +* Salamangreat + +* SkyStriker + +* ST1732 + +* Toadally Awesome + +* Trickstar + +* Yosenju + +* Zexal Weapons + +* Zoodiac + +### Unfinished decks + +* Blackwing + +* CyberDragon + +* Evilswarm + +* Gravekeeper + +* Graydle + +* Lightsworn + +* Nekroz + +### Server mode + +WindBot can run as a "server", provide a http interface to create bot. + +eg. `http://127.0.0.1:2399/?name=%E2%91%A8&deck=Blue-Eyes&host=127.0.0.1&port=7911&dialog=cirno.zh-CN` + +In this situation, it will be multi-threaded. This can be useful for servers, since it don't use large amount memory. + +The parameters are same as commandlines, but low cased. + +### Known issues + +* If one chain includes two activation that use `AI.SelectCard`, the second one won't select correctly. + +### Changelog + +#### v0x134A (2019-05-30) + + - Update YGOPro protrol to 0x134A + - New decks: Altergeist, BlueEyesMaxDragon, GrenMajuThunderBoarder, Level8, Orcust, Phantasm, Salamangreat + - Use LINQ in codes + - Add random bot feature to BotWrapper + - Add `ClientCard.Sequence`, `ClientCard.ProcCompleted`, `ClientCard.IsSpecialSummoned` + - Add `ClientCard.EquipCards`, `ClientCard.OwnTargets`, handle equip cards + - Add `ClientCard.IsCode`, handle card alias + - Add `ClientCard.GetLinkedZones`, `ClientCard.HasSetcode` + - Add `ClientField.UnderAttack`, `ClientField.GetLinkedZones`, `ClientField.GetFieldSpellCard` + - Add `Duel.SummoningCards`, `Duel.LastSummonedCards` + - Add `Util.GetTotalAttackingMonsterAttack`, `Util.GetBotAvailZonesFromExtraDeck` + - Add `GetMatchingCards`, `GetFirstMatchingCard`, `IsExistingMatchingCard` + - Add `ExecutorType.GoToBattlePhase`, `ExecutorType.GoToMainPhase2`, `ExecutorType.GoToEndPhase` + - Add `DefaultScapegoat`, `DefaultMaxxC`, `DefaultAshBlossomAndJoyousSpring`, `DefaultGhostOgreAndSnowRabbit`, `DefaultGhostBelleAndHauntedMansion`, `DefaultEffectVeiler`, `DefaultCalledByTheGrave`, `DefaultInfiniteImpermanence` + - Rename `AIFunctions` to `AIUtil` (Usage: `AI.Utils.` -> `Util.`) + - Rename `AIFunctions.CompareCardAttack` to `CardContainer.CompareCardAttack` + - Update `Util.SelectPreferredCards` and `Util.CheckSelectCount` to return the result + - Update `ClientField.HasInMonstersZone` to support check face-up card + - Update `AI.SelectCard` [\#59](https://github.com/IceYGO/windbot/pull/59) + - Handle swap control of cards + - Change some `int location` to `CardLocation location` + - Update default `OnPreBattleBetween` to recognize more cards + - Misc updates to default executors + - Misc updates to the AI of some decks + - Update the known card enums + - Fix `CardSelector.Select` + - Fix `OnSelectEffectYn` didn't have `ActivateDescription` + - Fix `ClientCard.Attacked` + - Fix infinite activation of ZexalWeapons + +#### v0x1344 (2018-06-05) + + - Update YGOPro protrol to 0x1344 + - New decks: DarkMagician, SkyStriker + - Add param to turn chat off + - Add param to print verbose log + - Add part of `Zones` enum and `AI.SelectPlace` + - Add `ClientCard.IsTuner`, `ClientCard.LinkMarker`, `ClientCard.HasLinkMarker` + - Add `ShouldNotBeTarget` and `ShouldBeDisabledBeforeItUseEffectMonster` enum + - Add `AI.Utils.GetBestBotMonster`, `AI.Utils.GetWorstBotMonster` and `AI.Utils.ChainContainPlayer` + - Add `Executor.OnCardSorting` and `Executor.OnDraw` + - Add `ClientField.GetColumnCount` and `ClientField.HasInHandOrInSpellZone` etc. + - Misc updates to LightswornShaddoldinosour and ChainBurn deck + - Misc updates to default executors + - Fix OnSelectUnselectCard + - Fix OnMove to keep card data when moving + +#### v0x1343 (2018-04-11) + + - Update YGOPro protrol to 0x1343 + - New decks: Trickstar, LightswornShaddoldinosour, ChainBurn + - Update `OnBattle`, add `Executor.OnSelectAttacker` and `Executor.OnSelectAttackTarget` + - Add `Executor.OnSelectPosition`, `Executor.OnSelectBattleReplay` + - Add `Bot.BattlingMonster` + - Add and update some default executors + - Change `Duel.LifePoints[0]` to `Bot.LifePoints` + - Change `LastChainPlayer` and `CurrentChain` to `Duel` class + - Change `ChainContainsCard` and `GetLastChainCard` etc. to `AI.Utils` class + - Fix turn count in match duel + - Fix don't turn 0 atk monster to atk pos + +#### v0x1342 (2017-12-26) + + - Update YGOPro protrol to 0x1342 + - Add Linux BotWrapper (a simple bash script) + +#### v0x1341 (2017-11-27) + + - Update YGOPro protrol to 0x1341 + - Change the program to x86 only + - Add BotWrapper for YGOPro bot mode + - Add `AI.SelectMaterials`, `OnSelectFusionMaterial`, `OnSelectPendulumSummon`, `AI.Utils.SelectPreferredCards` etc. + - Fix `AI.Utils.GetBestEnemySpell` to not return normal spell currently activating + - Fix AI don't attack defense Crystal Wing or S39 + - Fix ZexalWeapons AI don't change defense S39 back + - Minor updates + +#### v0x1340 (2017-11-06) + + - Update YGOPro protrol to 0x1340 + - Add support for the New Master Rule + - Decks update + - New commandline parameters + - Add support for Match and TAG duel + - Add server mode + - Bot dialogs now customable + - Only use normal deck when random picking decks + - Send sorry when the AI did something wrong that make the duel can't continue (for example, selected illegal card) + - Send info when the deck of the AI is illegal (for example, lflist dismatch) + - Fix the issue that the bot will attack _Dupe Frog_ with low attack monster when there is monster next to _Dupe Frog_ + - Fix the issue that synchro summon stuck in some condition [\#7](https://github.com/IceYGO/windbot/issues/7) + - Fix C#6.0 (VS2015) support + - Fix `OnUpdateData` + - New and updated `DefaultExecutor` + - New and updated `AI.Utils`, `ClientCard`, `ClientField` functions + - Add `OnNewTurn`, `AI.SelectYesNo`, `AI.SelectThirdCard`, `Duel.ChainTargets`, `Duel.LastSummonPlayer` + - Shortcut `Bot` for `Duel.Fields[0]`, `Enemy` for `Duel.Fields[1]` + - `CardId` is now class instead of enum so `(int)` is no longer needed + - Update the known card enums, add `Floodgate`, `OneForXyz`, `FusionSpell`, `MonsterHasPreventActivationEffectInBattle` + - Update `OnPreBattleBetween` to calculate the ATK of cards like _Number S39: Utopia the Lightning_ + - Update direct attack handling + +#### v0x133D (2017-09-24) + + - Update YGOPro protrol to 0x133D + - Use the latest YGOSharp.Network to improve performances + - Update the namespace of `YGOSharp.OCGWrapper` + - Fix the default trap cards not always activating + +### TODO list + +* More decks + +* Documents for creating AI + +* `AI.SelectPlace` for linked zones or not linked zones + +* `AI.SelectTribute` + +* Get equip of card. + +* Better new master rule support + +* Update the known card enums + +* More default common cards executor diff --git a/README.md b/README.md index f549e519..cbe74cf6 100644 --- a/README.md +++ b/README.md @@ -1,273 +1,102 @@ -# WindBot +# WindBot Ignite -A C# bot for YGOPro, compatible with the [YGOSharp](https://github.com/IceYGO/ygosharp) and [SRVPro](https://github.com/moecube/srvpro) server. +A fork of [IceYGO's WindBot](https://github.com/IceYGO/windbot), ported to the +[Project Ignis: EDOPro](https://github.com/edo9300/edopro) network protocol. -### How to use: +This is a simple, deterministic artificial intelligence that connects as a +virtual player to the YGOPro room system. Decks for this bot player **must** be +specifically prepared and compiled as individual executors. -* Compile `WindBot.sln` using Visual Studio or Mono. - -* Put `cards.cdb` next to the compiled `WindBot.exe`. - -* Run YGOPro, create a host. - -* Run WindBot and observe. - -### Supported commandlines - -`Name` -The nickname for the bot. - -`Deck` -The deck to be used by the bot. Available decks are listed below. Keep empty to use random deck. - -`Dialog` -The dialog texts to be used by the bot. See Dialogs folder for list. - -`Host` -The IP of the host to be connected to. - -`Port` -The port of the host to be connected to. - -`HostInfo` -The host info (password) to be used. - -`Version` -The version of YGOPro. - -`Hand` -If you are testing deck, you may want to make sure the bot go first or second. `Hand=1` will make the bot always show Scissors, 2 for Rock, 3 for Paper. - -`Chat` -False to turn the chat of bot off. - -`Debug` -Print verbose log of card movement info. False at default. (May be updated in future) - -`ServerMode` and `ServerPort` -WindBot can run as a "server", provide a http interface to create bot. - -### Available decks - -**Easy**: - -* Burn - -* Frog - -* Horus - -* MokeyMokey - -* MokeyMokeyKing - -* OldSchool - -**Normal**: +Written in C# targeting .NET Framework 4. Use Visual Studio 2015 or newer. +## Available decks and executors +* ABC * Altergeist - * Blue-Eyes - -* BlueEyesMaxDragon - -* ChainBurn - -* DarkMagician - -* Dragun - +* Blue-Eyes Ritual +* Burn +* Chain Burn +* Cyberse +* Dark Magician +* Dragma * Dragunity - -* GrenMajuThunderBoarder - -* Level VIII - -* LightswornShaddoldinosour - +* Dragun of Red-Eyes +* Frog +* Gren Maju Stun +* Horus +* Lightsworn Shaddoll Dino +* Mathmech +* Normal Monster Mash +* Normal Monster Mash II * Orcust - -* Phantasm - * Qliphort - +* R5NK * Rainbow - -* Rank V - +* Rose Scrap Synchro * Salamangreat - -* SkyStriker - -* ST1732 - +* Sky Striker +* Time Thief * Toadally Awesome - * Trickstar - +* Windwitch Gusto +* Witchcrafter Grass * Yosenju - -* Zexal Weapons - +* ZEXAL Weapon * Zoodiac -### Unfinished decks - -* Blackwing - -* CyberDragon - -* Evilswarm - -* Gravekeeper - -* Graydle - -* Lightsworn - -* Nekroz - -### Server mode - -WindBot can run as a "server", provide a http interface to create bot. - -eg. `http://127.0.0.1:2399/?name=%E2%91%A8&deck=Blue-Eyes&host=127.0.0.1&port=7911&dialog=cirno.zh-CN` - -In this situation, it will be multi-threaded. This can be useful for servers, since it don't use large amount memory. - -The parameters are same as commandlines, but low cased. - -### Known issues - -* If one chain includes two activation that use `AI.SelectCard`, the second one won't select correctly. - -### Changelog - -#### v0x134A (2019-05-30) - - - Update YGOPro protrol to 0x134A - - New decks: Altergeist, BlueEyesMaxDragon, GrenMajuThunderBoarder, Level8, Orcust, Phantasm, Salamangreat - - Use LINQ in codes - - Add random bot feature to BotWrapper - - Add `ClientCard.Sequence`, `ClientCard.ProcCompleted`, `ClientCard.IsSpecialSummoned` - - Add `ClientCard.EquipCards`, `ClientCard.OwnTargets`, handle equip cards - - Add `ClientCard.IsCode`, handle card alias - - Add `ClientCard.GetLinkedZones`, `ClientCard.HasSetcode` - - Add `ClientField.UnderAttack`, `ClientField.GetLinkedZones`, `ClientField.GetFieldSpellCard` - - Add `Duel.SummoningCards`, `Duel.LastSummonedCards` - - Add `Util.GetTotalAttackingMonsterAttack`, `Util.GetBotAvailZonesFromExtraDeck` - - Add `GetMatchingCards`, `GetFirstMatchingCard`, `IsExistingMatchingCard` - - Add `ExecutorType.GoToBattlePhase`, `ExecutorType.GoToMainPhase2`, `ExecutorType.GoToEndPhase` - - Add `DefaultScapegoat`, `DefaultMaxxC`, `DefaultAshBlossomAndJoyousSpring`, `DefaultGhostOgreAndSnowRabbit`, `DefaultGhostBelleAndHauntedMansion`, `DefaultEffectVeiler`, `DefaultCalledByTheGrave`, `DefaultInfiniteImpermanence` - - Rename `AIFunctions` to `AIUtil` (Usage: `AI.Utils.` -> `Util.`) - - Rename `AIFunctions.CompareCardAttack` to `CardContainer.CompareCardAttack` - - Update `Util.SelectPreferredCards` and `Util.CheckSelectCount` to return the result - - Update `ClientField.HasInMonstersZone` to support check face-up card - - Update `AI.SelectCard` [\#59](https://github.com/IceYGO/windbot/pull/59) - - Handle swap control of cards - - Change some `int location` to `CardLocation location` - - Update default `OnPreBattleBetween` to recognize more cards - - Misc updates to default executors - - Misc updates to the AI of some decks - - Update the known card enums - - Fix `CardSelector.Select` - - Fix `OnSelectEffectYn` didn't have `ActivateDescription` - - Fix `ClientCard.Attacked` - - Fix infinite activation of ZexalWeapons - -#### v0x1344 (2018-06-05) - - - Update YGOPro protrol to 0x1344 - - New decks: DarkMagician, SkyStriker - - Add param to turn chat off - - Add param to print verbose log - - Add part of `Zones` enum and `AI.SelectPlace` - - Add `ClientCard.IsTuner`, `ClientCard.LinkMarker`, `ClientCard.HasLinkMarker` - - Add `ShouldNotBeTarget` and `ShouldBeDisabledBeforeItUseEffectMonster` enum - - Add `AI.Utils.GetBestBotMonster`, `AI.Utils.GetWorstBotMonster` and `AI.Utils.ChainContainPlayer` - - Add `Executor.OnCardSorting` and `Executor.OnDraw` - - Add `ClientField.GetColumnCount` and `ClientField.HasInHandOrInSpellZone` etc. - - Misc updates to LightswornShaddoldinosour and ChainBurn deck - - Misc updates to default executors - - Fix OnSelectUnselectCard - - Fix OnMove to keep card data when moving - -#### v0x1343 (2018-04-11) - - - Update YGOPro protrol to 0x1343 - - New decks: Trickstar, LightswornShaddoldinosour, ChainBurn - - Update `OnBattle`, add `Executor.OnSelectAttacker` and `Executor.OnSelectAttackTarget` - - Add `Executor.OnSelectPosition`, `Executor.OnSelectBattleReplay` - - Add `Bot.BattlingMonster` - - Add and update some default executors - - Change `Duel.LifePoints[0]` to `Bot.LifePoints` - - Change `LastChainPlayer` and `CurrentChain` to `Duel` class - - Change `ChainContainsCard` and `GetLastChainCard` etc. to `AI.Utils` class - - Fix turn count in match duel - - Fix don't turn 0 atk monster to atk pos - -#### v0x1342 (2017-12-26) - - - Update YGOPro protrol to 0x1342 - - Add Linux BotWrapper (a simple bash script) - -#### v0x1341 (2017-11-27) - - - Update YGOPro protrol to 0x1341 - - Change the program to x86 only - - Add BotWrapper for YGOPro bot mode - - Add `AI.SelectMaterials`, `OnSelectFusionMaterial`, `OnSelectPendulumSummon`, `AI.Utils.SelectPreferredCards` etc. - - Fix `AI.Utils.GetBestEnemySpell` to not return normal spell currently activating - - Fix AI don't attack defense Crystal Wing or S39 - - Fix ZexalWeapons AI don't change defense S39 back - - Minor updates - -#### v0x1340 (2017-11-06) - - - Update YGOPro protrol to 0x1340 - - Add support for the New Master Rule - - Decks update - - New commandline parameters - - Add support for Match and TAG duel - - Add server mode - - Bot dialogs now customable - - Only use normal deck when random picking decks - - Send sorry when the AI did something wrong that make the duel can't continue (for example, selected illegal card) - - Send info when the deck of the AI is illegal (for example, lflist dismatch) - - Fix the issue that the bot will attack _Dupe Frog_ with low attack monster when there is monster next to _Dupe Frog_ - - Fix the issue that synchro summon stuck in some condition [\#7](https://github.com/IceYGO/windbot/issues/7) - - Fix C#6.0 (VS2015) support - - Fix `OnUpdateData` - - New and updated `DefaultExecutor` - - New and updated `AI.Utils`, `ClientCard`, `ClientField` functions - - Add `OnNewTurn`, `AI.SelectYesNo`, `AI.SelectThirdCard`, `Duel.ChainTargets`, `Duel.LastSummonPlayer` - - Shortcut `Bot` for `Duel.Fields[0]`, `Enemy` for `Duel.Fields[1]` - - `CardId` is now class instead of enum so `(int)` is no longer needed - - Update the known card enums, add `Floodgate`, `OneForXyz`, `FusionSpell`, `MonsterHasPreventActivationEffectInBattle` - - Update `OnPreBattleBetween` to calculate the ATK of cards like _Number S39: Utopia the Lightning_ - - Update direct attack handling - -#### v0x133D (2017-09-24) - - - Update YGOPro protrol to 0x133D - - Use the latest YGOSharp.Network to improve performances - - Update the namespace of `YGOSharp.OCGWrapper` - - Fix the default trap cards not always activating - -### TODO list - -* More decks - -* Documents for creating AI - -* `AI.SelectPlace` for linked zones or not linked zones - -* `AI.SelectTribute` - -* Get equip of card. - -* Better new master rule support - -* Update the known card enums - -* More default common cards executor +## Contributing + +Pull requests are welcome for fixes and new additions! It might take some time +for them to be evaluated since we are pretty swamped with a lot work to be done. + +Please keep bug reports on Discord so we can verify them first. + +For new additions, please make sure you add new code files to both the WindBot +and libWindbot projects. You need only worry about testing the WindBot project. + +## Other architectural changes from upstream +[Old README](https://github.com/ProjectIgnis/windbot/tree/master/README-old.md), +including some command-line documentation. + +The Visual Studio project has been merged with +[libWindbot](https://github.com/mercury233/libWindbot), meant for use as an +Android aar. Most of the code is shared with the main WindBot project, minus +the few specific bindings to call the bot as a library instead of a separate +process. The repository structure has been improved to keep the sources for +YGOSharp around as a result, but sqlite3 DLLs are still sitting around. + +ExecutorBase is a refactor to experiment with loading additional executors +from DLLs in an executor folder. See SampleExecutor for an example project using +this experimental feature. + +### libWindbot + +To actually compile libWindbot including the post-build task that produces the +Android aar artifact, you will need the following EXACT setup. You _will_ have a +bad day otherwise and this has been kept concise. + +- The postbuild event runs on Windows only. +- You must use Visual Studio 2017. Visual Studio 2019 does not work. +- You need Visual Studio workloads for Android (Xamarin and native development). +- You must install the 32-bit Mono SDK. The 64-bit version does not work. +- In the Visual Studio 2017 `Tools > Options > Xamarin > Android Settings`, + ensure the SDK, NDK, and JDK all point to valid paths. They should be set + correctly by default. You can use Microsoft-provided installations or share + these with Android Studio. + - In addition to the default Android SDK tools, install Platform 24 + (Android 7.0). No newer platform works. + - The NDK path must point to an r15c installation. Visual Studio 2017 should + already have installed it somewhere but you can download this unsupported + old version from the Android developer site. No newer NDK works. + +These are all quirks of the 0.4.0 NuGet version of +[Embeddinator-4000](https://github.com/mono/Embeddinator-4000), used to +transform the .NET DLL into a native library for Android. + +## License + +WindBot Ignite is free/libree and open source software licensed under the GNU +Affero General Public License, version 3 or later. Please see +[LICENSE](https://github.com/ProjectIgnis/windbot/blob/master/LICENSE) and +[COPYING](https://github.com/ProjectIgnis/windbot/blob/master/COPYING) for more +details. diff --git a/SampleExecutor.sln b/SampleExecutor.sln new file mode 100644 index 00000000..c7e5fa3a --- /dev/null +++ b/SampleExecutor.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.960 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleExecutor", "SampleExecutor\SampleExecutor.csproj", "{612BF3AB-571D-4ADF-AC20-47C20A7E991D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExecutorBase", "ExecutorBase\ExecutorBase.csproj", "{A1583FD7-7985-47DD-A835-8134DBF5811C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {612BF3AB-571D-4ADF-AC20-47C20A7E991D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {612BF3AB-571D-4ADF-AC20-47C20A7E991D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {612BF3AB-571D-4ADF-AC20-47C20A7E991D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {612BF3AB-571D-4ADF-AC20-47C20A7E991D}.Release|Any CPU.Build.0 = Release|Any CPU + {A1583FD7-7985-47DD-A835-8134DBF5811C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1583FD7-7985-47DD-A835-8134DBF5811C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1583FD7-7985-47DD-A835-8134DBF5811C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1583FD7-7985-47DD-A835-8134DBF5811C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {06D9979A-1D52-4DD2-A3A9-4644C7304AAE} + EndGlobalSection +EndGlobal diff --git a/SampleExecutor/SampleExecutor.cs b/SampleExecutor/SampleExecutor.cs new file mode 100644 index 00000000..a13dde9d --- /dev/null +++ b/SampleExecutor/SampleExecutor.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using WindBot; +using WindBot.Game; +using WindBot.Game.AI; +using YGOSharp.OCGWrapper.Enums; + +namespace WindBot.Game.AI.Decks +{ + [Deck("Sample", "AI_Sample")] + public class SampleExecutor : DefaultExecutor + { + public SampleExecutor(GameAI ai, Duel duel) + : base(ai, duel) + { + } +} +} \ No newline at end of file diff --git a/SampleExecutor/SampleExecutor.csproj b/SampleExecutor/SampleExecutor.csproj new file mode 100644 index 00000000..96a65f1c --- /dev/null +++ b/SampleExecutor/SampleExecutor.csproj @@ -0,0 +1,55 @@ + + + + + Debug + AnyCPU + {612BF3AB-571D-4ADF-AC20-47C20A7E991D} + Library + Properties + SampleExecutor + SampleExecutor + v4.0 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + {a1583fd7-7985-47dd-a835-8134dbf5811c} + ExecutorBase + + + + \ No newline at end of file diff --git a/WindBot.cs b/WindBot.cs new file mode 100644 index 00000000..156deb5a --- /dev/null +++ b/WindBot.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using WindBot.Game; +using WindBot.Game.AI; +using YGOSharp.OCGWrapper; + +namespace WindBot +{ + public class WindBot + { + public static void InitAndroid(string assetPath) + { + Program.Rand = new Random(); + Program.AssetPath = assetPath; + DecksManager.Init(); + try + { + NamedCardsManager.Init(assetPath + "/cards.cdb"); + } + catch (Exception e) + { + Logger.WriteErrorLine(e.ToString()); + } + } + + private static IList ParseArgs(string arg) + { + return Regex.Split(arg, "(?<=^[^\']*(?:\'[^\']*\'[^\']*)*) (?=(?:[^\']*\'[^\']*\')*[^\']*$)").ToList(); // https://stackoverflow.com/questions/4780728/regex-split-string-preserving-quotes/ + } + + public static void RunAndroid(string arg) + { + IList args = ParseArgs(arg); + WindBotInfo Info = new WindBotInfo(); + foreach (string param in args) + { + string[] p = Regex.Split(param, "[=]"); + p[1] = p[1].Replace("'", ""); + if (p[0] == "Name") Info.Name = p[1]; + if (p[0] == "Deck") Info.Deck = p[1]; + if (p[0] == "Dialog") Info.Dialog = p[1]; + if (p[0] == "Port") Info.Port = int.Parse(p[1]); + if (p[0] == "Hand") Info.Hand = int.Parse(p[1]); + if (p[0] == "Host") Info.Host = p[1]; + if (p[0] == "HostInfo") Info.HostInfo = p[1]; + if (p[0] == "Version") Info.Version = int.Parse(p[1]); + if (p[0] == "Chat") Info.Chat = int.Parse(p[1]) != 0; + if (p[0] == "Debug") Info.Debug = int.Parse(p[1]) != 0; + } + Thread workThread = new Thread(new ParameterizedThreadStart(Run)); + workThread.Start(Info); + } + + private static void Run(object o) + { +#if !DEBUG + try + { + //all errors will be catched instead of causing the program to crash. +#endif + WindBotInfo Info = (WindBotInfo)o; + GameClient client = new GameClient(Info); + client.Start(); + Logger.DebugWriteLine(client.Username + " started."); + while (client.Connection.IsConnected) + { +#if !DEBUG + try + { +#endif + client.Tick(); + Thread.Sleep(30); +#if !DEBUG + } + catch (Exception ex) + { + Logger.WriteErrorLine("Tick Error: " + ex); + } +#endif + } + Logger.DebugWriteLine(client.Username + " end."); +#if !DEBUG + } + catch (Exception ex) + { + Logger.WriteErrorLine("Run Error: " + ex); + } +#endif + } + } + + public class Program + { + public static string AssetPath; + internal static Random Rand; + } +} diff --git a/WindBot.csproj b/WindBot.csproj index e714a41e..97edfd8b 100644 --- a/WindBot.csproj +++ b/WindBot.csproj @@ -37,6 +37,10 @@ WindBot.ico + + $(MSBuildProjectDirectory)/out/$(MSBuildProjectName)/bin + $(MSBuildProjectDirectory)/out/$(MSBuildProjectName)/obj + .\Mono.Data.Sqlite.dll @@ -47,28 +51,18 @@ - - .\YGOSharp.Network.dll - - - .\YGOSharp.OCGWrapper.dll - - - .\YGOSharp.OCGWrapper.Enums.dll - - - - - - - - + + + + + + @@ -77,6 +71,7 @@ + @@ -94,6 +89,7 @@ + @@ -103,38 +99,28 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -162,6 +148,12 @@ + + + {a1583fd7-7985-47dd-a835-8134dbf5811c} + ExecutorBase + + +