From e242003d4c587b7170f529f09789a447eb13e5a9 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sat, 8 Jul 2023 14:15:47 +0000 Subject: [PATCH 1/6] Update amethyst_dragon_explosive_crystal.txt --- .../res/cardsfolder/a/amethyst_dragon_explosive_crystal.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/a/amethyst_dragon_explosive_crystal.txt b/forge-gui/res/cardsfolder/a/amethyst_dragon_explosive_crystal.txt index c2338666ee4..8abdf33ff66 100644 --- a/forge-gui/res/cardsfolder/a/amethyst_dragon_explosive_crystal.txt +++ b/forge-gui/res/cardsfolder/a/amethyst_dragon_explosive_crystal.txt @@ -12,5 +12,5 @@ ALTERNATE Name:Explosive Crystal ManaCost:4 R Types:Sorcery Adventure -A:SP$ DealDamage | ValidTgts$ Any to distribute damage to | NumDmg$ 4 | TargetMin$ 0 | TargetMax$ 4 | DividedAsYouChoose$ 4 | SpellDescription$ CARDNAME deals 4 damage divided as you choose among any number of targets. +A:SP$ DealDamage | ValidTgts$ Any | NumDmg$ 4 | TargetMin$ 0 | TargetMax$ 4 | DividedAsYouChoose$ 4 | SpellDescription$ CARDNAME deals 4 damage divided as you choose among any number of targets. Oracle:Explosive Crystal deals 4 damage divided as you choose among any number of targets. From f757192df53e0f278789086332b6cbc8f94dc3e5 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sun, 9 Jul 2023 10:34:31 +0800 Subject: [PATCH 2/6] format win/loss ratio --- .../src/forge/adventure/scene/PlayerStatisticScene.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java b/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java index 1d51a099308..94992e38bc3 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java @@ -34,6 +34,7 @@ import forge.model.FModel; import forge.player.GamePlayerUtil; import org.apache.commons.lang3.tuple.Pair; +import java.text.DecimalFormat; import java.util.Map; public class PlayerStatisticScene extends UIScene { @@ -52,6 +53,7 @@ public class PlayerStatisticScene extends UIScene { boolean toggle = false; AchievementCollection planeswalkers, achievements, cardActivation; Scene lastGameScene; + DecimalFormat df; private PlayerStatisticScene() { super(Forge.isLandscapeMode() ? "ui/statistic.json" : "ui/statistic_portrait.json"); @@ -216,7 +218,9 @@ public class PlayerStatisticScene extends UIScene { totalLoss.setText(String.valueOf(Current.player().getStatistic().totalLoss())); } if (lossWinRatio != null) { - lossWinRatio.setText(Float.toString(Current.player().getStatistic().winLossRatio())); + if (df == null) + df = new DecimalFormat("#.##"); + lossWinRatio.setText(df.format(Current.player().getStatistic().winLossRatio())); } if (eventMatchWins != null) { eventMatchWins.setText(String.valueOf(Current.player().getStatistic().eventWins())); From 3ec52cae4bd0d480a65792cd66cf4c00d70f46ec Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sun, 9 Jul 2023 10:48:34 +0800 Subject: [PATCH 3/6] move decimal format to TextUtil --- forge-core/src/main/java/forge/util/TextUtil.java | 4 ++++ .../src/forge/adventure/scene/PlayerStatisticScene.java | 7 ++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/forge-core/src/main/java/forge/util/TextUtil.java b/forge-core/src/main/java/forge/util/TextUtil.java index 9496391736a..5854c0a0223 100644 --- a/forge-core/src/main/java/forge/util/TextUtil.java +++ b/forge-core/src/main/java/forge/util/TextUtil.java @@ -42,6 +42,10 @@ public class TextUtil { return Normalizer.normalize(text, Normalizer.Form.NFD); } + private static final DecimalFormat df = new DecimalFormat("#.##"); + public static String decimalFormat(float value) { + return df.format(value); + } /** * Safely converts an object to a String. * diff --git a/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java b/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java index 94992e38bc3..9b224eb2e1d 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/PlayerStatisticScene.java @@ -32,9 +32,9 @@ import forge.localinstance.achievements.CardActivationAchievements; import forge.localinstance.achievements.PlaneswalkerAchievements; import forge.model.FModel; import forge.player.GamePlayerUtil; +import forge.util.TextUtil; import org.apache.commons.lang3.tuple.Pair; -import java.text.DecimalFormat; import java.util.Map; public class PlayerStatisticScene extends UIScene { @@ -53,7 +53,6 @@ public class PlayerStatisticScene extends UIScene { boolean toggle = false; AchievementCollection planeswalkers, achievements, cardActivation; Scene lastGameScene; - DecimalFormat df; private PlayerStatisticScene() { super(Forge.isLandscapeMode() ? "ui/statistic.json" : "ui/statistic_portrait.json"); @@ -218,9 +217,7 @@ public class PlayerStatisticScene extends UIScene { totalLoss.setText(String.valueOf(Current.player().getStatistic().totalLoss())); } if (lossWinRatio != null) { - if (df == null) - df = new DecimalFormat("#.##"); - lossWinRatio.setText(df.format(Current.player().getStatistic().winLossRatio())); + lossWinRatio.setText(TextUtil.decimalFormat(Current.player().getStatistic().winLossRatio())); } if (eventMatchWins != null) { eventMatchWins.setText(String.valueOf(Current.player().getStatistic().eventWins())); From 0c8b51ccf2194a5c516a272f740cbe8bd99e64ec Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sun, 9 Jul 2023 11:14:53 +0800 Subject: [PATCH 4/6] Update TextUtil.java --- forge-core/src/main/java/forge/util/TextUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-core/src/main/java/forge/util/TextUtil.java b/forge-core/src/main/java/forge/util/TextUtil.java index 5854c0a0223..53305344d0d 100644 --- a/forge-core/src/main/java/forge/util/TextUtil.java +++ b/forge-core/src/main/java/forge/util/TextUtil.java @@ -1,5 +1,6 @@ package forge.util; +import java.text.DecimalFormat; import java.text.Normalizer; import java.util.ArrayList; import java.util.List; From 62e3472d6a909e0c2f1b1b4ebfde1258d69c9f43 Mon Sep 17 00:00:00 2001 From: Agetian Date: Sun, 9 Jul 2023 13:04:44 +0300 Subject: [PATCH 5/6] Implement AI for Arena (#3434) * - AI: Shouldn't miss Grothama's triggers even when it doesn't want to activate them. * - Consolidate the logic in FightAi * - Clean up * - Fix imports * - Clean up * - Implement AI for Arena. * - Hook up the AI for Magus of the Arena - Simplify implementation for the relevant cards --- .../src/main/java/forge/ai/SpecialCardAi.java | 44 +++++++++++++++++++ .../main/java/forge/ai/ability/FightAi.java | 1 - .../src/main/java/forge/ai/ability/TapAi.java | 5 ++- .../main/java/forge/ai/ability/TapAiBase.java | 9 ++++ forge-gui/res/cardsfolder/a/arena.txt | 8 ++-- .../res/cardsfolder/m/magus_of_the_arena.txt | 8 ++-- 6 files changed, 63 insertions(+), 12 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index 92385a56eee..04eae1847b4 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -22,6 +22,7 @@ import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import forge.ai.ability.AnimateAi; +import forge.ai.ability.FightAi; import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.ManaCost; @@ -79,6 +80,49 @@ import java.util.List; */ public class SpecialCardAi { + // Arena and Magus of the Arena + public static class Arena { + public static boolean consider(final Player ai, final SpellAbility sa) { + final Game game = ai.getGame(); + + if (!game.getPhaseHandler().is(PhaseType.END_OF_TURN) || game.getPhaseHandler().getNextTurn() != ai) { + return false; // at opponent's EOT only, to conserve mana + } + + CardCollection aiCreatures = ai.getCreaturesInPlay(); + if (aiCreatures.isEmpty()) { + return false; + } + + for (Player opp : ai.getOpponents()) { + CardCollection oppCreatures = opp.getCreaturesInPlay(); + if (oppCreatures.isEmpty()) { + continue; + } + + for (Card aiCreature : aiCreatures) { + boolean canKillAll = true; + for (Card oppCreature : oppCreatures) { + if (FightAi.canKill(oppCreature, aiCreature, 0)) { + canKillAll = false; + break; + } + if (!FightAi.canKill(aiCreature, oppCreature, 0)) { + canKillAll = false; + break; + } + } + if (canKillAll) { + sa.getTargets().clear(); + sa.getTargets().add(aiCreature); + return true; + } + } + } + return sa.isTargetNumberValid(); + } + } + // Black Lotus and Lotus Bloom public static class BlackLotus { public static boolean consider(final Player ai, final SpellAbility sa, final ManaCostBeingPaid cost) { diff --git a/forge-ai/src/main/java/forge/ai/ability/FightAi.java b/forge-ai/src/main/java/forge/ai/ability/FightAi.java index 265b74cc85f..d0022ccffe0 100644 --- a/forge-ai/src/main/java/forge/ai/ability/FightAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/FightAi.java @@ -30,7 +30,6 @@ public class FightAi extends SpellAbilityAi { // everything is defined or targeted above, can't do anything there unless a specific logic is set if (sa.hasParam("Defined") && !sa.usesTargeting()) { - // TODO extend Logic for cards like Arena return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/TapAi.java b/forge-ai/src/main/java/forge/ai/ability/TapAi.java index 6f600e4c55c..796a600baf1 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TapAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TapAi.java @@ -46,8 +46,11 @@ public class TapAi extends TapAiBase { final Card source = sa.getHostCard(); final Cost abCost = sa.getPayCosts(); - if ("GoblinPolkaBand".equals(sa.getParam("AILogic"))) { + final String aiLogic = sa.getParamOrDefault("AILogic", ""); + if ("GoblinPolkaBand".equals(aiLogic)) { return SpecialCardAi.GoblinPolkaBand.consider(ai, sa); + } else if ("Arena".equals(aiLogic)) { + return SpecialCardAi.Arena.consider(ai, sa); } if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) { diff --git a/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java b/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java index 940c667ecf6..8e7ac2bff0c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java @@ -325,6 +325,15 @@ public abstract class TapAiBase extends SpellAbilityAi { @Override public boolean chkAIDrawback(SpellAbility sa, Player ai) { final Card source = sa.getHostCard(); + final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer"); + + if (oppTargetsChoice && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) { + // canPlayAI (sa activated by ai) + Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0); + sa.setTargetingPlayer(targetingPlayer); + sa.getTargets().clear(); + return targetingPlayer.getController().chooseTargetsFor(sa); + } boolean randomReturn = true; diff --git a/forge-gui/res/cardsfolder/a/arena.txt b/forge-gui/res/cardsfolder/a/arena.txt index 4f75021fb8b..c0edecbf5b2 100644 --- a/forge-gui/res/cardsfolder/a/arena.txt +++ b/forge-gui/res/cardsfolder/a/arena.txt @@ -1,9 +1,7 @@ Name:Arena ManaCost:no cost Types:Land -A:AB$ Tap | Cost$ 3 T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ DBTap | AlwaysRemember$ True | SpellDescription$ Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. (Each deals damage equal to its power to the other.) -SVar:DBTap:DB$ Tap | TargetingPlayer$ Player.Opponent | TargetingPlayerControls$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature you control | SubAbility$ DBFight | AlwaysRemember$ True -SVar:DBFight:DB$ Fight | Defined$ Remembered | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -AI:RemoveDeck:All +A:AB$ Tap | Cost$ 3 T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ DBTap | AILogic$ Arena | SpellDescription$ Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. (Each deals damage equal to its power to the other.) +SVar:DBTap:DB$ Tap | TargetingPlayer$ Player.Opponent | TargetingPlayerControls$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature you control | SubAbility$ DBFight +SVar:DBFight:DB$ Fight | Defined$ Targeted Oracle:{3}, {T}: Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. (Each deals damage equal to its power to the other.) diff --git a/forge-gui/res/cardsfolder/m/magus_of_the_arena.txt b/forge-gui/res/cardsfolder/m/magus_of_the_arena.txt index 7fd0ebcff07..417d170dd33 100644 --- a/forge-gui/res/cardsfolder/m/magus_of_the_arena.txt +++ b/forge-gui/res/cardsfolder/m/magus_of_the_arena.txt @@ -2,9 +2,7 @@ Name:Magus of the Arena ManaCost:4 R R Types:Creature Human Wizard PT:5/5 -A:AB$ Tap | Cost$ 3 T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ DBTap | AlwaysRemember$ True | SpellDescription$ Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. (Each deals damage equal to its power to the other.) -SVar:DBTap:DB$ Tap | TargetingPlayer$ Player.Opponent | TargetingPlayerControls$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature you control | SubAbility$ DBFight | AlwaysRemember$ True -SVar:DBFight:DB$ Fight | Defined$ Remembered | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -AI:RemoveDeck:All +A:AB$ Tap | Cost$ 3 T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ DBTap | AILogic$ Arena | SpellDescription$ Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. (Each deals damage equal to its power to the other.) +SVar:DBTap:DB$ Tap | TargetingPlayer$ Player.Opponent | TargetingPlayerControls$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature you control | SubAbility$ DBFight +SVar:DBFight:DB$ Fight | Defined$ Targeted Oracle:{3}, {T}: Tap target creature you control and target creature of an opponent's choice they control. Those creatures fight each other. (Each deals damage equal to its power to the other.) From 491ccd1719525c0793c122ef941cec3dfc1a7645 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sun, 9 Jul 2023 13:09:37 +0200 Subject: [PATCH 6/6] Fix missing reorder (#3435) * Fix missing reorder * Fix Ward check for all subs --- .../src/main/java/forge/ai/AiController.java | 37 +++++++++---------- .../src/main/java/forge/ai/ComputerUtil.java | 10 ++--- .../main/java/forge/ai/ComputerUtilCard.java | 12 ++---- .../main/java/forge/ai/ability/PumpAi.java | 4 -- .../ability/effects/ChangeZoneEffect.java | 4 ++ .../cardsfolder/c/congregation_at_dawn.txt | 2 +- .../res/cardsfolder/d/dwarven_recruiter.txt | 2 +- .../res/cardsfolder/g/goblin_recruiter.txt | 2 +- .../res/cardsfolder/i/insidious_dreams.txt | 2 +- .../res/cardsfolder/l/lim_duls_vault.txt | 2 +- forge-gui/res/cardsfolder/n/nova_pentacle.txt | 2 +- forge-gui/res/cardsfolder/s/scouting_trek.txt | 2 +- .../res/cardsfolder/u/ulvenwald_tracker.txt | 4 +- 13 files changed, 38 insertions(+), 47 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index d36f180b66d..72020540213 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -807,22 +807,24 @@ public class AiController { // Account for possible Ward after the spell is fully targeted // TODO: ideally, this should be done while targeting, so that a different target can be preferred if the best // one is warded and can't be paid for. (currently it will be stuck with the target until it could pay) - if (sa.usesTargeting() && (!sa.isSpell() || CardFactoryUtil.isCounterable(host))) { - for (Card tgt : sa.getTargets().getTargetCards()) { - // TODO some older cards don't use the keyword, so check for trigger instead - if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(host.getController())) { - int amount = 0; - Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt); - if (wardCost.hasManaCost()) { - amount = wardCost.getTotalMana().getCMC(); - if (amount > 0 && !ComputerUtilCost.canPayCost(sa, player, true)) { - return AiPlayDecision.CantAfford; + if (!sa.isSpell() || CardFactoryUtil.isCounterable(host)) { + for (TargetChoices tc : sa.getAllTargetChoices()) { + for (Card tgt : tc.getTargetCards()) { + // TODO some older cards don't use the keyword, so check for trigger instead + if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(host.getController())) { + int amount = 0; + Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt); + if (wardCost.hasManaCost()) { + amount = wardCost.getTotalMana().getCMC(); + if (amount > 0 && !ComputerUtilCost.canPayCost(sa, player, true)) { + return AiPlayDecision.CantAfford; + } + } + SpellAbilityAi topAI = new SpellAbilityAi() { + }; + if (!topAI.willPayCosts(player, sa, wardCost, host)) { + return AiPlayDecision.CostNotAcceptable; } - } - SpellAbilityAi topAI = new SpellAbilityAi() { - }; - if (!topAI.willPayCosts(player, sa, wardCost, host)) { - return AiPlayDecision.CostNotAcceptable; } } } @@ -1332,10 +1334,7 @@ public class AiController { if (sa == null) { return null; } - - final List abilities = Lists.newArrayList(); - abilities.add(sa); - return abilities; + return Lists.newArrayList(sa); } public List chooseSpellAbilityToPlay() { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 439dbc05cdc..c408f45f413 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -1720,14 +1720,10 @@ public class ComputerUtil { return threatened; } } else { - objects = topStack.getTargets(); final List canBeTargeted = new ArrayList<>(); - for (Object o : objects) { - if (o instanceof GameEntity) { - final GameEntity ge = (GameEntity) o; - if (ge.canBeTargetedBy(topStack)) { - canBeTargeted.add(ge); - } + for (GameEntity ge : topStack.getTargets().getTargetEntities()) { + if (ge.canBeTargetedBy(topStack)) { + canBeTargeted.add(ge); } } if (canBeTargeted.isEmpty()) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index f2ca7b1972f..850c8f4f5cf 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -1865,7 +1865,6 @@ public class ComputerUtilCard { */ public static boolean canPumpAgainstRemoval(Player ai, SpellAbility sa) { final List objects = ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa, true); - final CardCollection threatenedTargets = new CardCollection(); if (!sa.usesTargeting()) { final List cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); @@ -1877,14 +1876,11 @@ public class ComputerUtilCard { // For pumps without targeting restrictions, just return immediately until this is fleshed out. return false; } - CardCollection targetables = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa); - targetables = ComputerUtil.getSafeTargets(ai, sa, targetables); - for (final Card c : targetables) { - if (objects.contains(c)) { - threatenedTargets.add(c); - } - } + CardCollection threatenedTargets = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa); + threatenedTargets = ComputerUtil.getSafeTargets(ai, sa, threatenedTargets); + threatenedTargets.retainAll(objects); + if (!threatenedTargets.isEmpty()) { sortByEvaluateCreature(threatenedTargets); for (Card c : threatenedTargets) { 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 bf0ce6ad9d4..7cfe1d541a0 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -292,10 +292,6 @@ public class PumpAi extends PumpAiBase { if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger()); - if (sourceName.equals("Necropolis Fiend")) { - xPay = Math.min(xPay, sa.getActivatingPlayer().getCardsIn(ZoneType.Graveyard).size()); - sa.setSVar("X", Integer.toString(xPay)); - } sa.setXManaCostPaid(xPay); defense = xPay; if (numDefense.equals("-X")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index 2fab13d9f4a..4cee72092bc 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -1252,6 +1252,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect { player.shuffle(sa); } + if (sa.hasParam("Reorder")) { + chosenCards = new CardCollection(decider.getController().orderMoveToZoneList(chosenCards, destination, sa)); + } + // remove Controlled While Searching if (controlTimestamp != null) { player.removeController(controlTimestamp); diff --git a/forge-gui/res/cardsfolder/c/congregation_at_dawn.txt b/forge-gui/res/cardsfolder/c/congregation_at_dawn.txt index d9109e29af9..e335be6e6f2 100644 --- a/forge-gui/res/cardsfolder/c/congregation_at_dawn.txt +++ b/forge-gui/res/cardsfolder/c/congregation_at_dawn.txt @@ -1,5 +1,5 @@ Name:Congregation at Dawn ManaCost:G G W Types:Instant -A:SP$ ChangeZone | Cost$ G G W | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Creature | ChangeNum$ 3 | SpellDescription$ Search your library for up to three creature cards, reveal them, then shuffle and put those cards on top in any order. +A:SP$ ChangeZone | Cost$ G G W | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Creature | ChangeNum$ 3 | Reorder$ True | SpellDescription$ Search your library for up to three creature cards, reveal them, then shuffle and put those cards on top in any order. Oracle:Search your library for up to three creature cards, reveal them, then shuffle and put those cards on top in any order. diff --git a/forge-gui/res/cardsfolder/d/dwarven_recruiter.txt b/forge-gui/res/cardsfolder/d/dwarven_recruiter.txt index 455de4be4d7..465835dc385 100644 --- a/forge-gui/res/cardsfolder/d/dwarven_recruiter.txt +++ b/forge-gui/res/cardsfolder/d/dwarven_recruiter.txt @@ -3,7 +3,7 @@ ManaCost:2 R Types:Creature Dwarf PT:2/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME enters the battlefield, search your library for any number of Dwarf cards, reveal them, then shuffle and put those cards on top in any order. -SVar:TrigChangeZone:DB$ ChangeZone | ChangeNum$ X | ChangeType$ Dwarf | Origin$ Library | Destination$ Library | LibraryPosition$ 0 +SVar:TrigChangeZone:DB$ ChangeZone | ChangeNum$ X | ChangeType$ Dwarf | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | Reorder$ True SVar:X:Count$InYourLibrary.Dwarf AI:RemoveDeck:All DeckNeeds:Type$Dwarf diff --git a/forge-gui/res/cardsfolder/g/goblin_recruiter.txt b/forge-gui/res/cardsfolder/g/goblin_recruiter.txt index cd96d2a1b06..4d67d3c93a7 100644 --- a/forge-gui/res/cardsfolder/g/goblin_recruiter.txt +++ b/forge-gui/res/cardsfolder/g/goblin_recruiter.txt @@ -3,7 +3,7 @@ ManaCost:1 R Types:Creature Goblin PT:1/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME enters the battlefield, search your library for any number of Goblin cards, reveal them, then shuffle and put those cards on top in any order. -SVar:TrigChangeZone:DB$ ChangeZone | ChangeNum$ X | ChangeType$ Goblin | Origin$ Library | Destination$ Library | LibraryPosition$ 0 +SVar:TrigChangeZone:DB$ ChangeZone | ChangeNum$ X | ChangeType$ Goblin | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | Reorder$ True SVar:X:Count$InYourLibrary.Goblin DeckNeeds:Type$Goblin #TODO: The AI generally is able to use this card, but will basically place all of its goblins on top of the library in no specific order, which is not very smart. Might need some improvement before RemoveDeck is removed. Currently adding a restriction to at least only play it if the AI has enough lands out on the battlefield already. diff --git a/forge-gui/res/cardsfolder/i/insidious_dreams.txt b/forge-gui/res/cardsfolder/i/insidious_dreams.txt index 07b630d560e..bc8b070bcb9 100644 --- a/forge-gui/res/cardsfolder/i/insidious_dreams.txt +++ b/forge-gui/res/cardsfolder/i/insidious_dreams.txt @@ -1,7 +1,7 @@ Name:Insidious Dreams ManaCost:3 B Types:Instant -A:SP$ ChangeZone | Cost$ 3 B Discard | Origin$ Library | Destination$ Library | ChangeType$ Card | ChangeNum$ X | LibraryPosition$ 0 | Mandatory$ True | SpellDescription$ Search your library for X cards, then shuffle and put those cards on top in any order. +A:SP$ ChangeZone | Cost$ 3 B Discard | Origin$ Library | Destination$ Library | ChangeType$ Card | ChangeNum$ X | LibraryPosition$ 0 | Mandatory$ True | Reorder$ True | SpellDescription$ Search your library for X cards, then shuffle and put those cards on top in any order. SVar:X:Count$xPaid AI:RemoveDeck:All Oracle:As an additional cost to cast this spell, discard X cards.\nSearch your library for X cards, then shuffle and put those cards on top in any order. diff --git a/forge-gui/res/cardsfolder/l/lim_duls_vault.txt b/forge-gui/res/cardsfolder/l/lim_duls_vault.txt index 6252acb4d04..732e3a72526 100644 --- a/forge-gui/res/cardsfolder/l/lim_duls_vault.txt +++ b/forge-gui/res/cardsfolder/l/lim_duls_vault.txt @@ -7,7 +7,7 @@ SVar:CheckLifePaid:DB$ StoreSVar | SVar$ LifePaid | Type$ Number | Expression$ 1 SVar:DBResetRem:DB$ Cleanup | ClearRemembered$ True | SubAbility$ GoToBottom SVar:GoToBottom:DB$ Dig | DigNum$ 5 | ChangeNum$ All | DestinationZone$ Library | LibraryPosition$ -1 | NoLooking$ True | SubAbility$ DBLookAgain | StackDescription$ None SVar:DBLookAgain:DB$ PeekAndReveal | PeekAmount$ 5 | NoReveal$ True | RememberPeeked$ True | StackDescription$ None -SVar:DBShuffle:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Card.IsRemembered | ChangeNum$ 5 | SubAbility$ DBReset | Hidden$ True | SelectPrompt$ Pick 1 on the top of library | Mandatory$ True | NoReveal$ True | NoLooking$ True | StackDescription$ None +SVar:DBShuffle:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Card.IsRemembered | ChangeNum$ 5 | SubAbility$ DBReset | Hidden$ True | SelectPrompt$ Pick 1 on the top of library | Mandatory$ True | NoReveal$ True | NoLooking$ True | Reorder$ True | StackDescription$ None SVar:DBReset:DB$ StoreSVar | SVar$ LifePaid | Type$ Number | Expression$ 0 | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:LifePaid:Number$0 diff --git a/forge-gui/res/cardsfolder/n/nova_pentacle.txt b/forge-gui/res/cardsfolder/n/nova_pentacle.txt index df84516ff16..66427dae7f8 100644 --- a/forge-gui/res/cardsfolder/n/nova_pentacle.txt +++ b/forge-gui/res/cardsfolder/n/nova_pentacle.txt @@ -2,7 +2,7 @@ Name:Nova Pentacle ManaCost:4 Types:Artifact A:AB$ ChooseSource | Cost$ 3 T | Choices$ Card,Emblem | AILogic$ NeedsPrevention | SubAbility$ DBEffect | SpellDescription$ The next time a source of your choice would deal damage to you this turn, that damage is dealt to target creature of an opponent's choice instead. -SVar:DBEffect:DB$ Effect | TargetingPlayer$ Player.Opponent | ValidTgts$ Creature | TgtPrompt$ Select target creature to redirect the damage to | ReplacementEffects$ SelflessDamage | ExileOnMoved$ Battlefield | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SubAbility$ DBCleanup | ConditionDefined$ ChosenCard | ConditionPresent$ Card,Emblem +SVar:DBEffect:DB$ Effect | TargetingPlayer$ Player.Opponent | ValidTgts$ Creature | TgtPrompt$ Select target creature to redirect the damage to | ReplacementEffects$ SelflessDamage | ExileOnMoved$ Battlefield | RememberObjects$ Targeted | SubAbility$ DBCleanup | ConditionDefined$ ChosenCard | ConditionPresent$ Card,Emblem SVar:SelflessDamage:Event$ DamageDone | ValidTarget$ You | ValidSource$ Card.ChosenCardStrict,Emblem.ChosenCard | ReplaceWith$ SelflessDmg | DamageTarget$ Remembered | Description$ The next time a source of your choice would deal damage to you this turn, that damage is dealt to target creature of an opponent's choice instead. SVar:SelflessDmg:DB$ ReplaceEffect | VarName$ Affected | VarValue$ Remembered | VarType$ Card | SubAbility$ ExileEffect SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile diff --git a/forge-gui/res/cardsfolder/s/scouting_trek.txt b/forge-gui/res/cardsfolder/s/scouting_trek.txt index dcb2d5b1301..05bd132aa0e 100644 --- a/forge-gui/res/cardsfolder/s/scouting_trek.txt +++ b/forge-gui/res/cardsfolder/s/scouting_trek.txt @@ -1,7 +1,7 @@ Name:Scouting Trek ManaCost:1 G Types:Sorcery -A:SP$ ChangeZone | Cost$ 1 G | ChangeNum$ X | ChangeType$ Land.Basic | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | SpellDescription$ Search your library for any number of basic land cards, reveal those cards, then shuffle and put them on top. +A:SP$ ChangeZone | Cost$ 1 G | ChangeNum$ X | ChangeType$ Land.Basic | Origin$ Library | Destination$ Library | Reorder$ True | LibraryPosition$ 0 | SpellDescription$ Search your library for any number of basic land cards, reveal those cards, then shuffle and put them on top. SVar:X:Count$InYourLibrary.Land.Basic AI:RemoveDeck:All Oracle:Search your library for any number of basic land cards, reveal those cards, then shuffle and put them on top. diff --git a/forge-gui/res/cardsfolder/u/ulvenwald_tracker.txt b/forge-gui/res/cardsfolder/u/ulvenwald_tracker.txt index cdd07d0b52a..995fb89f839 100644 --- a/forge-gui/res/cardsfolder/u/ulvenwald_tracker.txt +++ b/forge-gui/res/cardsfolder/u/ulvenwald_tracker.txt @@ -2,7 +2,7 @@ Name:Ulvenwald Tracker ManaCost:G Types:Creature Human Shaman PT:1/1 -A:AB$ Pump | Cost$ 1 G T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ TrackerFight | StackDescription$ None -SVar:TrackerFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature | TargetUnique$ True | TgtPrompt$ Select another target creature | SpellDescription$ Target creature you control fights another target creature. +A:AB$ Pump | Cost$ 1 G T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ TrackerFight | StackDescription$ None | SpellDescription$ Target creature you control fights another target creature. +SVar:TrackerFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature | TargetUnique$ True | TgtPrompt$ Select another target creature AI:RemoveDeck:All Oracle:{1}{G}, {T}: Target creature you control fights another target creature.