From 80576a6e19bbc48e1b2dd134ac9d9ff2997d6f71 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Wed, 22 Jan 2020 09:57:36 +0800 Subject: [PATCH 01/83] fix bot list --- BotWrapper/bot.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BotWrapper/bot.conf b/BotWrapper/bot.conf index 56296ab2..12b55831 100644 --- a/BotWrapper/bot.conf +++ b/BotWrapper/bot.conf @@ -122,7 +122,7 @@ 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 !试作型机器人1732 Name=试作型机器人1732 Deck=ST1732 Dialog=zh-CN From 4e5a0aa72f8dd4877b09c5c95919580fd6953b12 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Thu, 23 Jan 2020 20:51:56 +0800 Subject: [PATCH 02/83] version --- WindBotInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WindBotInfo.cs b/WindBotInfo.cs index 916108ef..351814e0 100644 --- a/WindBotInfo.cs +++ b/WindBotInfo.cs @@ -22,7 +22,7 @@ public WindBotInfo() Host = "127.0.0.1"; Port = 7911; HostInfo = ""; - Version = 0x134b; + Version = 0x1350; Hand = 0; Debug = false; Chat = true; From 917a4664b4895765d6e56ac5aa536264dc597a24 Mon Sep 17 00:00:00 2001 From: edo9300 Date: Wed, 26 Feb 2020 22:00:16 +0100 Subject: [PATCH 03/83] Always return yes on rematch request --- Game/GameBehavior.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Game/GameBehavior.cs b/Game/GameBehavior.cs index 48bcc184..1aad523d 100644 --- a/Game/GameBehavior.cs +++ b/Game/GameBehavior.cs @@ -1442,6 +1442,8 @@ private void OnSelectYesNo(BinaryReader packet) 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); From 4ce13bca79f3dac4390235a40afb701025c3bdd1 Mon Sep 17 00:00:00 2001 From: edo9300 Date: Thu, 27 Feb 2020 22:31:33 +0100 Subject: [PATCH 04/83] Fix windbot not correctly parsing field info --- Game/GameBehavior.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Game/GameBehavior.cs b/Game/GameBehavior.cs index 1aad523d..a1c68239 100644 --- a/Game/GameBehavior.cs +++ b/Game/GameBehavior.cs @@ -839,6 +839,10 @@ private void OnUpdateData(BinaryReader packet) long len = card.Update(packet, _duel); packet.BaseStream.Position = pos + len; } + else + { + packet.BaseStream.Position += 2; + } } } } From 4d54bde90aa95e812b7a77d62ac308862c3f3fee Mon Sep 17 00:00:00 2001 From: kevinlul <6320810+kevinlul@users.noreply.github.com> Date: Mon, 9 Mar 2020 19:20:26 -0400 Subject: [PATCH 05/83] Remove single quotes from deck codenames --- bots.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bots.json b/bots.json index e9a13801..17e9efc8 100644 --- a/bots.json +++ b/bots.json @@ -103,7 +103,7 @@ }, { "name": "R5NK", - "deck": "'Rank V'", + "deck": "Rank V", "difficulty": 2, "masterRules": [3, 4, 5] }, @@ -115,7 +115,7 @@ }, { "name": "Rose Scrap Synchro", - "deck": "'Level VIII'", + "deck": "Level VIII", "difficulty": 2, "masterRules": [3, 4, 5] }, @@ -133,7 +133,7 @@ }, { "name": "Toadally Awesome", - "deck": "'Toadally Awesome'", + "deck": "Toadally Awesome", "difficulty": 2, "masterRules": [3, 4, 5] }, @@ -151,7 +151,7 @@ }, { "name": "ZEXAL Weapon", - "deck": "'Zexal Weapons'", + "deck": "Zexal Weapons", "difficulty": 2, "masterRules": [3, 4, 5] }, From 0465f8a3e2e5e0bd61e4ca40ea23abf0dee40119 Mon Sep 17 00:00:00 2001 From: edo9300 Date: Tue, 10 Mar 2020 01:35:59 +0100 Subject: [PATCH 06/83] Set card controller from the various game messages --- Game/ClientCard.cs | 9 +++++---- Game/ClientField.cs | 4 ++-- Game/Duel.cs | 15 ++++++++------- Game/GameBehavior.cs | 24 ++++++++++++------------ 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/Game/ClientCard.cs b/Game/ClientCard.cs index 78707f38..92efa174 100644 --- a/Game/ClientCard.cs +++ b/Game/ClientCard.cs @@ -32,7 +32,7 @@ public class ClientCard 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 Controller { get; set; } public int Disabled { get; private set; } public int ProcCompleted { get; private set; } public int SelectSeq { get; set; } @@ -53,16 +53,17 @@ public class ClientCard 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 controller) + : this(id, loc, -1 , 0, controller) { } - public ClientCard(int id, CardLocation loc, int sequence, int position) + 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(); diff --git a/Game/ClientField.cs b/Game/ClientField.cs index 6b4eff5e..b5479de9 100644 --- a/Game/ClientField.cs +++ b/Game/ClientField.cs @@ -34,9 +34,9 @@ public void Init(int deck, int extra) ExtraDeck = new List(); for (int i = 0; i < deck; ++i) - Deck.Add(new ClientCard(0, CardLocation.Deck, -1)); + Deck.Add(new ClientCard(0, CardLocation.Deck, -1, 0)); for (int i = 0; i < extra; ++i) - ExtraDeck.Add(new ClientCard(0, CardLocation.Extra, -1)); + ExtraDeck.Add(new ClientCard(0, CardLocation.Extra, -1, 0)); } public int GetMonstersExtraZoneCount() diff --git a/Game/Duel.cs b/Game/Duel.cs index 115547bb..18a1aa69 100644 --- a/Game/Duel.cs +++ b/Game/Duel.cs @@ -99,25 +99,25 @@ 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)); + 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)); + 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)); + 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); + 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); + 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)); + 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)); + Fields[player].ExtraDeck.Add(new ClientCard(cardId, loc, -1, pos, player)); break; } } @@ -127,6 +127,7 @@ public void AddCard(CardLocation loc, ClientCard card, int player, int seq, int card.Location = loc; card.Sequence = seq; card.Position = pos; + card.Controller = player; card.SetId(id); switch (loc) { diff --git a/Game/GameBehavior.cs b/Game/GameBehavior.cs index a1c68239..7f9edca4 100644 --- a/Game/GameBehavior.cs +++ b/Game/GameBehavior.cs @@ -433,7 +433,7 @@ private void OnDraw(BinaryReader packet) 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)); + _duel.Fields[player].Hand.Add(new ClientCard(0, CardLocation.Hand, -1, player)); } _ai.OnDraw(player); } @@ -498,20 +498,20 @@ private void OnTagSwap(BinaryReader packet) _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].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)); + _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)); + _duel.Fields[player].Hand.Add(new ClientCard(code, CardLocation.Hand,-1, player)); packet.ReadInt32(); // position } } @@ -738,7 +738,7 @@ private void OnChainEnd(BinaryReader packet) private void OnCardSorting(BinaryReader packet) { - /*int player =*/ GetLocalPlayer(packet.ReadByte()); + int player = GetLocalPlayer(packet.ReadByte()); IList originalCards = new List(); IList cards = new List(); int count = packet.ReadInt32(); @@ -750,7 +750,7 @@ private void OnCardSorting(BinaryReader packet) int seq = packet.ReadInt32(); ClientCard card; if (((int)loc & (int)CardLocation.Overlay) != 0) - card = new ClientCard(id, CardLocation.Overlay, -1); + card = new ClientCard(id, CardLocation.Overlay, -1, player); else card = _duel.GetCard(controler, loc, seq); if (card == null) continue; @@ -923,7 +923,7 @@ private void OnSelectBattleCmd(BinaryReader packet) private void InternalOnSelectCard(BinaryReader packet, Func, int, int, int, bool, IList> func, bool tribute = false) { - packet.ReadByte(); // player + int player = packet.ReadByte(); bool cancelable = packet.ReadByte() != 0; int min = packet.ReadInt32(); int max = packet.ReadInt32(); @@ -943,9 +943,9 @@ private void InternalOnSelectCard(BinaryReader packet, Func, i } ClientCard card; if (((int)info.location & (int)CardLocation.Overlay) != 0) - card = new ClientCard(id, CardLocation.Overlay, -1); + card = new ClientCard(id, CardLocation.Overlay, -1, player); else if (info.location == 0) - card = new ClientCard(id, 0, 0); + card = new ClientCard(id, 0, 0, player); else card = _duel.GetCard(info.controler, (CardLocation)info.location, info.sequence); if (card == null) continue; @@ -987,7 +987,7 @@ private void InternalOnSelectCard(BinaryReader packet, Func, i private void InternalOnSelectUnselectCard(BinaryReader packet, Func, int, int, int, bool, IList> func) { - packet.ReadByte(); // player + int player = packet.ReadByte(); bool finishable = packet.ReadByte() != 0; bool cancelable = packet.ReadByte() != 0 || finishable; int min = packet.ReadInt32(); @@ -1001,9 +1001,9 @@ private void InternalOnSelectUnselectCard(BinaryReader packet, Func Date: Fri, 13 Mar 2020 17:06:15 +1300 Subject: [PATCH 07/83] Fix the ID for Silver's Cry in the Horus executor. (#4) Also updates the name to Silver's Cry. --- Game/AI/Decks/HorusExecutor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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; From ea0a56eee7e4049acbd9800bf9b12dd452c50271 Mon Sep 17 00:00:00 2001 From: edo9300 Date: Mon, 16 Mar 2020 20:04:34 +0100 Subject: [PATCH 08/83] Merge with libwindbot --- .gitignore | 1 + Game/AI/Dialogs.cs | 4 + Game/ClientCard.cs | 2 +- Game/Deck.cs | 4 + Logger.cs | 15 ++ Properties/AssemblyInfoLib.cs | 30 +++ WindBot.cs | 94 ++++++++ WindBot.sln | 24 +- YGOSharp.Network/AsyncBinaryClient.cs | 174 +++++++++++++++ YGOSharp.Network/AsyncNetworkServer.cs | 67 ++++++ YGOSharp.Network/AsyncYGOClient.cs | 42 ++++ YGOSharp.Network/BinaryClient.cs | 229 ++++++++++++++++++++ YGOSharp.Network/Enums/CtosMessage.cs | 23 ++ YGOSharp.Network/Enums/GameState.cs | 12 + YGOSharp.Network/Enums/PlayerChange.cs | 10 + YGOSharp.Network/Enums/PlayerState.cs | 8 + YGOSharp.Network/Enums/PlayerType.cs | 23 ++ YGOSharp.Network/Enums/StocMessage.cs | 26 +++ YGOSharp.Network/NetworkClient.cs | 161 ++++++++++++++ YGOSharp.Network/NetworkServer.cs | 88 ++++++++ YGOSharp.Network/Utils/BinaryExtensions.cs | 32 +++ YGOSharp.Network/YGOClient.cs | 42 ++++ YGOSharp.OCGWrapper.Enums/CardAttribute.cs | 13 ++ YGOSharp.OCGWrapper.Enums/CardLinkMarker.cs | 15 ++ YGOSharp.OCGWrapper.Enums/CardLocation.cs | 17 ++ YGOSharp.OCGWrapper.Enums/CardPosition.cs | 14 ++ YGOSharp.OCGWrapper.Enums/CardRace.cs | 30 +++ YGOSharp.OCGWrapper.Enums/CardType.cs | 31 +++ YGOSharp.OCGWrapper.Enums/DuelPhase.cs | 16 ++ YGOSharp.OCGWrapper.Enums/GameMessage.cs | 101 +++++++++ YGOSharp.OCGWrapper.Enums/Query.cs | 29 +++ YGOSharp.OCGWrapper/Card.cs | 98 +++++++++ YGOSharp.OCGWrapper/CardsManager.cs | 45 ++++ YGOSharp.OCGWrapper/NamedCard.cs | 21 ++ YGOSharp.OCGWrapper/NamedCardsManager.cs | 31 +++ libWindbot.csproj | 173 +++++++++++++++ 36 files changed, 1741 insertions(+), 4 deletions(-) create mode 100644 Properties/AssemblyInfoLib.cs create mode 100644 WindBot.cs create mode 100644 YGOSharp.Network/AsyncBinaryClient.cs create mode 100644 YGOSharp.Network/AsyncNetworkServer.cs create mode 100644 YGOSharp.Network/AsyncYGOClient.cs create mode 100644 YGOSharp.Network/BinaryClient.cs create mode 100644 YGOSharp.Network/Enums/CtosMessage.cs create mode 100644 YGOSharp.Network/Enums/GameState.cs create mode 100644 YGOSharp.Network/Enums/PlayerChange.cs create mode 100644 YGOSharp.Network/Enums/PlayerState.cs create mode 100644 YGOSharp.Network/Enums/PlayerType.cs create mode 100644 YGOSharp.Network/Enums/StocMessage.cs create mode 100644 YGOSharp.Network/NetworkClient.cs create mode 100644 YGOSharp.Network/NetworkServer.cs create mode 100644 YGOSharp.Network/Utils/BinaryExtensions.cs create mode 100644 YGOSharp.Network/YGOClient.cs create mode 100644 YGOSharp.OCGWrapper.Enums/CardAttribute.cs create mode 100644 YGOSharp.OCGWrapper.Enums/CardLinkMarker.cs create mode 100644 YGOSharp.OCGWrapper.Enums/CardLocation.cs create mode 100644 YGOSharp.OCGWrapper.Enums/CardPosition.cs create mode 100644 YGOSharp.OCGWrapper.Enums/CardRace.cs create mode 100644 YGOSharp.OCGWrapper.Enums/CardType.cs create mode 100644 YGOSharp.OCGWrapper.Enums/DuelPhase.cs create mode 100644 YGOSharp.OCGWrapper.Enums/GameMessage.cs create mode 100644 YGOSharp.OCGWrapper.Enums/Query.cs create mode 100644 YGOSharp.OCGWrapper/Card.cs create mode 100644 YGOSharp.OCGWrapper/CardsManager.cs create mode 100644 YGOSharp.OCGWrapper/NamedCard.cs create mode 100644 YGOSharp.OCGWrapper/NamedCardsManager.cs create mode 100644 libWindbot.csproj 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/Game/AI/Dialogs.cs b/Game/AI/Dialogs.cs index d0f7fa30..a0a86ae2 100644 --- a/Game/AI/Dialogs.cs +++ b/Game/AI/Dialogs.cs @@ -59,7 +59,11 @@ public Dialogs(GameClient game) _game = game; DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(DialogsData)); string dialogfilename = game.Dialog; +#if !LIBWINDBOT using (FileStream fs = File.OpenRead("Dialogs/" + dialogfilename + ".json")) +#else + using (FileStream fs = File.OpenRead(Path.Combine(WindBot.AssetPath, "Dialogs/", dialogfilename + ".json"))) +#endif { DialogsData data = (DialogsData)serializer.ReadObject(fs); _welcome = data.welcome; diff --git a/Game/ClientCard.cs b/Game/ClientCard.cs index 92efa174..d48091c4 100644 --- a/Game/ClientCard.cs +++ b/Game/ClientCard.cs @@ -54,7 +54,7 @@ public class ClientCard public IDictionary ActionActivateIndex { get; private set; } public ClientCard(int id, CardLocation loc, int sequence, int controller) - : this(id, loc, -1 , 0, controller) + : this(id, loc, -1, 0, controller) { } diff --git a/Game/Deck.cs b/Game/Deck.cs index 6bf18a90..ca7dec8c 100644 --- a/Game/Deck.cs +++ b/Game/Deck.cs @@ -33,7 +33,11 @@ public static Deck Load(string name) StreamReader reader = null; try { +#if !LIBWINDBOT reader = new StreamReader(new FileStream("Decks/" + name + ".ydk", FileMode.Open, FileAccess.Read)); +#else + reader = new StreamReader(new FileStream(Path.Combine(WindBot.AssetPath, "Decks/", name + ".ydk"), FileMode.Open, FileAccess.Read)); +#endif Deck deck = new Deck(); bool main = true; diff --git a/Logger.cs b/Logger.cs index 277b5c5e..0dffa0c5 100644 --- a/Logger.cs +++ b/Logger.cs @@ -1,4 +1,7 @@ using System; +#if LIBWINDBOT +using Android.Util; +#endif namespace WindBot { @@ -6,20 +9,32 @@ 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/Properties/AssemblyInfoLib.cs b/Properties/AssemblyInfoLib.cs new file mode 100644 index 00000000..55275f03 --- /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")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[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/WindBot.cs b/WindBot.cs new file mode 100644 index 00000000..4f6f88e1 --- /dev/null +++ b/WindBot.cs @@ -0,0 +1,94 @@ +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 string AssetPath; + + public static void InitAndroid(string assetPath) + { + Program.Rand = new Random(); + AssetPath = assetPath; + DecksManager.Init(); + } + + 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 + { + internal static Random Rand; + } +} diff --git a/WindBot.sln b/WindBot.sln index 67551b02..2cb3b72e 100644 --- a/WindBot.sln +++ b/WindBot.sln @@ -1,28 +1,46 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.960 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindBot", "WindBot.csproj", "{3E7FAF67-A27D-4A61-B161-93AD4414183E}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotWrapper", "BotWrapper\BotWrapper.csproj", "{0665CA3B-C14F-40EC-ABFB-AD46A695F5A3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "libWindbot", "libWindbot.csproj", "{5BCF813B-671E-4B2C-B01E-3EACDC536B65}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Debug|Any CPU.ActiveCfg = Debug|x86 {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Debug|x86.ActiveCfg = Debug|x86 {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Debug|x86.Build.0 = Debug|x86 + {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Release|Any CPU.ActiveCfg = Release|x86 {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Release|x86.ActiveCfg = Release|x86 {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Release|x86.Build.0 = Release|x86 + {0665CA3B-C14F-40EC-ABFB-AD46A695F5A3}.Debug|Any CPU.ActiveCfg = Debug|x86 {0665CA3B-C14F-40EC-ABFB-AD46A695F5A3}.Debug|x86.ActiveCfg = Debug|x86 {0665CA3B-C14F-40EC-ABFB-AD46A695F5A3}.Debug|x86.Build.0 = Debug|x86 + {0665CA3B-C14F-40EC-ABFB-AD46A695F5A3}.Release|Any CPU.ActiveCfg = Release|x86 {0665CA3B-C14F-40EC-ABFB-AD46A695F5A3}.Release|x86.ActiveCfg = Release|x86 - {0665CA3B-C14F-40EC-ABFB-AD46A695F5A3}.Release|x86.Build.0 = Release|x86 + {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Debug|x86.ActiveCfg = Debug|Any CPU + {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Debug|x86.Build.0 = Debug|Any CPU + {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Release|Any CPU.Build.0 = Release|Any CPU + {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Release|x86.ActiveCfg = Release|Any CPU + {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {86299176-31F0-4A85-9786-8252D709AF71} + EndGlobalSection EndGlobal diff --git a/YGOSharp.Network/AsyncBinaryClient.cs b/YGOSharp.Network/AsyncBinaryClient.cs new file mode 100644 index 00000000..972ab675 --- /dev/null +++ b/YGOSharp.Network/AsyncBinaryClient.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; + +namespace YGOSharp.Network +{ + public class AsyncBinaryClient + { + public event Action Connected; + public event Action Disconnected; + public event Action PacketReceived; + + protected int MaxPacketLength = 0xFFFF; + protected int HeaderSize = 2; + protected bool IsHeaderSizeIncluded = false; + + private NetworkClient _client; + + private List _receiveBuffer = new List(); + private byte[] _lengthBuffer = new byte[16]; + + private int _pendingLength; + + public bool IsConnected + { + get { return _client.IsConnected; } + } + + public IPAddress RemoteIPAddress + { + get { return _client.RemoteIPAddress; } + } + + public AsyncBinaryClient(NetworkClient client) + { + _client = client; + + client.Connected += Client_Connected; + client.Disconnected += Client_Disconnected; + client.DataReceived += Client_DataReceived; + + if (_client.IsConnected) + { + _client.BeginReceive(); + } + } + + public void Connect(IPAddress address, int port) + { + _client.BeginConnect(address, port); + } + + public void Initialize(Socket socket) + { + _client.Initialize(socket); + } + + public void Send(byte[] packet) + { + if (packet.Length > MaxPacketLength) + { + throw new Exception("Tried to send a too large packet"); + } + + int packetLength = packet.Length; + if (IsHeaderSizeIncluded) packetLength += HeaderSize; + + byte[] header; + if (HeaderSize == 2) + { + header = BitConverter.GetBytes((ushort)packetLength); + } + else if (HeaderSize == 4) + { + header = BitConverter.GetBytes(packetLength); + } + else + { + throw new Exception("Unsupported header size: " + HeaderSize); + } + byte[] data = new byte[packet.Length + HeaderSize]; + Array.Copy(header, 0, data, 0, header.Length); + Array.Copy(packet, 0, data, header.Length, packet.Length); + _client.BeginSend(data); + } + + public void Close(Exception error = null) + { + _client.Close(error); + } + + private void Client_Connected() + { + Connected?.Invoke(); + } + + private void Client_Disconnected(Exception ex) + { + Disconnected?.Invoke(ex); + } + + private void Client_DataReceived(byte[] data) + { + _receiveBuffer.AddRange(data); + ExtractPackets(); + } + + private void ExtractPackets() + { + bool hasExtracted; + do + { + if (_pendingLength == 0) + { + hasExtracted = ExtractPendingLength(); + } + else + { + hasExtracted = ExtractPendingPacket(); + } + } + while (hasExtracted); + } + + private bool ExtractPendingLength() + { + if (_receiveBuffer.Count >= HeaderSize) + { + _receiveBuffer.CopyTo(0, _lengthBuffer, 0, HeaderSize); + if (HeaderSize == 2) + { + _pendingLength = BitConverter.ToUInt16(_lengthBuffer, 0); + } + else if (HeaderSize == 4) + { + _pendingLength = BitConverter.ToInt32(_lengthBuffer, 0); + } + else + { + throw new Exception("Unsupported header size: " + HeaderSize); + } + _receiveBuffer.RemoveRange(0, HeaderSize); + + if (IsHeaderSizeIncluded) _pendingLength -= HeaderSize; + + if (_pendingLength < 0 || _pendingLength > MaxPacketLength) + { + _client.Close(new Exception("Tried to receive a too large packet")); + return false; + } + + return true; + } + return false; + } + + private bool ExtractPendingPacket() + { + if (_receiveBuffer.Count >= _pendingLength) + { + byte[] packet = new byte[_pendingLength]; + + _receiveBuffer.CopyTo(0, packet, 0, _pendingLength); + _receiveBuffer.RemoveRange(0, _pendingLength); + _pendingLength = 0; + + PacketReceived?.Invoke(packet); + return true; + } + return false; + } + } +} diff --git a/YGOSharp.Network/AsyncNetworkServer.cs b/YGOSharp.Network/AsyncNetworkServer.cs new file mode 100644 index 00000000..c1540bf6 --- /dev/null +++ b/YGOSharp.Network/AsyncNetworkServer.cs @@ -0,0 +1,67 @@ +using System; +using System.Net; +using System.Net.Sockets; + +namespace YGOSharp.Network +{ + public class AsyncNetworkServer + { + public bool IsListening { get; private set; } + + public event Action ClientConnected; + + private TcpListener _listener; + private bool _isClosed; + + public AsyncNetworkServer(IPAddress address, int port) + { + _listener = new TcpListener(address, port); + } + + public void Start() + { + if (!IsListening && !_isClosed) + { + IsListening = true; + _listener.Start(); + BeginAcceptSocket(); + } + } + + public void Close() + { + if (!_isClosed) + { + _isClosed = true; + IsListening = false; + _listener.Stop(); + } + } + + private void BeginAcceptSocket() + { + try + { + _listener.BeginAcceptSocket(AcceptSocketCallback, null); + } + catch (Exception) + { + Close(); + } + } + + private void AcceptSocketCallback(IAsyncResult result) + { + try + { + Socket socket = _listener.EndAcceptSocket(result); + ClientConnected?.Invoke(new NetworkClient(socket)); + BeginAcceptSocket(); + } + catch (Exception) + { + Close(); + } + } + } +} diff --git a/YGOSharp.Network/AsyncYGOClient.cs b/YGOSharp.Network/AsyncYGOClient.cs new file mode 100644 index 00000000..6ccb9c3e --- /dev/null +++ b/YGOSharp.Network/AsyncYGOClient.cs @@ -0,0 +1,42 @@ +using System.IO; +using YGOSharp.Network.Enums; + +namespace YGOSharp.Network +{ + public class AsyncYGOClient : AsyncBinaryClient + { + public AsyncYGOClient() + : base(new NetworkClient()) + { + } + + public AsyncYGOClient(NetworkClient client) + : base(client) + { + } + + public void Send(BinaryWriter writer) + { + Send(((MemoryStream)writer.BaseStream).ToArray()); + } + + public void Send(CtosMessage message) + { + using (BinaryWriter writer = new BinaryWriter(new MemoryStream())) + { + writer.Write((byte)message); + Send(writer); + } + } + + public void Send(CtosMessage message, int value) + { + using (BinaryWriter writer = new BinaryWriter(new MemoryStream())) + { + writer.Write((byte)message); + writer.Write(value); + Send(writer); + } + } + } +} diff --git a/YGOSharp.Network/BinaryClient.cs b/YGOSharp.Network/BinaryClient.cs new file mode 100644 index 00000000..091c84e4 --- /dev/null +++ b/YGOSharp.Network/BinaryClient.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; + +namespace YGOSharp.Network +{ + public class BinaryClient + { + public event Action Connected; + public event Action Disconnected; + public event Action PacketReceived; + + protected int MaxPacketLength = 0xFFFF; + protected int HeaderSize = 2; + protected bool IsHeaderSizeIncluded = false; + + private NetworkClient _client; + + private List _receiveBuffer = new List(); + private Queue _pendingPackets = new Queue(); + private byte[] _lengthBuffer = new byte[16]; + + private int _pendingLength; + private bool _wasConnected; + private bool _wasDisconnected; + private bool _wasDisconnectedEventFired; + private Exception _closingException; + + public bool IsConnected + { + get { return !_wasDisconnectedEventFired; } + } + + public IPAddress RemoteIPAddress + { + get { return _client.RemoteIPAddress; } + } + + public BinaryClient(NetworkClient client) + { + _client = client; + + client.Connected += Client_Connected; + client.Disconnected += Client_Disconnected; + client.DataReceived += Client_DataReceived; + + if (_client.IsConnected) + { + _client.BeginReceive(); + } + } + + public void Connect(IPAddress address, int port) + { + _client.BeginConnect(address, port); + } + + public void Initialize(Socket socket) + { + _client.Initialize(socket); + } + + public void Update() + { + if (_wasConnected) + { + _wasConnected = false; + Connected?.Invoke(); + } + ReceivePendingPackets(); + if (_wasDisconnected && !_wasDisconnectedEventFired) + { + _wasDisconnectedEventFired = true; + Disconnected?.Invoke(_closingException); + } + } + + public void Send(byte[] packet) + { + if (packet.Length > MaxPacketLength) + { + throw new Exception("Tried to send a too large packet"); + } + + int packetLength = packet.Length; + if (IsHeaderSizeIncluded) packetLength += HeaderSize; + + byte[] header; + if (HeaderSize == 2) + { + header = BitConverter.GetBytes((ushort)packetLength); + } + else if (HeaderSize == 4) + { + header = BitConverter.GetBytes(packetLength); + } + else + { + throw new Exception("Unsupported header size: " + HeaderSize); + } + byte[] data = new byte[packet.Length + HeaderSize]; + Array.Copy(header, 0, data, 0, header.Length); + Array.Copy(packet, 0, data, header.Length, packet.Length); + _client.BeginSend(data); + } + + public void Close(Exception error = null) + { + _client.Close(error); + } + + private void ReceivePendingPackets() + { + bool hasReceived; + do + { + byte[] packet = null; + lock (_pendingPackets) + { + if (_pendingPackets.Count > 0) + { + packet = _pendingPackets.Dequeue(); + } + } + hasReceived = false; + if (packet != null) + { + hasReceived = true; + using (MemoryStream stream = new MemoryStream(packet, false)) + { + using (BinaryReader reader = new BinaryReader(stream)) + { + PacketReceived?.Invoke(reader); + } + } + } + } + while (hasReceived); + } + + private void Client_Connected() + { + _wasConnected = true; + } + + private void Client_Disconnected(Exception ex) + { + _wasDisconnected = true; + _closingException = ex; + } + + private void Client_DataReceived(byte[] data) + { + _receiveBuffer.AddRange(data); + ExtractPackets(); + } + + private void ExtractPackets() + { + bool hasExtracted; + do + { + if (_pendingLength == 0) + { + hasExtracted = ExtractPendingLength(); + } + else + { + hasExtracted = ExtractPendingPacket(); + } + } + while (hasExtracted); + } + + private bool ExtractPendingLength() + { + if (_receiveBuffer.Count >= HeaderSize) + { + _receiveBuffer.CopyTo(0, _lengthBuffer, 0, HeaderSize); + if (HeaderSize == 2) + { + _pendingLength = BitConverter.ToUInt16(_lengthBuffer, 0); + } + else if (HeaderSize == 4) + { + _pendingLength = BitConverter.ToInt32(_lengthBuffer, 0); + } + else + { + throw new Exception("Unsupported header size: " + HeaderSize); + } + _receiveBuffer.RemoveRange(0, HeaderSize); + + if (IsHeaderSizeIncluded) _pendingLength -= HeaderSize; + + if (_pendingLength < 0 || _pendingLength > MaxPacketLength) + { + _client.Close(new Exception("Tried to receive a too large packet")); + return false; + } + + return true; + } + return false; + } + + private bool ExtractPendingPacket() + { + if (_receiveBuffer.Count >= _pendingLength) + { + byte[] packet = new byte[_pendingLength]; + + _receiveBuffer.CopyTo(0, packet, 0, _pendingLength); + _receiveBuffer.RemoveRange(0, _pendingLength); + _pendingLength = 0; + + lock (_pendingPackets) + { + _pendingPackets.Enqueue(packet); + } + + return true; + } + return false; + } + } +} diff --git a/YGOSharp.Network/Enums/CtosMessage.cs b/YGOSharp.Network/Enums/CtosMessage.cs new file mode 100644 index 00000000..4356f8a8 --- /dev/null +++ b/YGOSharp.Network/Enums/CtosMessage.cs @@ -0,0 +1,23 @@ +namespace YGOSharp.Network.Enums +{ + public enum CtosMessage + { + Response = 0x1, + UpdateDeck = 0x2, + HandResult = 0x3, + TpResult = 0x4, + PlayerInfo = 0x10, + CreateGame = 0x11, + JoinGame = 0x12, + LeaveGame = 0x13, + Surrender = 0x14, + TimeConfirm = 0x15, + Chat = 0x16, + HsToDuelist = 0x20, + HsToObserver = 0x21, + HsReady = 0x22, + HsNotReady = 0x23, + HsKick = 0x24, + HsStart = 0x25 + } +} diff --git a/YGOSharp.Network/Enums/GameState.cs b/YGOSharp.Network/Enums/GameState.cs new file mode 100644 index 00000000..8f2999ff --- /dev/null +++ b/YGOSharp.Network/Enums/GameState.cs @@ -0,0 +1,12 @@ +namespace YGOSharp.Network.Enums +{ + public enum GameState + { + Lobby = 0, + Hand = 1, + Starting = 2, + Duel = 3, + End = 4, + Side = 5 + } +} \ No newline at end of file diff --git a/YGOSharp.Network/Enums/PlayerChange.cs b/YGOSharp.Network/Enums/PlayerChange.cs new file mode 100644 index 00000000..7b28ca64 --- /dev/null +++ b/YGOSharp.Network/Enums/PlayerChange.cs @@ -0,0 +1,10 @@ +namespace YGOSharp.Network.Enums +{ + public enum PlayerChange + { + Observe = 0x8, + Ready = 0x9, + NotReady = 0xA, + Leave = 0xB + } +} diff --git a/YGOSharp.Network/Enums/PlayerState.cs b/YGOSharp.Network/Enums/PlayerState.cs new file mode 100644 index 00000000..22091f63 --- /dev/null +++ b/YGOSharp.Network/Enums/PlayerState.cs @@ -0,0 +1,8 @@ +namespace YGOSharp.Network.Enums +{ + public enum PlayerState + { + None = 0, + Response = 1 + } +} \ No newline at end of file diff --git a/YGOSharp.Network/Enums/PlayerType.cs b/YGOSharp.Network/Enums/PlayerType.cs new file mode 100644 index 00000000..9d17d6f5 --- /dev/null +++ b/YGOSharp.Network/Enums/PlayerType.cs @@ -0,0 +1,23 @@ +namespace YGOSharp.Network.Enums +{ + public enum PlayerType + { + Undefined = -1, + Player1 = 0, + Player2 = 1, + Player3 = 2, + Player4 = 3, + Player5 = 4, + Player6 = 5, + Observer = 7, + Host = 0x10, + Red = 11, + Green = 12, + Blue = 13, + BabyBlue = 14, + Pink = 15, + Yellow = 16, + White = 17, + Gray = 18 + } +} \ No newline at end of file diff --git a/YGOSharp.Network/Enums/StocMessage.cs b/YGOSharp.Network/Enums/StocMessage.cs new file mode 100644 index 00000000..a559c4aa --- /dev/null +++ b/YGOSharp.Network/Enums/StocMessage.cs @@ -0,0 +1,26 @@ +namespace YGOSharp.Network.Enums +{ + public enum StocMessage + { + GameMsg = 0x1, + ErrorMsg = 0x2, + SelectHand = 0x3, + SelectTp = 0x4, + HandResult = 0x5, + TpResult = 0x6, + ChangeSide = 0x7, + WaitingSide = 0x8, + CreateGame = 0x11, + JoinGame = 0x12, + TypeChange = 0x13, + LeaveGame = 0x14, + DuelStart = 0x15, + DuelEnd = 0x16, + Replay = 0x17, + TimeLimit = 0x18, + Chat = 0x19, + HsPlayerEnter = 0x20, + HsPlayerChange = 0x21, + HsWatchChange = 0x22 + } +} diff --git a/YGOSharp.Network/NetworkClient.cs b/YGOSharp.Network/NetworkClient.cs new file mode 100644 index 00000000..a7f81785 --- /dev/null +++ b/YGOSharp.Network/NetworkClient.cs @@ -0,0 +1,161 @@ +using System; +using System.Net; +using System.Net.Sockets; + +namespace YGOSharp.Network +{ + public class NetworkClient + { + public event Action Connected; + public event Action Disconnected; + public event Action DataReceived; + + public bool IsConnected { get; private set; } + + public IPAddress RemoteIPAddress + { + get { return _endPoint.Address; } + } + + private const int BufferSize = 4096; + + private Socket _socket; + private IPEndPoint _endPoint; + private bool _isClosed; + private byte[] _receiveBuffer = new byte[BufferSize]; + + public NetworkClient() + { + } + + public NetworkClient(Socket socket) + { + Initialize(socket); + } + + public void Initialize(Socket socket) + { + _endPoint = (IPEndPoint)socket.RemoteEndPoint; + _socket = socket; + IsConnected = true; + Connected?.Invoke(); + } + + public void BeginConnect(IPAddress address, int port) + { + if (!IsConnected && !_isClosed) + { + IsConnected = true; + try + { + _endPoint = new IPEndPoint(address, port); + _socket = new Socket(_endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + _socket.BeginConnect(_endPoint, new AsyncCallback(ConnectCallback), null); + } + catch (Exception ex) + { + Close(ex); + } + } + } + + public void BeginSend(byte[] data) + { + try + { + _socket.BeginSend(data, 0, data.Length, SocketFlags.None, SendCallback, data.Length); + } + catch (Exception ex) + { + Close(ex); + } + } + + public void BeginReceive() + { + try + { + _socket.BeginReceive(_receiveBuffer, 0, _receiveBuffer.Length, SocketFlags.None, ReceiveCallback, null); + } + catch (Exception ex) + { + Close(ex); + } + } + + public void Close(Exception error = null) + { + if (!_isClosed) + { + _isClosed = true; + try + { + if (_socket != null) + { + _socket.Close(); + } + } + catch (Exception ex) + { + ex = new AggregateException(error, ex); + } + IsConnected = false; + Disconnected?.Invoke(error); + } + } + + private void ConnectCallback(IAsyncResult result) + { + try + { + _socket.EndConnect(result); + } + catch (Exception ex) + { + Close(ex); + return; + } + Connected?.Invoke(); + BeginReceive(); + } + + private void SendCallback(IAsyncResult result) + { + try + { + int bytesSent = _socket.EndSend(result); + if (bytesSent != (int)result.AsyncState) + { + Close(); + } + } + catch (Exception ex) + { + Close(ex); + } + } + + private void ReceiveCallback(IAsyncResult result) + { + int bytesRead; + try + { + bytesRead = _socket.EndReceive(result); + } + catch (Exception ex) + { + Close(ex); + return; + } + if (bytesRead == 0) + { + Close(); + return; + } + byte[] data = new byte[bytesRead]; + Array.Copy(_receiveBuffer, data, bytesRead); + DataReceived?.Invoke(data); + BeginReceive(); + } + } +} diff --git a/YGOSharp.Network/NetworkServer.cs b/YGOSharp.Network/NetworkServer.cs new file mode 100644 index 00000000..43fd5ab2 --- /dev/null +++ b/YGOSharp.Network/NetworkServer.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; + +namespace YGOSharp.Network +{ + public class NetworkServer + { + public bool IsListening { get; private set; } + + public event Action ClientConnected; + + private TcpListener _listener; + private bool _isClosed; + + private List _acceptedClients = new List(); + + public NetworkServer(IPAddress address, int port) + { + _listener = new TcpListener(address, port); + } + + public void Start() + { + if (!IsListening && !_isClosed) + { + IsListening = true; + _listener.Start(); + BeginAcceptSocket(); + } + } + + public void Close() + { + if (!_isClosed) + { + _isClosed = true; + IsListening = false; + _listener.Stop(); + } + } + + public void Update() + { + List clients = new List(); + lock (_acceptedClients) + { + clients.AddRange(_acceptedClients); + _acceptedClients.Clear(); + } + foreach (NetworkClient client in clients) + { + ClientConnected?.Invoke(client); + } + } + + private void BeginAcceptSocket() + { + try + { + _listener.BeginAcceptSocket(AcceptSocketCallback, null); + } + catch (Exception) + { + Close(); + } + } + + private void AcceptSocketCallback(IAsyncResult result) + { + try + { + Socket socket = _listener.EndAcceptSocket(result); + NetworkClient client = new NetworkClient(socket); + lock (_acceptedClients) + { + _acceptedClients.Add(client); + } + BeginAcceptSocket(); + } + catch (Exception) + { + Close(); + } + } + } +} diff --git a/YGOSharp.Network/Utils/BinaryExtensions.cs b/YGOSharp.Network/Utils/BinaryExtensions.cs new file mode 100644 index 00000000..213e4a64 --- /dev/null +++ b/YGOSharp.Network/Utils/BinaryExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.IO; +using System.Text; + +namespace YGOSharp.Network.Utils +{ + public static class BinaryExtensions + { + public static void WriteUnicode(this BinaryWriter writer, string text, int len) + { + byte[] unicode = Encoding.Unicode.GetBytes(text); + byte[] result = new byte[len * 2]; + int max = len * 2 - 2; + Array.Copy(unicode, result, unicode.Length > max ? max : unicode.Length); + writer.Write(result); + } + + public static string ReadUnicode(this BinaryReader reader, int len) + { + byte[] unicode = reader.ReadBytes(len * 2); + string text = Encoding.Unicode.GetString(unicode); + int index = text.IndexOf('\0'); + if (index > 0) text = text.Substring(0, index); + return text; + } + + public static byte[] ReadToEnd(this BinaryReader reader) + { + return reader.ReadBytes((int)(reader.BaseStream.Length - reader.BaseStream.Position)); + } + } +} diff --git a/YGOSharp.Network/YGOClient.cs b/YGOSharp.Network/YGOClient.cs new file mode 100644 index 00000000..40da886a --- /dev/null +++ b/YGOSharp.Network/YGOClient.cs @@ -0,0 +1,42 @@ +using System.IO; +using YGOSharp.Network.Enums; + +namespace YGOSharp.Network +{ + public class YGOClient : BinaryClient + { + public YGOClient() + : base(new NetworkClient()) + { + } + + public YGOClient(NetworkClient client) + : base(client) + { + } + + public void Send(BinaryWriter writer) + { + Send(((MemoryStream)writer.BaseStream).ToArray()); + } + + public void Send(CtosMessage message) + { + using (BinaryWriter writer = new BinaryWriter(new MemoryStream())) + { + writer.Write((byte)message); + Send(writer); + } + } + + public void Send(CtosMessage message, int value) + { + using (BinaryWriter writer = new BinaryWriter(new MemoryStream())) + { + writer.Write((byte)message); + writer.Write(value); + Send(writer); + } + } + } +} diff --git a/YGOSharp.OCGWrapper.Enums/CardAttribute.cs b/YGOSharp.OCGWrapper.Enums/CardAttribute.cs new file mode 100644 index 00000000..a8d9b433 --- /dev/null +++ b/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/YGOSharp.OCGWrapper.Enums/CardLinkMarker.cs b/YGOSharp.OCGWrapper.Enums/CardLinkMarker.cs new file mode 100644 index 00000000..434cd8c3 --- /dev/null +++ b/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/YGOSharp.OCGWrapper.Enums/CardLocation.cs b/YGOSharp.OCGWrapper.Enums/CardLocation.cs new file mode 100644 index 00000000..3155eda0 --- /dev/null +++ b/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/YGOSharp.OCGWrapper.Enums/CardPosition.cs b/YGOSharp.OCGWrapper.Enums/CardPosition.cs new file mode 100644 index 00000000..147f326f --- /dev/null +++ b/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/YGOSharp.OCGWrapper.Enums/CardRace.cs b/YGOSharp.OCGWrapper.Enums/CardRace.cs new file mode 100644 index 00000000..c1f104ab --- /dev/null +++ b/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/YGOSharp.OCGWrapper.Enums/CardType.cs b/YGOSharp.OCGWrapper.Enums/CardType.cs new file mode 100644 index 00000000..b5f44d9e --- /dev/null +++ b/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/YGOSharp.OCGWrapper.Enums/DuelPhase.cs b/YGOSharp.OCGWrapper.Enums/DuelPhase.cs new file mode 100644 index 00000000..f95ce874 --- /dev/null +++ b/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/YGOSharp.OCGWrapper.Enums/GameMessage.cs b/YGOSharp.OCGWrapper.Enums/GameMessage.cs new file mode 100644 index 00000000..14fc59ca --- /dev/null +++ b/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/YGOSharp.OCGWrapper.Enums/Query.cs b/YGOSharp.OCGWrapper.Enums/Query.cs new file mode 100644 index 00000000..31d32eb6 --- /dev/null +++ b/YGOSharp.OCGWrapper.Enums/Query.cs @@ -0,0 +1,29 @@ +namespace YGOSharp.OCGWrapper.Enums +{ + public enum Query + { + 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 + } +} \ No newline at end of file diff --git a/YGOSharp.OCGWrapper/Card.cs b/YGOSharp.OCGWrapper/Card.cs new file mode 100644 index 00000000..48b92505 --- /dev/null +++ b/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/YGOSharp.OCGWrapper/CardsManager.cs b/YGOSharp.OCGWrapper/CardsManager.cs new file mode 100644 index 00000000..0fef6f72 --- /dev/null +++ b/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/YGOSharp.OCGWrapper/NamedCard.cs b/YGOSharp.OCGWrapper/NamedCard.cs new file mode 100644 index 00000000..effc4983 --- /dev/null +++ b/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/YGOSharp.OCGWrapper/NamedCardsManager.cs b/YGOSharp.OCGWrapper/NamedCardsManager.cs new file mode 100644 index 00000000..56265e52 --- /dev/null +++ b/YGOSharp.OCGWrapper/NamedCardsManager.cs @@ -0,0 +1,31 @@ +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; + + public static void Init(string databaseFullPath) + { + //_cards = new Dictionary(); + } + + 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);*/ + } + } +} \ No newline at end of file diff --git a/libWindbot.csproj b/libWindbot.csproj new file mode 100644 index 00000000..e37b2baf --- /dev/null +++ b/libWindbot.csproj @@ -0,0 +1,173 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {5BCF813B-671E-4B2C-B01E-3EACDC536B65} + {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {9ef11e43-1701-4396-8835-8392d57abb70} + Library + Properties + libWindbot + libWindbot + 512 + Off + false + v7.1 + + + true + full + false + bin\Debug\ + DEBUG;TRACE;LIBWINDBOT + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE;LIBWINDBOT + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.4.0 + + + + + + + set E4K_OUTPUT="$(SolutionDir)output" +if exist %25E4K_OUTPUT%25 rmdir /S /Q %25E4K_OUTPUT%25 +"$(NuGetPackageRoot)embeddinator-4000\0.4.0\tools\Embeddinator-4000.exe" "$(TargetPath)" --gen=Java --platform=Android --outdir=%25E4K_OUTPUT%25 -c -v + + + + \ No newline at end of file From b1cdc7caf3bdbe121b4f232f50a3fdbd171e513f Mon Sep 17 00:00:00 2001 From: edo9300 Date: Mon, 16 Mar 2020 20:08:34 +0100 Subject: [PATCH 09/83] Remove BotWrapper project --- WindBot.sln | 7 ------- libWindbot.csproj | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/WindBot.sln b/WindBot.sln index 2cb3b72e..3353d3b7 100644 --- a/WindBot.sln +++ b/WindBot.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 15.0.28307.960 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindBot", "WindBot.csproj", "{3E7FAF67-A27D-4A61-B161-93AD4414183E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotWrapper", "BotWrapper\BotWrapper.csproj", "{0665CA3B-C14F-40EC-ABFB-AD46A695F5A3}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "libWindbot", "libWindbot.csproj", "{5BCF813B-671E-4B2C-B01E-3EACDC536B65}" EndProject Global @@ -23,11 +21,6 @@ Global {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Release|Any CPU.ActiveCfg = Release|x86 {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Release|x86.ActiveCfg = Release|x86 {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Release|x86.Build.0 = Release|x86 - {0665CA3B-C14F-40EC-ABFB-AD46A695F5A3}.Debug|Any CPU.ActiveCfg = Debug|x86 - {0665CA3B-C14F-40EC-ABFB-AD46A695F5A3}.Debug|x86.ActiveCfg = Debug|x86 - {0665CA3B-C14F-40EC-ABFB-AD46A695F5A3}.Debug|x86.Build.0 = Debug|x86 - {0665CA3B-C14F-40EC-ABFB-AD46A695F5A3}.Release|Any CPU.ActiveCfg = Release|x86 - {0665CA3B-C14F-40EC-ABFB-AD46A695F5A3}.Release|x86.ActiveCfg = Release|x86 {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Debug|Any CPU.Build.0 = Debug|Any CPU {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Debug|x86.ActiveCfg = Debug|Any CPU diff --git a/libWindbot.csproj b/libWindbot.csproj index e37b2baf..a6edf9c3 100644 --- a/libWindbot.csproj +++ b/libWindbot.csproj @@ -118,8 +118,8 @@ + - From 651569ae3a05914abcdd2a69fe8f370946c0578e Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Mon, 30 Mar 2020 20:52:26 +1300 Subject: [PATCH 10/83] Fix Tribute Summoning crashing, and SelectCard(ClientCard) not working The cause was that, when tributing, InternalOnSelectCard would try to read from the packet to initialize the LocationInfo twice, so it would try to read beyond the end of the stream and crash, and when not tributing, it wouldn't read the LocationInfo from the packet at all, so it couldn't find any specific card. Make it read the right amount once in each case. --- Game/GameBehavior.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Game/GameBehavior.cs b/Game/GameBehavior.cs index 7f9edca4..9281d8be 100644 --- a/Game/GameBehavior.cs +++ b/Game/GameBehavior.cs @@ -933,7 +933,7 @@ private void InternalOnSelectCard(BinaryReader packet, Func, i for (int i = 0; i < count; ++i) { int id = packet.ReadInt32(); - LocationInfo info = tribute ? new LocationInfo(packet, _duel.IsFirst) : new LocationInfo(); + LocationInfo info = !tribute ? new LocationInfo(packet, _duel.IsFirst) : new LocationInfo(); if (tribute) { info.controler = packet.ReadByte(); From 4c12c06ace47e189e51688fbea744028088aa862 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sun, 29 Mar 2020 22:37:08 +1300 Subject: [PATCH 11/83] Fix Windbot build I found this fix at https://stackoverflow.com/a/60037713 --- WindBot.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WindBot.csproj b/WindBot.csproj index e714a41e..9e3f48a8 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 From 0e84a429b36fb0159cb3cd5f391488aba6e39bbd Mon Sep 17 00:00:00 2001 From: kevinlul <6320810+kevinlul@users.noreply.github.com> Date: Tue, 31 Mar 2020 17:02:06 -0400 Subject: [PATCH 12/83] [Travis] Build desktop WindBot only --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 032ddc5b..1df48999 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ 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 +- dotnet build WindBot.csproj --configuration=Release after_success: - cd bin - mv Release WindBot From 3f3b31d4e95520383409442d0f8f6f6fbbdc7574 Mon Sep 17 00:00:00 2001 From: kevinlul <6320810+kevinlul@users.noreply.github.com> Date: Wed, 1 Apr 2020 23:29:05 -0400 Subject: [PATCH 13/83] Add authors to license --- LICENSE | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index d4118327..3b2a84ce 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, ScienceBall +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 From 57c72a8997d803e2e798fa25a9a7a038f848b27b Mon Sep 17 00:00:00 2001 From: edo9300 Date: Thu, 2 Apr 2020 16:47:02 +0200 Subject: [PATCH 14/83] Use new joinGame packet structure --- Game/GameClient.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Game/GameClient.cs b/Game/GameClient.cs index ee299203..c5884bbe 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; @@ -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); } From 64e0b64ae14114f5767b0949a2b39383454baa28 Mon Sep 17 00:00:00 2001 From: kevinlul <6320810+kevinlul@users.noreply.github.com> Date: Thu, 2 Apr 2020 12:19:16 -0400 Subject: [PATCH 15/83] Fix ScienceBall's name --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 3b2a84ce..896d34ed 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ WindBot Ignite, a C# AI for Project Ignis: EDOPro --------------------------------------------------- Copyright (C) 2019-2020 Edoardo Lolletti (edo9300) - Kevin Lu, ScienceBall + Kevin Lu, Gareth Jones See version history at https://github.com/ProjectIgnis/windbot for a full list of contributors. From 92fd4224e761c7373ec5c0d6cb91132108932fa0 Mon Sep 17 00:00:00 2001 From: kevinlul <6320810+kevinlul@users.noreply.github.com> Date: Thu, 2 Apr 2020 22:38:56 -0400 Subject: [PATCH 16/83] Fix joining version --- Game/GameClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Game/GameClient.cs b/Game/GameClient.cs index c5884bbe..b24619a7 100644 --- a/Game/GameClient.cs +++ b/Game/GameClient.cs @@ -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() From 9ecfd39764077ce5356eaaccd484e776a4e9f65e Mon Sep 17 00:00:00 2001 From: kevinlul <6320810+kevinlul@users.noreply.github.com> Date: Thu, 2 Apr 2020 22:48:21 -0400 Subject: [PATCH 17/83] Set fallback version to 38.0.0 (8.0) --- WindBotInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WindBotInfo.cs b/WindBotInfo.cs index 916108ef..345401c7 100644 --- a/WindBotInfo.cs +++ b/WindBotInfo.cs @@ -22,7 +22,7 @@ public WindBotInfo() Host = "127.0.0.1"; Port = 7911; HostInfo = ""; - Version = 0x134b; + Version = 38|8<<16; Hand = 0; Debug = false; Chat = true; From 316c0d6a6473f47d0112a3efebe2fadd496dbcbb Mon Sep 17 00:00:00 2001 From: kevinlul <6320810+kevinlul@users.noreply.github.com> Date: Thu, 2 Apr 2020 22:59:08 -0400 Subject: [PATCH 18/83] Update handshake --- Game/GameBehavior.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Game/GameBehavior.cs b/Game/GameBehavior.cs index 9281d8be..0ce50294 100644 --- a/Game/GameBehavior.cs +++ b/Game/GameBehavior.cs @@ -185,7 +185,7 @@ private void OnJoinGame(BinaryReader packet) /*int draw_count =*/ packet.ReadByte(); /*int time_limit =*/ packet.ReadUInt16(); /*align =*/ packet.ReadBytes(4); - const ulong SERVER_HANDSHAKE = 4903489263569811227; + const ulong SERVER_HANDSHAKE = 4680591157758091777; ulong handshake = packet.ReadUInt64(); if (handshake != SERVER_HANDSHAKE) { From 7651d1d45a2b6a8230b16bd30bdd393065d7a295 Mon Sep 17 00:00:00 2001 From: edo9300 Date: Fri, 10 Apr 2020 15:04:38 +0200 Subject: [PATCH 19/83] Update AIUtil.GetStringId --- Game/AI/AIUtil.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Game/AI/AIUtil.cs b/Game/AI/AIUtil.cs index eeeb8bf0..96ea8b5b 100644 --- a/Game/AI/AIUtil.cs +++ b/Game/AI/AIUtil.cs @@ -231,9 +231,9 @@ public ClientCard GetPZone(int player, int id) } } - public int GetStringId(int id, int option) + public long GetStringId(int id, int option) { - return id * 16 + option; + return (option & 0xfffff) | id << 20; } public bool IsTurn1OrMain2() From 2afb80a962c93f7a8fdc82c5fbfd5d745d14fe96 Mon Sep 17 00:00:00 2001 From: edo9300 Date: Fri, 10 Apr 2020 16:34:25 +0200 Subject: [PATCH 20/83] Fix instances of select hint --- Game/AI/Decks/AltergeistExecutor.cs | 8 +- Game/AI/Decks/BlueEyesExecutor.cs | 2 +- Game/AI/Decks/DoEveryThingExecutor.cs | 2 +- Game/AI/Decks/DragunExecutor.cs | 2 +- Game/AI/Decks/Level8Executor.cs | 2 +- Game/AI/Decks/OrcustExecutor.cs | 2312 ++++++++++++------------- Game/AI/Decks/SalamangreatExecutor.cs | 2 +- Game/AI/Executor.cs | 6 +- Game/GameAI.cs | 22 +- Game/GameBehavior.cs | 8 +- 10 files changed, 1183 insertions(+), 1183 deletions(-) diff --git a/Game/AI/Decks/AltergeistExecutor.cs b/Game/AI/Decks/AltergeistExecutor.cs index cc4f0daf..2a517411 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 @@ -2745,10 +2745,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 +2817,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/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/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/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/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/Executor.cs b/Game/AI/Executor.cs index b4c5bc4c..3f3b4980 100644 --- a/Game/AI/Executor.cs +++ b/Game/AI/Executor.cs @@ -106,13 +106,13 @@ 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) + 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, int hint, bool mode) + public virtual IList OnSelectSum(IList cards, int sum, int min, int max, long hint, bool mode) { // For overriding return null; @@ -170,7 +170,7 @@ public virtual int OnSelectOption(IList options) return -1; } - public virtual int OnSelectPlace(int cardId, int player, CardLocation location, int available) + public virtual int OnSelectPlace(long cardId, int player, CardLocation location, int available) { // For overriding return 0; diff --git a/Game/GameAI.cs b/Game/GameAI.cs index 1249e3d9..54e1ea1c 100644 --- a/Game/GameAI.cs +++ b/Game/GameAI.cs @@ -223,13 +223,13 @@ public BattlePhaseAction OnSelectBattleCmd(BattlePhase battle) /// 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) + public IList OnSelectCard(IList cards, int min, int max, long 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; + 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); @@ -479,7 +479,7 @@ public int OnSelectOption(IList options) return 0; // Always select the first option. } - public int OnSelectPlace(int cardId, int player, CardLocation location, int available) + public int OnSelectPlace(long cardId, int player, CardLocation location, int available) { int selector_selected = m_place; m_place = 0; @@ -526,10 +526,10 @@ public CardPosition OnSelectPosition(int cardId, IList positions) /// Maximum cards. /// True for exact equal. /// - public IList OnSelectSum(IList cards, int sum, int min, int max, int hint, bool mode) + public IList OnSelectSum(IList cards, int sum, int min, int max, long hint, bool mode) { - const int HINTMSG_RELEASE = 500; - const int HINTMSG_SMATERIAL = 512; + const long HINTMSG_RELEASE = 500; + const long HINTMSG_SMATERIAL = 512; IList selected = Executor.OnSelectSum(cards, sum, min, max, hint, mode); if (selected != null) @@ -695,7 +695,7 @@ public IList OnSelectSum(IList cards, int sum, int min, /// 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) + public IList OnSelectTribute(IList cards, int min, int max, long hint, bool cancelable) { // Always choose the minimum and lowest atk. List sorted = new List(); diff --git a/Game/GameBehavior.cs b/Game/GameBehavior.cs index 0ce50294..881fde46 100644 --- a/Game/GameBehavior.cs +++ b/Game/GameBehavior.cs @@ -26,7 +26,7 @@ public class GameBehavior private Duel _duel; private int _hand; private bool _debug; - private int _select_hint; + private long _select_hint; private GameMessage _lastMessage; public class LocationInfo { @@ -378,7 +378,7 @@ private void OnHint(BinaryReader packet) { int type = packet.ReadByte(); int player = packet.ReadByte(); - int data = packet.ReadInt32(); + long data = packet.ReadInt64(); if (type == 1) // HINT_EVENT { if (data == 24) // battling @@ -921,7 +921,7 @@ private void OnSelectBattleCmd(BinaryReader packet) Connection.Send(CtosMessage.Response, _ai.OnSelectBattleCmd(battle).ToValue()); } - private void InternalOnSelectCard(BinaryReader packet, Func, int, int, int, bool, IList> func, bool tribute = false) + private void InternalOnSelectCard(BinaryReader packet, Func, int, int, long, bool, IList> func, bool tribute = false) { int player = packet.ReadByte(); bool cancelable = packet.ReadByte() != 0; @@ -985,7 +985,7 @@ private void InternalOnSelectCard(BinaryReader packet, Func, i Connection.Send(reply); } - private void InternalOnSelectUnselectCard(BinaryReader packet, Func, int, int, int, bool, IList> func) + private void InternalOnSelectUnselectCard(BinaryReader packet, Func, int, int, long, bool, IList> func) { int player = packet.ReadByte(); bool finishable = packet.ReadByte() != 0; From 14db8feb8c89ba399b488ff5cb06dfef2c6f4263 Mon Sep 17 00:00:00 2001 From: ScienceBall <60090468+ScienceBall@users.noreply.github.com> Date: Tue, 14 Apr 2020 12:36:36 +1200 Subject: [PATCH 21/83] Check for MP2 and EP on end BP to always switch to the correct phase (#8) This adds some support for Speed Duels, Terminal World, and other cases when Main Phase 2 is not a thing. --- Game/GameAI.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Game/GameAI.cs b/Game/GameAI.cs index 54e1ea1c..634dbc38 100644 --- a/Game/GameAI.cs +++ b/Game/GameAI.cs @@ -184,7 +184,10 @@ public BattlePhaseAction OnSelectBattleCmd(BattlePhase battle) return result; if (attackers.Count == 0) - return ToMainPhase2(); + { + if (battle.CanMainPhaseTwo) return ToMainPhase2(); + else if (battle.CanEndPhase) return ToEndPhase(); + } if (defenders.Count == 0) { @@ -208,10 +211,10 @@ public BattlePhaseAction OnSelectBattleCmd(BattlePhase battle) } } - if (!battle.CanMainPhaseTwo) + if (!battle.CanMainPhaseTwo && !battle.CanEndPhase) return Attack(attackers[0], (defenders.Count == 0) ? null : defenders[0]); - return ToMainPhase2(); + return battle.CanMainPhaseTwo ? ToMainPhase2() : ToEndPhase(); } /// From 6e01d0aaf0ffb12a6e60dfa712936ace818588d3 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Mon, 13 Apr 2020 02:23:19 +1200 Subject: [PATCH 22/83] Allow absolute path to cards database Useful for debugging from Visual Studio, making it easier to specify the DbPath in the command line arguments --- Program.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Program.cs b/Program.cs index 9245c83d..a3adb8e4 100644 --- a/Program.cs +++ b/Program.cs @@ -50,11 +50,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)) From a6913c51bdf3b02e95d40dbecbf5d15cd31fc9c1 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Mon, 13 Apr 2020 20:04:04 +1200 Subject: [PATCH 23/83] For Debug configuration, build desktop WindBot only Previously it was building libWindbot and not desktop Windbot --- WindBot.sln | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WindBot.sln b/WindBot.sln index 3353d3b7..77bb63f0 100644 --- a/WindBot.sln +++ b/WindBot.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.960 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30002.166 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindBot", "WindBot.csproj", "{3E7FAF67-A27D-4A61-B161-93AD4414183E}" EndProject @@ -16,13 +16,13 @@ Global EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Debug|Any CPU.ActiveCfg = Debug|x86 + {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Debug|Any CPU.Build.0 = Debug|x86 {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Debug|x86.ActiveCfg = Debug|x86 {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Debug|x86.Build.0 = Debug|x86 {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Release|Any CPU.ActiveCfg = Release|x86 {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Release|x86.ActiveCfg = Release|x86 {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Release|x86.Build.0 = Release|x86 {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Debug|Any CPU.Build.0 = Debug|Any CPU {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Debug|x86.ActiveCfg = Debug|Any CPU {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Debug|x86.Build.0 = Debug|Any CPU {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Release|Any CPU.ActiveCfg = Release|Any CPU From 562140cd33c24d963be979eed8605db0269283f2 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Mon, 13 Apr 2020 20:28:33 +1200 Subject: [PATCH 24/83] Fix infinite loop when trying to Tribute Summon If the AI went second, and it tried to Tribute Summon and recieved SelectTribute instead of SelectUnselect (both can happen depending on gamestate), then it would read from the wrong player's field when matching the list of possible tributes (from the packet) to ClientCards. So it would find zero valid tributes, and cancel the Tribute Summon, unless the opponent coincidentally had a monster in their same-numbered monster zone(s) as the possible tribute(s). If the AI cancelled the Tribute Summon, the conditions that made the Executor try to Tribute Summon would be unchanged, so it would try to Tribute Summon again, and go into an infinite loop. With each loop, it would send a chat message saying that it was summoning the monster. Fixed by copying the logic from LocationInfo.Read. Fixes #6 --- Game/GameBehavior.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Game/GameBehavior.cs b/Game/GameBehavior.cs index 881fde46..47c0529d 100644 --- a/Game/GameBehavior.cs +++ b/Game/GameBehavior.cs @@ -937,6 +937,8 @@ private void InternalOnSelectCard(BinaryReader packet, Func, i if (tribute) { info.controler = packet.ReadByte(); + if (!_duel.IsFirst) + info.controler = 1 - info.controler; info.location = packet.ReadByte(); info.sequence = packet.ReadInt32(); packet.ReadByte(); From fac265405893a140ba7e56a9a473b92a464a123c Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Tue, 14 Apr 2020 13:39:15 +1200 Subject: [PATCH 25/83] Build both libWindbot and WindBot projects --- WindBot.sln | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WindBot.sln b/WindBot.sln index 77bb63f0..29ff5f16 100644 --- a/WindBot.sln +++ b/WindBot.sln @@ -20,9 +20,11 @@ Global {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Debug|x86.ActiveCfg = Debug|x86 {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Debug|x86.Build.0 = Debug|x86 {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Release|Any CPU.ActiveCfg = Release|x86 + {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Release|Any CPU.Build.0 = Release|x86 {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Release|x86.ActiveCfg = Release|x86 {3E7FAF67-A27D-4A61-B161-93AD4414183E}.Release|x86.Build.0 = Release|x86 {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Debug|Any CPU.Build.0 = Debug|Any CPU {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Debug|x86.ActiveCfg = Debug|Any CPU {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Debug|x86.Build.0 = Debug|Any CPU {5BCF813B-671E-4B2C-B01E-3EACDC536B65}.Release|Any CPU.ActiveCfg = Release|Any CPU From 7ef151b89fb7a8875aa89448c8334764dd900b57 Mon Sep 17 00:00:00 2001 From: Wind2009-Louse Date: Thu, 16 Apr 2020 16:26:36 +0800 Subject: [PATCH 26/83] Fix SkyStriker's hand effect (#120) --- Game/AI/Decks/SkyStrikerExecutor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Game/AI/Decks/SkyStrikerExecutor.cs b/Game/AI/Decks/SkyStrikerExecutor.cs index 05d9c156..51e1688f 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); From c515c111d7d53073814142a8bc987243fe6fe853 Mon Sep 17 00:00:00 2001 From: Wind2009-Louse Date: Thu, 16 Apr 2020 16:26:50 +0800 Subject: [PATCH 27/83] update enum (#119) --- Game/AI/Enums/Floodgate.cs | 20 ++++++++++++++++++- Game/AI/Enums/InvincibleMonster.cs | 4 +++- ...houldBeDisabledBeforeItUseEffectMonster.cs | 6 ++++++ Game/AI/Enums/ShouldNotBeMonsterTarget.cs | 3 ++- Game/AI/Enums/ShouldNotBeTarget.cs | 1 + 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Game/AI/Enums/Floodgate.cs b/Game/AI/Enums/Floodgate.cs index fa300a30..22075fd5 100644 --- a/Game/AI/Enums/Floodgate.cs +++ b/Game/AI/Enums/Floodgate.cs @@ -82,6 +82,24 @@ 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 } } diff --git a/Game/AI/Enums/InvincibleMonster.cs b/Game/AI/Enums/InvincibleMonster.cs index 11561562..bd9bc680 100644 --- a/Game/AI/Enums/InvincibleMonster.cs +++ b/Game/AI/Enums/InvincibleMonster.cs @@ -64,6 +64,8 @@ public enum InvincibleMonster NumberF0UtopicFutureFutureSlash = 43490025, NumberF0UtopicFuture = 65305468, GoukiTheGiantOgre = 47946130, - BorrelswordDragon = 85289965 + BorrelswordDragon = 85289965, + NumberF0UtopicFutureDragon = 26973555, + BorrelendDragon = 98630720 } } diff --git a/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs b/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs index b219d850..3f11d700 100644 --- a/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs +++ b/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs @@ -44,6 +44,12 @@ public enum ShouldBeDisabledBeforeItUseEffectMonster PaladinofDarkDragon = 71408082, PaladinofPhotonDragon = 85346853, TwinPhotonLizard = 29455728, + TimeThiefRegulator = 19891131, + MathmechNabla = 53577438, + NidhoggGeneraiderBossofIce = 49275969, + HoarrGeneraiderBossofRumbling = 68199168, + RedFamiliar = 8372133, + AccesscodeTalker = 86066372, CosmoBrain = 85679527, ShiranuiSolitaire = 94801854, diff --git a/Game/AI/Enums/ShouldNotBeMonsterTarget.cs b/Game/AI/Enums/ShouldNotBeMonsterTarget.cs index 3a9b33d4..b5cb0f6f 100644 --- a/Game/AI/Enums/ShouldNotBeMonsterTarget.cs +++ b/Game/AI/Enums/ShouldNotBeMonsterTarget.cs @@ -13,6 +13,7 @@ public enum ShouldNotBeMonsterTarget CXyzSimontheGreatMoralLeader = 41147577, PaleozoicAnomalocaris = 61307542, PaleozoicOpabinia = 37649320, - BorreloadDragon = 31833038 + BorreloadDragon = 31833038, + BorrelendDragon = 98630720 } } diff --git a/Game/AI/Enums/ShouldNotBeTarget.cs b/Game/AI/Enums/ShouldNotBeTarget.cs index 58fc6167..7adcc587 100644 --- a/Game/AI/Enums/ShouldNotBeTarget.cs +++ b/Game/AI/Enums/ShouldNotBeTarget.cs @@ -49,5 +49,6 @@ public enum ShouldNotBeTarget Blackwing_FullArmoredWing = 54082269, DragunofRedEyes = 37818794, RedEyesBDragon = 74677422, // sometimes the name of DragunofRedEyes will be changed to RedEyesBDragon + TheArrivalCyberseIgnister = 11738489 } } From ce5cb55ce236b23efa9638765d507cf585f7efb8 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Thu, 16 Apr 2020 16:53:57 +0800 Subject: [PATCH 28/83] fix hand chaining card unknown --- Game/GameBehavior.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Game/GameBehavior.cs b/Game/GameBehavior.cs index 043efc5d..a796ae62 100644 --- a/Game/GameBehavior.cs +++ b/Game/GameBehavior.cs @@ -683,12 +683,14 @@ private void OnPosChange(BinaryReader packet) private void OnChaining(BinaryReader packet) { - packet.ReadInt32(); // card id + int cardId = packet.ReadInt32(); int pcc = GetLocalPlayer(packet.ReadByte()); int pcl = packet.ReadByte(); int pcs = packet.ReadSByte(); int subs = packet.ReadSByte(); ClientCard card = _duel.GetCard(pcc, pcl, pcs, subs); + 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)"); From 4f91bc034d864210881ad8ddc4cd564e0df4332b Mon Sep 17 00:00:00 2001 From: mercury233 Date: Thu, 16 Apr 2020 17:00:56 +0800 Subject: [PATCH 29/83] always activate Santa Claws --- Game/AI/DefaultExecutor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Game/AI/DefaultExecutor.cs b/Game/AI/DefaultExecutor.cs index 6c5be767..00d38ebf 100644 --- a/Game/AI/DefaultExecutor.cs +++ b/Game/AI/DefaultExecutor.cs @@ -38,6 +38,8 @@ protected class _CardId 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; @@ -87,6 +89,7 @@ protected DefaultExecutor(GameAI ai, Duel duel) : base(ai, duel) { AddExecutor(ExecutorType.Activate, _CardId.ChickenGame, DefaultChickenGame); + AddExecutor(ExecutorType.Activate, _CardId.SantaClaws); } /// From c17b9771c2ca78a7e0654e76e95e05947944bb67 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Thu, 16 Apr 2020 17:08:26 +0800 Subject: [PATCH 30/83] don't attack Hamon the Lord --- Game/AI/DefaultExecutor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Game/AI/DefaultExecutor.cs b/Game/AI/DefaultExecutor.cs index 00d38ebf..3eb0b663 100644 --- a/Game/AI/DefaultExecutor.cs +++ b/Game/AI/DefaultExecutor.cs @@ -46,6 +46,7 @@ protected class _CardId 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; @@ -200,6 +201,9 @@ public override bool OnPreBattleBetween(ClientCard attacker, ClientCard defender 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; From 2468bed338b1e8fe3d3bf78f898cd2f503677f22 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Thu, 16 Apr 2020 21:34:21 +0800 Subject: [PATCH 31/83] add PureWinds deck --- Decks/AI_PureWinds.ydk | 74 ++ Game/AI/Decks/PureWindsExecutor.cs | 1055 ++++++++++++++++++++++++++++ Game/AI/Enums/DangerousMonster.cs | 1 + WindBot.csproj | 1 + 4 files changed, 1131 insertions(+) create mode 100644 Decks/AI_PureWinds.ydk create mode 100644 Game/AI/Decks/PureWindsExecutor.cs 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/Game/AI/Decks/PureWindsExecutor.cs b/Game/AI/Decks/PureWindsExecutor.cs new file mode 100644 index 00000000..a4f95f16 --- /dev/null +++ b/Game/AI/Decks/PureWindsExecutor.cs @@ -0,0 +1,1055 @@ +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; + //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.SolemnStrike, base.DefaultSolemnStrike); + AddExecutor(ExecutorType.Activate, CardId.SolemnWarning, base.DefaultSolemnWarning); + AddExecutor(ExecutorType.Activate, CardId.ForbiddenChalice, ForbiddenChaliceeff); + AddExecutor(ExecutorType.Activate, CardId.CrystalWingSynchroDragon, CrystalWingSynchroDragoneff); + 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); + } + 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 ((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); + 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/Enums/DangerousMonster.cs b/Game/AI/Enums/DangerousMonster.cs index 996b0062..66276534 100644 --- a/Game/AI/Enums/DangerousMonster.cs +++ b/Game/AI/Enums/DangerousMonster.cs @@ -23,5 +23,6 @@ public enum DangerousMonster ElShaddollConstruct = 20366274, ZushintheSleepingGiant = 67547370, Heart_eartHDragon = 97403510, + DaigustoSphreeze = 29552709, } } diff --git a/WindBot.csproj b/WindBot.csproj index 1eada237..70863945 100644 --- a/WindBot.csproj +++ b/WindBot.csproj @@ -68,6 +68,7 @@ + From b115f4a535cf9fe3b29ba16ddc5365188b85675f Mon Sep 17 00:00:00 2001 From: Sun <24259556+sunx2@users.noreply.github.com> Date: Thu, 16 Apr 2020 21:55:48 +0800 Subject: [PATCH 32/83] Added time thief and Mathmech deck (#122) Co-authored-by: mercury233 --- Decks/AI_Mathmech.ydk | 59 +++++ Decks/AI_Timethief.ydk | 59 +++++ Game/AI/Decks/MathMechExecutor.cs | 369 +++++++++++++++++++++++++++++ Game/AI/Decks/TimeThiefExecutor.cs | 285 ++++++++++++++++++++++ WindBot.csproj | 2 + 5 files changed, 774 insertions(+) create mode 100644 Decks/AI_Mathmech.ydk create mode 100644 Decks/AI_Timethief.ydk create mode 100644 Game/AI/Decks/MathMechExecutor.cs create mode 100644 Game/AI/Decks/TimeThiefExecutor.cs 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_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/Game/AI/Decks/MathMechExecutor.cs b/Game/AI/Decks/MathMechExecutor.cs new file mode 100644 index 00000000..0d031ea4 --- /dev/null +++ b/Game/AI/Decks/MathMechExecutor.cs @@ -0,0 +1,369 @@ +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; + } + } + + } + +} \ No newline at end of file diff --git a/Game/AI/Decks/TimeThiefExecutor.cs b/Game/AI/Decks/TimeThiefExecutor.cs new file mode 100644 index 00000000..80bc617a --- /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); + + //special summons + AddExecutor(ExecutorType.SpSummon,Monsters.PhotonTrasher ,SummonToDef); + AddExecutor(ExecutorType.SpSummon,Monsters.TimeThiefRegulator, SummonToDef); + AddExecutor(ExecutorType.SpSummon,Monsters.TimeThiefWinder, SummonToDef); + AddExecutor(ExecutorType.SpSummon,Monsters.PerformTrickClown, SummonToDef); + AddExecutor(ExecutorType.SpSummon,Monsters.TimeThiefCronocorder, SummonToDef); + AddExecutor(ExecutorType.SpSummon,Monsters.TimeThiefBezelShip, SummonToDef); + + //normal summons + AddExecutor(ExecutorType.Summon,Monsters.TimeThiefRegulator ); + 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.PhotonTrasher); + AddExecutor(ExecutorType.Activate,Monsters.TimeThiefCronocorder); + AddExecutor(ExecutorType.Activate,Monsters.PerformTrickClown); + 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; + } + + + + } + +} diff --git a/WindBot.csproj b/WindBot.csproj index 70863945..dc9df2c1 100644 --- a/WindBot.csproj +++ b/WindBot.csproj @@ -68,6 +68,7 @@ + @@ -78,6 +79,7 @@ + From 9442096f954a31266639397b5bed0fb27ed104c5 Mon Sep 17 00:00:00 2001 From: Wind2009-Louse Date: Fri, 17 Apr 2020 21:12:51 +0800 Subject: [PATCH 33/83] Executor update (#123) --- Game/AI/Decks/MathMechExecutor.cs | 10 ++++++++++ Game/AI/Decks/PureWindsExecutor.cs | 2 +- Game/AI/Decks/TimeThiefExecutor.cs | 28 ++++++++++++++-------------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/Game/AI/Decks/MathMechExecutor.cs b/Game/AI/Decks/MathMechExecutor.cs index 0d031ea4..4b947f5f 100644 --- a/Game/AI/Decks/MathMechExecutor.cs +++ b/Game/AI/Decks/MathMechExecutor.cs @@ -364,6 +364,16 @@ private bool MagmaSummon() } } + public override int OnSelectPlace(int 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); + } + } } \ No newline at end of file diff --git a/Game/AI/Decks/PureWindsExecutor.cs b/Game/AI/Decks/PureWindsExecutor.cs index a4f95f16..f77152bd 100644 --- a/Game/AI/Decks/PureWindsExecutor.cs +++ b/Game/AI/Decks/PureWindsExecutor.cs @@ -220,10 +220,10 @@ public PureWindsExecutor(GameAI ai, Duel duel) : base(ai, duel) { //counter - AddExecutor(ExecutorType.Activate, CardId.SolemnStrike, base.DefaultSolemnStrike); 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); diff --git a/Game/AI/Decks/TimeThiefExecutor.cs b/Game/AI/Decks/TimeThiefExecutor.cs index 80bc617a..efd28a58 100644 --- a/Game/AI/Decks/TimeThiefExecutor.cs +++ b/Game/AI/Decks/TimeThiefExecutor.cs @@ -69,16 +69,9 @@ public TimeThiefExecutor(GameAI ai, Duel duel) AddExecutor(ExecutorType.SpellSet,Traps.TimeThiefRetrograte); AddExecutor(ExecutorType.SpellSet,Traps.TimeThiefFlyBack); - //special summons - AddExecutor(ExecutorType.SpSummon,Monsters.PhotonTrasher ,SummonToDef); - AddExecutor(ExecutorType.SpSummon,Monsters.TimeThiefRegulator, SummonToDef); - AddExecutor(ExecutorType.SpSummon,Monsters.TimeThiefWinder, SummonToDef); - AddExecutor(ExecutorType.SpSummon,Monsters.PerformTrickClown, SummonToDef); - AddExecutor(ExecutorType.SpSummon,Monsters.TimeThiefCronocorder, SummonToDef); - AddExecutor(ExecutorType.SpSummon,Monsters.TimeThiefBezelShip, SummonToDef); - //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 ); @@ -100,12 +93,9 @@ public TimeThiefExecutor(GameAI ai, Duel duel) //monster effects AddExecutor(ExecutorType.Activate,Monsters.TimeThiefRegulator , RegulatorEffect); AddExecutor(ExecutorType.Activate,Monsters.TimeThiefWinder); - AddExecutor(ExecutorType.Activate,Monsters.PhotonTrasher); AddExecutor(ExecutorType.Activate,Monsters.TimeThiefCronocorder); - AddExecutor(ExecutorType.Activate,Monsters.PerformTrickClown); - AddExecutor(ExecutorType.Activate,Monsters.TimeThiefBezelShip); - - + AddExecutor(ExecutorType.Activate,Monsters.PerformTrickClown, TrickClownEffect); + AddExecutor(ExecutorType.Activate,Monsters.TimeThiefBezelShip); } private bool SummonToDef() @@ -278,8 +268,18 @@ private bool FoolishBurialTarget() return true; } + private bool TrickClownEffect() + { + if (Bot.LifePoints <= 1000) + { + return false; + } + AI.SelectPosition(CardPosition.FaceUpDefence); + return true; + } + + - } } From 909b4e8ac8c54cf445190f1e12f9a18c73684d3a Mon Sep 17 00:00:00 2001 From: Wind2009-Louse Date: Fri, 24 Apr 2020 20:32:26 +0800 Subject: [PATCH 34/83] Add Witchcraft deck (#125) --- Decks/AI_Witchcraft.ydk | 81 + Game/AI/Decks/WitchcraftExecutor.cs | 2898 +++++++++++++++++++++++++++ WindBot.csproj | 1 + 3 files changed, 2980 insertions(+) create mode 100644 Decks/AI_Witchcraft.ydk create mode 100644 Game/AI/Decks/WitchcraftExecutor.cs 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/Game/AI/Decks/WitchcraftExecutor.cs b/Game/AI/Decks/WitchcraftExecutor.cs new file mode 100644 index 00000000..9a48821e --- /dev/null +++ b/Game/AI/Decks/WitchcraftExecutor.cs @@ -0,0 +1,2898 @@ +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.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); + AddExecutor(ExecutorType.Activate, CardId.Schmietta, DeckSSWitchcraft); + AddExecutor(ExecutorType.Activate, CardId.Pittore, DeckSSWitchcraft); + AddExecutor(ExecutorType.Activate, CardId.Potterie, DeckSSWitchcraft); + AddExecutor(ExecutorType.Activate, CardId.Genni, DeckSSWitchcraft); + + // 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; + + // 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.Race & (int)CardRace.SpellCaster) != 0) != 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.Race & (int)CardRace.SpellCaster) != 0) != 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(); + // CalledbytheGrave刷新 + 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, int 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 ((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.GetCardCount(CardId.MagiciansRestage); + 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(); + list.Add(0); + list.Add(1); + list.Add(2); + list.Add(3); + list.Add(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; + + // 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; + + 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.Race & (int)CardRace.SpellCaster) != 0) == null + && (summoned || Bot.Hand.GetFirstMatchingCard(card => (card.Race & (int)CardRace.SpellCaster) != 0) == 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 (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; + } + } + } + + // 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); + } + 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; + } + // destroy monster + if (Enemy.MonsterZone.GetFirstMatchingCard(card => card.IsFloodgate() && card.IsAttack()) != null + || Enemy.MonsterZone.GetMatchingCardsCount(card => card.IsAttack() && card.Attack >= bestPower) >= 2) + { + AI.SelectOption(Util.GetStringId(CardId.LightningStorm, 0)); + SelectSTPlace(null, true); + return true; + } + // destroy spell/trap + if (Enemy.GetSpellCount() >= 2 || Util.GetProblematicEnemySpell() != null) + { + AI.SelectOption(Util.GetStringId(CardId.LightningStorm, 1)); + SelectSTPlace(null, true); + return true; + } + return false; + } + + // 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.Id == 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) + { + int code = Util.GetLastChainCard().Id; + 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.Race & (int)CardRace.SpellCaster) != 0) == 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 + || LastChainCard.IsDisabled() || LastChainCard.IsShouldNotBeTarget() || LastChainCard.IsShouldNotBeSpellTrapTarget())) + return false; + if (Card.Location == CardLocation.SpellZone) + { + for (int i = 0; i < 5; ++i) + { + if (Bot.SpellZone[i] == Card) + { + 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() + { + // search + //if (ActivateDescription == Util.GetStringId(CardId.Patronus, 0)) + 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)) + { + return true; + } else + { + return Util.IsAllEnemyBetterThanValue(Card.Attack, true); + } + } + // recycle from grave + if (Card.Location == CardLocation.Grave) + { + List enemy_danger = CheckDangerousCardinEnemyGrave(); + if (enemy_danger.Count > 0) + { + AI.SelectCard(enemy_danger); + return true; + } + if (!Bot.HasInHandOrInSpellZoneOrInGraveyard(CardId.Holiday) && Bot.HasInGraveyard(important_witchcraft)) + { + AI.SelectCard(important_witchcraft); + 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); + 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; + } + } +} \ No newline at end of file diff --git a/WindBot.csproj b/WindBot.csproj index dc9df2c1..f99877ea 100644 --- a/WindBot.csproj +++ b/WindBot.csproj @@ -97,6 +97,7 @@ + From 26acbca254d6b35ad3276f6609141842d4c86715 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Fri, 24 Apr 2020 21:25:35 +0800 Subject: [PATCH 35/83] update bots list --- BotWrapper/bot.conf | 20 +++++++++++---- Dialogs/verre.zh-CN.json | 53 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 Dialogs/verre.zh-CN.json diff --git a/BotWrapper/bot.conf b/BotWrapper/bot.conf index 12b55831..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 !复制梁龙-转生炎兽 @@ -124,6 +124,11 @@ Name=永远之魂 Deck=Horus Dialog=soul.zh-CN 老式龙族卡组。 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 由三盒ST17和三盒SD32组成的卡组。 @@ -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/Dialogs/verre.zh-CN.json b/Dialogs/verre.zh-CN.json new file mode 100644 index 00000000..0574e1c5 --- /dev/null +++ b/Dialogs/verre.zh-CN.json @@ -0,0 +1,53 @@ +{ + "welcome": [ + "啊~~让我再睡一会嘛~", + "AI功能正在测试中,遇到问题请及时反馈。" + ], + "deckerror": [ + "我的卡组里{0}不能用,我回去睡觉了。" + ], + "duelstart": [ + "好困……", + "啊呀,对面看上去好厉害的样子。" + ], + "newturn": [ + "我的回合,抽卡!", + "我抽了一张卡。" + ], + "endturn": [ + "不想干活怎么办……", + "就这样吧,该你了。" + ], + "directattack": [ + "{0},直接攻击!", + "不过如此嘛,直接攻击!", + "快走开,我要回去睡觉了。", + ], + "attack": [ + "{0},攻击{1},我来为你加油!", + "{0},替我打倒{1}!" + ], + "ondirectattack": [ + "啊啊……", + "啊啊啊……", + "{0}好可怕啊……", + "我累了……" + ], + "facedownmonstername": "怪兽", + "activate": [ + "我发动{0}的效果。", + "{0}效果发动。" + ], + "summon": [ + "我召唤{0}。", + "出来吧,{0}!", + "{0},来帮我一下。" + ], + "setmonster": [ + "……" + ], + "chaining": [ + "我使用{0}的力量。", + "等一下,我发动{0}。" + ] +} From 0b47df762053cbc53db30f20bcbc7d7a7182a039 Mon Sep 17 00:00:00 2001 From: edo9300 Date: Fri, 24 Apr 2020 18:40:03 +0200 Subject: [PATCH 36/83] Fix Utils.GetStringId, due to data sizes, the shifting of teh id was beign capped to int, thus making the function behave wrongly --- Game/AI/AIUtil.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Game/AI/AIUtil.cs b/Game/AI/AIUtil.cs index 96ea8b5b..68da61bd 100644 --- a/Game/AI/AIUtil.cs +++ b/Game/AI/AIUtil.cs @@ -231,9 +231,9 @@ public ClientCard GetPZone(int player, int id) } } - public long GetStringId(int id, int option) + public long GetStringId(long id, int option) { - return (option & 0xfffff) | id << 20; + return (option & 0xfffff) | (id << 20); } public bool IsTurn1OrMain2() From 8c3b26dce17de57dafe01e4c4e9af632e39ea9df Mon Sep 17 00:00:00 2001 From: Wind2009-Louse Date: Sat, 25 Apr 2020 23:57:14 +0800 Subject: [PATCH 37/83] update (#126) --- Dialogs/verre.zh-CN.json | 14 ++--- Game/AI/Decks/AltergeistExecutor.cs | 7 +-- Game/AI/Decks/TrickstarExecutor.cs | 7 +-- Game/AI/Decks/WitchcraftExecutor.cs | 52 +++++++++++-------- Game/AI/Enums/Floodgate.cs | 8 ++- ...houldBeDisabledBeforeItUseEffectMonster.cs | 1 + Game/ClientCard.cs | 5 ++ Game/GameBehavior.cs | 7 ++- 8 files changed, 56 insertions(+), 45 deletions(-) diff --git a/Dialogs/verre.zh-CN.json b/Dialogs/verre.zh-CN.json index 0574e1c5..a20d7584 100644 --- a/Dialogs/verre.zh-CN.json +++ b/Dialogs/verre.zh-CN.json @@ -1,6 +1,7 @@ { "welcome": [ "啊~~让我再睡一会嘛~", + "你要加入公会吗?", "AI功能正在测试中,遇到问题请及时反馈。" ], "deckerror": [ @@ -12,7 +13,7 @@ ], "newturn": [ "我的回合,抽卡!", - "我抽了一张卡。" + "魔力补充!" ], "endturn": [ "不想干活怎么办……", @@ -24,7 +25,7 @@ "快走开,我要回去睡觉了。", ], "attack": [ - "{0},攻击{1},我来为你加油!", + "{0},攻击{1}!", "{0},替我打倒{1}!" ], "ondirectattack": [ @@ -35,11 +36,10 @@ ], "facedownmonstername": "怪兽", "activate": [ - "我发动{0}的效果。", - "{0}效果发动。" + "{0}的效果发动!" ], "summon": [ - "我召唤{0}。", + "召唤{0}!", "出来吧,{0}!", "{0},来帮我一下。" ], @@ -47,7 +47,7 @@ "……" ], "chaining": [ - "我使用{0}的力量。", - "等一下,我发动{0}。" + "发动{0}!", + "要不是有{0},我都快睡着了。" ] } diff --git a/Game/AI/Decks/AltergeistExecutor.cs b/Game/AI/Decks/AltergeistExecutor.cs index cc4f0daf..0b901663 100644 --- a/Game/AI/Decks/AltergeistExecutor.cs +++ b/Game/AI/Decks/AltergeistExecutor.cs @@ -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) { 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 index 9a48821e..b3d1dec7 100644 --- a/Game/AI/Decks/WitchcraftExecutor.cs +++ b/Game/AI/Decks/WitchcraftExecutor.cs @@ -88,6 +88,10 @@ public WitchcraftExecutor(GameAI ai, Duel duel) 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); @@ -132,10 +136,6 @@ public WitchcraftExecutor(GameAI ai, Duel duel) AddExecutor(ExecutorType.Activate, CardId.Patronus, PatronusActivate); AddExecutor(ExecutorType.Activate, CardId.MagiciansRestage, MagiciansRestageActivate); AddExecutor(ExecutorType.Activate, CardId.Holiday, HolidayActivate); - AddExecutor(ExecutorType.Activate, CardId.Schmietta, DeckSSWitchcraft); - AddExecutor(ExecutorType.Activate, CardId.Pittore, DeckSSWitchcraft); - AddExecutor(ExecutorType.Activate, CardId.Potterie, DeckSSWitchcraft); - AddExecutor(ExecutorType.Activate, CardId.Genni, DeckSSWitchcraft); // summon AddExecutor(ExecutorType.Summon, CardId.Schmietta, WitchcraftSummon); @@ -227,7 +227,7 @@ public override void OnChaining(int player, ClientCard card) // MagiciansLeftHand / MagicianRightHand if (!MagicianRightHand_used && card.IsSpell() && card.Controller == 1) { - if (Bot.MonsterZone.GetFirstMatchingCard(c => (c.Race & (int)CardRace.SpellCaster) != 0) != null + if (Bot.MonsterZone.GetFirstMatchingCard(c => c.HasRace(CardRace.SpellCaster)) != null && Bot.HasInSpellZone(CardId.MagicianRightHand, true)) { Logger.DebugWriteLine("MagicianRightHand negate: " + card.Name ?? "???"); @@ -236,7 +236,7 @@ public override void OnChaining(int player, ClientCard card) } if (!MagiciansLeftHand_used && card.IsTrap() && card.Controller == 1) { - if (Bot.MonsterZone.GetFirstMatchingCard(c => (c.Race & (int)CardRace.SpellCaster) != 0) != null + if (Bot.MonsterZone.GetFirstMatchingCard(c => c.HasRace(CardRace.SpellCaster)) != null && Bot.HasInSpellZone(CardId.MagiciansLeftHand, true)) { Logger.DebugWriteLine("MagiciansLeftHand negate: " + card.Name ?? "???"); @@ -422,7 +422,8 @@ public override CardPosition OnSelectPosition(int cardId, IList po { return base.OnSelectPosition(cardId, positions); } - if ((Duel.Player == 1 && (cardId == CardId.MadameVerre || + if (!Enemy.HasInMonstersZone(_CardId.BlueEyesChaosMAXDragon) + && (Duel.Player == 1 && (cardId == CardId.MadameVerre || Util.GetOneEnemyBetterThanValue(Data.Attack + 1) != null)) || cardId == CardId.MaxxC || cardId == CardId.AshBlossom_JoyousSpring) { @@ -490,7 +491,7 @@ 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.GetCardCount(CardId.MagiciansRestage); + 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) @@ -859,12 +860,7 @@ public bool NegatedCheck(bool disablecheck = true){ /// 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(); - 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) { @@ -890,6 +886,7 @@ public void SelectSTPlace(ClientCard card = null, bool avoid_Impermanence = fals // 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 }; @@ -1118,7 +1115,14 @@ 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; } @@ -1132,12 +1136,11 @@ public bool SpellsActivateNoCost() 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.Race & (int)CardRace.SpellCaster) != 0) == null - && (summoned || Bot.Hand.GetFirstMatchingCard(card => (card.Race & (int)CardRace.SpellCaster) != 0) == null)) + 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; @@ -1301,6 +1304,7 @@ public bool DeckSSWitchcraft() { AI.SelectNextCard(CardId.Haine, CardId.MadameVerre, CardId.GolemAruru); } + UseSSEffect.Add(Card.Id); return true; } @@ -1919,7 +1923,7 @@ public bool CalledbytheGraveActivate() 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.Id == code) != null) + if (Enemy.Graveyard.GetFirstMatchingCard(card => card.IsMonster() && card.IsOriginalCode(code)) != null) { if (!(Card.Location == CardLocation.SpellZone)) { @@ -2011,9 +2015,11 @@ public bool CrossoutDesignatorActivate() { if (NegatedCheck(true) || CheckLastChainNegated()) return false; // negate - if (Duel.LastChainPlayer == 1) + 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) @@ -2065,7 +2071,7 @@ public bool ScrollActivate() { return false; } - if (Bot.MonsterZone.GetFirstMatchingCard(card => (card.Race & (int)CardRace.SpellCaster) != 0) == null) + if (Bot.MonsterZone.GetFirstMatchingCard(card => card.HasRace(CardRace.SpellCaster)) == null) { return false; } @@ -2204,7 +2210,7 @@ public bool InfiniteImpermanenceActivate() // negate monsters if ((LastChainCard == null || LastChainCard.Controller != 1 || LastChainCard.Location != CardLocation.MonsterZone - || LastChainCard.IsDisabled() || LastChainCard.IsShouldNotBeTarget() || LastChainCard.IsShouldNotBeSpellTrapTarget())) + || CheckLastChainNegated() || LastChainCard.IsShouldNotBeTarget() || LastChainCard.IsShouldNotBeSpellTrapTarget())) return false; if (Card.Location == CardLocation.SpellZone) { @@ -2468,7 +2474,7 @@ public bool PSYOmegaActivate() // banish hands if (Card.Location == CardLocation.MonsterZone) { - if (Duel.Player == 1 || Bot.HasInMonstersZone(CardId.PSYLambda)) + if (Duel.Player == 1 || Bot.HasInMonstersZone(CardId.PSYLambda) || (Util.IsChainTarget(Card)) ) { return true; } else diff --git a/Game/AI/Enums/Floodgate.cs b/Game/AI/Enums/Floodgate.cs index 22075fd5..9141c503 100644 --- a/Game/AI/Enums/Floodgate.cs +++ b/Game/AI/Enums/Floodgate.cs @@ -100,6 +100,12 @@ public enum Floodgate RedSupernovaDragon = 99585850, NumberF0UtopicFutureDragon = 26973555, InvokedAugoeides = 97300502, - DragonmaidStrahl = 24799107 + DragonmaidStrahl = 24799107, + RavenousCrocodragonArchethys = 87188910, + AdamancipatorRisenRaptite = 73079836, + AdamancipatorRisenDragite = 9464441, + TeardroptheRikkaQueen = 33779875, + CeruleanSkyFire = 54828837, + SacredBeastAwakening = 53701259 } } diff --git a/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs b/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs index 3f11d700..ff5df104 100644 --- a/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs +++ b/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs @@ -50,6 +50,7 @@ public enum ShouldBeDisabledBeforeItUseEffectMonster HoarrGeneraiderBossofRumbling = 68199168, RedFamiliar = 8372133, AccesscodeTalker = 86066372, + ChaosSummoningBeast = 27439792, CosmoBrain = 85679527, ShiranuiSolitaire = 94801854, diff --git a/Game/ClientCard.cs b/Game/ClientCard.cs index af1bf0d4..4a4ece3c 100644 --- a/Game/ClientCard.cs +++ b/Game/ClientCard.cs @@ -251,6 +251,11 @@ 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; diff --git a/Game/GameBehavior.cs b/Game/GameBehavior.cs index a796ae62..30e0aff0 100644 --- a/Game/GameBehavior.cs +++ b/Game/GameBehavior.cs @@ -522,14 +522,17 @@ private void OnDamage(BinaryReader packet) 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() + ")"); + 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(); + 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) From 37c22985e8d14cfe48fd5f49e5c1ebed692f4c33 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Sun, 26 Apr 2020 00:03:06 +0800 Subject: [PATCH 38/83] update dialog --- Dialogs/verre.zh-CN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Dialogs/verre.zh-CN.json b/Dialogs/verre.zh-CN.json index a20d7584..3c423a17 100644 --- a/Dialogs/verre.zh-CN.json +++ b/Dialogs/verre.zh-CN.json @@ -48,6 +48,7 @@ ], "chaining": [ "发动{0}!", + "等一下,我发动{0}。", "要不是有{0},我都快睡着了。" ] } From e1a9d61aa912416a0d3172c9a2d04b8fe0f4f042 Mon Sep 17 00:00:00 2001 From: edo9300 Date: Mon, 27 Apr 2020 09:43:32 +0200 Subject: [PATCH 39/83] Remove YGOSharp dlls, use source directly instead, add support for rematch packet --- Game/ClientCard.cs | 2 +- Game/GameBehavior.cs | 6 +++ WindBot.csproj | 46 ++++++++++++++++------- YGOSharp.Network.dll | Bin 17408 -> 0 bytes YGOSharp.Network/Enums/CtosMessage.cs | 3 +- YGOSharp.Network/Enums/StocMessage.cs | 4 +- YGOSharp.OCGWrapper.Enums.dll | Bin 9728 -> 0 bytes YGOSharp.OCGWrapper.Enums/Query.cs | 5 ++- YGOSharp.OCGWrapper.dll | Bin 16384 -> 0 bytes YGOSharp.OCGWrapper/NamedCardsManager.cs | 41 +++++++++++++++++--- 10 files changed, 83 insertions(+), 24 deletions(-) delete mode 100644 YGOSharp.Network.dll delete mode 100644 YGOSharp.OCGWrapper.Enums.dll delete mode 100644 YGOSharp.OCGWrapper.dll diff --git a/Game/ClientCard.cs b/Game/ClientCard.cs index d48091c4..4522e81e 100644 --- a/Game/ClientCard.cs +++ b/Game/ClientCard.cs @@ -195,7 +195,7 @@ public long Update(BinaryReader packet, Duel duel) LinkMarker = packet.ReadInt32(); break; } - case 0x80000000: //Query.End + case (uint)Query.End: //Query.End return packet.BaseStream.Position - pos; default: { diff --git a/Game/GameBehavior.cs b/Game/GameBehavior.cs index 47c0529d..dedd3eae 100644 --- a/Game/GameBehavior.cs +++ b/Game/GameBehavior.cs @@ -110,6 +110,7 @@ private void RegisterPackets() _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); @@ -231,6 +232,11 @@ private void OnChangeSide(BinaryReader packet) _ai.OnJoinGame(); } + private void OnRematch(BinaryReader packet) + { + Connection.Send(CtosMessage.RematchResponse, (byte)(1)); + } + private void OnTypeChange(BinaryReader packet) { int type = packet.ReadByte(); diff --git a/WindBot.csproj b/WindBot.csproj index 9e3f48a8..4bf1734d 100644 --- a/WindBot.csproj +++ b/WindBot.csproj @@ -1,4 +1,4 @@ - + @@ -37,10 +37,10 @@ WindBot.ico - - $(MSBuildProjectDirectory)/out/$(MSBuildProjectName)/bin - $(MSBuildProjectDirectory)/out/$(MSBuildProjectName)/obj - + + $(MSBuildProjectDirectory)/out/$(MSBuildProjectName)/bin + $(MSBuildProjectDirectory)/out/$(MSBuildProjectName)/obj + .\Mono.Data.Sqlite.dll @@ -51,15 +51,6 @@ - - .\YGOSharp.Network.dll - - - .\YGOSharp.OCGWrapper.dll - - - .\YGOSharp.OCGWrapper.Enums.dll - @@ -139,6 +130,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/YGOSharp.Network.dll b/YGOSharp.Network.dll deleted file mode 100644 index 1df1f9c03b4d013a090f137cfa9dce7a58156f3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17408 zcmeHueRLehacA|+?Cf9{ga8m+LLxyeKR~TO00ar}OX3#@@B`vE0g|F*iC6$baKpt8 zxVs<;L3F_ABeEkaauWNTrzoOq`Ofmm7oVexshs>wI{uJsDQ87WV#P8oJ==+$=V!<7 z{CrYkiTPD^?_vRv6qCEZ?q!$zo385a>gwvMuAaeg=N*rdO+*fUmo5>V!<9c91+Na{ zsIIv2&nxKJvX|$dQ#)Rs-*YsTiw|dm!EACU-k;25f_%Ksk7q|R@l+<>xw|_)6b$&y zGiF2<8PR(-6YWqoZQJ~dI}4+|Nekjp)k3ru99MI{c?Yg>{Ce>ts+6*&^=1a^FQ2nO z@cCoY#s|15|C_iPl3Dnig52F)=p}lC4KejIPEjeB#imIh)J&gA(0(QQ(QL$FJnG5zEz__0s_wimv0RWLcZ=EBS1k#vyu! z?TQcUW>JXZD~N8dCsM-wx5La9n{Gxo^slnUfKJ4XhV4WZQ6ygtez|oobG>i^mn9K5 z-1_|nw7*innb+74)5Une((y3D9E`_YjEBqg@uv2Z@vfmCTm|C+OUHvS9*?;g50~lV zP3|!8(LNLxI@9gxKprvpiOuBlk2s!?9{lhxn4Kf!$8S0Uhhc%8ww} z)af!N60^X!?9(jPw%&6qAS@RHFIa^VbFO2~Z04*n97K8nY3qO)d)S-nP z^H9XFLTPoqpmUP)TbSCS9l5^ATHFxKL(hiWw5tin4sK-DE^Ca&2 z>L9Y%Um|GHRAwa*2ySFa*h*me-H6-tqXxFm573T3T2e5d1aZODqKUG>^4pwQhGqNhO@YR9?++T8F|g^)igC%}k8KqP5^dGK}WLmRA(2 zWN~L}lV(pAmN;N*#u{cf#I!&3VOEd{yRhZVd|CGLom!Br8EnASF&D~krJ1wv3`f+9 z8C7hwsc*6uFLB*qG4$r)=2>G=up1V@^`%hUZx9F=AG>C0976=hF>?{en9TGzp4w01 z_)7iu72?jx-Ne?P!MglsD}f+&o8s0OqR?rowPtg4!u7d2NU+J-!`+4tn-B0 zd-Z%a(ND&^hJG*{)*QQ5>3HybjsxamJZw1A<6vq(8ShH{8muw6q!W8jaW;dqJAuw- zI{$x^7Y`qk&tN6aXOLLPXGl~i;)D`8jSMxB+K`+kQX7lqLoU2M*_X|o zBI^BfC);BpcWUeKZr{N1{2*+DUAL+RM&)%YkInF^?F6Ep~f(XR_h`>uE(K9a{UU_O0LIzFPH0ClF5B+Y?JGmi-gCXjSE+pyvIeI>!nWTdLD~& zJy+^nuPYb92B)R6kn6d{)P>fwDGVdYbWD7Sb3OOgf)9x#&5>NMtHc?R>v_>HpX=w~ zY%>ZUz!r}^rewY>VEKTSSjhDjJ4iN?yq)mDi&xqM!ZT#R^) zZdm(|oH%s8vuHoXj$LkVM=C1a`!FrOS7Gke_x6c?ux;@k=W6}*-i|z0I-c31nS1rU zeWIU?cMbh;EP>tQlgEP+ytgwKmNboTh_?PPUWV2%%b@(Xt(k*>iIkHKGR?lVM~ENG4k$$eg4) z&}8vN9E#io?{GLu=`+u4+Hx08T2OROOPE>yMZ!^3uLFB!>FtoV$#pbq5zW>%u?U$@uZy-x0AnCb zi-yD=qE{1V_d>G7R9s_UJZq{b=x!0tpOa)^`Vl+04ftu;L^X%48ofLd@+X~n7c3~q zvS}}5;arUv;R)Npc9~nG-14-DRc@1K#5K6$!mtfnKI|rTpskKAea{h=dk!lhibHPU zJbc}CdOU;)j<-5Yq<}VsGiF$HKyoVG7i=A;%tf3!my6S>{UlDW)bE2nXbdMwv8Gi>KhqEmg@_qWc_*Ez`QQ&K(_-@2_BLeSal|%iLe# zhUnVvuMV&R|8MTEri;4F{gos4oK>(}q6gNngZ;pWQ+tPwG!$wNmAhg0k(AdG0xnUW zj=FFF`RrpHKfE6xWmj2t?H$2EmLSsH;C7Uw;aG48*K#kmtPGEUf;)uS@IlK8?nE*8 z0KgRQLh(>aL_joc>b`B0GEYNsexVa>%`MHXEv?JefWT9oG_Vo@xA22R_u~3S6y@%G zHkBF7af{orF`mbD;r?!_jq7L63%Bm?!nRKd&^Q{=!cFO*4>W&}Ue&?6KPxMP`_ezF zR*HdvVjF&lA!4CWS)SKIRl@E}$Z|Y)e#XS-Y`JoGq+xjm&Hb?*2p_$}OkhV1A=Rux4fCuS_T)Or1L+*F8zz(8-nQ&b5;xP6+9*Uoz5>kmzD^ASvbG%9CTf3 ziad<=|1Hcon;3sCey)QHh+3!g&DFT-qmn zU-#@k4_9%UG+|WUOZQ9yYL?FnC?~TS>o&Yz}WOQcrkHMX5&W zUPGOS8Po~&D`|NhEfDGlLd~O9LOmt@Zlc?SdL4B&)QB8?1}Wrs%aUcAK^-E6#cQ@t zl|2CJ148|y#nduG{Y>gQ4E459w;Af=LhUrvMd^2#DY{SXvlV?)9RN01ciRsArF#VU z%MjzUlmg$5rh)6cVc-t$B=E~@Q8OG@SmmqWSPJHzA`VC{Vw{JO72lC z`Rjt;7n%PM{!iePO17b`qBYQA@AxHXtLS5q$ABxMPXM(h>qOft=~)^1lbi_U| z{?pKZ1wJ2Td`|Ei;1p`l*?$6lbC~hD2;)};$>Z|fQkxS@3APIV_oaP8TJ}b{*~P;IK)3e#r|b++uB3e!1F z)xPR|&koaV3q%TaMiwiN4jC%q{=kmVQA0g$owTA9!+{dx(FOY_b~!B)>I^MJ5YM2M zhWdi~i9Lh54E1Yu(yE|dL;b8wVefdzP@i?Xt?TF%Zi9%<(7^~(e=pR7-YTj4nXdD; zy0g&lZBy5$er{J%OTFmgod8uuzNRpv#j2X_(G=~ED(qWN8R`OTTSG4hb%ySXFjbCo zFOPnG_&rray+S<*DT-#%P?7qcJ&R6giXOM#v19aTk-B8hrq_y8$f>0`4#I`iyn${v z6tCtSdel(7nsey|L-A_XA%W_Cl~%bkk5(IMA*dT^$WXkRaXMuvUd=c?q$yd=IMw5@ z#%si@IiK1L#jClHIt}%|V2MTal%aTS7tuaG9+6>VWp?IYels6Qwbb|I1pQhl?eht0;{y*onXPm)r47YaYM`eK z#a3EEZy4%t)vI+L){brH+ zfxVReSX0C{YoTu&Y6I-KjCz*Jc=QR_td*WJ)OSJ(Cz`hn#nI75M_ah%87hNkt)OQO z^;7pfwUUtDr0yM1t7up#?enYYl&%w>UrqO$I`;WB^t_?i=Wn53Xi9wkR;pUgG9MJ5 zUq`nZihX_^^%{!TeH}TidUTF}b+pY;90BX-yihs<*3-*H>U;Kj`WsD21Z<#xDpHs1 zjnu{mI*k6f6>{2XT&R!JD^|?upsyP0Yt}qx3%zToF{|F$hCYfegx(*WD7QIZ#wW@< zrTp;-bJCH@N@Q-2%fBc5`>r6fwC~H(SIgwTDl7UGA5;acYtl#S{C}_2mFBKzr?FJ? z`)FdmdKRTPU0P}1Yne4$15dbNNt-?!W}7q$r%)~<-W#icA-Z15^MMgK^s8Patr2V& z>=N87ct|iQm;%nC<5C_KJR|rt5R%os_6E9M-D+<~`A4A*I2SViLxLX#K7r@1j5UH! z0xRiewWI8Ff`3YD)cnYE)UIBOei`TE?eO?=;7gG6p7mG24biuN?}UGZ_6OKF86bQ_TMd>yz? zy-pG5kV>j=V012rusd*VNKoz5tZMj0%=YLZ`V7h+ zlk&6lIm^R)SziD?PhSL{r{{q$&;{U2^mX93>04<3inM=?z75WI=&ylq(BA>yr0)ZN zNIwMrn0^BMDg6xiPxQZmzofT;Z_~TLUz3H=Nri!qDhGP13RtOFs~W{4)+!!xuKE@* zu6V3PY7S}}rA>>pS*_yWtQF2i;dDx`ZPIIp^x7l6dQ?4H-mX~N4=A2juWA5iKrICx zRc*kuS_2%GHhIM}I;D6<<0AQfk^GQIepn=jH3 zjtia@JTJIVu?|VaTAdMmOHf&&r{K8YS;6yyZwg+pxy@UG%8~Yh8wGm>#|6&{o)>&m z@GU_V;$CmMj1*>!3vLwb6&x2lD|lXTV;Q&X6&x2lD;SURTs8{!3a+HPV2@AJ_sCYB zTB&xbyVZT_8TA*c!b(^Nt$ypYWntH~@n%s7Ul&h2!txa4KGlWH_D$si8!RL++@n?i zC-eEaNh!xJw$xH>$aEmpg;YcaZBva7(NkskGXrgRxm?OAEpx30%`7}2u;d$tmY!%@ z37HOf9;EZnMH?Q&!j2k7pII_m40V-wa^iuXpfX(X&>9?mHNnDLc2RH^VYjZNdZ2}~ z$qe*e5A<;AtN{NephX*SFW})RLnXee@w4!Zp$aXLF~DDgXGfKE3($k7)}Xu==+O>5 zda9(Iz#C{j#$QT*NcXDw)^^Jo=R8;X*^JBwQxrc>SPw~gGVd9inUi@BqbEWC+wsfd z_b7gKs-AK@K-cEX$dI2+_ND#aWwdw1AK`0ze?AptP}q^m<#E}QPLBFn95sWkOuluw zp$pemL${gh3bfmghRaqO#!6bdGZ+|2`|GKDG?({>n!9$>iDY`jKYW-5lKCVJ<@$qc zI@MRG?+DUqUzEr-Z}l^NHq}q<0|T_rAE2(>Ha|JwXS-AP_+6R)^vD1+GkHIY*E5fy zygxJGA4z3UI(#yj>r6p*CIe~yz~&QvCch<><*HP!BOT;WN)8Mh?kKdNtUnZ-@JkpS z=@dGZFb4hn;jY|7kK7cR2c4)D=^|o+R~Taoo12zuThL_^&!{trH+7marB2v&pWpAN zPGF8^#7Vq@q|$b*!k#3on4j8p3U_UZ1=b%se0UQ&;eqUyl%F0TRI=mv8E~dmc8>Vz z%^6Se%==rDLq6^BlP7fPwjh-e76kOc zOKDdC*pm&QY>?*BP1H!Sn_W7ui#l{Vf-EbHK{7cE!9GlSOrQ1-Bbq?Mpo3gsE4AlF zGyUCpCV5z0(lgQJgGXU962lQsDwX!L5_9M>(4NPG_P!DBJd!G*I{m(p!9kwoL<@92 zkjkYd@!E4af2c1#+LOwcHZ8KVh~%MU_Sl4!o@5qkZ^7&B{>h-UV)H3@R1W*nMB9Rx zEztRZpS^q~;_gUla3q`LJ#Bj5PCwV5O$|@3)OL^_`}}nBl$3JQJ7d`fM*8#9D>{PV z(QIn)=w++mIGNFjKKn*8`P7ilV`7}XR63O}sogXR<9GVUd22>|=anpL!S7 z-o?RVqE^RGfzaMfyZdr}_JmIpyC-cLIdVjAq;OvZS2B$~6~zJn)b1mNy}0Kn+;{+i zhXD|8T+mx!F~&)6ffKaZ<;}#J&C+nst$tqr>4v&T`f~C|9D*BWIk3e)cjhooHXCHQ zz~k)jGlTh~gw@X``}2FSJD{my9v+@V?o6K2+mxvm*Y8NC(|y>Tu+3x!iiELEkv5xi zk#6tr_lNU3`ie{)UY$OKZOd&>;j$-{IYx)J?(ROC%nmp2^7HJn#9Q1nZu3AoEfd<5 z%pnS4(;?c4eJ49=2yHa#5m$iAW`|(#ks;ce_5BQ`@zZI!urB-jBPK_R*gZiGD;S{F z;9yq5A6paVz^udl*d}1M2@d;bPhfVMuFR3Z?7}_6SdL&A`--2;(xy}NU^d(D<~5`UkZ>Ko1b^QrV@*gS!p zF;8)$%gMrW_~!ktJ;+vBBrf3<%E;HkGwy7nLkB^Q3gJ3J}sDH&#)=L5?m$CV0S$QGVWt2snlL*IEW2C;E7o~#J4fj z9%#7-iCVMu3KUs~{nE;g0Nb~OyzFCeP<04Fpq@)l{lVM}r-l^(^C z4Uh7PpW1tH-cONF0~Dt`1;8ArjL=ELV{V#~XgP?wI1QuRPsdQs6SeL~>l}@MJBvPqy>9J=jzVyoWBfEaP{NQch zNWc6;!gB%T#Fg?wfT~$stYEO*S=BpL^(K#9gOSb9s2w;s2u*+o7`hE*bhc9(6B@ zRpVbOkJ}2Ds__d|<6n!$ZPxZVR`|NOTb;s;y{hpSnPw&HxEFNA43t-$f+Nh;hOXWl z!BY#El&uOS52@ zp$Z-tPQ^79yl%1>ssbj2E;iPs!t)>};(hU+4;;9@?adE4xTnXPWC%b9e;a^%G2rPn z9``X=41AmJ@NK$-w&YYX@N*JpCPNJaUiQWNhYWFs`3wsf7BVbisAs^B@H7{ISFbNQ ze6#QHYc$Rb27Vml@LMeoKcsQ+HZR_7X1E?;yVYG`9VdJB6-(!OmTRG=))jk(Y!{BK z8`XmCoAPnKvLArA7r!Kar_ODPAaO8;{uhckJ|~doCN$1Pmy^gi01K&Bt2H_ zU^Y37JFenM2&dcNWDXC$@XUrkv+%mf)TE8~dU)vwf4X(M`h8RrEl^ZfJmkl}@^oBR z#Fw`$ThRo-`}Q~EFPbY`R~Rdey6Biory?@XpL`l*cX%gL2%FV#Z^JM4da{@oVfx0#3#g5#4# zuzTWNwaf6i-8TGg??t6N-(wD!`OlUIsGAN0H`6|py6``(cY*E#Zb6wpUvhr_o_?Ar zP7IE|;;nD_@D8GV-!O3L#1HQs$+=()@^%_|y9;N5BRB={XAyBP&h$w+A@B(zk9Gkl z{rSA}CWI>Jlt-%+_=C`Bs-)+oO$+9&zirgVHo)^xwnqo#4dKgYmOM@w`qM1WZ*?7? zRx#1_WxM5`QHuuj91bk;q(=_q7nQP&Kqd? zy9K+6D$e5o>a%kCDa?BrdbJdPgr~r>(4H;FC#Ni~N!W5i+xHu@8E33CzWljc-f-Ii zIfEjFEijC^^GpVzHO^~dvB|tRJ&m9(wMm?oL&s&b0&jHc680*5@W1@erJcl+}pUE<& zuF}*sS}lFrF*`mT55WV|uxcFMKY4Yh_T%xc>9a@vUk<%=%QdzBk4FEWS>Rs*i29XL diff --git a/YGOSharp.Network/Enums/CtosMessage.cs b/YGOSharp.Network/Enums/CtosMessage.cs index 4356f8a8..59d1ed69 100644 --- a/YGOSharp.Network/Enums/CtosMessage.cs +++ b/YGOSharp.Network/Enums/CtosMessage.cs @@ -18,6 +18,7 @@ public enum CtosMessage HsReady = 0x22, HsNotReady = 0x23, HsKick = 0x24, - HsStart = 0x25 + HsStart = 0x25, + RematchResponse = 0xf0 } } diff --git a/YGOSharp.Network/Enums/StocMessage.cs b/YGOSharp.Network/Enums/StocMessage.cs index a559c4aa..92d6a5c1 100644 --- a/YGOSharp.Network/Enums/StocMessage.cs +++ b/YGOSharp.Network/Enums/StocMessage.cs @@ -21,6 +21,8 @@ public enum StocMessage Chat = 0x19, HsPlayerEnter = 0x20, HsPlayerChange = 0x21, - HsWatchChange = 0x22 + HsWatchChange = 0x22, + Rematch = 0xf1, + WaitingRematch = 0xf2 } } diff --git a/YGOSharp.OCGWrapper.Enums.dll b/YGOSharp.OCGWrapper.Enums.dll deleted file mode 100644 index f44aab10181cb77567f773f809ab99b4a9dee7fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9728 zcmeI1Yj7OZmB-J$(;AJy7&{m+V8~z_FksJ0et-Z6B+tmQuq+A9$S)M^)=W#&anJO) zyT|hIT7{TEfRGckXs9KXXTP8)ZRK=+xyO5$_ z|L1hiNDgGT*xe8LKui7WbIv{Y-gBSb)4gWnE{YM6f%C%;i4Nk*M~Cpg#{`n6fA;a| zbfD$M^AD=t7thZXZ9iG@+%eBACG%#v>;}mZE9q6sNxPiP^kvFA2I5~!hUnN#G_5Xwl54UNy5vegUo|CvDzLT(&<~#tAWhmD#SKl4 zB+=%S=X-f*5?cp^IL`BMPB}Udeaf>O7mgC^W+ZZ1^-hM!M+eag5vE`t_bU#LO;ZyM zB#9OvCdJ18#jzrL?Ftp;=2*5ZO|_<$v@U78f(vUR2W&^eyjzL(;<~>DayIbn@|e#a zxiiACoHvxE^^ke!=5-JCWNNiY`UWf71AjT)Qdk<`WxMv=xk^oG_lqar7e%Mc6y{eu2?%S z((aXfmq*$|qFonh=ZH33*P1!slaaQ&g|&Z#7LT7nV|wkxW5yT2&SvHVn%SqA#}dr8 zCT0Qly8pRNWmwXRxl=Q%!sEp9YJ+)1k zyiNF;q&6e9p6kKpZAks1a8C=%s}!?MGlzwj5z7z7nRA<%*Nf*&;Wra(IZaA@N44v zq1b;XmTuuPvEMIwpBBqR3+J`O^Os_Oi`Y^V&*#Nb7VZ3$Y&-xkv2JYxa3X?0z%LyG4Fi%70KicO=;Ud+~o+e2z&x%O&+B z@qb?|cZub=$ghe2c~Z`eBJY>F-VpgZ@wrGWyQTcc#Xck& zgs(}Bed7NGDS5T{q@?z7$@Q%894YN&;p@^@N5m&9sd35mtXMKqPF7??YUviv6`#kX zgk$0}B=ue<_DhAgNXZXNRF@!EeIEWQ@nwwN-A&B5ME+Odd%`2a*W;Y}ACT+0PSsz9 zr9aMmqM3Q0Sh7tlUnJ}_SpJ1#z9w?B_&h1{8)EmRge@X(6YduNNO*06^Ij$8S47Sd zTiT_ZYlI6?R}1~HS=gD0uM=(%n&9bVi@a4>6DIIjGY{u9 zS^&TQe1X0T9;Q9uOY|W4D*XUF zD)AhXc;1wF-j+y?OC;||B=1Tj?@J`4xYdT@ND_)8nWi|B8HyvBr8tt=ies3gIEJL+ z80IUEVS(Zp7AlURRsB7 zV6XlbIG}ffIsH9wz1|BB>qo%N`bS_v{}?RlgP^0I1S|R(Fwj2-x9bUY5V^n2hQ{Qx(7U}`@my53%;q>gKz84gU9s< z_^#%;_rB)&LouFvMvUiPBF3|BT8w*gMvQ0OtQgO_IWhK6#@K(p_%9Iug)yG1t&(f0 zPcajsm6?n`g1)$Y4g#Ft}#MUcs5Fmjy0xTZX@PmTE50DR2}PT+GaXc zYttsm7OSJ9j@7k2@Jt%26wJWNn1M;1TdO9O{JiTq_6T+2w{5kYw>ks-b{U}_f1o^xMo43q5kqHM@3-<)IU}2DL7WTR8L%C20^GxpqfQfX&@z3uEMnKzs$CH&j3X4o&dyqas5#Rc zi^Qz!g|_}}mgkr?NoNe2d8<*MC}DX64kzSX-_O_(R+zq(u|_S75v@c@#vLzD)rTr| zEsa+aQ7ft2ELm$T-$#pS!1d7yWk?&{(ui$QZ#Hi_77a!hs}k5y!!DyvyAOFWsq#g; zjPmSle31dzh_S^eTaF{r0D7(JR7;|)am#*Sc_MS`aQ3TZuP5*0?)0p`K?YdTA}vk$r;Pf zdp0_HYF@-$u^q`VXgTI~k^GOAJ>X$!=Yx+Xq}@u*v&V`bn^dZp<=SMP!D>0MOBUCO zdPZ!=4yL4bd*(I_I+h&Cg28JH%GNNVrBswL6*XN_qQ%Yf> z*7$I?{UUXlUQm>Ijp(qgG^X_wb*K_4(K^jo@Ef$Sgm^VT&W@WE2|7v{^(?;_$y=+I zAB3{Y+0615>a~Fk20U6JY15%T*DK+YsRDy`z?WWB;n0Te{%q0oDyjZ-_d3t4R4gym zRj!u&RKan=6@Ud2Skai(tJafz~(@+`nhgMd33d*|Zb@MW5&@~QrD2t~N&oN4S7Pi}l zS|3UaH-voXfK`Q!0iy-)XyZ9i?U&iw*wqmXJi~pES8}fEA(UuCib~jE`F$=8__ch| zMYojmMGtBXV8@{f<PWSK-H8!Osq@disgemS>7var08+{S`Qos(Iu zZ`gg+6803z6|3a}uDogRkgvgrL*+dP-CSmmDzkr_xek5gHHzRoSmRRPnrM`^MbrcK`Xq-oJ6h&*|$B#%~L9GdVp!x6catmSRPt>Gid;eKLyjr4G{#1h1_0o?!Ov__@USf2*5UVJrME%tbq(cxTm5Y@@~nBl(dOk znbSy-IbYXg3H4f|0cKl8uD!gyMQdwNWTb>bwqkpBKzs`ywEU_Q5N@4iuG~ZqR4jqu1NU2$#h}oh$E<$+2L!-Q4%ELjZ^TWUjtSF?&4KS1k%vx_Hje+6?IZoGAZS)pO zrs&?zJyb4?)R1C%HB5RN9JB&(eeFhhXd9UM=(=;Bxu)-~(XrgVzxYxU8A+uQu_UP` z2s3B0I-Q{tkKH)D_N=8xzhdzFT>~$O$>8Te1J8qG@Oxn+$>?D4D{5ncfw!XoybXS1 zYw){c1Miw)LuLa{t^hI{_*D*cFeVtt2WW=4;!&L6J_AX{1cMaNA(Cd*z^h@fnQ=0M z-wzx7n%X#(@fpTxj9HA+8F;Tv#+i)SjI$X0?ZDuV0mf$;=QHLq<}v0oE?`{9xQMZU zaWUf(#$v`Y#$}Al8SRYajL$KyW?aMQWUOR#GgdKr7}qjZGkO^VjO!R##zw~Vj9~_U zxQVzhQizv6t~6;|Gibj6;BqCt~rLd``nT1LrK9vvJPBnZ!9C=YoXd34-H1e#f8F zXYk#x&niv4=+F&Gp6SKRQ{wrpriw_!AzW(lV>%4QGbk`{dgx>-+}L)25aGw zFnE;L;y3#yu!{yE_25fuAM_q@W%$*Vo-}^@LHNsE<}vbwSKe{>;3C5%BdQ<+DwJxH zhjMJZ!L(3jnMR5C3;yiMxpGJ~VfB%2;?1UwRNhqh*l!fEw=2XHz{`dm&)6R+DI-3u zXm|Lxlt#D@@TT-aJ!BHrdEmIzUBO#b3sO=d<7+@gSdz$@M~!)0 zJ<%Q1JtBELs^R?z;cRZyQIU4k&}(=B0e?PvklUgS)E)KXFr5wmdK)rG^Wnj5tDMmG zFh>?~aPL)6Pd{Srrgc(#J>nEbtV~tZ#_ejw<~CXOT9oaB;IJZ z;vN1nyiX1#zWEVdg3_m!AI_oz>MKc)ZIK=kSH#3$X8WVMc@}Xy>tn!LI9mUg_U2?< z2T+HL`l=YyU`kI?Gy^`_crKOkJh99_9@Re{({{d3U|MmBgPDBR2AAU&m9IpJ?DDYQ< zBE;28U#O-(iG6+9b86eymksYv=bfDIP58-4CzZ@*y@E69I{sAFNoSp&fgxwo8*|&| z%&{*HO%L`GZBsgZ>b~3dmU=ry+OoLnAX*EK6>zI};p*VK4_~4h!L`6|X0ZSAISB|p ze{|Y-C#&-R$~7dJh0lw_L<5ZMBbr|}!i?}!B&sUm|0h?c?7FnFKyRJZzP;ccE`XlD z1^_Y{Yv?y9`8e>Z-Ou|eP~zJ@ASS-+@SXM9i0Nwg-HZoC@wEsc+qwqdS)YwWn`V)M z{utxu5BsK3gyy#reKx_d`@j3l>(J=qVNfE6!* z$RSdcsAm$r8uLFeAu{u00L4hafY8kEV+N*(H5!<)pBadUag@~(=*&FI@&wG7M_G_S z*JjIw9g41kPf(~m$~}5$ZhMUY_KLXafn|HwLeumX0&V-@8@AaB;zAIAca(ifECSl1 zkVhJlso@f`d5An#7y*xJwFD-`M7t{8^i~2rhMCZ`mU$h9Of9TH2inj{EaqO!qf6MR znOKTTT#w$99*)*aZ$J|cm_oGNE-q`{GH_Z0|26L-5W06A%+CT zDv@qkq8*8X({hof&j<1DLl7_!&A^5rVAPfvf`Cz1Vh92Tawupb$dVxFEvvKyf@A6<^&jXh4{1v`COSd3 zU7$IHFa|^w2r33dOG69hPNzYRo6O6_2$-hVd(9j;nGaVFC8cD#ashLX~Qz4CJOxKj?gjiS zETB4=s#6~<;QXpnS_0W?&Y*vT31pTzgXtFniDk~f2_ZN_GZ>!`&@gAVbq>TxPg}96fI3@%PnKRH80&UG1_-{lLtAJv?Y2+dT5yUnRpG&$U*1?^wSti47;QD90ajU}FhdG< zHR`0g&B5B#sEwKiOZbJf;ram29%WxN4?(qCt3m=pvs>n4t?Gnm2J1PG&BwGwK`I35 z0YY!fN<#Sv(h%#}XgR7KLBTTWbt96%8Z!g3p+vUq4j8)en(7p&g0FNhgofVKKP z>7^T2sunCXuG+R7UZg~P(=6Yvw(=5?5*t-ZD)j0B!m8*O_1 z3e}K65kRe!VZ{ScwBD2fL=l{M9Z|w4_;@he#%t9^pj)3nnfaCN4MH#okq_Gk?Gh+8 z=5mMk3h5dn;#iu9A6U}8*>Y{*bpjJ(YKdzBxzKVFwEVfo%icz{!B{0cGu$5Km&HLT z7fsE>gxM_z!)|$lnI-ksfL<=+H0WmdiD|Dr!z(rywnZgC(w*&QVl7&k-sOPtsQ*o@ zG7npn-8wf~AFcPWzHILbmT5;gi(9LLYt*|EOr9e{jv2_I+7->ZhyByG7eJYFzjbbK zHFd+gnr%c|wP=g(;k;pc8vr6Vt@>Jhi=Q8WXU&>7gp0QmAYm}r4$yMFCi`DvBh*{9 z`7K($kJ{LV;JghqzG7AAUwCouMyl>9jihvK*z;gw z7aU1G#?D6@_BO}czACC!E7qOQfDEr3kn@|dV5X|=OTTvW`jQX9{0389FQI4|#YlI< z!Gq3epkOWM{1L}HEFq62Eue@KKKo6)GIHO_u?6$c7H&`(FO~7x-cLNre!TvAX!WOFY zZDAv@UEad%$`P83aJMa_P$4whLUnsf``AWmc1w6Wbsq?D zxK1-OGzVL)${uA$HLn*Yu)hVwW)QRXH&mutE&oxtXNy8Xj%OysKvb52H>t9VL1jS5Ew{rFO z)kc%yUm47A6X`us#ur4I;x^rAszpgSFRVhryXEoX0KZEQztrLs91UO7FVG{Wc1&1U_Ov4bIPX)~qpD^BXqfJ@BWR zzGH2T)zJy<9mAx5Hkt2RtaDi4lIZ6!x=!?n0L$!fycHe8P0}Z#`|WRvVyvX;^PT#%E(sx4YV{@^r6H6Vdb>WqOuZ3bt2dUZ&*)d` zeDV%@cu?=sao7*3r>RRvO%ADBqg!;8mylYh_v_2bRIX}|o+wlAX(RfIGIdkzN_`~_ zI?Q;85>;t^HN72DUo>+1TDnRM-A|v36+rC^sjtRv)z{GzA+^-LL%)LlGNe`+%kf;{ zO`-0xPSbJyYWhc^j#=*@hp(m|hq9lZaYH?E8@Q(2b$&pCRj^iBR@4^w&%L=6Usgbn_KCLQ1%$cu$BIUP&4QK*NgvFcI%TKbAmchQXT`}%eCt&lpY9inaYQ=#r?eAa$a-$uh1v)2z`-k;X{ z>Bk{;T6=*Pwx+0`H?62uNs5_+oWBhe}C#6E_3)=Ve zT{In1uY=k{CqnAsDup`z>5>dt@G)w@eK+Qvp2BE9Mu{>NRX5ORNbRorzJ4PWLh7s9 zyZSzQE~NfR`W0IzYdH__jk)`{DVw)!JP+urhHHJs00t&IN zF>=&%DSLJY9nn@82k7yTS_Ihv`n*ub=-cqcrxSeiz&t!!b(!JQAB5ERmyqf}FUO-NN(GyD-`f#!N`PqF&W|joZr9SM+Vhu`>07{t@HO zkP6z}RhGS~-(Y;aOnpUnjZc)R7xV+hJ!iDLw=8>AKV;lrroN)j7!Q;wZpWu1MRTHO z!w-eO(&BT{m*XsDJb)S@2{E=$VFxwnb{zI1bT?p0n!zTZ)2Pi-){M{}p{+^RfgdFu ztzei{D0S!z}CitQNESA^!mf=S#qO3$G8Qon)!l^)M*%dH~P$~>c0 zJX%wuC=aO7YgIKhI$7A@6!Ca9dQu{G2{;;kO2!fg)M!F<92xSkprN3XZ2> z=-mh-Hk;4$ZirOTSH+VcChVa`FTxY#l0+=XI*yy74~e54aQ?Go;B(N_=mqh7HaoEwc=vG52YplJ3?RdAjDT1LT|imnhiyVdNuuHgT>ROGeT9t5is2@bHz!`faK1hq`GxjK5OkGtzrPc3O9ijt@YyS@Q-=jxpTD==Tf%El= z*nPlv#vcIxYgLb+?aNh90N!SOmX51#{b_ncET5(;>Cfmi?WC8fRXE3$UbTXL2E2yu zSD(gR!XxTg`@5vn^RZJDQ}3e+cZ#)6vDQidlirnHKAYxdIjKh(GBVe?1pzr#OAeXs3DfIN*i!GGG(E3b=y43%HsL=v;}1U$`?D{&iFjd`S2o6_}#+ z;M`0pz&zz5r*S5EE}|5}WdfVj30UqDyj$?3;3>h63I4diF9|#?P-$X8V3)w8Ku2f( zMuGbT76qOZ__DxL0#6Gh>?!a^pd)ajzP#w6ya8mz>Na;2`macDez^1 zrv#EI`T{o!JY_S7kbkfvaHBvAHQ&}YP?7GWFVJ7n`!ruQswJvP?Nq;|PN;j;@2D@~ z*JFiU0Z+u}J^C%wP^x11w#_iDp8zZxjQ3g$-_#lYMBrN{flZPnqrFfdG$N|)FzZVC75uk=FnFIV{K#iJl zm#*+UU@q`WP$BTkC!mJTVydl{igbk8JxUeV=;O4eGO4Q+N_hZ^aZI5k+V_YLuZx={=!i zv=H|Rh8Cyga-Bo9a-B;@L|%&r|16(}pLF@UfZo7;jG;BqXN10h9+c~a^fWv$w8eBQ z&iaP7lokqq8Rc*qHnb*sPvdJd-Nbf_T%Bis_G4c`*vp?+)JuY&sg=V%a<<;>qBpzk zH`F^q@{HK{@@!OL4)N*h%}!0aesVP9?psM+sY2Sz0_p4aG8wl_g-Qj_hiq40wy=5? zZE*_$($|v~#iT#Iuf$&4Nt>s#sePR^;TA@A4R2mYSM_^iQyKRf>Zk2AG@UQFlkI&2 zC9Ee|NQ$l?c{CEFJL!)Jk}LClH=pyed3Tg1^C{2Iq(@6ODn94ix42o?Pp8CW-x#q$ zTv9NH%T6~rMqOjfcE>1}Nlrs|jIQE7uDN++WK%MAGoIpYPP-X!`^L`Y?M$ZJbGbuq zA%zz^&J}DMN+mJ$b4>62CH(El$@6FrPv_3V?QzFlgyUS}T?IUg9GxnhUxXo^+hm)2 z(9N97?s4-eKb_;;I9J$}NhkB?vIh$1@Ora|cp>Q*wkCn?WYE_$+U-qFGCFi1lP;UVIjPu-% zq}XoOJv35CPE-it25N%#abE0nk!)jPdtQsVn z^ctef4N@&|Te1$ZPh%zJdFj&_MmUSe;`l$^jzNFX$GxFeDVKW?2hUHk!@` z%{WD+nIIf*K|=2Z-<`}%xq@v?=H*)8MqX+H0S>H$DJ_OdI0CpcT=^6@j-GutS#YU; zDpN?OlKDc9n{g+YvmI|04HXz0LBg?$m-X6t8ry?3lQEVTh;&q;FrXX;=SvMi>#>Sx z%T#(6wJUoNg=0L;A>>)F==HdxQxg-+ukc`YcRHUwgV&YMyOX1t>EU!?wrZK}C&%2$ zqZGs(k(o1IhHLVA*mNDlpsrcqI5OHuYWO(Po)atdM>nDh?1)R!G| z4-bsf=+rpLqTifJPS7}m-Ym*FHiFUy6Pj-i!^;w04)T)^4NZ;a1FXE5rZSnqWMMxm zZ7$&>zULK$o5wOMZDwie%Hf-z&ZfGPnan7vGf6?JP~jS?FoK#~rcuNtgQf-q8*?)5pox@S4a;5 zws*_G(Eg;KYai&|@)19o%ej90SSBMYZ4*ih!sI3=sUK1Ir$d76^x+^72AKK_yeoK< zgwjF-IRGvp+q{%y4PgWUQcCCGyi%SXcRW0^5N|8lY;b@n?<{2ocRT3i)10t)A0R|H zl9*C7Jm?~gAnHAwa%I<(LCjhJVPz`siX{)E!XW!5(RfFBPwLB#dqHZ2+fJGpJU7A7 zV?^ldT^~%|9=GKvlOs+I)M(jyPz8J%$Z|0F>;j_qFjjQI4XJ@_P?JM)Q#y;oSV(X` zyp18(q$SgV<0Z}rQhyvaxxUMOn(voI&0Y2fdr+`#`84DfdVSyXOZ134n_|;pnvU$3 zjm{_U5DsE=p5zG6U2L)|o6Jn#0<{tJ8(^h#DUO`Hd@2V}YEOl{?Z}0L?qC7UI4K#y zfs`*izj7=I762bm$}^NsPGr42_KSS5z{--+;)V^(L~h9S52jP@IpUz6xqew5lzJo% z&bLtKsAH{hoym(Mvft&2S6BXgacJkU<*|!q3(0i0++Qih+bbozESq)EB~KDGkD;dv zgL0D16KW|u$DMU}?N1-xlrH3vH0eAp9G-!K#0D31j8#Pgqc>r>kQZDKa*PD?Nh29| zydc)8uZ!yw*w}A5J^8}6x~F^}D+cilkxJ@!d#K1X<|hvYm=xI3;Y(za@u{*}fDQ37 zpy5Ms7hCs1m-qA13=T6sg(u!HT8`Rv?lkb+H1>Nrob;r9j0S}U@fqnoFqPz;pk$#G zTE73OZO8p&o>F-7o~9fXh%V@%B(6!^!4Bc6{uCrByvKJnZl50?!jC=M@a?8yh|@HN z+fxTh1-T~i_#};nd7<)XR6tV?TCQ*kG>t9-9F-PBaM6M-_-MtQFwUJLys$Ki&I_}+ zH;bIDjYDrU9zPA>X3By6z+t>~!1r{$-Qsoze222)or4x>;LIuDQICsj3axx_^0O?A zg2#Th+ z90bl9PTMr*ISbx6t}L6BEBD5M;B-#I0}dE{{oWsJ?e1^xyZ7~%Uj6WCuZ9dqDNA=q zMS#@SGFi<)6S_;_E`wBcH6Ha;S65pL>WZJy8td>fzG}3rs9I3BL)FcXE~uMkZpYSoJ)ptWkvY>bdv9I3YOW6R=* z)o4W=h374%1C#S8;;;|Uw`x1!rDXxwmL1jWX6lN^>WX&;?pbwJrlYCqx|xVW;EH!( zM!}p%@GWXsmId?Zq!AbH1ihgXRagA39#xIp1zd`P3l#oD@IFoPd(dM~;SvYQ(%B!& z!pMpbs%ka@Por9G@oo-jq#FF=2B^B?{piJZ^m#gWKM%o$weLd5MpU)NMs#W68;eAB zI0n$F1nP|N49L+P}Tq$Z2>q zbhpB94DD1kyT;X3)_yxOXbhFc;5Q13kF~4fzYCwul&a+H4XC}utZYi%3!rwAIduonG4c$c?m#UVXO1@(aFB_nqhoDYmRxF!k05C*3s>7QkcU7l%@Pzk z=x=cZy*G020%9>ow}O;q59T8vfQ``M(8Tawj6>`M@(%g07Byn6@jzg`E{SWMy9nMk z)+N!c3^dLH9&sRv)g{pmV~#PuF$ON)S+VfNAID(uK&tWB6~CL|vxMKM8rJIhu422F zEuYGV_xs+Vyn>!4dUF-om75Qu7Q8Xk<#abWhl1M?XWa9h!hY8YpEWpmcIe}vjGBno zIPk%PT(d9b;;>3|k)p-rTTkc9Pdb6XS=F&}O&b7SD83X=vy=#$ zFN^p|2M%?7GOyt_PFdn~@^8NUd3!jxGW>vXhp}@%rN40sBqQCP-;>GoMju2q2#} zhH%p0lf{UfI5q?4&r8OSJ_vpR2q!iCHDH5CgD3n!l7|djdhk`S%E!3Pvc2PB1`Yvy z1mMrbWB?9BnglnG?Vpc{!9hV3=<`M+7bEx-95}NyaUM(j4lDoOQu^14?E*2twy_+1 zKK4w?2`mdM{NigDlBXNL_Bi;>ll#uep_dOOrNc?-2Gr>@|S23S)wHBd?xh~XFljvJPbS=!a$x4V z%-|o(); + try + { + if (!File.Exists(databaseFullPath)) + { + throw new Exception("Could not find the cards database."); + } + + _cards = new Dictionary(); + + 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];*/ + 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);*/ + NamedCard card = new NamedCard(reader); + _cards.Add(card.Id, card); } } } \ No newline at end of file From 01c4761832a9c0c31777183572e6934e83a63f43 Mon Sep 17 00:00:00 2001 From: edo9300 Date: Mon, 27 Apr 2020 09:52:27 +0200 Subject: [PATCH 40/83] Update assembly properties --- Properties/AssemblyInfo.cs | 6 +++--- Properties/AssemblyInfoLib.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) 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 index 55275f03..7828aa41 100644 --- a/Properties/AssemblyInfoLib.cs +++ b/Properties/AssemblyInfoLib.cs @@ -10,8 +10,8 @@ [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("libWindbot")] -[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyProduct("libWindBot Ignite")] +[assembly: AssemblyCopyright("Copyright (C) 2020 edo9300 and others")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] From b990df023fe1bcfe5f152442b93f3cb6dccfc4d7 Mon Sep 17 00:00:00 2001 From: edo9300 Date: Mon, 27 Apr 2020 20:58:31 +0200 Subject: [PATCH 41/83] Add support for executors loading via dll --- ExecutorBase/ExecutorBase.csproj | 96 + {Game => ExecutorBase/Game}/AI/AIUtil.cs | 890 +++---- .../Game}/AI/CardContainer.cs | 308 +-- .../Game}/AI/CardExecutor.cs | 34 +- .../Game}/AI/CardExtension.cs | 158 +- .../Game}/AI/CardSelector.cs | 206 +- .../Game}/AI/DeckAttribute.cs | 44 +- .../Game}/AI/DefaultExecutor.cs | 2188 ++++++++-------- .../Game/AI/Enums/DangerousMonster.cs | 27 + ExecutorBase/Game/AI/Enums/Floodgate.cs | 87 + ExecutorBase/Game/AI/Enums/FusionSpell.cs | 59 + .../Game/AI/Enums/InvincibleMonster.cs | 69 + ExecutorBase/Game/AI/Enums/OneForXyz.cs | 23 + .../Enums/PreventActivationEffectInBattle.cs | 15 + ...houldBeDisabledBeforeItUseEffectMonster.cs | 54 + .../Game/AI/Enums/ShouldNotBeMonsterTarget.cs | 18 + .../Game/AI/Enums/ShouldNotBeSpellTarget.cs | 14 + .../Game/AI/Enums/ShouldNotBeTarget.cs | 53 + {Game => ExecutorBase/Game}/AI/Executor.cs | 497 ++-- .../Game}/AI/ExecutorType.cs | 30 +- {Game => ExecutorBase/Game}/AI/Zones.cs | 46 +- {Game => ExecutorBase/Game}/BattlePhase.cs | 38 +- .../Game}/BattlePhaseAction.cs | 64 +- {Game => ExecutorBase/Game}/ClientCard.cs | 824 +++--- {Game => ExecutorBase/Game}/ClientField.cs | 702 +++--- {Game => ExecutorBase/Game}/Deck.cs | 160 +- {Game/AI => ExecutorBase/Game}/Dialogs.cs | 362 +-- {Game => ExecutorBase/Game}/Duel.cs | 380 +-- {Game => ExecutorBase/Game}/GameAI.cs | 2214 ++++++++--------- {Game => ExecutorBase/Game}/MainPhase.cs | 54 +- .../Game}/MainPhaseAction.cs | 84 +- Logger.cs => ExecutorBase/Logger.cs | 78 +- ExecutorBase/Properties/AssemblyInfo.cs | 36 + .../CardAttribute.cs | 0 .../CardLinkMarker.cs | 0 .../CardLocation.cs | 0 .../CardPosition.cs | 0 .../YGOSharp.OCGWrapper.Enums}/CardRace.cs | 0 .../YGOSharp.OCGWrapper.Enums}/CardType.cs | 0 .../YGOSharp.OCGWrapper.Enums}/DuelPhase.cs | 0 .../YGOSharp.OCGWrapper.Enums}/GameMessage.cs | 0 .../YGOSharp.OCGWrapper.Enums}/Query.cs | 0 .../YGOSharp.OCGWrapper}/Card.cs | 0 .../YGOSharp.OCGWrapper}/CardsManager.cs | 0 .../YGOSharp.OCGWrapper}/NamedCard.cs | 0 .../YGOSharp.OCGWrapper}/NamedCardsManager.cs | 0 Game/AI/Decks/AltergeistExecutor.cs | 2 +- Game/AI/DecksManager.cs | 19 +- Game/GameBehavior.cs | 4 +- Game/GameClient.cs | 4 +- WindBot.csproj | 40 +- WindBot.sln | 14 +- libWindbot.csproj | 41 +- 53 files changed, 5278 insertions(+), 4758 deletions(-) create mode 100644 ExecutorBase/ExecutorBase.csproj rename {Game => ExecutorBase/Game}/AI/AIUtil.cs (97%) rename {Game => ExecutorBase/Game}/AI/CardContainer.cs (97%) rename {Game => ExecutorBase/Game}/AI/CardExecutor.cs (96%) rename {Game => ExecutorBase/Game}/AI/CardExtension.cs (97%) rename {Game => ExecutorBase/Game}/AI/CardSelector.cs (96%) rename {Game => ExecutorBase/Game}/AI/DeckAttribute.cs (96%) rename {Game => ExecutorBase/Game}/AI/DefaultExecutor.cs (97%) create mode 100644 ExecutorBase/Game/AI/Enums/DangerousMonster.cs create mode 100644 ExecutorBase/Game/AI/Enums/Floodgate.cs create mode 100644 ExecutorBase/Game/AI/Enums/FusionSpell.cs create mode 100644 ExecutorBase/Game/AI/Enums/InvincibleMonster.cs create mode 100644 ExecutorBase/Game/AI/Enums/OneForXyz.cs create mode 100644 ExecutorBase/Game/AI/Enums/PreventActivationEffectInBattle.cs create mode 100644 ExecutorBase/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs create mode 100644 ExecutorBase/Game/AI/Enums/ShouldNotBeMonsterTarget.cs create mode 100644 ExecutorBase/Game/AI/Enums/ShouldNotBeSpellTarget.cs create mode 100644 ExecutorBase/Game/AI/Enums/ShouldNotBeTarget.cs rename {Game => ExecutorBase/Game}/AI/Executor.cs (95%) rename {Game => ExecutorBase/Game}/AI/ExecutorType.cs (94%) rename {Game => ExecutorBase/Game}/AI/Zones.cs (95%) rename {Game => ExecutorBase/Game}/BattlePhase.cs (96%) rename {Game => ExecutorBase/Game}/BattlePhaseAction.cs (95%) rename {Game => ExecutorBase/Game}/ClientCard.cs (97%) rename {Game => ExecutorBase/Game}/ClientField.cs (96%) rename {Game => ExecutorBase/Game}/Deck.cs (96%) rename {Game/AI => ExecutorBase/Game}/Dialogs.cs (88%) rename {Game => ExecutorBase/Game}/Duel.cs (97%) rename {Game => ExecutorBase/Game}/GameAI.cs (96%) rename {Game => ExecutorBase/Game}/MainPhase.cs (97%) rename {Game => ExecutorBase/Game}/MainPhaseAction.cs (95%) rename Logger.cs => ExecutorBase/Logger.cs (96%) create mode 100644 ExecutorBase/Properties/AssemblyInfo.cs rename {YGOSharp.OCGWrapper.Enums => ExecutorBase/YGOSharp.OCGWrapper.Enums}/CardAttribute.cs (100%) rename {YGOSharp.OCGWrapper.Enums => ExecutorBase/YGOSharp.OCGWrapper.Enums}/CardLinkMarker.cs (100%) rename {YGOSharp.OCGWrapper.Enums => ExecutorBase/YGOSharp.OCGWrapper.Enums}/CardLocation.cs (100%) rename {YGOSharp.OCGWrapper.Enums => ExecutorBase/YGOSharp.OCGWrapper.Enums}/CardPosition.cs (100%) rename {YGOSharp.OCGWrapper.Enums => ExecutorBase/YGOSharp.OCGWrapper.Enums}/CardRace.cs (100%) rename {YGOSharp.OCGWrapper.Enums => ExecutorBase/YGOSharp.OCGWrapper.Enums}/CardType.cs (100%) rename {YGOSharp.OCGWrapper.Enums => ExecutorBase/YGOSharp.OCGWrapper.Enums}/DuelPhase.cs (100%) rename {YGOSharp.OCGWrapper.Enums => ExecutorBase/YGOSharp.OCGWrapper.Enums}/GameMessage.cs (100%) rename {YGOSharp.OCGWrapper.Enums => ExecutorBase/YGOSharp.OCGWrapper.Enums}/Query.cs (100%) rename {YGOSharp.OCGWrapper => ExecutorBase/YGOSharp.OCGWrapper}/Card.cs (100%) rename {YGOSharp.OCGWrapper => ExecutorBase/YGOSharp.OCGWrapper}/CardsManager.cs (100%) rename {YGOSharp.OCGWrapper => ExecutorBase/YGOSharp.OCGWrapper}/NamedCard.cs (100%) rename {YGOSharp.OCGWrapper => ExecutorBase/YGOSharp.OCGWrapper}/NamedCardsManager.cs (100%) diff --git a/ExecutorBase/ExecutorBase.csproj b/ExecutorBase/ExecutorBase.csproj new file mode 100644 index 00000000..b0f8c1ae --- /dev/null +++ b/ExecutorBase/ExecutorBase.csproj @@ -0,0 +1,96 @@ + + + + + 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 97% rename from Game/AI/AIUtil.cs rename to ExecutorBase/Game/AI/AIUtil.cs index 68da61bd..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 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; - } - } +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..080eda06 100644 --- a/Game/AI/DefaultExecutor.cs +++ b/ExecutorBase/Game/AI/DefaultExecutor.cs @@ -1,1094 +1,1094 @@ -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; +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; + } + } +} diff --git a/ExecutorBase/Game/AI/Enums/DangerousMonster.cs b/ExecutorBase/Game/AI/Enums/DangerousMonster.cs new file mode 100644 index 00000000..0c861f8e --- /dev/null +++ b/ExecutorBase/Game/AI/Enums/DangerousMonster.cs @@ -0,0 +1,27 @@ +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, + } +} diff --git a/ExecutorBase/Game/AI/Enums/Floodgate.cs b/ExecutorBase/Game/AI/Enums/Floodgate.cs new file mode 100644 index 00000000..fa300a30 --- /dev/null +++ b/ExecutorBase/Game/AI/Enums/Floodgate.cs @@ -0,0 +1,87 @@ +namespace WindBot.Game.AI.Enums +{ + /// + /// Cards that restrict player from performing some action. Bot will preferentially destroy them. + /// + public enum Floodgate + { + BarrierStatueoftheTorrent = 10963799, + BarrierStatueoftheDrought = 19740112, + BarrierStatueoftheHeavens = 46145256, + BarrierStatueoftheInferno = 47961808, + BarrierStatueoftheStormwinds = 73356503, + BarrierStatueoftheAbyss = 84478195, + ThunderKingRaiOh = 71564252, + FossilDynaPachycephalo = 42009836, + VanitysFiend = 47084486, + MajestysFiend = 33746252, + VanitysRuler = 72634965, + KycootheGhostDestroyer = 88240808, + ConsecratedLight = 2980764, + ArchlordKristya = 59509952, + KoakiMeiruDrago = 12435193, + DenkoSekka = 13974207, + ZapMustung = 29951323, + Jinzo = 77585513, + SpellCanceller = 84636823, + LevelLimitAreaB = 3136426, + DimensionalFissure = 81674782, + Necrovalley = 47355498, + SavageColosseum = 32391631, + SecretVillageoftheSpellcasters = 68462976, + SwordsofRevealingLight = 72302403, + MessengerofPeace = 44656491, + KaiserColosseum = 35059553, + DomainoftheTrueMonarchs = 84171830, + ZombieWorld = 4064256, + ImperialOrder = 61740673, + MacroCosmos = 30241314, + MindDrain = 68937720, + SoulDrain = 73599290, + SkillDrain = 82732705, + Eisbahn = 54059040, + GozenMatch = 53334471, + RivalryofWarlords = 90846359, + AntiSpellFragrance = 58921041, + LightImprisoningMirror = 53341729, + ShadowImprisoningMirror = 99735427, + WallofRevealingLight = 17078030, + GravityBind = 85742772, + VanitysEmptiness = 5851097, + Lose1Turn = 24348804, + Reqliate = 20426907, + SummonLimit = 23516703, + AndtheBandPlayedOn = 47594939, + StygianDirge = 81489939, + RoyalDecree = 51452091, + ImperialIronWall = 30459350, + DNASurgery = 74701381, + NaturiaExterio = 99916754, + TheLastWarriorfromAnotherPlanet = 86099788, + ThousandEyesRestrict = 63519819, + MaskedHERODarkLaw = 58481572, + NaturiaBeast = 33198837, + NaturiaBarkion = 2956282, + EvilswarmOphion = 91279700, + MermailAbyssgaios = 74371660, + AbyssDweller = 21044178, + ZoodiacDrident = 48905153, + InvokedMechaba = 75286621, + ElShaddollShekhinaga = 74822425, + ElShaddollConstruct = 20366274, + ElShaddollGrysra = 48424886, + ElShaddollWinda = 94977269, + UltimateConductorTytanno = 18940556, + OvertexCoatls = 41782653, + FirePrison = 269510, + LairOfDarkness = 59160188, + SuperboltThunderDragon = 15291624, + ThunderDragonLord = 41685633, + CyberDragonInfinity = 10443957, + ImperialCustom = 9995766, + InspectorBoarder = 15397015, + Mashoudou = 76375976, + EternalSoul = 48680970, + MarincessBattleOcean = 91027843 + } +} diff --git a/ExecutorBase/Game/AI/Enums/FusionSpell.cs b/ExecutorBase/Game/AI/Enums/FusionSpell.cs new file mode 100644 index 00000000..b4859b55 --- /dev/null +++ b/ExecutorBase/Game/AI/Enums/FusionSpell.cs @@ -0,0 +1,59 @@ +namespace WindBot.Game.AI.Enums +{ + public enum FusionSpell + { + GemKnightFusion = 1264319, + TheEyeofTimaeus = 1784686, + InstantFusion = 1845204, + OverloadFusion = 3659803, + FrightfurFusion = 6077601, + RedEyesFusion = 6172122, + Ostinato = 9113513, + MagicalizeFusion = 11827244, + DarkCalling = 12071500, + VehicroidConnectionZone = 23299957, + Polymerization = 24094653, + MiracleSynchroFusion = 36484016, + PowerBond = 37630732, + ParticleFusion = 39261576, + ShaddollFusion = 44394295, + TheTerminusoftheBurningAbyss = 44771289, + MiracleFusion = 45906428, + OddEyesFusion = 48144509, + ParallelWorldFusion = 54283059, + AncientGearFusion = 64061284, + PendulumFusion = 65646587, + CynetFusion = 65801012, + AbsorbFusion = 71422989, + DragonsMirror = 71490127, + MetalfoesFusion = 73594093, + EidolonSummoningMagic = 74063034, + FusionSubstitute = 74335036, + TranscendentalPolymerization = 76647978, + CyberdarkImpact = 80033124, + DarkFusion = 94820406, + ThunderDragonFusion = 95238394, + TheBookoftheLaw = 458748, + ElShaddollFusion = 6417578, + FlashFusion = 17236839, + FullmetalfoesFusion = 39564736, + DestructionSwordsmanFusion = 41940225, + SuperPolymerization = 48130397, + CyberloadFusion = 55704856, + RelinquishedFusion = 78063197, + BrilliantFusion = 7394770, + ForbiddenDarkContractwiththeSwampKing = 10833828, + Fortissimo = 11493868, + VoidImagination = 31444249, + FrightfurFactory = 43698897, + DarkContractwiththeSwampKing = 73360025, + NepheShaddollFusion = 60226558, + FusionGate = 33550694, + + DFusion = 26841274, + PyroxeneFusion = 55824220, + FragmentFusion = 72029628, + NecroFusion = 81223446, + PredaplantVerteAnaconda = 70369116, + } +} diff --git a/ExecutorBase/Game/AI/Enums/InvincibleMonster.cs b/ExecutorBase/Game/AI/Enums/InvincibleMonster.cs new file mode 100644 index 00000000..207954ee --- /dev/null +++ b/ExecutorBase/Game/AI/Enums/InvincibleMonster.cs @@ -0,0 +1,69 @@ +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 + } +} diff --git a/ExecutorBase/Game/AI/Enums/OneForXyz.cs b/ExecutorBase/Game/AI/Enums/OneForXyz.cs new file mode 100644 index 00000000..074fae2a --- /dev/null +++ b/ExecutorBase/Game/AI/Enums/OneForXyz.cs @@ -0,0 +1,23 @@ +namespace WindBot.Game.AI.Enums +{ + public enum OneForXyz + { + ZoodiacThoroughblade = 77150143, + ZoodiacViper = 31755044, + ZoodiacCluckle = 20155904, + ZoodiacRabbina = 4367330, + ZoodiacRam = 4145852, + ZoodiacMarmorat = 78872731, + ZoodiacTigress = 11510448, + ZoodiacHammerkong = 14970113, + ZoodiacLyca = 41375811, + ZoodiacDrancia = 48905153, + ZoodiacBoarbow = 74393852, + ZoodiacBroadbull = 85115440, + Number62 = 31801517, + GalaxyEyesCipherDragon = 18963306, + Number107 = 88177324, + CyberDragonNova = 58069384, + Number39 = 84013237 + } +} diff --git a/ExecutorBase/Game/AI/Enums/PreventActivationEffectInBattle.cs b/ExecutorBase/Game/AI/Enums/PreventActivationEffectInBattle.cs new file mode 100644 index 00000000..34f98e6d --- /dev/null +++ b/ExecutorBase/Game/AI/Enums/PreventActivationEffectInBattle.cs @@ -0,0 +1,15 @@ +namespace WindBot.Game.AI.Enums +{ + public enum PreventActivationEffectInBattle + { + Deskbot009 = 25494711, + ArchfiendBlackSkullDragon = 45349196, + FrightfurChimera = 83866861, + GladiatorBeastNerokius = 29357956, + GemKnightCitrine = 67985943, + FrightfurSheep = 57477163, + SamuraiDestroyer = 40509732, + ArmadesKeeperOfBoundaries = 88033975, + NumberS39UtopiaTheLightning = 56832966, + } +} diff --git a/ExecutorBase/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs b/ExecutorBase/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs new file mode 100644 index 00000000..4fab9a27 --- /dev/null +++ b/ExecutorBase/Game/AI/Enums/ShouldBeDisabledBeforeItUseEffectMonster.cs @@ -0,0 +1,54 @@ +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 + } +} diff --git a/ExecutorBase/Game/AI/Enums/ShouldNotBeMonsterTarget.cs b/ExecutorBase/Game/AI/Enums/ShouldNotBeMonsterTarget.cs new file mode 100644 index 00000000..f31e31cc --- /dev/null +++ b/ExecutorBase/Game/AI/Enums/ShouldNotBeMonsterTarget.cs @@ -0,0 +1,18 @@ +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 + } +} diff --git a/ExecutorBase/Game/AI/Enums/ShouldNotBeSpellTarget.cs b/ExecutorBase/Game/AI/Enums/ShouldNotBeSpellTarget.cs new file mode 100644 index 00000000..fa78eaf9 --- /dev/null +++ b/ExecutorBase/Game/AI/Enums/ShouldNotBeSpellTarget.cs @@ -0,0 +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 + } +} diff --git a/ExecutorBase/Game/AI/Enums/ShouldNotBeTarget.cs b/ExecutorBase/Game/AI/Enums/ShouldNotBeTarget.cs new file mode 100644 index 00000000..3c470650 --- /dev/null +++ b/ExecutorBase/Game/AI/Enums/ShouldNotBeTarget.cs @@ -0,0 +1,53 @@ +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 + } +} diff --git a/Game/AI/Executor.cs b/ExecutorBase/Game/AI/Executor.cs similarity index 95% rename from Game/AI/Executor.cs rename to ExecutorBase/Game/AI/Executor.cs index 3f3b4980..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, 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); - } - } +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 97% rename from Game/ClientCard.cs rename to ExecutorBase/Game/ClientCard.cs index 4522e81e..a41e0e76 100644 --- a/Game/ClientCard.cs +++ b/ExecutorBase/Game/ClientCard.cs @@ -1,413 +1,413 @@ -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 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); - } - } +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 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 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 b5479de9..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, 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())); - } - } +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/Deck.cs b/ExecutorBase/Game/Deck.cs similarity index 96% rename from Game/Deck.cs rename to ExecutorBase/Game/Deck.cs index ca7dec8c..b6e32e5a 100644 --- a/Game/Deck.cs +++ b/ExecutorBase/Game/Deck.cs @@ -1,81 +1,81 @@ -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 - { -#if !LIBWINDBOT - reader = new StreamReader(new FileStream("Decks/" + name + ".ydk", FileMode.Open, FileAccess.Read)); -#else - reader = new StreamReader(new FileStream(Path.Combine(WindBot.AssetPath, "Decks/", name + ".ydk"), FileMode.Open, FileAccess.Read)); -#endif - - 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; - } - } - } +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 + { +#if !LIBWINDBOT + reader = new StreamReader(new FileStream("Decks/" + name + ".ydk", FileMode.Open, FileAccess.Read)); +#else + reader = new StreamReader(new FileStream(Path.Combine(WindBot.AssetPath, "Decks/", name + ".ydk"), FileMode.Open, FileAccess.Read)); +#endif + + 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 diff --git a/Game/AI/Dialogs.cs b/ExecutorBase/Game/Dialogs.cs similarity index 88% rename from Game/AI/Dialogs.cs rename to ExecutorBase/Game/Dialogs.cs index a0a86ae2..edf8eac1 100644 --- a/Game/AI/Dialogs.cs +++ b/ExecutorBase/Game/Dialogs.cs @@ -1,181 +1,181 @@ -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; -#if !LIBWINDBOT - using (FileStream fs = File.OpenRead("Dialogs/" + dialogfilename + ".json")) -#else - using (FileStream fs = File.OpenRead(Path.Combine(WindBot.AssetPath, "Dialogs/", dialogfilename + ".json"))) -#endif - { - 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) + { + Chat = chat; + DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(DialogsData)); +#if !LIBWINDBOT + using (FileStream fs = File.OpenRead("Dialogs/" + dialogfilename + ".json")) +#else + using (FileStream fs = File.OpenRead(Path.Combine(WindBot.AssetPath, "Dialogs/", dialogfilename + ".json"))) +#endif + { + 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 97% rename from Game/Duel.cs rename to ExecutorBase/Game/Duel.cs index 18a1aa69..63a6205e 100644 --- a/Game/Duel.cs +++ b/ExecutorBase/Game/Duel.cs @@ -1,191 +1,191 @@ -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, 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; - } - } +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, 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; + } + } } \ No newline at end of file diff --git a/Game/GameAI.cs b/ExecutorBase/Game/GameAI.cs similarity index 96% rename from Game/GameAI.cs rename to ExecutorBase/Game/GameAI.cs index 634dbc38..b02b2322 100644 --- a/Game/GameAI.cs +++ b/ExecutorBase/Game/GameAI.cs @@ -1,1108 +1,1106 @@ -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) - { - 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) - { - 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, 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) - { - 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 class GameAI + { + public Duel Duel { get; private set; } + public Executor Executor { get; set; } + + public Dialogs _dialogs; + + public GameAI(Duel duel, string dialog, System.Action chat) + { + Duel = duel; + + _dialogs = new Dialogs(dialog, chat); + } + + /// + /// 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) + { + 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, 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) + { + 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 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/Logger.cs b/ExecutorBase/Logger.cs similarity index 96% rename from Logger.cs rename to ExecutorBase/Logger.cs index 0dffa0c5..086a3667 100644 --- a/Logger.cs +++ b/ExecutorBase/Logger.cs @@ -1,40 +1,40 @@ -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 - } - } +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/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/YGOSharp.OCGWrapper.Enums/CardAttribute.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardAttribute.cs similarity index 100% rename from YGOSharp.OCGWrapper.Enums/CardAttribute.cs rename to ExecutorBase/YGOSharp.OCGWrapper.Enums/CardAttribute.cs diff --git a/YGOSharp.OCGWrapper.Enums/CardLinkMarker.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardLinkMarker.cs similarity index 100% rename from YGOSharp.OCGWrapper.Enums/CardLinkMarker.cs rename to ExecutorBase/YGOSharp.OCGWrapper.Enums/CardLinkMarker.cs diff --git a/YGOSharp.OCGWrapper.Enums/CardLocation.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardLocation.cs similarity index 100% rename from YGOSharp.OCGWrapper.Enums/CardLocation.cs rename to ExecutorBase/YGOSharp.OCGWrapper.Enums/CardLocation.cs diff --git a/YGOSharp.OCGWrapper.Enums/CardPosition.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardPosition.cs similarity index 100% rename from YGOSharp.OCGWrapper.Enums/CardPosition.cs rename to ExecutorBase/YGOSharp.OCGWrapper.Enums/CardPosition.cs diff --git a/YGOSharp.OCGWrapper.Enums/CardRace.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardRace.cs similarity index 100% rename from YGOSharp.OCGWrapper.Enums/CardRace.cs rename to ExecutorBase/YGOSharp.OCGWrapper.Enums/CardRace.cs diff --git a/YGOSharp.OCGWrapper.Enums/CardType.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/CardType.cs similarity index 100% rename from YGOSharp.OCGWrapper.Enums/CardType.cs rename to ExecutorBase/YGOSharp.OCGWrapper.Enums/CardType.cs diff --git a/YGOSharp.OCGWrapper.Enums/DuelPhase.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/DuelPhase.cs similarity index 100% rename from YGOSharp.OCGWrapper.Enums/DuelPhase.cs rename to ExecutorBase/YGOSharp.OCGWrapper.Enums/DuelPhase.cs diff --git a/YGOSharp.OCGWrapper.Enums/GameMessage.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/GameMessage.cs similarity index 100% rename from YGOSharp.OCGWrapper.Enums/GameMessage.cs rename to ExecutorBase/YGOSharp.OCGWrapper.Enums/GameMessage.cs diff --git a/YGOSharp.OCGWrapper.Enums/Query.cs b/ExecutorBase/YGOSharp.OCGWrapper.Enums/Query.cs similarity index 100% rename from YGOSharp.OCGWrapper.Enums/Query.cs rename to ExecutorBase/YGOSharp.OCGWrapper.Enums/Query.cs diff --git a/YGOSharp.OCGWrapper/Card.cs b/ExecutorBase/YGOSharp.OCGWrapper/Card.cs similarity index 100% rename from YGOSharp.OCGWrapper/Card.cs rename to ExecutorBase/YGOSharp.OCGWrapper/Card.cs diff --git a/YGOSharp.OCGWrapper/CardsManager.cs b/ExecutorBase/YGOSharp.OCGWrapper/CardsManager.cs similarity index 100% rename from YGOSharp.OCGWrapper/CardsManager.cs rename to ExecutorBase/YGOSharp.OCGWrapper/CardsManager.cs diff --git a/YGOSharp.OCGWrapper/NamedCard.cs b/ExecutorBase/YGOSharp.OCGWrapper/NamedCard.cs similarity index 100% rename from YGOSharp.OCGWrapper/NamedCard.cs rename to ExecutorBase/YGOSharp.OCGWrapper/NamedCard.cs diff --git a/YGOSharp.OCGWrapper/NamedCardsManager.cs b/ExecutorBase/YGOSharp.OCGWrapper/NamedCardsManager.cs similarity index 100% rename from YGOSharp.OCGWrapper/NamedCardsManager.cs rename to ExecutorBase/YGOSharp.OCGWrapper/NamedCardsManager.cs diff --git a/Game/AI/Decks/AltergeistExecutor.cs b/Game/AI/Decks/AltergeistExecutor.cs index 2a517411..d9bf02bb 100644 --- a/Game/AI/Decks/AltergeistExecutor.cs +++ b/Game/AI/Decks/AltergeistExecutor.cs @@ -6,7 +6,7 @@ namespace WindBot.Game.AI.Decks { - [Deck("Altergeist", "AI_Altergeist")] + [Deck("Altergsasaseist", "AI_Altergeist")] public class AltergeistExecutor : DefaultExecutor { diff --git a/Game/AI/DecksManager.cs b/Game/AI/DecksManager.cs index 09c15ead..7d00f69f 100644 --- a/Game/AI/DecksManager.cs +++ b/Game/AI/DecksManager.cs @@ -45,6 +45,21 @@ public static void Init() } } } + Assembly assembly = Assembly.LoadFrom("Altergeist.dll"); + Type[] types2 = assembly.GetTypes(); + foreach (Type type in types2) + { + 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); @@ -52,12 +67,10 @@ public static void Init() Logger.WriteLine("Decks initialized, " + _decks.Count + " found."); } - public static Executor Instantiate(GameAI ai, Duel duel) + public static Executor Instantiate(GameAI ai, Duel duel, string deck) { DeckInstance infos; - string deck = ai.Game.Deck; - if (deck != null && _decks.ContainsKey(deck)) { infos = _decks[deck]; diff --git a/Game/GameBehavior.cs b/Game/GameBehavior.cs index dedd3eae..8004d3ee 100644 --- a/Game/GameBehavior.cs +++ b/Game/GameBehavior.cs @@ -69,8 +69,8 @@ public GameBehavior(GameClient game) _room = new Room(); _duel = new Duel(); - _ai = new GameAI(Game, _duel); - _ai.Executor = DecksManager.Instantiate(_ai, _duel); + _ai = new GameAI(_duel, Game.Dialog, Game.Chat); + _ai.Executor = DecksManager.Instantiate(_ai, _duel, Game.Deck); Deck = Deck.Load(_ai.Executor.Deck); _select_hint = 0; diff --git a/Game/GameClient.cs b/Game/GameClient.cs index b24619a7..52a41a37 100644 --- a/Game/GameClient.cs +++ b/Game/GameClient.cs @@ -69,8 +69,10 @@ 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); diff --git a/WindBot.csproj b/WindBot.csproj index 4bf1734d..8e3b23d3 100644 --- a/WindBot.csproj +++ b/WindBot.csproj @@ -54,12 +54,6 @@ - - - - - - @@ -98,8 +92,6 @@ - - @@ -110,23 +102,10 @@ - - - - - - - - - - - - - @@ -144,19 +123,6 @@ - - - - - - - - - - - - - @@ -184,6 +150,12 @@ + + + {a1583fd7-7985-47dd-a835-8134dbf5811c} + ExecutorBase + + - \ No newline at end of file + From d442250716bc0c00764418b4859c25b10eb1aa3b Mon Sep 17 00:00:00 2001 From: edo9300 Date: Tue, 19 May 2020 21:49:00 +0200 Subject: [PATCH 68/83] Support for new deckerror message structure --- Game/GameBehavior.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Game/GameBehavior.cs b/Game/GameBehavior.cs index 815b5c68..c4bf0ef7 100644 --- a/Game/GameBehavior.cs +++ b/Game/GameBehavior.cs @@ -355,11 +355,13 @@ private void OnErrorMsg(BinaryReader packet) packet.ReadByte(); packet.ReadByte(); packet.ReadByte(); - int pcode = packet.ReadInt32(); if (msg == 2) //ERRMSG_DECKERROR { - int code = pcode & 0xFFFFFFF; - int flag = pcode >> 28; + int flag = packet.ReadInt32(); + packet.ReadInt32(); + packet.ReadInt32(); + packet.ReadInt32(); + int code = packet.ReadInt32(); if (flag <= 5) //DECKERROR_CARDCOUNT { NamedCard card = NamedCard.Get(code); From d8c1231a94d007ae47247e85735b5f9da58299a1 Mon Sep 17 00:00:00 2001 From: Kevin Lu <6320810+kevinlul@users.noreply.github.com> Date: Thu, 21 May 2020 01:13:12 -0400 Subject: [PATCH 69/83] Add CI for libWindbot (#18) One fast job to build for desktop and uploading those release archives. One slower job to build the Android aar. Closes #10. --- .travis.yml | 36 ++++++++++++++++++++++++++---------- ci/install-sdk-ndk.sh | 20 ++++++++++++++++++++ libWindbot.csproj | 2 +- 3 files changed, 47 insertions(+), 11 deletions(-) create mode 100755 ci/install-sdk-ndk.sh diff --git a/.travis.yml b/.travis.yml index 794d96e6..b6c5c33e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,14 +5,29 @@ git: env: global: - ARTIFACT="WindBotIgnite-$(date +%Y-%m-%d)-$TRAVIS_COMMIT.zip" -script: -- export PATH="/c/Program Files (x86)/Microsoft Visual Studio/2017/BuildTools/MSBuild/15.0/Bin":$PATH -- dotnet build WindBot.csproj --configuration=Release -before_deploy: -- cd bin -- mv Release WindBot -- 7z a -tzip "$ARTIFACT" WindBot -- cd .. + # 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 + 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: + - mv output/libWindbot.aar . deploy: - provider: script skip_cleanup: true @@ -21,8 +36,9 @@ deploy: repo: ProjectIgnis/windbot - provider: releases skip_cleanup: true - api_key: $DEPLOY_TOKEN + api_key: $RELEASES_TOKEN file: - - bin/$ARTIFACT + - $ARTIFACT + - libWindbot.aar on: tags: true diff --git a/ci/install-sdk-ndk.sh b/ci/install-sdk-ndk.sh new file mode 100755 index 00000000..433cd143 --- /dev/null +++ b/ci/install-sdk-ndk.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -euxo pipefail + +# These registry entries are normally set through the GUI: Tools\Options\Xamarin\Android Settings +reg add 'HKCU\SOFTWARE\Xamarin\VisualStudio\15.0_e43b966e\Android' -v AndroidSdkDirectory -t REG_SZ -d "$ANDROID_SDK_ROOT" -f +# Sometimes installed by Microsoft in C:\ProgramData\Microsoft\AndroidNDK64 but not present on CI +reg add 'HKCU\SOFTWARE\Xamarin\VisualStudio\15.0_e43b966e\Android' -v AndroidNdkDirectory -t REG_SZ -d "C:\android-ndk-r15c" -f +# Visual Studio Installer provides this JDK for Android development +reg add 'HKCU\SOFTWARE\Xamarin\VisualStudio\15.0_e43b966e\Android' -v JavaSdkDirectory -t REG_SZ -d "$JAVA_HOME" -f + +# Manually install Android SDK Platform 24, the most recent version that still works with Embeddinator 0.4.0 +cd "$ANDROID_SDK_ROOT" +(yes || true) | tools/bin/sdkmanager.bat --sdk_root=. "platforms;android-24" +cd - + +# Manually install Android NDK r15c, the most recent version that still works with Embeddinator 0.4.0 +curl --retry 5 --connect-timeout 30 --location --remote-header-name --remote-name https://dl.google.com/android/repository/android-ndk-r15c-windows-x86_64.zip +echo "970bb2496de0eada74674bb1b06d79165f725696 *android-ndk-r15c-windows-x86_64.zip" | sha1sum -c +7z x android-ndk-r15c-windows-x86_64.zip -o'C:' diff --git a/libWindbot.csproj b/libWindbot.csproj index 2c6b7ebc..e709dd37 100644 --- a/libWindbot.csproj +++ b/libWindbot.csproj @@ -132,7 +132,7 @@ if exist %25E4K_OUTPUT%25 rmdir /S /Q %25E4K_OUTPUT%25 "$(NuGetPackageRoot)embeddinator-4000\0.4.0\tools\Embeddinator-4000.exe" "$(TargetPath)" --gen=Java --platform=Android --outdir=%25E4K_OUTPUT%25 -c -v -