diff --git a/Mage.Sets/src/mage/cards/c/CaptiveAudience.java b/Mage.Sets/src/mage/cards/c/CaptiveAudience.java index 39e42a75026..7b728bb0de3 100644 --- a/Mage.Sets/src/mage/cards/c/CaptiveAudience.java +++ b/Mage.Sets/src/mage/cards/c/CaptiveAudience.java @@ -8,6 +8,7 @@ import mage.abilities.effects.common.CreateTokenAllEffect; import mage.abilities.effects.common.EntersBattlefieldUnderControlOfOpponentOfChoiceEffect; import mage.abilities.effects.common.SetPlayerLifeSourceEffect; import mage.abilities.effects.common.discard.DiscardHandControllerEffect; +import mage.abilities.hint.common.ModesAlreadyUsedHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -32,13 +33,22 @@ public final class CaptiveAudience extends CardImpl { Ability ability = new BeginningOfUpkeepTriggeredAbility( new SetPlayerLifeSourceEffect(4), TargetController.YOU, false ); + ability.setModeTag("life total becomes 4"); ability.getModes().setEachModeOnlyOnce(true); // • Discard your hand. - ability.addMode(new Mode(new DiscardHandControllerEffect())); + ability.addMode( + new Mode(new DiscardHandControllerEffect()) + .setModeTag("discard hand") + ); // • Each opponent creates five 2/2 black Zombie creature tokens. - ability.addMode(new Mode(new CreateTokenAllEffect(new ZombieToken(), 5, TargetController.OPPONENT))); + ability.addMode( + new Mode(new CreateTokenAllEffect(new ZombieToken(), 5, TargetController.OPPONENT)) + .setModeTag("opponents create Zombies") + ); + + ability.addHint(ModesAlreadyUsedHint.instance); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/d/DemonicPact.java b/Mage.Sets/src/mage/cards/d/DemonicPact.java index c4028787a5c..6b4d337a523 100644 --- a/Mage.Sets/src/mage/cards/d/DemonicPact.java +++ b/Mage.Sets/src/mage/cards/d/DemonicPact.java @@ -1,7 +1,6 @@ package mage.cards.d; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; @@ -11,6 +10,7 @@ import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseGameSourceControllerEffect; import mage.abilities.effects.common.discard.DiscardTargetEffect; +import mage.abilities.hint.common.ModesAlreadyUsedHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -18,6 +18,8 @@ import mage.constants.TargetController; import mage.target.common.TargetAnyTarget; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** * * @author LevelX2 @@ -30,6 +32,7 @@ public final class DemonicPact extends CardImpl { // At the beginning of your upkeep, choose one that hasn't been chosen // - Demonic Pact deals 4 damage to any target and you gain 4 life; Ability ability = new BeginningOfUpkeepTriggeredAbility(new DamageTargetEffect(4), TargetController.YOU, false); + ability.setModeTag("deals damage and gain life"); ability.getModes().setEachModeOnlyOnce(true); ability.addTarget(new TargetAnyTarget()); Effect effect = new GainLifeEffect(4); @@ -38,17 +41,21 @@ public final class DemonicPact extends CardImpl { // - Target opponent discards two cards Mode mode = new Mode(new DiscardTargetEffect(2)); + mode.setModeTag("opponent discard"); mode.addTarget(new TargetOpponent()); ability.addMode(mode); // - Draw two cards mode = new Mode(new DrawCardSourceControllerEffect(2)); + mode.setModeTag("draw"); ability.addMode(mode); // - You lose the game. mode = new Mode(new LoseGameSourceControllerEffect()); + mode.setModeTag("lose the game"); ability.addMode(mode); + ability.addHint(ModesAlreadyUsedHint.instance); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/g/GalaGreeters.java b/Mage.Sets/src/mage/cards/g/GalaGreeters.java index fe9dc63d303..6b9cec9b537 100644 --- a/Mage.Sets/src/mage/cards/g/GalaGreeters.java +++ b/Mage.Sets/src/mage/cards/g/GalaGreeters.java @@ -7,6 +7,7 @@ import mage.abilities.common.AllianceAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.common.ModesAlreadyUsedHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -32,14 +33,20 @@ public final class GalaGreeters extends CardImpl { // Alliance — Whenever another creature enters the battlefield under your control, choose one that hasn't been chosen this turn— // • Put a +1/+1 counter on Gala Greeters. Ability ability = new AllianceAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance())); + ability.setModeTag("put +1/+1 counter"); ability.getModes().setEachModeOnlyOnce(true); ability.getModes().setResetEachTurn(true); // • Create a tapped Treasure token. - ability.addMode(new Mode(new CreateTokenEffect(new TreasureToken(), 1, true, false))); + ability.addMode( + new Mode(new CreateTokenEffect(new TreasureToken(), 1, true, false)) + .setModeTag("create tapped Treasure") + ); // • You gain 2 life. - ability.addMode(new Mode(new GainLifeEffect(2))); + ability.addMode(new Mode(new GainLifeEffect(2)).setModeTag("gain life")); + + ability.addHint(ModesAlreadyUsedHint.instance); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/g/GandalfTheGrey.java b/Mage.Sets/src/mage/cards/g/GandalfTheGrey.java index 664fdd09c5e..662d27071b6 100644 --- a/Mage.Sets/src/mage/cards/g/GandalfTheGrey.java +++ b/Mage.Sets/src/mage/cards/g/GandalfTheGrey.java @@ -8,6 +8,7 @@ import mage.abilities.effects.common.CopyTargetSpellEffect; import mage.abilities.effects.common.DamagePlayersEffect; import mage.abilities.effects.common.MayTapOrUntapTargetEffect; import mage.abilities.effects.common.PutOnLibrarySourceEffect; +import mage.abilities.hint.common.ModesAlreadyUsedHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -48,17 +49,29 @@ public final class GandalfTheGrey extends CardImpl { Ability ability = new SpellCastControllerTriggeredAbility( new MayTapOrUntapTargetEffect(), StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false ); + ability.setModeTag("tap or untap"); ability.addTarget(new TargetPermanent()); ability.getModes().setEachModeOnlyOnce(true); // * Gandalf the Grey deals 3 damage to each opponent. - ability.addMode(new Mode(new DamagePlayersEffect(3, TargetController.OPPONENT))); + ability.addMode( + new Mode(new DamagePlayersEffect(3, TargetController.OPPONENT)) + .setModeTag("damage opponents") + ); // * Copy target instant or sorcery spell you control. You may choose new targets for the copy. - ability.addMode(new Mode(new CopyTargetSpellEffect()).addTarget(new TargetSpell(filter))); + ability.addMode( + new Mode(new CopyTargetSpellEffect()).addTarget(new TargetSpell(filter)) + .setModeTag("copy spell") + ); // * Put Gandalf on top of its owner's library. - ability.addMode(new Mode(new PutOnLibrarySourceEffect(true))); + ability.addMode( + new Mode(new PutOnLibrarySourceEffect(true)) + .setModeTag("put on top of library") + ); + + ability.addHint(ModesAlreadyUsedHint.instance); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/h/HenrikaDomnathi.java b/Mage.Sets/src/mage/cards/h/HenrikaDomnathi.java index d765c39f1e8..4fc88b8e6a7 100644 --- a/Mage.Sets/src/mage/cards/h/HenrikaDomnathi.java +++ b/Mage.Sets/src/mage/cards/h/HenrikaDomnathi.java @@ -8,6 +8,7 @@ import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.LoseLifeSourceControllerEffect; import mage.abilities.effects.common.SacrificeAllEffect; import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.hint.common.ModesAlreadyUsedHint; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; @@ -42,16 +43,20 @@ public final class HenrikaDomnathi extends CardImpl { Ability ability = new BeginningOfCombatTriggeredAbility(new SacrificeAllEffect( 1, StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT ), TargetController.YOU, false); + ability.setModeTag("each player sacrifice"); ability.getModes().setEachModeOnlyOnce(true); // • You draw a card and you lose 1 life. Mode mode = new Mode(new DrawCardSourceControllerEffect(1).setText("you draw a card")); mode.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and")); + mode.setModeTag("draw and lose life"); ability.addMode(mode); // • Transform Henrika Domnathi. - ability.addMode(new Mode(new TransformSourceEffect())); + ability.addMode(new Mode(new TransformSourceEffect()).setModeTag("transform")); this.addAbility(new TransformAbility()); + + ability.addHint(ModesAlreadyUsedHint.instance); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/k/KarganIntimidator.java b/Mage.Sets/src/mage/cards/k/KarganIntimidator.java index d5869b20fe2..40e26efb220 100644 --- a/Mage.Sets/src/mage/cards/k/KarganIntimidator.java +++ b/Mage.Sets/src/mage/cards/k/KarganIntimidator.java @@ -10,6 +10,7 @@ import mage.abilities.effects.RestrictionEffect; import mage.abilities.effects.common.continuous.BecomesCreatureTypeTargetEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.hint.common.ModesAlreadyUsedHint; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -47,6 +48,7 @@ public final class KarganIntimidator extends CardImpl { Ability ability = new SimpleActivatedAbility( new BoostSourceEffect(1, 1, Duration.EndOfTurn), new GenericManaCost(1) ); + ability.setModeTag("gets +1/+1"); ability.getModes().setEachModeOnlyOnce(true); ability.getModes().setResetEachTurn(true); @@ -55,12 +57,16 @@ public final class KarganIntimidator extends CardImpl { Duration.EndOfTurn, SubType.COWARD ).setText("Target creature becomes a Coward until end of turn")); mode.addTarget(new TargetCreaturePermanent()); + mode.setModeTag("target becomes a Coward"); ability.addMode(mode); // • Target Warrior gains trample until end of turn. mode = new Mode(new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn)); mode.addTarget(new TargetPermanent(filter)); + mode.setModeTag("target gain trample"); ability.addMode(mode); + + ability.addHint(ModesAlreadyUsedHint.instance); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SolKanarTheTainted.java b/Mage.Sets/src/mage/cards/s/SolKanarTheTainted.java index fa25b50be49..4edd450e31b 100644 --- a/Mage.Sets/src/mage/cards/s/SolKanarTheTainted.java +++ b/Mage.Sets/src/mage/cards/s/SolKanarTheTainted.java @@ -9,6 +9,7 @@ import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.abilities.hint.common.ModesAlreadyUsedHint; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -51,18 +52,23 @@ public final class SolKanarTheTainted extends CardImpl { Ability ability = new BeginningOfEndStepTriggeredAbility( new DrawCardSourceControllerEffect(1), TargetController.YOU, false ); + ability.setModeTag("draw"); ability.getModes().setEachModeOnlyOnce(true); // * Each opponent loses 2 life and you gain 2 life. ability.addMode(new Mode(new LoseLifeOpponentsEffect(2)) - .addEffect(new GainLifeEffect(2).concatBy("and"))); + .addEffect(new GainLifeEffect(2).concatBy("and")) + .setModeTag("opponents lose life and you gain life")); // * Sol'Kanar the Tainted deals 3 damage to up to one other target creature or planeswalker. ability.addMode(new Mode(new DamageTargetEffect(3)) - .addTarget(new TargetPermanent(0, 1, filter))); + .addTarget(new TargetPermanent(0, 1, filter)) + .setModeTag("deals damage")); // * Exile Sol'Kanar, then return it to the battlefield under an opponent's control. - ability.addMode(new Mode(new SolKanarTheTaintedEffect())); + ability.addMode(new Mode(new SolKanarTheTaintedEffect()).setModeTag("exile then return")); + + ability.addHint(ModesAlreadyUsedHint.instance); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/t/ThreeBowlsOfPorridge.java b/Mage.Sets/src/mage/cards/t/ThreeBowlsOfPorridge.java index bbbb93a33b2..5972bb412a7 100644 --- a/Mage.Sets/src/mage/cards/t/ThreeBowlsOfPorridge.java +++ b/Mage.Sets/src/mage/cards/t/ThreeBowlsOfPorridge.java @@ -9,6 +9,7 @@ import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.SacrificeSourceEffect; import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.hint.common.ModesAlreadyUsedHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -18,14 +19,12 @@ import mage.target.common.TargetCreaturePermanent; import java.util.UUID; /** - * * @author Susucr */ public final class ThreeBowlsOfPorridge extends CardImpl { public ThreeBowlsOfPorridge(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); - this.subtype.add(SubType.FOOD); // {2}, {T}: Choose one that hasn't been chosen -- @@ -36,17 +35,21 @@ public final class ThreeBowlsOfPorridge extends CardImpl { ); ability.addCost(new TapSourceCost()); ability.addTarget(new TargetCreaturePermanent()); + ability.setModeTag("deals damage"); ability.getModes().setEachModeOnlyOnce(true); // * Tap target creature. Mode mode = new Mode(new TapTargetEffect()); mode.addTarget(new TargetCreaturePermanent()); + mode.setModeTag("tap"); ability.addMode(mode); // * Sacrifice Three Bowls of Porridge. You gain 3 life. mode = new Mode(new SacrificeSourceEffect()); mode.addEffect(new GainLifeEffect(3)); + mode.setModeTag("sacrifice and gain life"); ability.addMode(mode); + ability.addHint(ModesAlreadyUsedHint.instance); this.addAbility(ability); } @@ -59,4 +62,4 @@ public final class ThreeBowlsOfPorridge extends CardImpl { public ThreeBowlsOfPorridge copy() { return new ThreeBowlsOfPorridge(this); } -} \ No newline at end of file +} diff --git a/Mage/src/main/java/mage/abilities/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java index a2753500be9..7ccd5f34534 100644 --- a/Mage/src/main/java/mage/abilities/Ability.java +++ b/Mage/src/main/java/mage/abilities/Ability.java @@ -557,6 +557,11 @@ public interface Ability extends Controllable, Serializable { Ability addHint(Hint hint); + /** + * Tag the current mode to be retrieved elsewhere thanks to the tag. + */ + void setModeTag(String tag); + /** * For abilities with static icons * diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 801649f9b27..730815899de 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -1390,6 +1390,16 @@ public abstract class AbilityImpl implements Ability { return this; } + /** + * sets the mode tag for the current mode. + */ + @Override + public void setModeTag(String tag) { + if (getModes().getMode() != null) { + getModes().getMode().setModeTag(tag); + } + } + @Override public final List getIcons() { return getIcons(null); diff --git a/Mage/src/main/java/mage/abilities/Mode.java b/Mage/src/main/java/mage/abilities/Mode.java index 73862d86190..427e18bcfaf 100644 --- a/Mage/src/main/java/mage/abilities/Mode.java +++ b/Mage/src/main/java/mage/abilities/Mode.java @@ -17,6 +17,12 @@ public class Mode implements Serializable { protected final Targets targets; protected final Effects effects; protected String flavorWord; + /** + * Optional Tag to distinguish this mode from others. + * In the case of modes that players can only choose once, + * the tag is directly what is displayed in ModesAlreadyUsedHint + */ + protected String modeTag; public Mode(Effect effect) { this.id = UUID.randomUUID(); @@ -32,6 +38,7 @@ public class Mode implements Serializable { this.targets = mode.targets.copy(); this.effects = mode.effects.copy(); this.flavorWord = mode.flavorWord; + this.modeTag = mode.modeTag; } public UUID setRandomId() { @@ -71,6 +78,21 @@ public class Mode implements Serializable { return this; } + /** + * Tag the mode to be retrieved elsewhere thanks to the tag. + */ + public Mode setModeTag(String tag) { + this.modeTag = tag; + return this; + } + + /** + * @return the mode tag for this mode, if set + */ + public String getModeTag() { + return this.modeTag == null ? "" : this.modeTag; + } + public String getFlavorWord() { return flavorWord; } diff --git a/Mage/src/main/java/mage/abilities/Modes.java b/Mage/src/main/java/mage/abilities/Modes.java index e18e52e3e26..93827fa7530 100644 --- a/Mage/src/main/java/mage/abilities/Modes.java +++ b/Mage/src/main/java/mage/abilities/Modes.java @@ -15,6 +15,7 @@ import mage.util.CardUtil; import mage.util.RandomUtil; import java.util.*; +import java.util.stream.Stream; /** * @author BetaSteward_at_googlemail.com @@ -96,6 +97,15 @@ public class Modes extends LinkedHashMap { return modeToGet; } + public Stream stream() { + return super.values().stream(); + } + + public Stream streamAlreadySelected(Ability source, Game game) { + Set selected = getAlreadySelectedModes(source, game); + return stream().filter(m -> selected.contains(m.getId())); + } + public Mode getMode() { return currentMode; } diff --git a/Mage/src/main/java/mage/abilities/hint/common/ModesAlreadyUsedHint.java b/Mage/src/main/java/mage/abilities/hint/common/ModesAlreadyUsedHint.java new file mode 100644 index 00000000000..925bea52c2b --- /dev/null +++ b/Mage/src/main/java/mage/abilities/hint/common/ModesAlreadyUsedHint.java @@ -0,0 +1,44 @@ +package mage.abilities.hint.common; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.hint.Hint; +import mage.game.Game; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Hint at all the used modes for modal effects that use + * "Choose one that hasn't been chosen" or "Choose one that hasn't been chosen this turn". + *

+ * Note: the modes need to be set up with a modeTag in order for the hint to find them. + * + * @author Susucr + */ +public enum ModesAlreadyUsedHint implements Hint { + instance; + + @Override + public String getText(Game game, Ability ability) { + List used = ability + .getModes() + .streamAlreadySelected(ability, game) + .map(Mode::getModeTag) + .filter(tag -> tag != null && !tag.isEmpty()) + .collect(Collectors.toList()); + + if (used.isEmpty()) { + return ""; + } + + return "Already used" + + (ability.getModes().isResetEachTurn() ? " this turn" : "") + + ": [" + String.join(", ", used) + "]"; + } + + @Override + public ModesAlreadyUsedHint copy() { + return instance; + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index 9a8352879fc..f67a7f10dfd 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -699,6 +699,11 @@ public class StackAbility extends StackObjectImpl implements Ability { throw new IllegalArgumentException("Stack ability is not supports hint adding"); } + @Override + public void setModeTag(String tag) { + throw new IllegalArgumentException("Stack ability does not supports setting modeTag"); + } + @Override public List getIcons() { return this.ability.getIcons();