From c12057af552c9dadbc85dc7cb40f78c3bcd4d701 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Mon, 18 Apr 2022 20:58:59 -0400 Subject: [PATCH] [SNC] Implemented Rakish Revelers --- .../src/mage/cards/r/RakishRevelers.java | 44 +++++ .../src/mage/sets/StreetsOfNewCapenna.java | 1 + .../GiveManaAbilityAndCastSourceAbility.java | 171 ++++++++++++++++++ .../costs/common/ExileSourceFromHandCost.java | 2 + Mage/src/main/java/mage/util/CardUtil.java | 13 +- 5 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/r/RakishRevelers.java create mode 100644 Mage/src/main/java/mage/abilities/common/GiveManaAbilityAndCastSourceAbility.java diff --git a/Mage.Sets/src/mage/cards/r/RakishRevelers.java b/Mage.Sets/src/mage/cards/r/RakishRevelers.java new file mode 100644 index 00000000000..5c44f64529b --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RakishRevelers.java @@ -0,0 +1,44 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.GiveManaAbilityAndCastSourceAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.CitizenGreenWhiteToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RakishRevelers extends CardImpl { + + public RakishRevelers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{G}{W}"); + + this.subtype.add(SubType.ELF); + this.subtype.add(SubType.DRUID); + this.subtype.add(SubType.ROGUE); + this.power = new MageInt(5); + this.toughness = new MageInt(3); + + // When Rakish Revelers enters the battlefield, create a 1/1 green and white Citizen creature token. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new CitizenGreenWhiteToken()))); + + // {2}, Exile Rakish Revelers from your hand: Target land gains "{T}: Add {R}, {G}, or {W}" until Rakish Revelers is cast from exile. You may cast Rakish Revelers for as long as it remains exiled. + this.addAbility(new GiveManaAbilityAndCastSourceAbility("RGW")); + } + + private RakishRevelers(final RakishRevelers card) { + super(card); + } + + @Override + public RakishRevelers copy() { + return new RakishRevelers(this); + } +} diff --git a/Mage.Sets/src/mage/sets/StreetsOfNewCapenna.java b/Mage.Sets/src/mage/sets/StreetsOfNewCapenna.java index ea6e2daa2a5..ba647b405b2 100644 --- a/Mage.Sets/src/mage/sets/StreetsOfNewCapenna.java +++ b/Mage.Sets/src/mage/sets/StreetsOfNewCapenna.java @@ -190,6 +190,7 @@ public final class StreetsOfNewCapenna extends ExpansionSet { cards.add(new SetCardInfo("Raffine's Silencer", 90, Rarity.UNCOMMON, mage.cards.r.RaffinesSilencer.class)); cards.add(new SetCardInfo("Raffine's Tower", 254, Rarity.RARE, mage.cards.r.RaffinesTower.class)); cards.add(new SetCardInfo("Raffine, Scheming Seer", 213, Rarity.MYTHIC, mage.cards.r.RaffineSchemingSeer.class)); + cards.add(new SetCardInfo("Rakish Revelers", 214, Rarity.COMMON, mage.cards.r.RakishRevelers.class)); cards.add(new SetCardInfo("Ready to Rumble", 119, Rarity.COMMON, mage.cards.r.ReadyToRumble.class)); cards.add(new SetCardInfo("Refuse to Yield", 27, Rarity.UNCOMMON, mage.cards.r.RefuseToYield.class)); cards.add(new SetCardInfo("Reservoir Kraken", 56, Rarity.RARE, mage.cards.r.ReservoirKraken.class)); diff --git a/Mage/src/main/java/mage/abilities/common/GiveManaAbilityAndCastSourceAbility.java b/Mage/src/main/java/mage/abilities/common/GiveManaAbilityAndCastSourceAbility.java new file mode 100644 index 00000000000..db2f5016745 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/GiveManaAbilityAndCastSourceAbility.java @@ -0,0 +1,171 @@ +package mage.abilities.common; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.ActivatedAbilityImpl; +import mage.abilities.Mode; +import mage.abilities.costs.common.ExileSourceFromHandCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.mana.*; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.target.common.TargetLandPermanent; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public class GiveManaAbilityAndCastSourceAbility extends ActivatedAbilityImpl { + + // TODO: write automated tests for this (it works in manual testing) + public GiveManaAbilityAndCastSourceAbility(String colors) { + super(Zone.HAND, new GainManaAbilitiesWhileExiledEffect(colors), new GenericManaCost(2)); + this.addCost(new ExileSourceFromHandCost()); + this.addEffect(new CastExiledFromHandCardEffect()); + this.addTarget(new TargetLandPermanent()); + this.addWatcher(new WasCastFromExileWatcher()); + } + + private GiveManaAbilityAndCastSourceAbility(final GiveManaAbilityAndCastSourceAbility ability) { + super(ability); + } + + @Override + public GiveManaAbilityAndCastSourceAbility copy() { + return new GiveManaAbilityAndCastSourceAbility(this); + } +} + +class CastExiledFromHandCardEffect extends OneShotEffect { + + CastExiledFromHandCardEffect() { + super(Outcome.Benefit); + staticText = "You may cast {this} for as long as it remains exiled"; + } + + private CastExiledFromHandCardEffect(final CastExiledFromHandCardEffect effect) { + super(effect); + } + + @Override + public CastExiledFromHandCardEffect copy() { + return new CastExiledFromHandCardEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Optional.of(getValue("exiledHandCardRef")) + .filter(Objects::nonNull) + .map(MageObjectReference.class::cast) + .map(mor -> mor.getCard(game)) + .ifPresent(card -> CardUtil.makeCardPlayable( + game, source, card, Duration.Custom, false + )); + return true; + } +} + +class GainManaAbilitiesWhileExiledEffect extends ContinuousEffectImpl { + + private final String colors; + + GainManaAbilitiesWhileExiledEffect(String colors) { + super(Duration.Custom, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.colors = colors; + } + + private GainManaAbilitiesWhileExiledEffect(final GainManaAbilitiesWhileExiledEffect effect) { + super(effect); + this.colors = effect.colors; + } + + @Override + public GainManaAbilitiesWhileExiledEffect copy() { + return new GainManaAbilitiesWhileExiledEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (WasCastFromExileWatcher.check((MageObjectReference) getValue("exiledHandCardRef"), game)) { + discard(); + return false; + } + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + discard(); + return false; + } + for (char c : colors.toCharArray()) { + Ability ability; + switch (c) { + case 'W': + ability = new WhiteManaAbility(); + break; + case 'U': + ability = new BlueManaAbility(); + break; + case 'B': + ability = new BlackManaAbility(); + break; + case 'R': + ability = new RedManaAbility(); + break; + case 'G': + ability = new GreenManaAbility(); + break; + default: + continue; + } + permanent.addAbility(ability, source.getSourceId(), game); + } + return true; + } + + @Override + public String getText(Mode mode) { + return "target land gains \"{T}: Add " + + CardUtil.concatWithOr( + Arrays.stream(colors.split("")) + .map(s -> '{' + s + '}') + .collect(Collectors.toList()) + ) + + "\" until {this} is cast from exile"; + } +} + +class WasCastFromExileWatcher extends Watcher { + + private final Set morSet = new HashSet<>(); + + WasCastFromExileWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.SPELL_CAST || !Zone.EXILED.match(event.getZone())) { + return; + } + Spell spell = game.getSpell(event.getSourceId()); + if (spell != null) { + morSet.add(new MageObjectReference(spell.getMainCard(), game, -1)); + } + } + + static boolean check(MageObjectReference mor, Game game) { + return game + .getState() + .getWatcher(WasCastFromExileWatcher.class) + .morSet + .contains(mor); + } +} diff --git a/Mage/src/main/java/mage/abilities/costs/common/ExileSourceFromHandCost.java b/Mage/src/main/java/mage/abilities/costs/common/ExileSourceFromHandCost.java index 8d6ba5bc718..40ba91a5f36 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/ExileSourceFromHandCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/ExileSourceFromHandCost.java @@ -1,5 +1,6 @@ package mage.abilities.costs.common; +import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; @@ -29,6 +30,7 @@ public class ExileSourceFromHandCost extends CostImpl { Card card = game.getCard(source.getSourceId()); if (player != null && player.getHand().contains(source.getSourceId()) && card != null) { paid = player.moveCards(card, Zone.EXILED, source, game); + source.getEffects().setValue("exiledHandCardRef", new MageObjectReference(card, game)); } return paid; } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index f384fd416c7..9fa38520158 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1531,14 +1531,22 @@ public final class CardUtil { return "T" + gameState.getTurnNum() + "." + gameState.getTurn().getStep().getType().getStepShortText(); } + public static String concatWithOr(List strings) { + return concatWith(strings, "or"); + } + public static String concatWithAnd(List strings) { + return concatWith(strings, "and"); + } + + private static String concatWith(List strings, String last) { switch (strings.size()) { case 0: return ""; case 1: return strings.get(0); case 2: - return strings.get(0) + " and " + strings.get(1); + return strings.get(0) + " " + last + " " + strings.get(1); } StringBuilder sb = new StringBuilder(); for (int i = 0; i < strings.size(); i++) { @@ -1548,7 +1556,8 @@ public final class CardUtil { } sb.append(", "); if (i == strings.size() - 2) { - sb.append("and "); + sb.append(last); + sb.append(' '); } } return sb.toString();