From 1304069de3aaa7ba2d6d3c8f09b590f0b31bc11c Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 18 Sep 2013 14:30:48 +0200 Subject: [PATCH 1/4] Fixed a bug of CreateDelayedTriggeredAbilityEffect where the targetPointer wasn't copied correctly. --- .../effects/common/CreateDelayedTriggeredAbilityEffect.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage/src/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java b/Mage/src/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java index 8b8e17f4820..8ac5c4930d3 100644 --- a/Mage/src/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java +++ b/Mage/src/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java @@ -72,8 +72,9 @@ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect Date: Wed, 18 Sep 2013 16:24:31 +0200 Subject: [PATCH 2/4] Changed MonstosityAbility to support X value. --- ...ecomesMonstrousSourceTriggeredAbility.java | 8 +++ .../CreateDelayedTriggeredAbilityEffect.java | 1 - .../abilities/keyword/MonstrosityAbility.java | 61 ++++++++++++++----- 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/Mage/src/mage/abilities/common/BecomesMonstrousSourceTriggeredAbility.java b/Mage/src/mage/abilities/common/BecomesMonstrousSourceTriggeredAbility.java index 726fbd33a95..a8e1c783e1e 100644 --- a/Mage/src/mage/abilities/common/BecomesMonstrousSourceTriggeredAbility.java +++ b/Mage/src/mage/abilities/common/BecomesMonstrousSourceTriggeredAbility.java @@ -40,12 +40,15 @@ import mage.game.events.GameEvent.EventType; */ public class BecomesMonstrousSourceTriggeredAbility extends TriggeredAbilityImpl { + private int monstrosityValue; + public BecomesMonstrousSourceTriggeredAbility(Effect effect) { super(Zone.BATTLEFIELD, effect, false); } public BecomesMonstrousSourceTriggeredAbility(final BecomesMonstrousSourceTriggeredAbility ability) { super(ability); + this.monstrosityValue = ability.monstrosityValue; } @Override @@ -56,11 +59,16 @@ public class BecomesMonstrousSourceTriggeredAbility extends TriggeredAbilityImpl @Override public boolean checkTrigger(GameEvent event, Game game) { if (event.getType().equals(EventType.BECOMES_MONSTROUS) && event.getSourceId().equals(this.getSourceId())) { + this.monstrosityValue = event.getAmount(); return true; } return false; } + public int getMonstrosityValue() { + return monstrosityValue; + } + @Override public String getRule() { return "When {this} becomes monstrous, " + super.getRule(); diff --git a/Mage/src/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java b/Mage/src/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java index 8ac5c4930d3..ea507494391 100644 --- a/Mage/src/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java +++ b/Mage/src/mage/abilities/effects/common/CreateDelayedTriggeredAbilityEffect.java @@ -73,7 +73,6 @@ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect private int monstrosityValue; + /** + * + * @param manaString + * @param monstrosityValue use Integer.MAX_VALUE for monstrosity X. + */ public MonstrosityAbility(String manaString, int monstrosityValue) { - super(Zone.BATTLEFIELD, new ConditionalOneShotEffect( - new AddCountersSourceEffect(CounterType.P1P1.createInstance(monstrosityValue)), - new InvertCondition(MonstrousCondition.getInstance()), - ""), new ManaCostsImpl(manaString)); - this.addEffect(new ConditionalOneShotEffect( - new BecomeMonstrousSourceEffect(), - new InvertCondition(MonstrousCondition.getInstance()),"")); - + super(Zone.BATTLEFIELD, new BecomeMonstrousSourceEffect(),new ManaCostsImpl(manaString)); this.monstrosityValue = monstrosityValue; } @@ -78,13 +94,20 @@ public class MonstrosityAbility extends ActivatedAbilityImpl @Override public String getRule() { - return new StringBuilder(manaCosts.getText()).append(": Monstrosity ").append(monstrosityValue) - .append(". (If this creature isn't monstrous, put ").append(CardUtil.numberToText(monstrosityValue)) + return new StringBuilder(manaCosts.getText()).append(": Monstrosity ") + .append(monstrosityValue == Integer.MAX_VALUE ? "X":monstrosityValue) + .append(". (If this creature isn't monstrous, put ") + .append(monstrosityValue == Integer.MAX_VALUE ? "X":CardUtil.numberToText(monstrosityValue)) .append(" +1/+1 counters on it and it becomes monstrous.)").toString(); } -} + public int getMonstrosityValue() { + return monstrosityValue; + } + + +} class BecomeMonstrousSourceEffect extends OneShotEffect { public BecomeMonstrousSourceEffect() { @@ -104,9 +127,15 @@ class BecomeMonstrousSourceEffect extends OneShotEffect Date: Wed, 18 Sep 2013 16:25:02 +0200 Subject: [PATCH 3/4] [THS] Added Time to Feed and Polukranos World Eater. --- .../sets/theros/PolukranosWorldEater.java | 155 ++++++++++++++++++ .../src/mage/sets/theros/TimeToFeed.java | 151 +++++++++++++++++ 2 files changed, 306 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/theros/PolukranosWorldEater.java create mode 100644 Mage.Sets/src/mage/sets/theros/TimeToFeed.java diff --git a/Mage.Sets/src/mage/sets/theros/PolukranosWorldEater.java b/Mage.Sets/src/mage/sets/theros/PolukranosWorldEater.java new file mode 100644 index 00000000000..121533e53fd --- /dev/null +++ b/Mage.Sets/src/mage/sets/theros/PolukranosWorldEater.java @@ -0,0 +1,155 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.theros; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BecomesMonstrousSourceTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.MonstrosityAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.TargetController; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.Target; +import mage.target.common.TargetCreaturePermanentAmount; + +/** + * + * * The value of X in Polukranos’s last ability is equal to the value chosen + * for X when its activated ability was activated. + * + * * The number of targets chosen for the triggered ability must be at least one + * (if X wasn’t 0) and at most X. You choose the division of damage as you put + * the ability on the stack, not as it resolves. Each target must be assigned + * at least 1 damage. In multiplayer games, you may choose creatures controlled + * by different opponents. + * + * * If some, but not all, of the ability’s targets become illegal, you can’t change + * the division of damage. Damage that would’ve been dealt to illegal targets + * simply isn’t dealt. + * + * * As Polukranos’s triggered ability resolves, Polukranos deals damage first, then + * the target creatures do. Although no creature will die until after the ability + * finishes resolving, the order could matter if Polukranos has wither or infect. + * + * @author LevelX2 + */ +public class PolukranosWorldEater extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + static { + filter.add(new ControllerPredicate(TargetController.OPPONENT)); + } + + public PolukranosWorldEater(UUID ownerId) { + super(ownerId, 172, "Polukranos, World Eater", Rarity.MYTHIC, new CardType[]{CardType.CREATURE}, "{2}{G}{G}"); + this.expansionSetCode = "THS"; + this.supertype.add("Legendary"); + this.subtype.add("Hydra"); + + this.color.setGreen(true); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // {X}{X}{G}: Monstrosity X. + this.addAbility(new MonstrosityAbility("{X}{X}{G}", Integer.MAX_VALUE)); + // When Polukranos, World Eater becomes monstrous, it deals X damage divided as you choose among any number of target creatures your opponents control. Each of those creatures deals damage equal to its power to Polukranos. + Ability ability = new BecomesMonstrousSourceTriggeredAbility(new PolukranosWorldEaterEffect()); + ability.addTarget(new TargetCreaturePermanentAmount(1, filter)); + this.addAbility(ability); + + } + + @Override + public void adjustTargets(Ability ability, Game game) { + if (ability instanceof BecomesMonstrousSourceTriggeredAbility) { + int xValue = ((BecomesMonstrousSourceTriggeredAbility) ability).getMonstrosityValue(); + ability.getTargets().clear(); + ability.addTarget(new TargetCreaturePermanentAmount(xValue, filter)); + } + } + + public PolukranosWorldEater(final PolukranosWorldEater card) { + super(card); + } + + @Override + public PolukranosWorldEater copy() { + return new PolukranosWorldEater(this); + } +} + +class PolukranosWorldEaterEffect extends OneShotEffect { + + public PolukranosWorldEaterEffect() { + super(Outcome.Benefit); + this.staticText = "it deals X damage divided as you choose among any number of target creatures your opponents control. Each of those creatures deals damage equal to its power to Polukranos"; + } + + public PolukranosWorldEaterEffect(final PolukranosWorldEaterEffect effect) { + super(effect); + } + + @Override + public PolukranosWorldEaterEffect copy() { + return new PolukranosWorldEaterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (source.getTargets().size() > 0) { + Target multiTarget = source.getTargets().get(0); + Set permanents = new HashSet(); + for (UUID target: multiTarget.getTargets()) { + Permanent permanent = game.getPermanent(target); + if (permanent != null) { + permanents.add(permanent); + permanent.damage(multiTarget.getTargetAmount(target), source.getSourceId(), game, true, false); + } + } + // Each of those creatures deals damage equal to its power to Polukranos + Permanent sourceCreature = game.getPermanent(source.getSourceId()); + if (sourceCreature != null) { + for (Permanent permanent :permanents) { + sourceCreature.damage(permanent.getPower().getValue(), source.getSourceId(), game, true, false); + } + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/theros/TimeToFeed.java b/Mage.Sets/src/mage/sets/theros/TimeToFeed.java new file mode 100644 index 00000000000..6cc1e8f099a --- /dev/null +++ b/Mage.Sets/src/mage/sets/theros/TimeToFeed.java @@ -0,0 +1,151 @@ + +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.theros; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.FightTargetsEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.TargetController; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.target.Target; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * Time to Feed has two targets: a creature an opponent controls and a creature you control. + * If only one of those creatures is a legal target when Time to Feed tries to resolve, the + * creatures won’t fight and neither will deal or be dealt damage. However, you’ll still gain + * 3 life when the creature you don’t control dies that turn, even if it was the illegal target as Time to Feed resolved. + * If neither creature is a legal target when Time to Feed tries to resolve, the spell will + * be countered and none of its effects will happen. + * If the first target creature dies that turn, you’ll gain 3 life no matter what caused the creature to die or who controls the creature at that time. + * + * @author LevelX2 + */ +public class TimeToFeed extends CardImpl { + + private static final FilterCreaturePermanent filter1 = new FilterCreaturePermanent("creature an opponent controls"); + static { + filter1.add(new ControllerPredicate(TargetController.OPPONENT)); + } + + public TimeToFeed(UUID ownerId) { + super(ownerId, 181, "Time to Feed", Rarity.COMMON, new CardType[]{CardType.SORCERY}, "{2}{G}"); + this.expansionSetCode = "THS"; + + this.color.setGreen(true); + + // Choose target creature an opponent controls. When that creature dies this turn, you gain 3 life. Target creature you control fights that creature. + this.getSpellAbility().addEffect(new TimeToFeedTextEffect()); + Target target = new TargetCreaturePermanent(filter1); + target.setRequired(true); + this.getSpellAbility().addTarget(target); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent(true)); + this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new TimeToFeedDiesTargetTriggeredAbility())); + Effect effect = new FightTargetsEffect(); + effect.setText("Target creature you control fights that creature"); + this.getSpellAbility().addEffect(effect); + } + + public TimeToFeed(final TimeToFeed card) { + super(card); + } + + @Override + public TimeToFeed copy() { + return new TimeToFeed(this); + } +} + +class TimeToFeedTextEffect extends OneShotEffect { + + public TimeToFeedTextEffect() { + super(Outcome.Detriment); + this.staticText = "Choose target creature an opponent controls"; + } + + public TimeToFeedTextEffect(final TimeToFeedTextEffect effect) { + super(effect); + } + + @Override + public TimeToFeedTextEffect copy() { + return new TimeToFeedTextEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } +} + +class TimeToFeedDiesTargetTriggeredAbility extends DelayedTriggeredAbility { + + public TimeToFeedDiesTargetTriggeredAbility() { + super(new GainLifeEffect(3), Duration.EndOfTurn, false); + } + + public TimeToFeedDiesTargetTriggeredAbility(final TimeToFeedDiesTargetTriggeredAbility ability) { + super(ability); + } + + @Override + public TimeToFeedDiesTargetTriggeredAbility copy() { + return new TimeToFeedDiesTargetTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.ZONE_CHANGE && ((ZoneChangeEvent)event).isDiesEvent()) { + if (event.getTargetId().equals(getEffects().get(0).getTargetPointer().getFirst(game, this))) { + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "When that creature dies this turn, " + super.getRule(); + } +} From 472476db54c2496b6fd876f4c8eecaf5db28aeaa Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 18 Sep 2013 17:36:29 +0200 Subject: [PATCH 4/4] [THS] Added Rescue from the Underworld. --- .../sets/theros/RescueFromTheUnderworld.java | 272 ++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/theros/RescueFromTheUnderworld.java diff --git a/Mage.Sets/src/mage/sets/theros/RescueFromTheUnderworld.java b/Mage.Sets/src/mage/sets/theros/RescueFromTheUnderworld.java new file mode 100644 index 00000000000..ab69e333464 --- /dev/null +++ b/Mage.Sets/src/mage/sets/theros/RescueFromTheUnderworld.java @@ -0,0 +1,272 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.theros; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.Mode; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileSourceEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetCardInGraveyard; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * Once you announce you’re casting Rescue from the Underworld, no player may attempt to + * stop you from casting the spell by removing the creature you want to sacrifice. + * + * If you sacrifice a creature token to cast Rescue from the Underworld, it won’t return + * to the battlefield, although the target creature card will. + * + * If either the sacrificed creature or the target creature card leaves the graveyard + * before the delayed triggered ability resolves during your next upkeep, it won’t return. + * + * However, if the sacrificed creature is put into another public zone instead of the graveyard, + * perhaps because it’s your commander or because of another replacement effect, it will return + * to the battlefield from the zone it went to. + * + * Rescue from the Underworld is exiled as it resolves, not later as its delayed trigger resolves. + * + * + * @author LevelX2 + */ +public class RescueFromTheUnderworld extends CardImpl { + + public RescueFromTheUnderworld(UUID ownerId) { + super(ownerId, 102, "Rescue from the Underworld", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{4}{B}"); + this.expansionSetCode = "THS"; + + this.color.setBlack(true); + + // As an additional cost to cast Rescue from the Underworld, sacrifice a creature. + this.getSpellAbility().addCost(new SacrificeTargetCost(new TargetControlledCreaturePermanent())); + + // Choose target creature card in your graveyard. Return that card and the sacrificed card to the battlefield under your control at the beginning of your next upkeep. Exile Rescue from the Underworld. + this.getSpellAbility().addEffect(new RescueFromTheUnderworldTextEffect()); + this.getSpellAbility().addEffect(new RescueFromTheUnderworldCreateDelayedTriggeredAbilityEffect(new RescueFromTheUnderworldDelayedTriggeredAbility())); + Target target = new TargetCardInYourGraveyard(new FilterCreatureCard("creature card in your graveyard")); + target.setRequired(true); + this.getSpellAbility().addTarget(target); + this.getSpellAbility().addEffect(new ExileSourceEffect()); + } + + public RescueFromTheUnderworld(final RescueFromTheUnderworld card) { + super(card); + } + + @Override + public RescueFromTheUnderworld copy() { + return new RescueFromTheUnderworld(this); + } +} + +class RescueFromTheUnderworldTextEffect extends OneShotEffect { + + public RescueFromTheUnderworldTextEffect() { + super(Outcome.Benefit); + this.staticText = "Choose target creature card in your graveyard"; + } + + public RescueFromTheUnderworldTextEffect(final RescueFromTheUnderworldTextEffect effect) { + super(effect); + } + + @Override + public RescueFromTheUnderworldTextEffect copy() { + return new RescueFromTheUnderworldTextEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } +} + +class RescueFromTheUnderworldCreateDelayedTriggeredAbilityEffect extends OneShotEffect { + + protected DelayedTriggeredAbility ability; + + public RescueFromTheUnderworldCreateDelayedTriggeredAbilityEffect(DelayedTriggeredAbility ability) { + super(ability.getEffects().get(0).getOutcome()); + this.ability = ability; + } + + public RescueFromTheUnderworldCreateDelayedTriggeredAbilityEffect(final RescueFromTheUnderworldCreateDelayedTriggeredAbilityEffect effect) { + super(effect); + this.ability = effect.ability.copy(); + } + + @Override + public RescueFromTheUnderworldCreateDelayedTriggeredAbilityEffect copy() { + return new RescueFromTheUnderworldCreateDelayedTriggeredAbilityEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + DelayedTriggeredAbility delayedAbility = (DelayedTriggeredAbility) ability.copy(); + delayedAbility.setSourceId(source.getSourceId()); + delayedAbility.setControllerId(source.getControllerId()); + delayedAbility.getTargets().addAll(source.getTargets()); + for(Effect effect : delayedAbility.getEffects()) { + effect.getTargetPointer().init(game, source); + } + // add the scraificed creature as target + for (Cost cost :source.getCosts()) { + if (cost instanceof SacrificeTargetCost) { + SacrificeTargetCost sacCost = (SacrificeTargetCost) cost; + TargetCardInGraveyard target = new TargetCardInGraveyard(); + for(Permanent permanent : sacCost.getPermanents()) { + target.add(permanent.getId(), game); + delayedAbility.getTargets().add(target); + } + } + } + + game.addDelayedTriggeredAbility(delayedAbility); + return true; + } + + @Override + public String getText(Mode mode) { + return ability.getRule(); + } + +} + +class RescueFromTheUnderworldDelayedTriggeredAbility extends DelayedTriggeredAbility { + + public RescueFromTheUnderworldDelayedTriggeredAbility() { + this(new RescueFromTheUnderworldReturnEffect(), TargetController.YOU); + } + + public RescueFromTheUnderworldDelayedTriggeredAbility(Effect effect, TargetController targetController) { + super(effect); + } + + public RescueFromTheUnderworldDelayedTriggeredAbility(RescueFromTheUnderworldDelayedTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.UPKEEP_STEP_PRE && event.getPlayerId().equals(this.controllerId)) { + return true; + } + return false; + } + + @Override + public RescueFromTheUnderworldDelayedTriggeredAbility copy() { + return new RescueFromTheUnderworldDelayedTriggeredAbility(this); + } + + @Override + public String getRule() { + return "Return that card and the sacrificed card to the battlefield under your control at the beginning of your next upkeep"; + } +} + +class RescueFromTheUnderworldReturnEffect extends OneShotEffect { + + private boolean tapped; + + public RescueFromTheUnderworldReturnEffect() { + this(false); + } + + public RescueFromTheUnderworldReturnEffect(boolean tapped) { + super(Outcome.PutCreatureInPlay); + this.tapped = tapped; + } + + public RescueFromTheUnderworldReturnEffect(final RescueFromTheUnderworldReturnEffect effect) { + super(effect); + this.tapped = effect.tapped; + } + + @Override + public RescueFromTheUnderworldReturnEffect copy() { + return new RescueFromTheUnderworldReturnEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + boolean result = false; + // Target card comes only back if in graveyard + for (UUID targetId: getTargetPointer().getTargets(game, source)) { + Card card = game.getCard(targetId); + if (card != null) { + Player player = game.getPlayer(card.getOwnerId()); + if (player != null) { + if(card.putOntoBattlefield(game, Zone.GRAVEYARD, source.getSourceId(), source.getControllerId(), tapped)){ + result = true; + } + } + } + } + // However, if the sacrificed creature is put into another public zone instead of the graveyard, + // perhaps because it’s your commander or because of another replacement effect, it will return + // to the battlefield from the zone it went to. + if (source.getTargets().get(1) != null) { + for (UUID targetId: ((Target) source.getTargets().get(1)).getTargets()) { + Card card = game.getCard(targetId); + if (card != null && !card.isFaceDown()) { + Player player = game.getPlayer(card.getOwnerId()); + if (player != null) { + Zone currentZone = game.getState().getZone(card.getId()); + if (currentZone.equals(Zone.COMMAND) || currentZone.equals(Zone.GRAVEYARD) || currentZone.equals(Zone.EXILED)) { + if(card.putOntoBattlefield(game, currentZone, source.getSourceId(), source.getControllerId(), false)){ + result = true; + } + } + } + } + } + } + return result; + } + +}