diff --git a/Mage.Sets/src/mage/cards/a/AccursedWitch.java b/Mage.Sets/src/mage/cards/a/AccursedWitch.java index 497724b2b63..0bc6e8d72a2 100644 --- a/Mage.Sets/src/mage/cards/a/AccursedWitch.java +++ b/Mage.Sets/src/mage/cards/a/AccursedWitch.java @@ -1,25 +1,20 @@ - package mage.cards.a; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.SpellAbility; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellsCostModificationThatTargetSourceEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.FilterCard; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.Target; import mage.target.common.TargetOpponent; -import mage.util.CardUtil; import java.util.UUID; @@ -39,7 +34,10 @@ public final class AccursedWitch extends CardImpl { this.secondSideCardClazz = mage.cards.i.InfectiousCurse.class; // Spells your opponents cast that target Accursed Witch cost {1} less to cast. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new AccursedWitchSpellsCostReductionEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new SpellsCostModificationThatTargetSourceEffect(-1, new FilterCard("Spells"), TargetController.OPPONENT)) + ); + // When Accursed Witch dies, return it to the battlefield transformed under your control attached to target opponent. this.addAbility(new TransformAbility()); Ability ability = new DiesSourceTriggeredAbility(new AccursedWitchReturnTransformedEffect()); @@ -93,45 +91,3 @@ class AccursedWitchReturnTransformedEffect extends OneShotEffect { return true; } } - -class AccursedWitchSpellsCostReductionEffect extends CostModificationEffectImpl { - - AccursedWitchSpellsCostReductionEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment, CostModificationType.REDUCE_COST); - this.staticText = "Spells your opponents cast that target {this} cost {1} less to cast"; - } - - private AccursedWitchSpellsCostReductionEffect(AccursedWitchSpellsCostReductionEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - CardUtil.reduceCost(abilityToModify, 1); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (!(abilityToModify instanceof SpellAbility) || !game.getOpponents(source.getControllerId()).contains(abilityToModify.getControllerId())) { - return false; - } - for (UUID modeId : abilityToModify.getModes().getSelectedModes()) { - Mode mode = abilityToModify.getModes().get(modeId); - for (Target target : mode.getTargets()) { - for (UUID targetUUID : target.getTargets()) { - Permanent permanent = game.getPermanent(targetUUID); - if (permanent != null && permanent.getId().equals(source.getSourceId())) { - return true; - } - } - } - } - return false; - } - - @Override - public AccursedWitchSpellsCostReductionEffect copy() { - return new AccursedWitchSpellsCostReductionEffect(this); - } -} diff --git a/Mage.Sets/src/mage/cards/a/AnimarSoulOfElements.java b/Mage.Sets/src/mage/cards/a/AnimarSoulOfElements.java index e47b01a4681..1ac2052194d 100644 --- a/Mage.Sets/src/mage/cards/a/AnimarSoulOfElements.java +++ b/Mage.Sets/src/mage/cards/a/AnimarSoulOfElements.java @@ -82,9 +82,9 @@ class AnimarCostReductionEffect extends CostModificationEffectImpl { public boolean applies(Ability abilityToModify, Ability source, Game game) { if (abilityToModify instanceof SpellAbility) { if (abilityToModify.isControlledBy(source.getControllerId())) { - Card spell = ((SpellAbility) abilityToModify).getCharacteristics(game); - if (spell != null) { - return spell.isCreature(); + Card card = ((SpellAbility) abilityToModify).getCharacteristics(game); + if (card != null) { + return card.isCreature(); } } } diff --git a/Mage.Sets/src/mage/cards/a/ArcaneMelee.java b/Mage.Sets/src/mage/cards/a/ArcaneMelee.java index 9e45b06db79..2657238404a 100644 --- a/Mage.Sets/src/mage/cards/a/ArcaneMelee.java +++ b/Mage.Sets/src/mage/cards/a/ArcaneMelee.java @@ -1,17 +1,14 @@ - package mage.cards.a; -import java.util.UUID; -import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; -import mage.cards.Card; +import mage.abilities.effects.common.cost.SpellsCostReductionAllEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.game.Game; -import mage.util.CardUtil; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.common.FilterInstantOrSorceryCard; + +import java.util.UUID; /** * @author noxx @@ -22,7 +19,9 @@ public final class ArcaneMelee extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{U}"); // Instant and sorcery spells cost {2} less to cast. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ArcaneMeleeCostReductionEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new SpellsCostReductionAllEffect(new FilterInstantOrSorceryCard("Instant and sorcery spells"), 2)) + ); } public ArcaneMelee(final ArcaneMelee card) { @@ -34,39 +33,3 @@ public final class ArcaneMelee extends CardImpl { return new ArcaneMelee(this); } } - -class ArcaneMeleeCostReductionEffect extends CostModificationEffectImpl { - - ArcaneMeleeCostReductionEffect() { - super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST); - staticText = "Instant and sorcery spells cost {2} less to cast"; - } - - ArcaneMeleeCostReductionEffect(ArcaneMeleeCostReductionEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - SpellAbility spellAbility = (SpellAbility) abilityToModify; - CardUtil.adjustCost(spellAbility, 2); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (abilityToModify instanceof SpellAbility) { - Card sourceCard = game.getCard((abilityToModify).getSourceId()); - if (sourceCard != null && (sourceCard.isInstant() || sourceCard.isSorcery())) { - return true; - } - } - return false; - } - - @Override - public ArcaneMeleeCostReductionEffect copy() { - return new ArcaneMeleeCostReductionEffect(this); - } - -} diff --git a/Mage.Sets/src/mage/cards/a/AvatarOfFury.java b/Mage.Sets/src/mage/cards/a/AvatarOfFury.java index 9153c0a4f99..6489605608b 100644 --- a/Mage.Sets/src/mage/cards/a/AvatarOfFury.java +++ b/Mage.Sets/src/mage/cards/a/AvatarOfFury.java @@ -1,33 +1,41 @@ package mage.cards.a; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.continuous.BoostSourceEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.abilities.hint.ConditionHint; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.CostModificationType; import mage.constants.Duration; -import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; +import mage.filter.FilterPermanent; import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; import mage.game.Game; -import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class AvatarOfFury extends CardImpl { + private static final FilterPermanent filter = new FilterPermanent("artifact, creature, or enchantment"); + + static { + filter.add(Predicates.or(CardType.ARTIFACT.getPredicate(), + CardType.CREATURE.getPredicate(), + CardType.ENCHANTMENT.getPredicate())); + } + public AvatarOfFury(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{R}{R}"); this.subtype.add(SubType.AVATAR); @@ -36,9 +44,14 @@ public final class AvatarOfFury extends CardImpl { this.toughness = new MageInt(6); // If an opponent controls seven or more lands, Avatar of Fury costs {6} less to cast. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new AvatarOfFuryAdjustingCostsEffect())); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SpellCostReductionSourceEffect(6, AvatarOfFuryCondition.instance) + .setText("if an opponent controls seven or more lands, Avatar of Fury costs {6} less to cast")) + .addHint(new ConditionHint(AvatarOfFuryCondition.instance, "Opponent controls seven or more lands")) + ); + // Flying this.addAbility(FlyingAbility.getInstance()); + // {R}: Avatar of Fury gets +1/+0 until end of turn. this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new BoostSourceEffect(1, 0, Duration.EndOfTurn), new ManaCostsImpl("{R}"))); } @@ -53,39 +66,22 @@ public final class AvatarOfFury extends CardImpl { } } -class AvatarOfFuryAdjustingCostsEffect extends CostModificationEffectImpl { +enum AvatarOfFuryCondition implements Condition { - AvatarOfFuryAdjustingCostsEffect() { - super(Duration.EndOfGame, Outcome.Benefit, CostModificationType.REDUCE_COST); - staticText = "If an opponent controls seven or more lands, {this} costs {6} less to cast"; - } - - AvatarOfFuryAdjustingCostsEffect(AvatarOfFuryAdjustingCostsEffect effect) { - super(effect); - } + instance; @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - CardUtil.reduceCost(abilityToModify, 6); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (abilityToModify.getSourceId().equals(source.getSourceId()) - && (abilityToModify instanceof SpellAbility)) { - for (UUID playerId : game.getOpponents(abilityToModify.getControllerId())) { - if (game.getBattlefield().countAll(StaticFilters.FILTER_LAND, playerId, game) > 6) { - return true; - } + public boolean apply(Game game, Ability source) { + for (UUID playerId : game.getOpponents(source.getControllerId())) { + if (game.getBattlefield().countAll(StaticFilters.FILTER_LAND, playerId, game) > 6) { + return true; } } return false; } @Override - public AvatarOfFuryAdjustingCostsEffect copy() { - return new AvatarOfFuryAdjustingCostsEffect(this); + public String toString() { + return "an opponent controls seven or more lands"; } - } diff --git a/Mage.Sets/src/mage/cards/a/AvatarOfHope.java b/Mage.Sets/src/mage/cards/a/AvatarOfHope.java index d1e82950fe5..08957746eec 100644 --- a/Mage.Sets/src/mage/cards/a/AvatarOfHope.java +++ b/Mage.Sets/src/mage/cards/a/AvatarOfHope.java @@ -1,22 +1,24 @@ package mage.cards.a; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; import mage.abilities.effects.common.combat.CanBlockAdditionalCreatureEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.abilities.hint.ConditionHint; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; import mage.game.Game; import mage.players.Player; -import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author Plopman */ public final class AvatarOfHope extends CardImpl { @@ -29,9 +31,14 @@ public final class AvatarOfHope extends CardImpl { this.toughness = new MageInt(9); // If you have 3 or less life, Avatar of Hope costs {6} less to cast. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new AvatarOfHopeAdjustingCostsEffect())); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SpellCostReductionSourceEffect(6, AvatarOfHopeCondition.instance) + .setText("if you have 3 or less life, Avatar of Hope costs {6} less to cast")) + .addHint(new ConditionHint(AvatarOfHopeCondition.instance)) + ); + // Flying this.addAbility(FlyingAbility.getInstance()); + // Avatar of Hope can block any number of creatures. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CanBlockAdditionalCreatureEffect(0))); } @@ -46,39 +53,21 @@ public final class AvatarOfHope extends CardImpl { } } -class AvatarOfHopeAdjustingCostsEffect extends CostModificationEffectImpl { +enum AvatarOfHopeCondition implements Condition { - AvatarOfHopeAdjustingCostsEffect() { - super(Duration.EndOfGame, Outcome.Benefit, CostModificationType.REDUCE_COST); - staticText = "If you have 3 or less life, {this} costs {6} less to cast"; - } - - AvatarOfHopeAdjustingCostsEffect(AvatarOfHopeAdjustingCostsEffect effect) { - super(effect); - } + instance; @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - CardUtil.reduceCost(abilityToModify, 6); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (abilityToModify.getSourceId().equals(source.getSourceId()) - && (abilityToModify instanceof SpellAbility)) { - Player player = game.getPlayer(abilityToModify.getControllerId()); - if (player != null && player.getLife() < 4) { - return true; - } + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player != null && player.getLife() <= 3) { + return true; } - return false; } @Override - public AvatarOfHopeAdjustingCostsEffect copy() { - return new AvatarOfHopeAdjustingCostsEffect(this); + public String toString() { + return "you have 3 or less life"; } - } diff --git a/Mage.Sets/src/mage/cards/b/BorealElemental.java b/Mage.Sets/src/mage/cards/b/BorealElemental.java index bd0f8fcadaf..df54bda0db4 100644 --- a/Mage.Sets/src/mage/cards/b/BorealElemental.java +++ b/Mage.Sets/src/mage/cards/b/BorealElemental.java @@ -1,20 +1,17 @@ package mage.cards.b; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellsCostModificationThatTargetSourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.game.Game; -import mage.target.Target; -import mage.util.CardUtil; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.FilterCard; -import java.util.Collection; import java.util.UUID; /** @@ -33,7 +30,9 @@ public final class BorealElemental extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Spells your opponents cast that target Boreal Elemental cost {2} more to cast. - this.addAbility(new SimpleStaticAbility(new BorealElementalCostIncreaseEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new SpellsCostModificationThatTargetSourceEffect(2, new FilterCard("Spells"), TargetController.OPPONENT)) + ); } private BorealElemental(final BorealElemental card) { @@ -45,46 +44,3 @@ public final class BorealElemental extends CardImpl { return new BorealElemental(this); } } - -class BorealElementalCostIncreaseEffect extends CostModificationEffectImpl { - - BorealElementalCostIncreaseEffect() { - super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST); - staticText = "Spells your opponents cast that target {this} cost {2} more to cast"; - } - - private BorealElementalCostIncreaseEffect(BorealElementalCostIncreaseEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - SpellAbility spellAbility = (SpellAbility) abilityToModify; - CardUtil.adjustCost(spellAbility, -2); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (!(abilityToModify instanceof SpellAbility) - || !game.getOpponents(source.getControllerId()).contains(abilityToModify.getControllerId())) { - return false; - } - return abilityToModify - .getModes() - .getSelectedModes() - .stream() - .map(uuid -> abilityToModify.getModes().get(uuid)) - .map(Mode::getTargets) - .flatMap(Collection::stream) - .map(Target::getTargets) - .flatMap(Collection::stream) - .anyMatch(uuid -> uuid.equals(source.getSourceId())); - } - - @Override - public BorealElementalCostIncreaseEffect copy() { - return new BorealElementalCostIncreaseEffect(this); - } - -} diff --git a/Mage.Sets/src/mage/cards/c/CallapheBelovedOfTheSea.java b/Mage.Sets/src/mage/cards/c/CallapheBelovedOfTheSea.java index 4e245e095f1..1baa9de971d 100644 --- a/Mage.Sets/src/mage/cards/c/CallapheBelovedOfTheSea.java +++ b/Mage.Sets/src/mage/cards/c/CallapheBelovedOfTheSea.java @@ -2,23 +2,18 @@ package mage.cards.c; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.common.DevotionCount; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.effects.common.continuous.SetPowerSourceEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellsCostModificationThatTargetSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.predicate.Predicates; -import mage.game.Game; -import mage.target.Target; -import mage.util.CardUtil; -import java.util.Collection; import java.util.UUID; /** @@ -51,11 +46,12 @@ public final class CallapheBelovedOfTheSea extends CardImpl { ).addHint(DevotionCount.U.getHint())); // Creatures and enchantments you control have "Spells your opponents cast that target this permanent cost {1} more to cast". - this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( - new SimpleStaticAbility( - new CallapheBelovedOfTheSeaEffect() - ), Duration.WhileOnBattlefield, filter) - .withForceQuotes() + Ability gainAbility = new SimpleStaticAbility(Zone.BATTLEFIELD, + new SpellsCostModificationThatTargetSourceEffect(1, new FilterCard("Spells"), TargetController.OPPONENT) + .withTargetName("this permanent") + ); + this.addAbility(new SimpleStaticAbility( + new GainAbilityControlledEffect(gainAbility, Duration.WhileOnBattlefield, filter).withForceQuotes() )); } @@ -68,45 +64,3 @@ public final class CallapheBelovedOfTheSea extends CardImpl { return new CallapheBelovedOfTheSea(this); } } - -class CallapheBelovedOfTheSeaEffect extends CostModificationEffectImpl { - - CallapheBelovedOfTheSeaEffect() { - super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST); - staticText = "Spells your opponents cast that target this permanent cost {1} more to cast"; - } - - private CallapheBelovedOfTheSeaEffect(CallapheBelovedOfTheSeaEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - SpellAbility spellAbility = (SpellAbility) abilityToModify; - CardUtil.adjustCost(spellAbility, -1); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (!(abilityToModify instanceof SpellAbility) - || !game.getOpponents(source.getControllerId()).contains(abilityToModify.getControllerId())) { - return false; - } - return abilityToModify - .getModes() - .getSelectedModes() - .stream() - .map(uuid -> abilityToModify.getModes().get(uuid)) - .map(Mode::getTargets) - .flatMap(Collection::stream) - .map(Target::getTargets) - .flatMap(Collection::stream) - .anyMatch(uuid -> uuid.equals(source.getSourceId())); - } - - @Override - public CallapheBelovedOfTheSeaEffect copy() { - return new CallapheBelovedOfTheSeaEffect(this); - } -} diff --git a/Mage.Sets/src/mage/cards/e/ElderwoodScion.java b/Mage.Sets/src/mage/cards/e/ElderwoodScion.java index 778ffb55e52..eceff73b4f2 100644 --- a/Mage.Sets/src/mage/cards/e/ElderwoodScion.java +++ b/Mage.Sets/src/mage/cards/e/ElderwoodScion.java @@ -1,24 +1,21 @@ - package mage.cards.e; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellsCostModificationThatTargetSourceEffect; import mage.abilities.keyword.LifelinkAbility; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.game.Game; -import mage.target.Target; -import mage.util.CardUtil; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.FilterCard; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class ElderwoodScion extends CardImpl { @@ -32,12 +29,19 @@ public final class ElderwoodScion extends CardImpl { // Trample this.addAbility(TrampleAbility.getInstance()); + // Lifelink this.addAbility(LifelinkAbility.getInstance()); + // Spells you cast that target Elderwood Scion cost {2} less to cast. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ElderwoodScionCostReductionEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new SpellsCostModificationThatTargetSourceEffect(-2, new FilterCard("Spells"), TargetController.YOU)) + ); + // Spells your opponents cast that target Elderwood Scion cost {2} more to cast. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ElderwoodScionCostReductionEffect2())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new SpellsCostModificationThatTargetSourceEffect(2, new FilterCard("Spells"), TargetController.OPPONENT)) + ); } public ElderwoodScion(final ElderwoodScion card) { @@ -49,92 +53,3 @@ public final class ElderwoodScion extends CardImpl { return new ElderwoodScion(this); } } - -class ElderwoodScionCostReductionEffect extends CostModificationEffectImpl { - - private static final String effectText = "Spells you cast that target {this} cost {2} less to cast"; - - ElderwoodScionCostReductionEffect() { - super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST); - staticText = effectText; - } - - ElderwoodScionCostReductionEffect(ElderwoodScionCostReductionEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - SpellAbility spellAbility = (SpellAbility) abilityToModify; - CardUtil.adjustCost(spellAbility, 2); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (abilityToModify instanceof SpellAbility) { - if (abilityToModify.isControlledBy(source.getControllerId())) { - for (UUID modeId : abilityToModify.getModes().getSelectedModes()) { - Mode mode = abilityToModify.getModes().get(modeId); - for (Target target : mode.getTargets()) { - for (UUID targetUUID : target.getTargets()) { - if (targetUUID.equals(source.getSourceId())) { - return true; - } - } - } - } - } - } - return false; - } - - @Override - public ElderwoodScionCostReductionEffect copy() { - return new ElderwoodScionCostReductionEffect(this); - } - -} - -class ElderwoodScionCostReductionEffect2 extends CostModificationEffectImpl { - - private static final String effectText = "Spells your opponents cast that target {this} cost {2} more to cast"; - - ElderwoodScionCostReductionEffect2() { - super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST); - staticText = effectText; - } - - ElderwoodScionCostReductionEffect2(ElderwoodScionCostReductionEffect2 effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - SpellAbility spellAbility = (SpellAbility) abilityToModify; - CardUtil.increaseCost(spellAbility, 2); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (abilityToModify instanceof SpellAbility) { - if (game.getOpponents(source.getControllerId()).contains(abilityToModify.getControllerId())) { - for (Target target : abilityToModify.getTargets()) { - for (UUID targetUUID : target.getTargets()) { - if (targetUUID.equals(source.getSourceId())) { - return true; - } - } - } - } - } - return false; - } - - @Override - public ElderwoodScionCostReductionEffect2 copy() { - return new ElderwoodScionCostReductionEffect2(this); - } - -} diff --git a/Mage.Sets/src/mage/cards/g/Grapeshot.java b/Mage.Sets/src/mage/cards/g/Grapeshot.java index 82a6a8b5c67..5101aaa10fd 100644 --- a/Mage.Sets/src/mage/cards/g/Grapeshot.java +++ b/Mage.Sets/src/mage/cards/g/Grapeshot.java @@ -1,7 +1,5 @@ - package mage.cards.g; -import java.util.UUID; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.keyword.StormAbility; import mage.cards.CardImpl; @@ -9,19 +7,20 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.target.common.TargetAnyTarget; +import java.util.UUID; + /** - * * @author Plopman */ public final class Grapeshot extends CardImpl { public Grapeshot(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{R}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}"); // Grapeshot deals 1 damage to any target. this.getSpellAbility().addTarget(new TargetAnyTarget()); this.getSpellAbility().addEffect(new DamageTargetEffect(1)); + // Storm this.addAbility(new StormAbility()); } diff --git a/Mage.Sets/src/mage/cards/i/IcefallRegent.java b/Mage.Sets/src/mage/cards/i/IcefallRegent.java index 71e5d8e2670..582882559a8 100644 --- a/Mage.Sets/src/mage/cards/i/IcefallRegent.java +++ b/Mage.Sets/src/mage/cards/i/IcefallRegent.java @@ -2,17 +2,16 @@ package mage.cards.i; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.SpellAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.common.TapTargetEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellsCostModificationThatTargetSourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.FilterCard; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.events.GameEvent; @@ -20,7 +19,6 @@ import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; import mage.target.Target; import mage.target.common.TargetCreaturePermanent; -import mage.util.CardUtil; import mage.watchers.Watcher; import java.util.UUID; @@ -53,8 +51,9 @@ public final class IcefallRegent extends CardImpl { this.addAbility(ability, new IcefallRegentWatcher()); // Spells your opponents cast that target Icefall Regent cost {2} more to cast. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new IcefallRegentCostIncreaseEffect())); - + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new SpellsCostModificationThatTargetSourceEffect(2, new FilterCard("Spells"), TargetController.OPPONENT)) + ); } public IcefallRegent(final IcefallRegent card) { @@ -157,48 +156,3 @@ class IcefallRegentWatcher extends Watcher { //don't reset condition each turn - only when this leaves the battlefield } } - -class IcefallRegentCostIncreaseEffect extends CostModificationEffectImpl { - - - IcefallRegentCostIncreaseEffect() { - super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST); - staticText = "Spells your opponents cast that target {this} cost {2} more to cast"; - } - - private IcefallRegentCostIncreaseEffect(IcefallRegentCostIncreaseEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - SpellAbility spellAbility = (SpellAbility) abilityToModify; - CardUtil.adjustCost(spellAbility, -2); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (abilityToModify instanceof SpellAbility) { - if (game.getOpponents(source.getControllerId()).contains(abilityToModify.getControllerId())) { - for (UUID modeId : abilityToModify.getModes().getSelectedModes()) { - Mode mode = abilityToModify.getModes().get(modeId); - for (Target target : mode.getTargets()) { - for (UUID targetUUID : target.getTargets()) { - if (targetUUID.equals(source.getSourceId())) { - return true; - } - } - } - } - } - } - return false; - } - - @Override - public IcefallRegentCostIncreaseEffect copy() { - return new IcefallRegentCostIncreaseEffect(this); - } - -} diff --git a/Mage.Sets/src/mage/cards/j/JubilantSkybonder.java b/Mage.Sets/src/mage/cards/j/JubilantSkybonder.java index 3c82fe7b3eb..ee0d7622b27 100644 --- a/Mage.Sets/src/mage/cards/j/JubilantSkybonder.java +++ b/Mage.Sets/src/mage/cards/j/JubilantSkybonder.java @@ -2,24 +2,19 @@ package mage.cards.j; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.common.continuous.GainAbilityAllEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellsCostModificationThatTargetSourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.mageobject.AbilityPredicate; -import mage.game.Game; -import mage.target.Target; -import mage.util.CardUtil; -import java.util.Collection; import java.util.UUID; /** @@ -46,10 +41,12 @@ public final class JubilantSkybonder extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Creatures you control with flying have "Spells your opponents cast that target this creature cost {2} more to cast." - ContinuousEffect effect = new GainAbilityAllEffect( - new SimpleStaticAbility(new JubilantSkybonderEffect()), - Duration.WhileOnBattlefield, filter - ).withForceQuotes(); + Ability gainAbility = new SimpleStaticAbility(Zone.BATTLEFIELD, + new SpellsCostModificationThatTargetSourceEffect(2, new FilterCard("Spells"), TargetController.OPPONENT) + .withTargetName("this creature") + ); + + ContinuousEffect effect = new GainAbilityAllEffect(gainAbility, Duration.WhileOnBattlefield, filter).withForceQuotes(); effect.setDependedToType(DependencyType.AddingAbility); this.addAbility(new SimpleStaticAbility(effect)); } @@ -63,45 +60,3 @@ public final class JubilantSkybonder extends CardImpl { return new JubilantSkybonder(this); } } - -class JubilantSkybonderEffect extends CostModificationEffectImpl { - - JubilantSkybonderEffect() { - super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST); - staticText = "Spells your opponents cast that target this creature cost {2} more to cast"; - } - - private JubilantSkybonderEffect(JubilantSkybonderEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - SpellAbility spellAbility = (SpellAbility) abilityToModify; - CardUtil.adjustCost(spellAbility, -2); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (!(abilityToModify instanceof SpellAbility) - || !game.getOpponents(source.getControllerId()).contains(abilityToModify.getControllerId())) { - return false; - } - return abilityToModify - .getModes() - .getSelectedModes() - .stream() - .map(abilityToModify.getModes()::get) - .map(Mode::getTargets) - .flatMap(Collection::stream) - .map(Target::getTargets) - .flatMap(Collection::stream) - .anyMatch(source.getSourceId()::equals); - } - - @Override - public JubilantSkybonderEffect copy() { - return new JubilantSkybonderEffect(this); - } -} diff --git a/Mage.Sets/src/mage/cards/p/PursuedWhale.java b/Mage.Sets/src/mage/cards/p/PursuedWhale.java index fd9ca15ab87..d26a8f02d8c 100644 --- a/Mage.Sets/src/mage/cards/p/PursuedWhale.java +++ b/Mage.Sets/src/mage/cards/p/PursuedWhale.java @@ -2,22 +2,18 @@ package mage.cards.p; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.SpellAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellsCostModificationThatTargetSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; +import mage.filter.FilterCard; import mage.game.Game; import mage.game.permanent.token.PursuedWhaleToken; import mage.game.permanent.token.Token; -import mage.target.Target; -import mage.util.CardUtil; -import java.util.Collection; import java.util.UUID; /** @@ -36,7 +32,9 @@ public final class PursuedWhale extends CardImpl { this.addAbility(new EntersBattlefieldTriggeredAbility(new PursuedWhaleTokenEffect())); // Spells your opponents cast that target Pursued Whale cost {3} more to cast. - this.addAbility(new SimpleStaticAbility(new PursuedWhaleCostIncreaseEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new SpellsCostModificationThatTargetSourceEffect(3, new FilterCard("Spells"), TargetController.OPPONENT)) + ); } private PursuedWhale(final PursuedWhale card) { @@ -76,45 +74,3 @@ class PursuedWhaleTokenEffect extends OneShotEffect { return true; } } - -class PursuedWhaleCostIncreaseEffect extends CostModificationEffectImpl { - - PursuedWhaleCostIncreaseEffect() { - super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST); - staticText = "Spells your opponents cast that target {this} cost {3} more to cast"; - } - - private PursuedWhaleCostIncreaseEffect(PursuedWhaleCostIncreaseEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - SpellAbility spellAbility = (SpellAbility) abilityToModify; - CardUtil.adjustCost(spellAbility, -3); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (!(abilityToModify instanceof SpellAbility) - || !game.getOpponents(source.getControllerId()).contains(abilityToModify.getControllerId())) { - return false; - } - return abilityToModify - .getModes() - .getSelectedModes() - .stream() - .map(uuid -> abilityToModify.getModes().get(uuid)) - .map(Mode::getTargets) - .flatMap(Collection::stream) - .map(Target::getTargets) - .flatMap(Collection::stream) - .anyMatch(uuid -> uuid.equals(source.getSourceId())); - } - - @Override - public PursuedWhaleCostIncreaseEffect copy() { - return new PursuedWhaleCostIncreaseEffect(this); - } -} diff --git a/Mage.Sets/src/mage/cards/s/SphinxOfNewPrahv.java b/Mage.Sets/src/mage/cards/s/SphinxOfNewPrahv.java index 690a8c7f3b9..102695ce817 100644 --- a/Mage.Sets/src/mage/cards/s/SphinxOfNewPrahv.java +++ b/Mage.Sets/src/mage/cards/s/SphinxOfNewPrahv.java @@ -1,20 +1,17 @@ package mage.cards.s; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellsCostModificationThatTargetSourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.game.Game; -import mage.players.Player; -import mage.target.Target; -import mage.util.CardUtil; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.FilterCard; import java.util.UUID; @@ -37,7 +34,9 @@ public final class SphinxOfNewPrahv extends CardImpl { this.addAbility(VigilanceAbility.getInstance()); // Spells your opponents cast that target Sphinx of New Prahv cost {2} more to cast. - this.addAbility(new SimpleStaticAbility(new SphinxOfNewPrahvCostIncreaseEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new SpellsCostModificationThatTargetSourceEffect(2, new FilterCard("Spells"), TargetController.OPPONENT)) + ); } private SphinxOfNewPrahv(final SphinxOfNewPrahv card) { @@ -49,49 +48,3 @@ public final class SphinxOfNewPrahv extends CardImpl { return new SphinxOfNewPrahv(this); } } - -class SphinxOfNewPrahvCostIncreaseEffect extends CostModificationEffectImpl { - - SphinxOfNewPrahvCostIncreaseEffect() { - super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST); - staticText = "Spells your opponents cast that target {this} cost {2} more to cast"; - } - - private SphinxOfNewPrahvCostIncreaseEffect(SphinxOfNewPrahvCostIncreaseEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - SpellAbility spellAbility = (SpellAbility) abilityToModify; - CardUtil.adjustCost(spellAbility, -2); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null - || !(abilityToModify instanceof SpellAbility) - || !controller.hasOpponent(abilityToModify.getControllerId(), game)) { - return false; - } - for (UUID modeId : abilityToModify.getModes().getSelectedModes()) { - Mode mode = abilityToModify.getModes().get(modeId); - for (Target target : mode.getTargets()) { - for (UUID targetUUID : target.getTargets()) { - if (targetUUID.equals(source.getSourceId())) { - return true; - } - } - } - } - return false; - } - - @Override - public SphinxOfNewPrahvCostIncreaseEffect copy() { - return new SphinxOfNewPrahvCostIncreaseEffect(this); - } - -} diff --git a/Mage.Sets/src/mage/cards/s/SyrElenoraTheDiscerning.java b/Mage.Sets/src/mage/cards/s/SyrElenoraTheDiscerning.java index 0be67605193..558ccfb9096 100644 --- a/Mage.Sets/src/mage/cards/s/SyrElenoraTheDiscerning.java +++ b/Mage.Sets/src/mage/cards/s/SyrElenoraTheDiscerning.java @@ -1,23 +1,17 @@ package mage.cards.s; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.SpellAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.common.CardsInControllerHandCount; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.continuous.SetPowerSourceEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellsCostModificationThatTargetSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.game.Game; -import mage.target.Target; -import mage.util.CardUtil; +import mage.filter.FilterCard; -import java.util.Collection; import java.util.UUID; /** @@ -43,7 +37,9 @@ public final class SyrElenoraTheDiscerning extends CardImpl { this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1))); // Spells your opponents cast that target Syr Elenora cost {2} more to cast. - this.addAbility(new SimpleStaticAbility(new SyrElenoraTheDiscerningCostIncreaseEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new SpellsCostModificationThatTargetSourceEffect(2, new FilterCard("Spells"), TargetController.OPPONENT)) + ); } private SyrElenoraTheDiscerning(final SyrElenoraTheDiscerning card) { @@ -55,46 +51,3 @@ public final class SyrElenoraTheDiscerning extends CardImpl { return new SyrElenoraTheDiscerning(this); } } - -class SyrElenoraTheDiscerningCostIncreaseEffect extends CostModificationEffectImpl { - - SyrElenoraTheDiscerningCostIncreaseEffect() { - super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST); - staticText = "Spells your opponents cast that target {this} cost {2} more to cast"; - } - - private SyrElenoraTheDiscerningCostIncreaseEffect(SyrElenoraTheDiscerningCostIncreaseEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - SpellAbility spellAbility = (SpellAbility) abilityToModify; - CardUtil.adjustCost(spellAbility, -2); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if (!(abilityToModify instanceof SpellAbility) - || !game.getOpponents(source.getControllerId()).contains(abilityToModify.getControllerId())) { - return false; - } - return abilityToModify - .getModes() - .getSelectedModes() - .stream() - .map(uuid -> abilityToModify.getModes().get(uuid)) - .map(Mode::getTargets) - .flatMap(Collection::stream) - .map(Target::getTargets) - .flatMap(Collection::stream) - .anyMatch(uuid -> uuid.equals(source.getSourceId())); - } - - @Override - public SyrElenoraTheDiscerningCostIncreaseEffect copy() { - return new SyrElenoraTheDiscerningCostIncreaseEffect(this); - } - -} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/CostModificationTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/CostModificationTest.java index 40ffdc079f8..e7a6d7232de 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/CostModificationTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/CostModificationTest.java @@ -7,10 +7,7 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * - * also tests cost reduction effects - * - * @author BetaSteward + * @author BetaSteward, JayDi85 */ public class CostModificationTest extends CardTestPlayerBase { @@ -71,7 +68,7 @@ public class CostModificationTest extends CardTestPlayerBase { /** * Test that cost reduction also works with mana source restriction Myr * Superion Spend only mana produced by creatures to cast Myr Superion - * + *

* Etherium Sculptor {1}{U} Artifact Creature - Vedalken Artificer 1/2 * Artifact spells you cast cost {1} less to cast. */ @@ -143,7 +140,7 @@ public class CostModificationTest extends CardTestPlayerBase { } /* - * Reported bug: Grand Arbiter Augustin IV makes moth spells you cast and your opponent cast {1} more. Should only affect opponent's spells costs. + * Reported bug: Grand Arbiter Augustin IV makes moth spells you cast and your opponent cast {1} more. Should only affect opponent's spells costs. */ @Test public void testArbiterIncreasingCostBothPlayers() { @@ -244,6 +241,106 @@ public class CostModificationTest extends CardTestPlayerBase { assertCounterCount(playerA, "Animar, Soul of Elements", CounterType.P1P1, 2); assertTappedCount("Plains", true, 3); + } + @Test + public void test_ThatTargetSourceEffect_AccursedWitch_CanPlayWithReduction() { + // creature 4/2 + // Spells your opponents cast that target Accursed Witch cost {1} less to cast. + addCard(Zone.BATTLEFIELD, playerB, "Accursed Witch"); + // + // {1}{R} SORCERY + // Grapeshot deals 1 damage to any target. + addCard(Zone.HAND, playerA, "Grapeshot"); + addCard(Zone.HAND, playerA, "Mountain", 1); // play to add mana + + checkPlayableAbility("0 mana, can't", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grapeshot", false); + + // add 1 mana, can cast by target + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain"); + checkPlayableAbility("1 mana, can play", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grapeshot", true); + + // cast with target + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grapeshot", "Accursed Witch"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Grapeshot", 1); + } + + @Test + public void test_ThatTargetSourceEffect_AccursedWitch_CantPlayOnProtection() { + // creature 4/2 + // Spells your opponents cast that target Accursed Witch cost {1} less to cast. + addCard(Zone.BATTLEFIELD, playerB, "Accursed Witch"); + // + // {1}{R} SORCERY + // Grapeshot deals 1 damage to any target. + addCard(Zone.HAND, playerA, "Grapeshot"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + // Artifact — Equipment + // Equip {2} + // Equipped creature gets +2/+2 and has protection from red and from white. + addCard(Zone.BATTLEFIELD, playerB, "Sword of War and Peace"); + addCard(Zone.BATTLEFIELD, playerB, "Plains", 2); + + // 1 mana, can cast by target + checkPlayableAbility("1 mana, can play", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grapeshot", true); + + // add protection from red + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Equip {2}", "Accursed Witch"); + + // can't cast cause can't target to red + checkPlayableAbility("can't cast cause protection", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grapeshot", false); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_ThatTargetSourceEffect_BorealElemental() { + // use case: cost increase for getPlayable works only for no other targets available + // so if you can targets another target then allows to cast (don't apply cost increase) + + // creature 3/4 + // Spells your opponents cast that target Boreal Elemental cost {2} more to cast. + addCard(Zone.BATTLEFIELD, playerB, "Boreal Elemental"); + // + // {R} instant + // Engulfing Flames deals 1 damage to target creature. It can't be regenerated this turn. + addCard(Zone.HAND, playerA, "Engulfing Flames"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + addCard(Zone.HAND, playerA, "Grizzly Bears"); // {1}{G} + addCard(Zone.HAND, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + + // no second target, so must cost increase + checkPlayableAbility("one target, can't play", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Engulfing Flames", false); + + // prepare second target + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Forest"); + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 2); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPlayableAbility("two targets, can play", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Engulfing Flames", true); + + // try to cast (only one target possible to targets/play) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Engulfing Flames"); + //addTarget(playerA, "Boreal Elemental"); // you can't target Boreal Elemental cause it will increase cost + addTarget(playerA, "Grizzly Bears"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, "Engulfing Flames", 1); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 7e424803a8c..f8d33b3ffbb 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -1181,6 +1181,8 @@ public class TestPlayer implements Player { } if (mustHave && !founded) { + printStart("Available mana for " + computerPlayer.getName()); + printMana(game, computerPlayer.getManaAvailable(game)); printStart(action.getActionName()); printAbilities(game, computerPlayer.getPlayable(game, true)); printEnd(); @@ -1188,6 +1190,8 @@ public class TestPlayer implements Player { } if (!mustHave && founded) { + printStart("Available mana for " + computerPlayer.getName()); + printMana(game, computerPlayer.getManaAvailable(game)); printStart(action.getActionName()); printAbilities(game, computerPlayer.getPlayable(game, true)); printEnd(); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 5f1512e321f..fe26af024e6 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -769,7 +769,7 @@ public abstract class AbilityImpl implements Ability { if (ruleStart.length() > 1) { String end = ruleStart.substring(ruleStart.length() - 2).trim(); if (end.isEmpty() || end.equals(":") || end.equals(".")) { - rule = ruleStart + Character.toUpperCase(text.charAt(0)) + text.substring(1); + rule = ruleStart + CardUtil.getTextWithFirstCharUpperCase(text); } else { rule = ruleStart + text; } diff --git a/Mage/src/main/java/mage/abilities/effects/Effects.java b/Mage/src/main/java/mage/abilities/effects/Effects.java index 38c798b30fd..42698bba846 100644 --- a/Mage/src/main/java/mage/abilities/effects/Effects.java +++ b/Mage/src/main/java/mage/abilities/effects/Effects.java @@ -4,6 +4,7 @@ import mage.abilities.Ability; import mage.abilities.Mode; import mage.constants.Outcome; import mage.target.targetpointer.TargetPointer; +import mage.util.CardUtil; import java.util.ArrayList; @@ -31,7 +32,7 @@ public class Effects extends ArrayList { public String getTextStartingUpperCase(Mode mode) { String text = getText(mode); if (text.length() > 3) { - return Character.toUpperCase(text.charAt(0)) + text.substring(1); + return CardUtil.getTextWithFirstCharUpperCase(text); } return text; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostModificationThatTargetSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostModificationThatTargetSourceEffect.java new file mode 100644 index 00000000000..de525432ba5 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostModificationThatTargetSourceEffect.java @@ -0,0 +1,166 @@ +package mage.abilities.effects.common.cost; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.SpellAbility; +import mage.constants.CostModificationType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.Target; +import mage.util.CardUtil; + +import java.util.Collection; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author JayDi85 + */ +public class SpellsCostModificationThatTargetSourceEffect extends CostModificationEffectImpl { + + private final FilterCard spellFilter; + private final int modificationAmount; + private String targetName = "{this}"; + private TargetController targetController; + + public SpellsCostModificationThatTargetSourceEffect(int modificationAmount, FilterCard spellFilter, TargetController targetController) { + super(Duration.WhileOnBattlefield, Outcome.Neutral, modificationAmount < 0 ? CostModificationType.REDUCE_COST : CostModificationType.INCREASE_COST); + this.spellFilter = spellFilter; + this.modificationAmount = modificationAmount; + this.targetController = targetController; + + setText(); + } + + private void setText() { + // example: Spells your opponents cast that target Accursed Witch cost {1} less to cast. + StringBuilder sb = new StringBuilder(); + sb.append(this.spellFilter.getMessage()); + switch (this.targetController) { + case ANY: + break; + case YOU: + sb.append(" you"); + break; + case OPPONENT: + sb.append(" your opponents"); + break; + default: + throw new IllegalArgumentException("Unsupported target controller " + this.targetController); + } + + sb.append(" cast that target ").append(this.targetName); + if (this.modificationAmount < 0) { + sb.append(" cost {").append(-1 * this.modificationAmount).append("} less to cast"); + } else { + sb.append(" cost {").append(this.modificationAmount).append("} more to cast"); + } + this.staticText = sb.toString(); + } + + private SpellsCostModificationThatTargetSourceEffect(SpellsCostModificationThatTargetSourceEffect effect) { + super(effect); + this.spellFilter = effect.spellFilter; + this.modificationAmount = effect.modificationAmount; + this.targetName = effect.targetName; + this.targetController = effect.targetController; + } + + @Override + public SpellsCostModificationThatTargetSourceEffect copy() { + return new SpellsCostModificationThatTargetSourceEffect(this); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + if (this.modificationAmount >= 0) { + CardUtil.increaseCost(abilityToModify, this.modificationAmount); + } else { + CardUtil.reduceCost(abilityToModify, -1 * this.modificationAmount); + } + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + if (!(abilityToModify instanceof SpellAbility)) { + return false; + } + + Player sourceController = game.getPlayer(source.getControllerId()); + Player abilityController = game.getPlayer(abilityToModify.getControllerId()); + if (sourceController == null || abilityController == null) { + return false; + } + + switch (this.targetController) { + case ANY: + break; + case YOU: + if (!sourceController.getId().equals(abilityController.getId())) { + return false; + } + break; + case OPPONENT: + if (!sourceController.hasOpponent(abilityController.getId(), game)) { + return false; + } + break; + default: + return false; + } + + Spell spell = (Spell) game.getStack().getStackObject(abilityToModify.getId()); + if (spell != null && this.spellFilter.match(spell, game)) { + // real cast with put on stack + Set allTargets = getAllSelectedTargets(abilityToModify, source, game); + return allTargets.contains(source.getSourceId()); + } else { + // get playable and other staff without put on stack + // used at least for flashback ability because Flashback ability doesn't use stack + Set allTargets = getAllPossibleTargets(abilityToModify, source, game); + switch (this.getModificationType()) { + case REDUCE_COST: + // reduce all the time + return allTargets.contains(source.getSourceId()); + case INCREASE_COST: + // increase if can't target another (e.g. user can choose another target without cost increase) + return allTargets.contains(source.getSourceId()) && allTargets.size() <= 1; + } + } + return false; + } + + private Set getAllSelectedTargets(Ability abilityToModify, Ability source, Game game) { + return abilityToModify.getModes().getSelectedModes() + .stream() + .map(abilityToModify.getModes()::get) + .map(Mode::getTargets) + .flatMap(Collection::stream) + .map(Target::getTargets) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + } + + private Set getAllPossibleTargets(Ability abilityToModify, Ability source, Game game) { + return abilityToModify.getModes().values() + .stream() + .map(Mode::getTargets) + .flatMap(Collection::stream) + .map(t -> t.possibleTargets(abilityToModify.getSourceId(), abilityToModify.getControllerId(), game)) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + } + + public SpellsCostModificationThatTargetSourceEffect withTargetName(String targetName) { + this.targetName = targetName; + setText(); + return this; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionAllEffect.java index 3c79a578252..5b1dd4811cd 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionAllEffect.java @@ -1,8 +1,5 @@ package mage.abilities.effects.common.cost; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.UUID; import mage.Mana; import mage.abilities.Ability; import mage.abilities.SpellAbility; @@ -17,8 +14,11 @@ import mage.game.stack.Spell; import mage.players.Player; import mage.util.CardUtil; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author LevelX2 */ public class SpellsCostReductionAllEffect extends CostModificationEffectImpl { @@ -125,8 +125,10 @@ public class SpellsCostReductionAllEffect extends CostModificationEffectImpl { if (abilityToModify instanceof SpellAbility) { Spell spell = (Spell) game.getStack().getStackObject(abilityToModify.getId()); if (spell != null) { + // real cast with put on stack return this.filter.match(spell, game) && selectedByRuntimeData(spell, source, game); } else { + // get playable and other staff without put on stack // used at least for flashback ability because Flashback ability doesn't use stack Card sourceCard = game.getCard(abilityToModify.getSourceId()); return sourceCard != null && this.filter.match(sourceCard, game) && selectedByRuntimeData(sourceCard, source, game); diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionControllerEffect.java index 1fd38caa062..7fcab94aaf9 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionControllerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionControllerEffect.java @@ -1,7 +1,5 @@ package mage.abilities.effects.common.cost; -import java.util.LinkedHashSet; -import java.util.Set; import mage.MageObject; import mage.Mana; import mage.abilities.Ability; @@ -19,8 +17,10 @@ import mage.game.stack.Spell; import mage.players.Player; import mage.util.CardUtil; +import java.util.LinkedHashSet; +import java.util.Set; + /** - * * @author North */ public class SpellsCostReductionControllerEffect extends CostModificationEffectImpl { @@ -116,9 +116,11 @@ public class SpellsCostReductionControllerEffect extends CostModificationEffectI if (abilityToModify.isControlledBy(source.getControllerId())) { Spell spell = (Spell) game.getStack().getStackObject(abilityToModify.getId()); if (spell != null) { + // real cast with put on stack return this.filter.match(spell, source.getSourceId(), source.getControllerId(), game); } else { - // used at least for flashback ability because Flashback ability doesn't use stack or for getPlayables where spell is not cast yet + // get playable and other staff without put on stack + // used at least for flashback ability because Flashback ability doesn't use stack Card sourceCard = game.getCard(abilityToModify.getSourceId()); return sourceCard != null && this.filter.match(sourceCard, source.getSourceId(), source.getControllerId(), game); } diff --git a/Mage/src/main/java/mage/abilities/hint/ConditionHint.java b/Mage/src/main/java/mage/abilities/hint/ConditionHint.java index 4df8b1cf593..6347054b2f1 100644 --- a/Mage/src/main/java/mage/abilities/hint/ConditionHint.java +++ b/Mage/src/main/java/mage/abilities/hint/ConditionHint.java @@ -3,6 +3,7 @@ package mage.abilities.hint; import mage.abilities.Ability; import mage.abilities.condition.Condition; import mage.game.Game; +import mage.util.CardUtil; import java.awt.*; @@ -18,15 +19,19 @@ public class ConditionHint implements Hint { private Color falseColor; private Boolean useIcons; + public ConditionHint(Condition condition) { + this(condition, condition.toString()); + } + public ConditionHint(Condition condition, String textWithIcons) { this(condition, textWithIcons, null, textWithIcons, null, true); } public ConditionHint(Condition condition, String trueText, Color trueColor, String falseText, Color falseColor, Boolean useIcons) { this.condition = condition; - this.trueText = trueText; + this.trueText = CardUtil.getTextWithFirstCharUpperCase(trueText); this.trueColor = trueColor; - this.falseText = falseText; + this.falseText = CardUtil.getTextWithFirstCharUpperCase(falseText); this.falseColor = falseColor; this.useIcons = useIcons; } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 6836eab7165..e80259326d2 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -800,4 +800,12 @@ public final class CardUtil { return object.getAbilities(); } } + + public static String getTextWithFirstCharUpperCase(String text) { + if (text != null && text.length() >= 1) { + return Character.toUpperCase(text.charAt(0)) + text.substring(1); + } else { + return text; + } + } }