diff --git a/.gitattributes b/.gitattributes index bb4d08c4829..9927d48d5ed 100644 --- a/.gitattributes +++ b/.gitattributes @@ -18987,6 +18987,7 @@ forge-gui/res/quest/duels/Terminator[!!-~]3.dck -text forge-gui/res/quest/duels/Thanos[!!-~]3.dck -text forge-gui/res/quest/duels/The[!!-~]Great[!!-~]Gatsby[!!-~]1.dck -text forge-gui/res/quest/duels/The[!!-~]Great[!!-~]Gazoo[!!-~]3.dck -text +forge-gui/res/quest/duels/The[!!-~]Great[!!-~]and[!!-~]Powerful[!!-~]Trixie[!!-~]4.dck -text forge-gui/res/quest/duels/The[!!-~]Nac[!!-~]Mac[!!-~]Feegle[!!-~]3.dck -text forge-gui/res/quest/duels/The[!!-~]Shade[!!-~]3.dck -text forge-gui/res/quest/duels/The[!!-~]Spectre[!!-~]3.dck -text diff --git a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java index 3bc113cdfb2..c199127c724 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java @@ -19,6 +19,7 @@ import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; +import org.apache.commons.lang3.StringUtils; public class PermanentAi extends SpellAbilityAi { @@ -150,6 +151,46 @@ public class PermanentAi extends SpellAbilityAi { } } } + + // check for specific AI preferences + if (card.hasSVar("AICastPreference")) { + String pref = card.getSVar("AICastPreference"); + String[] groups = StringUtils.split(pref, "|"); + for (String group : groups) { + String[] elems = StringUtils.split(group.trim(), '$'); + String param = elems[0].trim(); + String value = elems[1].trim(); + if (param.equals("MustHaveInHand")) { + // Only cast if another card is present in hand (e.g. Illusions of Grandeur followed by Donate) + boolean hasCard = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.nameEquals(value)).size() > 0; + if (!hasCard) { + return false; + } + } else if (param.equals("MaxControlled")) { + // Only cast unless there are X or more cards like this on the battlefield under AI control already + int numControlled = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(card.getName())).size(); + if (numControlled >= Integer.parseInt(value)) { + return false; + } + } else if (elems[0].trim().equals("NumManaSources")) { + // Only cast if there are X or more mana sources controlled by the AI + CardCollection m = ComputerUtilMana.getAvailableMana(ai, true); + if (m.size() < Integer.parseInt(value)) { + return false; + } + } else if (elems[0].trim().equals("NumManaSourcesNextTurn")) { + // Only cast if there are X or more mana sources controlled by the AI *or* + // if there are X-1 mana sources in play but the AI has an extra land in hand + CardCollection m = ComputerUtilMana.getAvailableMana(ai, true); + int hasExtraLandInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size() > 0 ? 1 : 0; + if (m.size() + hasExtraLandInHand < Integer.parseInt(value)) { + return false; + } + } + } + + } + return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java index 5d8b6db4452..b462a32be35 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -5,6 +5,7 @@ import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollection; +import forge.game.card.CardCollectionView; import forge.game.card.CardLists; import forge.game.card.CounterType; import forge.game.card.CardPredicates.Presets; @@ -93,6 +94,20 @@ public class PumpAi extends PumpAiBase { sa.getTargets().add(lowest); return true; } + } else if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("Donate")) { + // Donate currently supports only one special code path (Illusions of Grandeur) + CardCollectionView aiList = ai.getCardsIn(ZoneType.Battlefield, "Illusions of Grandeur"); + if (aiList.size() > 0) { + // Donate an Illusions of Grandeur, step 1 - target the opponent. + if (sa.getParam("AILogic").equals("DonateTargetPlayer")) { + sa.resetTargets(); + sa.getTargets().add(ai.getOpponents().get(0)); // TODO: expand this to donate to an opp who doesn't have Illusions yet + return true; + } + } + // AI currently does not know how to handle Donate effectively otherwise + // TODO: enhance this for other cases (e.g. Donating a card with bad drawback to the opponent) + return false; } if (ComputerUtil.preventRunAwayActivations(sa)) { @@ -233,6 +248,13 @@ public class PumpAi extends PumpAiBase { } else { return false; } + } else if (sa.getParam("AILogic").equals("DonateTargetPerm")) { + // Illusions of Grandeur + Donate, step 2 - target Illusions. + CardCollectionView aiList = ai.getCardsIn(ZoneType.Battlefield, "Illusions of Grandeur"); + if (aiList.size() > 0) { + sa.getTargets().add(aiList.get(0)); + return true; + } } if (isFight) { return FightAi.canFightAi(ai, sa, attack, defense); diff --git a/forge-gui/res/cardsfolder/d/donate.txt b/forge-gui/res/cardsfolder/d/donate.txt index e4984f06bb6..2394b21a495 100644 --- a/forge-gui/res/cardsfolder/d/donate.txt +++ b/forge-gui/res/cardsfolder/d/donate.txt @@ -1,9 +1,9 @@ Name:Donate ManaCost:2 U Types:Sorcery -A:SP$ Pump | Cost$ 2 U | ValidTgts$ Player | TgtPrompt$ Select target player | SubAbility$ D1 | SpellDescription$ Target player gains control of target permanent you control. | StackDescription$ None +A:SP$ Pump | Cost$ 2 U | ValidTgts$ Player | TgtPrompt$ Select target player | SubAbility$ D1 | AILogic$ DonateTargetPlayer | SpellDescription$ Target player gains control of target permanent you control. | StackDescription$ None SVar:D1:DB$ Pump | RememberObjects$ Targeted | Static$ True | SubAbility$ D2 | StackDescription$ None -SVar:D2:DB$ Pump | ValidTgts$ Permanent.YouCtrl | TgtPrompt$ Select target permanent you control | SubAbility$ D3 | StackDescription$ None +SVar:D2:DB$ Pump | ValidTgts$ Permanent.YouCtrl | TgtPrompt$ Select target permanent you control | SubAbility$ D3 | AILogic$ DonateTargetPerm | StackDescription$ None SVar:D3:DB$ GainControl | Defined$ Targeted | NewController$ Remembered | SubAbility$ D4 SVar:D4:DB$ Cleanup | ClearRemembered$ True SVar:RemRandomDeck:True diff --git a/forge-gui/res/cardsfolder/i/illusions_of_grandeur.txt b/forge-gui/res/cardsfolder/i/illusions_of_grandeur.txt index b1eb7c71135..8d523cabbc4 100644 --- a/forge-gui/res/cardsfolder/i/illusions_of_grandeur.txt +++ b/forge-gui/res/cardsfolder/i/illusions_of_grandeur.txt @@ -6,6 +6,9 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigLoseLife | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME leaves the battlefield, you lose 20 life. SVar:TrigGainLife:AB$GainLife | Cost$ 0 | Defined$ TriggeredCardController | LifeAmount$ 20 SVar:TrigLoseLife:AB$LoseLife | Cost$ 0 | Defined$ TriggeredCardController | LifeAmount$ 20 -SVar:RemAIDeck:True +SVar:AICastPreference:MustHaveInHand$ Donate | MaxControlled$ 1 | NumManaSourcesNextTurn$ 5 +SVar:RemRandomDeck:True +SVar:DeckNeeds:Name$Donate +SVar:PlayMain1:TRUE SVar:Picture:http://www.wizards.com/global/images/magic/general/illusions_of_grandeur.jpg Oracle:Cumulative upkeep {2} (At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it.)\nWhen Illusions of Grandeur enters the battlefield, you gain 20 life.\nWhen Illusions of Grandeur leaves the battlefield, you lose 20 life. diff --git a/forge-gui/res/quest/duels/The Great and Powerful Trixie 4.dck b/forge-gui/res/quest/duels/The Great and Powerful Trixie 4.dck new file mode 100644 index 00000000000..e4f61b6e6e8 --- /dev/null +++ b/forge-gui/res/quest/duels/The Great and Powerful Trixie 4.dck @@ -0,0 +1,29 @@ +[duel] +[metadata] +Name=The Great and Powerful Trixie 4 +Title=The Great and Powerful Trixie +Difficulty=very hard +Description=Mono Blue Trix combo deck with Illusions of Grandeur and Donate +Icon=Great and Powerful Trixie.jpg +Deck Type=constructed +[main] +1 Ancestral Recall +4 Arcane Denial +4 Counterspell +1 Dig Through Time +2 Disdainful Stroke +4 Donate +2 Essence Scatter +4 Illusions of Grandeur +15 Island +4 Mana Leak +1 Mox Emerald +1 Mox Jet +1 Mox Pearl +1 Mox Ruby +1 Mox Sapphire +4 Propaganda +4 Sapphire Medallion +1 Treasure Cruise +3 Unsummon +4 Vapor Snag