From 605abc16245d242b6fb8c627831a3e9bafd74208 Mon Sep 17 00:00:00 2001 From: Jeff Date: Fri, 7 Dec 2018 16:19:26 -0600 Subject: [PATCH] - Added Limited Resources and Aether Tide. --- Mage.Sets/src/mage/cards/a/AetherTide.java | 144 ++++++++++++++++++ .../src/mage/cards/l/LimitedResources.java | 124 +++++++++++++++ .../src/mage/cards/m/MonstrousHound.java | 104 ++++++++++--- Mage.Sets/src/mage/cards/r/Rebound.java | 6 +- Mage.Sets/src/mage/sets/Exodus.java | 2 + .../other/TargetsOnlyOnePlayerPredicate.java | 48 ++++++ 6 files changed, 408 insertions(+), 20 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/a/AetherTide.java create mode 100644 Mage.Sets/src/mage/cards/l/LimitedResources.java create mode 100644 Mage/src/main/java/mage/filter/predicate/other/TargetsOnlyOnePlayerPredicate.java diff --git a/Mage.Sets/src/mage/cards/a/AetherTide.java b/Mage.Sets/src/mage/cards/a/AetherTide.java new file mode 100644 index 0000000000..fc3c6c4915 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AetherTide.java @@ -0,0 +1,144 @@ +package mage.cards.a; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.costs.Cost; +import mage.abilities.costs.VariableCostImpl; +import mage.abilities.costs.common.DiscardTargetCost; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.stack.StackObject; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInHand; + +/** + * + * @author jeffwadsworth + */ +public final class AetherTide extends CardImpl { + + public AetherTide(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{U}"); + + // As an additional cost to cast Aether Tide, discard X creature cards. + this.getSpellAbility().addCost(new AetherTideCost()); + + // Return X target creatures to their owners' hands. + this.getSpellAbility().addEffect(new ReturnToHandTargetPermanentEffect()); + + } + + public AetherTide(final AetherTide card) { + super(card); + } + + @Override + public AetherTide copy() { + return new AetherTide(this); + } +} + +class AetherTideCost extends VariableCostImpl { + + public AetherTideCost() { + super("discard X creature cards"); + text = "As an additional cost to cast {this}, discard X creature cards"; + } + + public AetherTideCost(AetherTideCost cost) { + super(cost); + } + + @Override + public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) { + return (game.getPlayer(controllerId).getHand().count(new FilterCreatureCard(), game) > 0); + } + + @Override + public int getMaxValue(Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + return controller.getHand().count(new FilterCreatureCard(), game); + } + return 0; + } + + @Override + public int getMinValue(Ability source, Game game) { + return 0; + } + + @Override + public Cost getFixedCostsFromAnnouncedValue(int xValue) { + TargetCardInHand target = new TargetCardInHand(xValue, new FilterCreatureCard()); + return new DiscardTargetCost(target); + } + + @Override + public int announceXValue(Ability source, Game game) { + int xValue = 0; + Player controller = game.getPlayer(source.getControllerId()); + StackObject stackObject = game.getStack().getStackObject(source.getId()); + if (controller != null + && stackObject != null) { + xValue = controller.announceXCost(getMinValue(source, game), getMaxValue(source, game), + "Announce the number of creature cards to discard", game, source, this); + } + return xValue; + } + + @Override + public AetherTideCost copy() { + return new AetherTideCost(this); + } + +} + +class ReturnToHandTargetPermanentEffect extends OneShotEffect { + + public ReturnToHandTargetPermanentEffect() { + super(Outcome.ReturnToHand); + setText("Return X target creatures to their owners' hands"); + } + + public ReturnToHandTargetPermanentEffect(final ReturnToHandTargetPermanentEffect effect) { + super(effect); + } + + @Override + public ReturnToHandTargetPermanentEffect copy() { + return new ReturnToHandTargetPermanentEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + int xPaid = source.getCosts().getVariableCosts().get(0).getAmount(); + if (controller != null + && xPaid > 0) { + int available = game.getBattlefield().count(new FilterCreaturePermanent(), + source.getSourceId(), + source.getControllerId(), game); + if (available > 0) { + TargetPermanent target = new TargetPermanent(Math.min(xPaid, available), + xPaid, + new FilterCreaturePermanent("creatures to return to their owner's hands"), + true); + if (controller.chooseTarget(outcome.Detriment, target, source, game)) { + controller.moveCards(new CardsImpl(target.getTargets()), Zone.HAND, source, game); + } + return true; + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LimitedResources.java b/Mage.Sets/src/mage/cards/l/LimitedResources.java new file mode 100644 index 0000000000..fc97e01423 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LimitedResources.java @@ -0,0 +1,124 @@ +package mage.cards.l; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalContinuousRuleModifyingEffect; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.common.FilterControlledLandPermanent; +import mage.filter.common.FilterLandPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.target.common.TargetLandPermanent; + +/** + * + * @author jeffwadsworth + */ +public final class LimitedResources extends CardImpl { + + public LimitedResources(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{W}"); + + // When Limited Resources enters the battlefield, each player chooses five lands he or she controls and sacrifices the rest. + this.addAbility(new EntersBattlefieldTriggeredAbility(new LimitedResourcesEffect(), false)); + + // Players can't play lands as long as ten or more lands are on the battlefield. + this.addAbility(new SimpleStaticAbility( + Zone.BATTLEFIELD, + new ConditionalContinuousRuleModifyingEffect( + new CantPlayLandEffect(), + new PermanentsOnTheBattlefieldCondition( + new FilterLandPermanent(), + ComparisonType.MORE_THAN, 9)))); + + } + + public LimitedResources(final LimitedResources card) { + super(card); + } + + @Override + public LimitedResources copy() { + return new LimitedResources(this); + } +} + +class LimitedResourcesEffect extends OneShotEffect { + + public LimitedResourcesEffect() { + super(Outcome.Benefit); + this.staticText = "each player chooses five lands he or she controls and sacrifices the rest"; + } + + public LimitedResourcesEffect(final LimitedResourcesEffect effect) { + super(effect); + } + + @Override + public LimitedResourcesEffect copy() { + return new LimitedResourcesEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + game.getState().getPlayersInRange(source.getControllerId(), game).forEach((playerId) -> { + Player player = game.getPlayer(playerId); + if (player != null) { + int lands = game.getBattlefield().countAll(new FilterControlledLandPermanent(), playerId, game); + TargetLandPermanent target = new TargetLandPermanent(Integer.min(5, lands)); + target.setNotTarget(true); + target.setRequired(true); + player.chooseTarget(outcome.Benefit, target, source, game); + game.getBattlefield().getAllActivePermanents(new FilterControlledLandPermanent(), playerId, game).stream().filter((land) -> (!target.getTargets().contains(land.getId()))).forEachOrdered((land) -> { + land.sacrifice(source.getSourceId(), game); + }); + } + }); + return true; + } +} + +class CantPlayLandEffect extends ContinuousRuleModifyingEffectImpl { + + public CantPlayLandEffect() { + super(Duration.WhileOnBattlefield, Outcome.Detriment); + this.staticText = "Players can't play lands as long as ten or more lands are on the battlefield"; + } + + public CantPlayLandEffect(final CantPlayLandEffect effect) { + super(effect); + } + + @Override + public CantPlayLandEffect copy() { + return new CantPlayLandEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.PLAY_LAND; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return true; + } + +} diff --git a/Mage.Sets/src/mage/cards/m/MonstrousHound.java b/Mage.Sets/src/mage/cards/m/MonstrousHound.java index 5bf9aa0f1f..06dc331a67 100644 --- a/Mage.Sets/src/mage/cards/m/MonstrousHound.java +++ b/Mage.Sets/src/mage/cards/m/MonstrousHound.java @@ -2,20 +2,20 @@ package mage.cards.m; import java.util.UUID; import mage.MageInt; +import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.ControlsPermanentsComparedToOpponentsCondition; -import mage.abilities.decorator.ConditionalRestrictionEffect; import mage.abilities.effects.Effect; -import mage.abilities.effects.common.combat.CantAttackAnyPlayerSourceEffect; -import mage.abilities.effects.common.combat.CantBlockSourceEffect; +import mage.abilities.effects.RestrictionEffect; import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.ComparisonType; import mage.constants.Duration; import mage.constants.Zone; -import mage.filter.common.FilterLandPermanent; +import mage.filter.common.FilterControlledLandPermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; /** * @@ -31,23 +31,15 @@ public final class MonstrousHound extends CardImpl { this.toughness = new MageInt(4); // Monstrous Hound can't attack unless you control more lands than defending player. - Effect effect = new ConditionalRestrictionEffect( - new CantAttackAnyPlayerSourceEffect(Duration.WhileOnBattlefield), - new ControlsPermanentsComparedToOpponentsCondition( - ComparisonType.FEWER_THAN, - new FilterLandPermanent())); + Effect effect = new CantAttackUnlessControllerControlsMoreLandsEffect(); this.addAbility(new SimpleStaticAbility( - Zone.BATTLEFIELD, + Zone.BATTLEFIELD, effect.setText("{this} can't attack unless you control more lands than defending player"))); // Monstrous Hound can't block unless you control more lands than attacking player. - Effect effect2 = new ConditionalRestrictionEffect( - new CantBlockSourceEffect(Duration.WhileOnBattlefield), - new ControlsPermanentsComparedToOpponentsCondition( - ComparisonType.FEWER_THAN, - new FilterLandPermanent())); + Effect effect2 = new CantBlockUnlessControllerControlsMoreLandsEffect(); this.addAbility(new SimpleStaticAbility( - Zone.BATTLEFIELD, + Zone.BATTLEFIELD, effect2.setText("{this} can't block unless you control more lands than attacking player"))); } @@ -61,3 +53,79 @@ public final class MonstrousHound extends CardImpl { return new MonstrousHound(this); } } + +class CantAttackUnlessControllerControlsMoreLandsEffect extends RestrictionEffect { + + CantAttackUnlessControllerControlsMoreLandsEffect() { + super(Duration.WhileOnBattlefield); + } + + CantAttackUnlessControllerControlsMoreLandsEffect(final CantAttackUnlessControllerControlsMoreLandsEffect effect) { + super(effect); + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + return permanent.getId().equals(source.getSourceId()); + } + + @Override + public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game) { + UUID defendingPlayerId; + Player defender = game.getPlayer(defenderId); + if (defender == null) { + Permanent permanent = game.getPermanent(defenderId); + if (permanent != null) { + defendingPlayerId = permanent.getControllerId(); + } else { + return false; + } + } else { + defendingPlayerId = defenderId; + } + if (defendingPlayerId != null) { + return game.getBattlefield().countAll(new FilterControlledLandPermanent(), + source.getControllerId(), game) > game.getBattlefield().countAll(new FilterControlledLandPermanent(), + defendingPlayerId, game); + } else { + return true; + } + } + + @Override + public CantAttackUnlessControllerControlsMoreLandsEffect copy() { + return new CantAttackUnlessControllerControlsMoreLandsEffect(this); + } +} + +class CantBlockUnlessControllerControlsMoreLandsEffect extends RestrictionEffect { + + CantBlockUnlessControllerControlsMoreLandsEffect() { + super(Duration.WhileOnBattlefield); + } + + CantBlockUnlessControllerControlsMoreLandsEffect(final CantBlockUnlessControllerControlsMoreLandsEffect effect) { + super(effect); + } + + @Override + public CantBlockUnlessControllerControlsMoreLandsEffect copy() { + return new CantBlockUnlessControllerControlsMoreLandsEffect(this); + } + + @Override + public boolean canBlock(Permanent attacker, Permanent blocker, Ability source, Game game) { + UUID attackingPlayerId = attacker.getControllerId(); + if (attackingPlayerId != null) { + return game.getBattlefield().countAll(new FilterControlledLandPermanent(), + source.getControllerId(), game) > game.getBattlefield().countAll(new FilterControlledLandPermanent(), + attackingPlayerId, game); + } + return true; + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + return permanent.getId().equals(source.getSourceId()); + } +} diff --git a/Mage.Sets/src/mage/cards/r/Rebound.java b/Mage.Sets/src/mage/cards/r/Rebound.java index deb926e0eb..5afdee305d 100644 --- a/Mage.Sets/src/mage/cards/r/Rebound.java +++ b/Mage.Sets/src/mage/cards/r/Rebound.java @@ -8,7 +8,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.filter.FilterSpell; -import mage.filter.predicate.other.TargetsPlayerPredicate; +import mage.filter.predicate.other.TargetsOnlyOnePlayerPredicate; import mage.game.Game; import mage.game.stack.Spell; import mage.players.Player; @@ -27,7 +27,7 @@ public final class Rebound extends CardImpl { // Change the target of target spell that targets only a player. The new target must be a player. this.getSpellAbility().addEffect(new ReboundEffect()); FilterSpell filter = new FilterSpell("spell that targets only a player"); - filter.add(new TargetsPlayerPredicate()); + filter.add(new TargetsOnlyOnePlayerPredicate()); this.getSpellAbility().addTarget(new TargetSpell(filter)); } @@ -68,6 +68,8 @@ class ReboundEffect extends OneShotEffect { TargetPlayer targetPlayer = new TargetPlayer(); if (controller.choose(Outcome.Neutral, targetPlayer, source.getSourceId(), game)) { spell.getSpellAbility().addTarget(targetPlayer); + game.informPlayers("The target of the spell was changed to " + targetPlayer.getTargetedName(game)); + return true; } } return false; diff --git a/Mage.Sets/src/mage/sets/Exodus.java b/Mage.Sets/src/mage/sets/Exodus.java index 8cdb6742d8..d6687e6cae 100644 --- a/Mage.Sets/src/mage/sets/Exodus.java +++ b/Mage.Sets/src/mage/sets/Exodus.java @@ -28,6 +28,7 @@ public final class Exodus extends ExpansionSet { this.numBoosterUncommon = 3; this.numBoosterRare = 1; this.ratioBoosterMythic = 0; + cards.add(new SetCardInfo("Aether Tide", 27, Rarity.COMMON, mage.cards.a.AetherTide.class)); cards.add(new SetCardInfo("Allay", 1, Rarity.COMMON, mage.cards.a.Allay.class)); cards.add(new SetCardInfo("Anarchist", 79, Rarity.COMMON, mage.cards.a.Anarchist.class)); cards.add(new SetCardInfo("Angelic Blessing", 2, Rarity.COMMON, mage.cards.a.AngelicBlessing.class)); @@ -75,6 +76,7 @@ public final class Exodus extends ExpansionSet { cards.add(new SetCardInfo("Keeper of the Mind", 36, Rarity.UNCOMMON, mage.cards.k.KeeperOfTheMind.class)); cards.add(new SetCardInfo("Killer Whale", 37, Rarity.UNCOMMON, mage.cards.k.KillerWhale.class)); cards.add(new SetCardInfo("Kor Chant", 9, Rarity.COMMON, mage.cards.k.KorChant.class)); + cards.add(new SetCardInfo("Limited Resources", 10, Rarity.RARE, mage.cards.l.LimitedResources.class)); cards.add(new SetCardInfo("Mage il-Vec", 86, Rarity.COMMON, mage.cards.m.MageIlVec.class)); cards.add(new SetCardInfo("Manabond", 113, Rarity.RARE, mage.cards.m.Manabond.class)); cards.add(new SetCardInfo("Mana Breach", 38, Rarity.UNCOMMON, mage.cards.m.ManaBreach.class)); diff --git a/Mage/src/main/java/mage/filter/predicate/other/TargetsOnlyOnePlayerPredicate.java b/Mage/src/main/java/mage/filter/predicate/other/TargetsOnlyOnePlayerPredicate.java new file mode 100644 index 0000000000..08433ae089 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/other/TargetsOnlyOnePlayerPredicate.java @@ -0,0 +1,48 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package mage.filter.predicate.other; + +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Mode; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.stack.StackObject; +import mage.players.Player; +import mage.target.Target; + +/** + * + * @author jeffwadsworth + */ +public class TargetsOnlyOnePlayerPredicate implements ObjectSourcePlayerPredicate> { + + public TargetsOnlyOnePlayerPredicate() { + } + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + StackObject object = game.getStack().getStackObject(input.getObject().getId()); + if (object != null) { + for (UUID modeId : object.getStackAbility().getModes().getSelectedModes()) { + Mode mode = object.getStackAbility().getModes().get(modeId); + for (Target target : mode.getTargets()) { + if (target.getTargets().size() == 1) { // only one player targeted + Player player = game.getPlayer(target.getFirstTarget()); + return player != null; + } + } + } + } + return false; + } + + @Override + public String toString() { + return "that targets only one player"; + } +}