From 4a432b61f96bea275861ae4720208413c330a4cb Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Mon, 30 Sep 2024 01:31:32 -0400 Subject: [PATCH] [DSK] Implement Niko, Light of Hope (#12942) Add UntilTheNextEndstep duration for 'until the beginning of the next end step' on the copy effect. --- .../src/mage/cards/n/NikoLightOfHope.java | 115 ++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + .../abilities/effects/ContinuousEffect.java | 2 + .../effects/ContinuousEffectImpl.java | 22 +++- .../effects/ContinuousEffectsList.java | 1 + .../main/java/mage/constants/Duration.java | 1 + 6 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/n/NikoLightOfHope.java diff --git a/Mage.Sets/src/mage/cards/n/NikoLightOfHope.java b/Mage.Sets/src/mage/cards/n/NikoLightOfHope.java new file mode 100644 index 00000000000..6cecc1acc09 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NikoLightOfHope.java @@ -0,0 +1,115 @@ +package mage.cards.n; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlTargetEffect; +import mage.cards.Card; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.ShardToken; +import mage.players.Player; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.FixedTarget; +import mage.util.functions.EmptyCopyApplier; + +/** + * + * @author Grath + */ +public final class NikoLightOfHope extends CardImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("nonlegendary creature you control"); + + static { + filter.add(Predicates.not(SuperType.LEGENDARY.getPredicate())); + } + + public NikoLightOfHope(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // When Niko, Light of Hope enters, create two Shard tokens. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new ShardToken(), 2))); + + // {2}, {T}: Exile target nonlegendary creature you control. Shards you control become copies of it until the beginning of the next end step. Return it to the battlefield under its owner's control at the beginning of the next end step. + Ability ability = new SimpleActivatedAbility(new NikoLightOfHopeEffect(), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetControlledCreaturePermanent(1, 1, filter, false)); + this.addAbility(ability); + } + + private NikoLightOfHope(final NikoLightOfHope card) { + super(card); + } + + @Override + public NikoLightOfHope copy() { + return new NikoLightOfHope(this); + } +} + +class NikoLightOfHopeEffect extends OneShotEffect { + + NikoLightOfHopeEffect() { + super(Outcome.Benefit); + staticText = "Exile target nonlegendary creature you control. Shards you control become copies of it until the beginning of the next end step. Return it to the battlefield under its owner's control at the beginning of the next end step."; + } + + private NikoLightOfHopeEffect(final NikoLightOfHopeEffect effect) { + super(effect); + } + + @Override + public NikoLightOfHopeEffect copy() { + return new NikoLightOfHopeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent != null && controller != null) { + if (permanent.moveToExile(source.getSourceId(), "Niko, Light of Hope", source, game)) { + FilterPermanent filter = new FilterPermanent("shards"); + filter.add(SubType.SHARD.getPredicate()); + for (Permanent copyTo : game.getBattlefield().getAllActivePermanents(filter, controller.getId(), game)) { + game.copyPermanent(Duration.UntilTheNextEndStep, permanent, copyTo.getId(), source, new EmptyCopyApplier()); + } + ExileZone exile = game.getExile().getExileZone(source.getSourceId()); + if (exile != null && !exile.isEmpty()) { + Card card = game.getCard(permanent.getId()); + if (card != null) { + Effect effect = new ReturnToBattlefieldUnderOwnerControlTargetEffect(false, false); + effect.setTargetPointer(new FixedTarget(card.getId())); + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), source); + } + } + return true; + } + } + return false; + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 1b304b8f2f6..941be07fedd 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -149,6 +149,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Murky Sewer", 263, Rarity.COMMON, mage.cards.m.MurkySewer.class)); cards.add(new SetCardInfo("Nashi, Searcher in the Dark", 223, Rarity.RARE, mage.cards.n.NashiSearcherInTheDark.class)); cards.add(new SetCardInfo("Neglected Manor", 264, Rarity.COMMON, mage.cards.n.NeglectedManor.class)); + cards.add(new SetCardInfo("Niko, Light of Hope", 224, Rarity.MYTHIC, mage.cards.n.NikoLightOfHope.class)); cards.add(new SetCardInfo("Norin, Swift Survivalist", 145, Rarity.UNCOMMON, mage.cards.n.NorinSwiftSurvivalist.class)); cards.add(new SetCardInfo("Oblivious Bookworm", 225, Rarity.UNCOMMON, mage.cards.o.ObliviousBookworm.class)); cards.add(new SetCardInfo("Optimistic Scavenger", 21, Rarity.UNCOMMON, mage.cards.o.OptimisticScavenger.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java index c1456292c37..3b0cddcbf60 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java @@ -74,6 +74,8 @@ public interface ContinuousEffect extends Effect { boolean isYourNextEndStep(Game game); + boolean isTheNextEndStep(Game game); + boolean isYourNextUpkeepStep(Game game); @Override diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java index 156e9400251..901dc44d102 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java @@ -56,9 +56,11 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu // until your next turn or until end of your next turn private UUID startingControllerId; // player to check for turn duration (can't different with real controller ability) + private UUID activePlayerId; // Player whose turn the effect started on private boolean startingTurnWasActive; // effect started during related players turn and related players turn was already active private int effectStartingOnTurn = 0; // turn the effect started - private int effectStartingEndStep = 0; + private int effectControllerStartingEndStep = 0; + private int effectActivePlayerStartingEndStep = 0; private int nextTurnNumber = Integer.MAX_VALUE; // effect is waiting for a step during your next turn, we store it if found. // set to the turn number on your next turn. private int effectStartingStepNum = 0; // Some continuous are waiting for the next step of a kind. @@ -93,7 +95,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu this.startingControllerId = effect.startingControllerId; this.startingTurnWasActive = effect.startingTurnWasActive; this.effectStartingOnTurn = effect.effectStartingOnTurn; - this.effectStartingEndStep = effect.effectStartingEndStep; + this.effectControllerStartingEndStep = effect.effectControllerStartingEndStep; this.dependencyTypes = effect.dependencyTypes; this.dependendToTypes = effect.dependendToTypes; this.characterDefining = effect.characterDefining; @@ -251,10 +253,12 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu @Override public void setStartingControllerAndTurnNum(Game game, UUID startingController, UUID activePlayerId) { this.startingControllerId = startingController; + this.activePlayerId = activePlayerId; this.startingTurnWasActive = activePlayerId != null && activePlayerId.equals(startingController); // you can't use "game" for active player cause it's called from tests/cheat too this.effectStartingOnTurn = game.getTurnNum(); - this.effectStartingEndStep = EndStepCountWatcher.getCount(startingController, game); + this.effectControllerStartingEndStep = EndStepCountWatcher.getCount(startingController, game); + this.effectActivePlayerStartingEndStep = EndStepCountWatcher.getCount(activePlayerId, game); this.effectStartingStepNum = game.getState().getStepNum(); } @@ -266,7 +270,12 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu @Override public boolean isYourNextEndStep(Game game) { - return EndStepCountWatcher.getCount(startingControllerId, game) > effectStartingEndStep; + return EndStepCountWatcher.getCount(startingControllerId, game) > effectControllerStartingEndStep; + } + + @Override + public boolean isTheNextEndStep(Game game) { + return EndStepCountWatcher.getCount(activePlayerId, game) > effectActivePlayerStartingEndStep; } public boolean isEndCombatOfYourNextTurn(Game game) { @@ -298,6 +307,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu case UntilYourNextTurn: case UntilEndOfYourNextTurn: case UntilYourNextEndStep: + case UntilTheNextEndStep: case UntilEndCombatOfYourNextTurn: case UntilYourNextUpkeepStep: break; @@ -342,6 +352,10 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu return this.isYourNextEndStep(game); } break; + case UntilTheNextEndStep: + if (player != null && player.isInGame()) { + return this.isTheNextEndStep(game); + } case UntilEndCombatOfYourNextTurn: if (player != null && player.isInGame()) { return this.isEndCombatOfYourNextTurn(game); diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java index c8707a7289f..a75c476971b 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectsList.java @@ -156,6 +156,7 @@ public class ContinuousEffectsList extends ArrayList case UntilEndOfYourNextTurn: case UntilEndCombatOfYourNextTurn: case UntilYourNextEndStep: + case UntilTheNextEndStep: case UntilYourNextUpkeepStep: // until your turn effects continue until real turn reached, their used it's own inactive method // 514.2 Second, the following actions happen simultaneously: all damage marked on permanents diff --git a/Mage/src/main/java/mage/constants/Duration.java b/Mage/src/main/java/mage/constants/Duration.java index dd14431a6b5..da37dd1e973 100644 --- a/Mage/src/main/java/mage/constants/Duration.java +++ b/Mage/src/main/java/mage/constants/Duration.java @@ -13,6 +13,7 @@ public enum Duration { EndOfTurn("until end of turn", true, true), UntilYourNextTurn("until your next turn", true, true), UntilYourNextEndStep("until your next end step", true, true), + UntilTheNextEndStep("until your next end step", true, true), UntilEndCombatOfYourNextTurn("until end of combat on your next turn", true, true), UntilYourNextUpkeepStep("until your next upkeep", true, true), UntilEndOfYourNextTurn("until the end of your next turn", true, true),