diff --git a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java index b1e981d9d6a..7a642b7a831 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java @@ -37,11 +37,6 @@ public class CharmAi extends SpellAbilityAi { min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParam("MinCharmNum"), sa) : num; } - // only randomize if not all possible together - if (num < choices.size() || source.hasKeyword(Keyword.ESCALATE)) { - Collections.shuffle(choices); - } - boolean timingRight = sa.isTrigger(); //is there a reason to play the charm now? // Reset the chosen list otherwise it will be locked in forever by earlier calls @@ -51,11 +46,15 @@ public class CharmAi extends SpellAbilityAi { if (!ai.equals(sa.getActivatingPlayer())) { // This branch is for "An Opponent chooses" Charm spells from Alliances // Current just choose the first available spell, which seem generally less disastrous for the AI. - //return choices.subList(0, 1); chosenList = choices.subList(1, choices.size()); } else if ("Triskaidekaphobia".equals(ComputerUtilAbility.getAbilitySourceName(sa))) { chosenList = chooseTriskaidekaphobia(choices, ai); } else { + // only randomize if not all possible together + if (num < choices.size() || source.hasKeyword(Keyword.ESCALATE)) { + Collections.shuffle(choices); + } + /* * The generic chooseOptionsAi uses canPlayAi() to determine good choices * which means most "bonus" effects like life-gain and random pumps will diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index c52d4594cb4..ae6cf563e02 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -916,6 +916,7 @@ public final class GameActionUtil { if (ability.getApi() == ApiType.Charm) { // reset chain ability.setSubAbility(null); + ability.setChosenList(null); } ability.clearTargets(); diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index b14163f6a40..ebe8381ceaa 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1050,7 +1050,7 @@ public class AbilityUtils { addPlayer(card.getRemembered(), defined, players); } else if (defined.startsWith("Imprinted")) { - addPlayer(Lists.newArrayList(card.getImprintedCards()), defined, players); + addPlayer(card.getImprintedCards(), defined, players); } else if (defined.startsWith("EffectSource")) { Card root = findEffectRoot(card); @@ -1093,9 +1093,9 @@ public class AbilityUtils { o = ((SpellAbility) c).getActivatingPlayer(); } else if (c instanceof Iterable) { // For merged permanent if (orCont) { - addPlayer(ImmutableList.copyOf(Iterables.filter((Iterable)c, Player.class)), "", players); + addPlayer(Iterables.filter((Iterable)c, Player.class), "", players); } - addPlayer(ImmutableList.copyOf(Iterables.filter((Iterable)c, Card.class)), "Controller", players); + addPlayer(Iterables.filter((Iterable)c, Card.class), "Controller", players); } } else if (defParsed.endsWith("Opponent")) { @@ -1205,6 +1205,9 @@ public class AbilityUtils { else if (defined.equals("DefendingPlayer")) { players.add(game.getCombat().getDefendingPlayerRelatedTo(card)); } + else if (defined.equals("ChoosingPlayer")) { + players.add(((SpellAbility) sa).getRootAbility().getChoosingPlayer()); + } else if (defined.equals("ChosenPlayer")) { final Player p = card.getChosenPlayer(); if (p != null) { @@ -1212,7 +1215,7 @@ public class AbilityUtils { } } else if (defined.startsWith("ChosenCard")) { - addPlayer(Lists.newArrayList(card.getChosenCards()), defined, players); + addPlayer(card.getChosenCards(), defined, players); } else if (defined.equals("SourceController")) { players.add(sa.getHostCard().getController()); @@ -3053,11 +3056,11 @@ public class AbilityUtils { return applyAbilityTextChangeEffects(val, ability); } - private static void addPlayer(Iterable objects, final String def, FCollection players) { + private static void addPlayer(Iterable objects, final String def, FCollection players) { addPlayer(objects, def, players, false); } - private static void addPlayer(Iterable objects, final String def, FCollection players, boolean skipRemembered) { + private static void addPlayer(Iterable objects, final String def, FCollection players, boolean skipRemembered) { for (Object o : objects) { if (o instanceof Player) { final Player p = (Player) o; diff --git a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java index 6fc489e3c64..20ab2ee1a5c 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java @@ -224,7 +224,7 @@ public class CharmEffect extends SpellAbilityEffect { //String choosers = sa.getParam("Chooser"); FCollection opponents = activator.getOpponents(); // all cards have Choser$ Opponent, so it's hardcoded here chooser = activator.getController().chooseSingleEntityForEffect(opponents, sa, "Choose an opponent", null); - source.setChosenPlayer(chooser); + sa.setChoosingPlayer(chooser); } List chosen = chooser.getController().chooseModeForAbility(sa, choices, min, num, canRepeat); diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index 0624524101e..de875ee33a9 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -83,7 +83,7 @@ public class CardFactory { out = assignNewId ? getCard(in.getPaperCard(), in.getOwner(), in.getGame()) : getCard(in.getPaperCard(), in.getOwner(), in.getId(), in.getGame()); } else { // token - out = CardFactory.copyStats(in, in.getController(), assignNewId); + out = copyStats(in, in.getController(), assignNewId); out.setToken(true); // need to copy this values for the tokens @@ -128,7 +128,7 @@ public class CardFactory { int id = game.nextCardId(); // need to create a physical card first, i need the original card faces - final Card copy = CardFactory.getCard(original.getPaperCard(), controller, id, game); + final Card copy = getCard(original.getPaperCard(), controller, id, game); if (original.isTransformable()) { // 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield, @@ -530,7 +530,7 @@ public class CardFactory { c.setSetCode(in.getSetCode()); for (final CardStateName state : in.getStates()) { - CardFactory.copyState(in, state, c, state); + copyState(in, state, c, state); } c.setState(in.getCurrentStateName(), false); diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 9363684128f..5ce952c8799 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -103,11 +103,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit // choices for constructor isPermanent argument private String originalDescription = "", description = ""; private String originalStackDescription = "", stackDescription = ""; - private ManaCost multiKickerManaCost; + private Player activatingPlayer; private Player targetingPlayer; + private Player choosingPlayer; private Pair controlledByPlayer; + private ManaCostBeingPaid manaCostBeingPaid; + private ManaCost multiKickerManaCost; private int spentPhyrexian = 0; private int paidLifeAmount = 0; @@ -147,7 +150,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private TreeBasedTable paidLists = TreeBasedTable.create(); private EnumMap triggeringObjects = AbilityKey.newMap(); - private EnumMap replacingObjects = AbilityKey.newMap(); private final List pipsToReduce = new ArrayList<>(); @@ -481,6 +483,13 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit targetingPlayer = targetingPlayer0; } + public Player getChoosingPlayer() { + return choosingPlayer; + } + public void setChoosingPlayer(Player choosingPlayer0) { + choosingPlayer = choosingPlayer0; + } + /** * @return returns who controls the controller of this sa when it is resolving (for Word of Command effect). Null means not being controlled by other */ diff --git a/forge-gui/res/cardsfolder/f/fatal_lore.txt b/forge-gui/res/cardsfolder/f/fatal_lore.txt index fd86a53e4d7..d50dc94130a 100644 --- a/forge-gui/res/cardsfolder/f/fatal_lore.txt +++ b/forge-gui/res/cardsfolder/f/fatal_lore.txt @@ -3,7 +3,7 @@ ManaCost:2 B B Types:Sorcery A:SP$ Charm | Cost$ 2 B B | Chooser$ Opponent | Choices$ DrawThree,DestroyAndDraw SVar:DrawThree:DB$ Draw | NumCards$ 3 | Defined$ You | SpellDescription$ You draw three cards. -SVar:DestroyAndDraw:DB$ Destroy | ValidTgts$ Creature.ChosenCtrl | TgtPrompt$ Select target creature | TargetMin$ 0 | TargetMax$ 2 | NoRegen$ True | SpellDescription$ You destroy up to two target creatures that opponent controls and that player draws up to three cards. Those creatures can't be regenerated. | SubAbility$ ChooserDraws -SVar:ChooserDraws:DB$ Draw | NumCards$ 3 | Defined$ ChosenPlayer | UpTo$ True +SVar:DestroyAndDraw:DB$ Destroy | ValidTgts$ Creature.ControlledBy ChoosingPlayer | TgtPrompt$ Select target creature | TargetMin$ 0 | TargetMax$ 2 | NoRegen$ True | SpellDescription$ You destroy up to two target creatures that opponent controls and that player draws up to three cards. Those creatures can't be regenerated. | SubAbility$ ChooserDraws +SVar:ChooserDraws:DB$ Draw | NumCards$ 3 | Defined$ ChoosingPlayer | UpTo$ True AI:RemoveDeck:All Oracle:An opponent chooses one —\n• You draw three cards.\n• You destroy up to two target creatures that player controls. They can't be regenerated. That player draws up to three cards. diff --git a/forge-gui/res/cardsfolder/m/misfortune.txt b/forge-gui/res/cardsfolder/m/misfortune.txt index e4d4f52eaee..bc08dfdc7a2 100644 --- a/forge-gui/res/cardsfolder/m/misfortune.txt +++ b/forge-gui/res/cardsfolder/m/misfortune.txt @@ -4,7 +4,7 @@ Types:Sorcery A:SP$ Charm | Cost$ 1 B R G | Chooser$ Opponent | Choices$ Fortune,Misfortune SVar:Fortune:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBGainLife | SpellDescription$ Put a +1/+1 counter on each creature you control. You gain 4 life. | SubAbility$ DBGainLife SVar:DBGainLife:DB$ GainLife | LifeAmount$ 4 -SVar:Misfortune:DB$ PutCounterAll | ValidCards$ Creature.ChosenCtrl | CounterType$ M1M1 | CounterNum$ 1 | SubAbility$ DBLoseLife | SpellDescription$ You put a -1/-1 counter on each creature that player controls and CARDNAME deals 4 damage to that player. | SubAbility$ DBDamage -SVar:DBDamage:DB$ DealDamage | Defined$ ChosenPlayer | NumDmg$ 4 +SVar:Misfortune:DB$ PutCounterAll | ValidCards$ Creature.ControlledBy ChoosingPlayer | CounterType$ M1M1 | CounterNum$ 1 | SubAbility$ DBLoseLife | SpellDescription$ You put a -1/-1 counter on each creature that player controls and CARDNAME deals 4 damage to that player. | SubAbility$ DBDamage +SVar:DBDamage:DB$ DealDamage | Defined$ ChoosingPlayer | NumDmg$ 4 SVar:ChooserDraws:DB$ Draw | NumCards$ 3 | Defined$ ChosenPlayer Oracle:An opponent chooses one —\n• You put a +1/+1 counter on each creature you control and gain 4 life.\n• You put a -1/-1 counter on each creature that player controls and Misfortune deals 4 damage to that player. diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index 83d9918c5d4..2eded9202f1 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -74,11 +74,13 @@ public class HumanPlaySpellAbility { if ("X".equals(ability.getParam("CharmNum"))) { // CR 601.4 if (!announceValuesLikeX()) { + game.clearTopLibsCast(ability); return false; } needX = false; } if (!CharmEffect.makeChoices(ability)) { + game.clearTopLibsCast(ability); // 603.3c If no mode is chosen, the ability is removed from the stack. return false; }