Add modeTag + hint of used modes for "choose one that hasn't been chosen" abilities. (#10860)

* Add modeTag + hint of used modes on Three Bowls of Porridge

* add ModesAlreadyUsedHint to all the cards.
This commit is contained in:
Susucre 2023-08-27 05:09:04 +02:00 committed by GitHub
parent dcca63b963
commit be4b568e88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 168 additions and 15 deletions

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}
}
}

View file

@ -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
*

View file

@ -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<CardIcon> getIcons() {
return getIcons(null);

View file

@ -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;
}

View file

@ -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<UUID, Mode> {
return modeToGet;
}
public Stream<Mode> stream() {
return super.values().stream();
}
public Stream<Mode> streamAlreadySelected(Ability source, Game game) {
Set<UUID> selected = getAlreadySelectedModes(source, game);
return stream().filter(m -> selected.contains(m.getId()));
}
public Mode getMode() {
return currentMode;
}

View file

@ -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".
* <p>
* 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<String> 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;
}
}

View file

@ -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<CardIcon> getIcons() {
return this.ability.getIcons();