From cf3feff76a247689ae5425eddfa75a20a273e888 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 29 Jun 2020 12:52:14 +0400 Subject: [PATCH] Cost reduction effects - refactor, removed redundant custom effects, added card hints; --- .../src/mage/cards/a/AncientStoneIdol.java | 63 ++------ .../src/mage/cards/a/AvatarOfGrowth.java | 6 +- Mage.Sets/src/mage/cards/b/BedlamReveler.java | 21 ++- Mage.Sets/src/mage/cards/b/BrineGiant.java | 55 ++----- .../src/mage/cards/c/CrypticSerpent.java | 13 +- Mage.Sets/src/mage/cards/d/DreamLeash.java | 11 +- Mage.Sets/src/mage/cards/e/Embercleave.java | 55 ++----- .../src/mage/cards/e/EmryLurkerOfTheLoch.java | 44 +----- .../src/mage/cards/f/FoulTongueShriek.java | 21 +-- Mage.Sets/src/mage/cards/g/GateColossus.java | 8 +- .../src/mage/cards/g/GatekeeperGargoyle.java | 2 +- .../src/mage/cards/g/GearseekerSerpent.java | 52 ++----- Mage.Sets/src/mage/cards/g/Ghoultree.java | 16 +- .../mage/cards/g/GlaiveOfTheGuildpact.java | 5 +- Mage.Sets/src/mage/cards/h/HoldTheGates.java | 11 +- Mage.Sets/src/mage/cards/k/KhalniHydra.java | 71 +++------ Mage.Sets/src/mage/cards/m/Molderhulk.java | 16 +- .../src/mage/cards/n/NemesisOfMortals.java | 12 +- .../src/mage/cards/o/OreScaleGuardian.java | 11 +- .../mage/cards/t/TheCauldronOfEternity.java | 63 ++------ .../src/mage/cards/t/TheCircleOfLoyalty.java | 61 +++----- .../src/mage/cards/t/TheMagicMirror.java | 15 +- .../mage/cards/t/TorgaarFamineIncarnate.java | 25 ++-- .../modification/CostReduceForEachTest.java | 140 ++++++++++++++++++ .../base/impl/CardTestPlayerAPIImpl.java | 6 + .../common/AttackingCreatureCount.java | 28 +++- .../common/AttackingFilterCreatureCount.java | 65 -------- .../common/GateYouControlCount.java | 4 +- .../continuous/BoostControlledEffect.java | 30 ++-- .../cost/CostModificationEffectImpl.java | 6 + ...ReductionForEachCardInGraveyardEffect.java | 59 -------- ...SpellCostReductionForEachSourceEffect.java | 93 ++++++++++++ .../cost/SpellCostReductionSourceEffect.java | 5 +- ...CostReductionSourceForOpponentsEffect.java | 42 ------ .../abilities/keyword/UndauntedAbility.java | 14 +- 35 files changed, 506 insertions(+), 643 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/CostReduceForEachTest.java delete mode 100644 Mage/src/main/java/mage/abilities/dynamicvalue/common/AttackingFilterCreatureCount.java delete mode 100644 Mage/src/main/java/mage/abilities/effects/common/cost/SourceCostReductionForEachCardInGraveyardEffect.java create mode 100644 Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionForEachSourceEffect.java delete mode 100644 Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionSourceForOpponentsEffect.java diff --git a/Mage.Sets/src/mage/cards/a/AncientStoneIdol.java b/Mage.Sets/src/mage/cards/a/AncientStoneIdol.java index bc7ddf8dac..499c85e265 100644 --- a/Mage.Sets/src/mage/cards/a/AncientStoneIdol.java +++ b/Mage.Sets/src/mage/cards/a/AncientStoneIdol.java @@ -1,31 +1,25 @@ package mage.cards.a; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.common.DiesTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.AttackingCreatureCount; import mage.abilities.effects.common.CreateTokenEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; -import mage.constants.SubType; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.ValueHint; import mage.abilities.keyword.FlashAbility; import mage.abilities.keyword.TrampleAbility; 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.common.FilterCreaturePermanent; -import mage.filter.predicate.permanent.AttackingPredicate; -import mage.game.Game; import mage.game.permanent.token.StoneTrapIdolToken; -import mage.util.CardUtil; + +import java.util.UUID; /** - * * @author TheElk801 */ public final class AncientStoneIdol extends CardImpl { @@ -41,7 +35,10 @@ public final class AncientStoneIdol extends CardImpl { this.addAbility(FlashAbility.getInstance()); // This spell costs {1} less to cast for each attacking creature. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new AncientStoneIdolCostReductionEffect())); + DynamicValue xValue = new AttackingCreatureCount(); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue)) + .addHint(new ValueHint("Attacking creatures", xValue)) + ); // Trample this.addAbility(TrampleAbility.getInstance()); @@ -59,41 +56,3 @@ public final class AncientStoneIdol extends CardImpl { return new AncientStoneIdol(this); } } - -class AncientStoneIdolCostReductionEffect extends CostModificationEffectImpl { - - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); - - static { - filter.add(AttackingPredicate.instance); - } - - public AncientStoneIdolCostReductionEffect() { - super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST); - staticText = "This spell costs {1} less to cast for each attacking creature"; - } - - protected AncientStoneIdolCostReductionEffect(AncientStoneIdolCostReductionEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - int reductionAmount = game.getBattlefield().count(filter, source.getSourceId(), source.getControllerId(), game); - CardUtil.reduceCost(abilityToModify, reductionAmount); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if ((abilityToModify instanceof SpellAbility) && abilityToModify.getSourceId().equals(source.getSourceId())) { - return game.getCard(abilityToModify.getSourceId()) != null; - } - return false; - } - - @Override - public AncientStoneIdolCostReductionEffect copy() { - return new AncientStoneIdolCostReductionEffect(this); - } -} diff --git a/Mage.Sets/src/mage/cards/a/AvatarOfGrowth.java b/Mage.Sets/src/mage/cards/a/AvatarOfGrowth.java index cd41b415dd..0bee9d45c8 100644 --- a/Mage.Sets/src/mage/cards/a/AvatarOfGrowth.java +++ b/Mage.Sets/src/mage/cards/a/AvatarOfGrowth.java @@ -4,8 +4,9 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.OpponentsCount; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.cost.SpellCostReductionSourceForOpponentsEffect; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -35,7 +36,8 @@ public final class AvatarOfGrowth extends CardImpl { this.toughness = new MageInt(4); // This spell costs {1} less to cast for each opponent you have. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SpellCostReductionSourceForOpponentsEffect("This spell costs {1} less to cast for each opponent you have"))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SpellCostReductionForEachSourceEffect(1, OpponentsCount.instance) + .setText("This spell costs {1} less to cast for each opponent you have"))); // Trample this.addAbility(TrampleAbility.getInstance()); diff --git a/Mage.Sets/src/mage/cards/b/BedlamReveler.java b/Mage.Sets/src/mage/cards/b/BedlamReveler.java index 6f6175f7cc..5e4a2b29cc 100644 --- a/Mage.Sets/src/mage/cards/b/BedlamReveler.java +++ b/Mage.Sets/src/mage/cards/b/BedlamReveler.java @@ -1,4 +1,3 @@ - package mage.cards.b; import mage.MageInt; @@ -8,7 +7,7 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.abilities.effects.common.cost.SourceCostReductionForEachCardInGraveyardEffect; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.effects.common.discard.DiscardHandControllerEffect; import mage.abilities.hint.ValueHint; import mage.abilities.keyword.ProwessAbility; @@ -26,27 +25,25 @@ import java.util.UUID; */ public final class BedlamReveler extends CardImpl { - private static final DynamicValue cardsCount = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY); - public BedlamReveler(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{R}{R}"); this.subtype.add(SubType.DEVIL, SubType.HORROR); this.power = new MageInt(3); this.toughness = new MageInt(4); - // Bedlam Reveler costs {1} less to cast for each instant or sorcery card in your graveyard. - this.addAbility(new SimpleStaticAbility( - Zone.ALL, new SourceCostReductionForEachCardInGraveyardEffect(StaticFilters.FILTER_CARD_INSTANT_AND_SORCERY) - ).addHint(new ValueHint("Instant and sorcery cards in your graveyard", cardsCount))); + // This spell costs {1} less to cast for each instant and sorcery card in your graveyard. + DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY); + Ability ability = new SimpleStaticAbility(Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue)); + ability.setRuleAtTheTop(true); + ability.addHint(new ValueHint("Instant or sourcery card in your graveyard", xValue)); + this.addAbility(ability); // Prowess this.addAbility(new ProwessAbility()); // When Bedlam Reveler enters the battlefield, discard your hand, then draw three cards. - Ability ability = new EntersBattlefieldTriggeredAbility( - new DiscardHandControllerEffect().setText("discard your hand,") - ); - ability.addEffect(new DrawCardSourceControllerEffect(3).setText("then draw three cards")); + ability = new EntersBattlefieldTriggeredAbility(new DiscardHandControllerEffect()); + ability.addEffect(new DrawCardSourceControllerEffect(3).concatBy(", then")); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/b/BrineGiant.java b/Mage.Sets/src/mage/cards/b/BrineGiant.java index 9b02061df9..98d9ca61f4 100644 --- a/Mage.Sets/src/mage/cards/b/BrineGiant.java +++ b/Mage.Sets/src/mage/cards/b/BrineGiant.java @@ -1,18 +1,17 @@ package mage.cards.b; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.hint.ValueHint; 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.filter.common.FilterControlledPermanent; -import mage.game.Game; -import mage.util.CardUtil; import java.util.UUID; @@ -21,8 +20,13 @@ import java.util.UUID; */ public final class BrineGiant extends CardImpl { - private static final DynamicValue xValue - = new PermanentsOnBattlefieldCount(BrineGiantCostReductionEffect.filter); + static final FilterControlledPermanent filter = new FilterControlledPermanent("enchantment you control"); + + static { + filter.add(CardType.ENCHANTMENT.getPredicate()); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); public BrineGiant(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{U}"); @@ -33,7 +37,7 @@ public final class BrineGiant extends CardImpl { // This spell costs {1} less to cast for each enchantment you control. this.addAbility(new SimpleStaticAbility( - Zone.ALL, new BrineGiantCostReductionEffect() + Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue) ).addHint(new ValueHint("Enchantments you control", xValue))); } @@ -45,39 +49,4 @@ public final class BrineGiant extends CardImpl { public BrineGiant copy() { return new BrineGiant(this); } -} - -class BrineGiantCostReductionEffect extends CostModificationEffectImpl { - - static final FilterControlledPermanent filter = new FilterControlledPermanent(); - - static { - filter.add(CardType.ENCHANTMENT.getPredicate()); - } - - BrineGiantCostReductionEffect() { - super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST); - staticText = "This spell costs {1} less to cast for each enchantment you control"; - } - - private BrineGiantCostReductionEffect(final BrineGiantCostReductionEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - int count = game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), game).size(); - CardUtil.reduceCost(abilityToModify, count); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - return abilityToModify.getSourceId().equals(source.getSourceId()); - } - - @Override - public BrineGiantCostReductionEffect copy() { - return new BrineGiantCostReductionEffect(this); - } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/c/CrypticSerpent.java b/Mage.Sets/src/mage/cards/c/CrypticSerpent.java index fb28357301..bf49861457 100644 --- a/Mage.Sets/src/mage/cards/c/CrypticSerpent.java +++ b/Mage.Sets/src/mage/cards/c/CrypticSerpent.java @@ -1,10 +1,11 @@ package mage.cards.c; import mage.MageInt; +import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; -import mage.abilities.effects.common.cost.SourceCostReductionForEachCardInGraveyardEffect; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -12,7 +13,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.StaticFilters; -import mage.filter.common.FilterInstantOrSorceryCard; import java.util.UUID; @@ -21,8 +21,6 @@ import java.util.UUID; */ public final class CrypticSerpent extends CardImpl { - private static final DynamicValue cardsCount = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY); - public CrypticSerpent(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}{U}"); @@ -31,8 +29,11 @@ public final class CrypticSerpent extends CardImpl { this.toughness = new MageInt(5); // Cryptic Serpent costs {1} less to cast for each instant and sorcery card in your graveyard. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SourceCostReductionForEachCardInGraveyardEffect(new FilterInstantOrSorceryCard())) - .addHint(new ValueHint("Instant and sorcery card in your graveyard", cardsCount))); + DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_INSTANT_AND_SORCERY); + Ability ability = new SimpleStaticAbility(Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue)); + ability.setRuleAtTheTop(true); + ability.addHint(new ValueHint("Instant and sorcery card in your graveyard", xValue)); + this.addAbility(ability); } public CrypticSerpent(final CrypticSerpent card) { diff --git a/Mage.Sets/src/mage/cards/d/DreamLeash.java b/Mage.Sets/src/mage/cards/d/DreamLeash.java index bccc18cffd..8fd1f3da98 100644 --- a/Mage.Sets/src/mage/cards/d/DreamLeash.java +++ b/Mage.Sets/src/mage/cards/d/DreamLeash.java @@ -1,7 +1,5 @@ - package mage.cards.d; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; @@ -10,21 +8,22 @@ import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Outcome; +import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; +import java.util.UUID; + /** - * * @author maxlebedev */ public final class DreamLeash extends CardImpl { public DreamLeash(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{3}{U}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{U}{U}"); this.subtype.add(SubType.AURA); // Enchant permanent @@ -55,7 +54,7 @@ class DreamLeashTarget extends TargetPermanent { @Override public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if(super.canTarget(controllerId, id, source, game)){ + if (super.canTarget(controllerId, id, source, game)) { Permanent permanent = game.getPermanent(id); return permanent.isTapped(); } diff --git a/Mage.Sets/src/mage/cards/e/Embercleave.java b/Mage.Sets/src/mage/cards/e/Embercleave.java index 2c0d2baa7d..35851640f0 100644 --- a/Mage.Sets/src/mage/cards/e/Embercleave.java +++ b/Mage.Sets/src/mage/cards/e/Embercleave.java @@ -1,13 +1,15 @@ package mage.cards.e; import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.AttackingCreatureCount; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.continuous.BoostEquippedEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.ValueHint; import mage.abilities.keyword.DoubleStrikeAbility; import mage.abilities.keyword.EquipAbility; import mage.abilities.keyword.FlashAbility; @@ -15,12 +17,8 @@ import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.predicate.permanent.AttackingPredicate; -import mage.game.Game; +import mage.filter.StaticFilters; import mage.target.common.TargetControlledCreaturePermanent; -import mage.util.CardUtil; import java.util.UUID; @@ -39,7 +37,10 @@ public final class Embercleave extends CardImpl { this.addAbility(FlashAbility.getInstance()); // This spell costs {1} less to cast for each attacking creature you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new EmbercleaveCostReductionEffect())); + DynamicValue xValue = new AttackingCreatureCount(StaticFilters.FILTER_PERMANENT_CREATURE_CONTROLLED); + this.addAbility(new SimpleStaticAbility(Zone.ALL, + new SpellCostReductionForEachSourceEffect(1, xValue) + ).addHint(new ValueHint("Attacking creature you control", xValue))); // When Embercleave enters the battlefield, attach it to target creature you control. Ability ability = new EntersBattlefieldTriggeredAbility(new AttachEffect( @@ -71,41 +72,3 @@ public final class Embercleave extends CardImpl { return new Embercleave(this); } } - -class EmbercleaveCostReductionEffect extends CostModificationEffectImpl { - - private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); - - static { - filter.add(AttackingPredicate.instance); - } - - EmbercleaveCostReductionEffect() { - super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST); - staticText = "This spell costs {1} less to cast for each attacking creature you control"; - } - - private EmbercleaveCostReductionEffect(EmbercleaveCostReductionEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - int reductionAmount = game.getBattlefield().count(filter, source.getSourceId(), source.getControllerId(), game); - CardUtil.reduceCost(abilityToModify, reductionAmount); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if ((abilityToModify instanceof SpellAbility) && abilityToModify.getSourceId().equals(source.getSourceId())) { - return game.getCard(abilityToModify.getSourceId()) != null; - } - return false; - } - - @Override - public EmbercleaveCostReductionEffect copy() { - return new EmbercleaveCostReductionEffect(this); - } -} diff --git a/Mage.Sets/src/mage/cards/e/EmryLurkerOfTheLoch.java b/Mage.Sets/src/mage/cards/e/EmryLurkerOfTheLoch.java index b1cc1188f4..bebd901a61 100644 --- a/Mage.Sets/src/mage/cards/e/EmryLurkerOfTheLoch.java +++ b/Mage.Sets/src/mage/cards/e/EmryLurkerOfTheLoch.java @@ -2,21 +2,21 @@ package mage.cards.e; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.common.ArtifactYouControlCount; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.common.PutTopCardOfLibraryIntoGraveControllerEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.common.ArtifactYouControlHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.StaticFilters; import mage.game.Game; import mage.target.common.TargetCardInYourGraveyard; -import mage.util.CardUtil; import java.util.UUID; @@ -35,7 +35,9 @@ public final class EmryLurkerOfTheLoch extends CardImpl { this.toughness = new MageInt(2); // This spell costs {1} less to cast for each artifact you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new EmryLurkerOfTheLochCostReductionEffect())); + this.addAbility(new SimpleStaticAbility(Zone.ALL, + new SpellCostReductionForEachSourceEffect(1, ArtifactYouControlCount.instance) + ).addHint(ArtifactYouControlHint.instance)); // When Emry, Lurker of the Loch enters the battlefield, put the top four cards of your library into your graveyard. this.addAbility(new EntersBattlefieldTriggeredAbility( @@ -58,40 +60,6 @@ public final class EmryLurkerOfTheLoch extends CardImpl { } } -class EmryLurkerOfTheLochCostReductionEffect extends CostModificationEffectImpl { - - EmryLurkerOfTheLochCostReductionEffect() { - super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST); - staticText = "This spell costs {1} less to cast for each artifact you control"; - } - - private EmryLurkerOfTheLochCostReductionEffect(final EmryLurkerOfTheLochCostReductionEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - int reductionAmount = game.getBattlefield().count( - StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT, - source.getSourceId(), source.getControllerId(), game - ); - CardUtil.reduceCost(abilityToModify, reductionAmount); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - return abilityToModify instanceof SpellAbility - && abilityToModify.getSourceId().equals(source.getSourceId()) - && game.getCard(abilityToModify.getSourceId()) != null; - } - - @Override - public EmryLurkerOfTheLochCostReductionEffect copy() { - return new EmryLurkerOfTheLochCostReductionEffect(this); - } -} - class EmryLurkerOfTheLochPlayEffect extends AsThoughEffectImpl { EmryLurkerOfTheLochPlayEffect() { diff --git a/Mage.Sets/src/mage/cards/f/FoulTongueShriek.java b/Mage.Sets/src/mage/cards/f/FoulTongueShriek.java index fda2829170..725a760e57 100644 --- a/Mage.Sets/src/mage/cards/f/FoulTongueShriek.java +++ b/Mage.Sets/src/mage/cards/f/FoulTongueShriek.java @@ -1,28 +1,26 @@ - package mage.cards.f; -import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.dynamicvalue.common.AttackingFilterCreatureCount; +import mage.abilities.dynamicvalue.common.AttackingCreatureCount; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.constants.TargetController; -import mage.filter.common.FilterCreaturePermanent; +import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class FoulTongueShriek extends CardImpl { public FoulTongueShriek(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{B}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{B}"); // Target opponent loses 1 life for each attacking creature you control. You gain that much life. this.getSpellAbility().addEffect(new FoulTongueShriekEffect()); @@ -41,12 +39,7 @@ public final class FoulTongueShriek extends CardImpl { } class FoulTongueShriekEffect extends OneShotEffect { - - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); - static { - filter.add(TargetController.YOU.getControllerPredicate()); - } - + public FoulTongueShriekEffect() { super(Outcome.Benefit); this.staticText = "Target opponent loses 1 life for each attacking creature you control. You gain that much life"; @@ -66,7 +59,7 @@ class FoulTongueShriekEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); Player targetOpponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller != null && targetOpponent != null) { - int amount = new AttackingFilterCreatureCount(filter).calculate(game, source, this); + int amount = new AttackingCreatureCount(StaticFilters.FILTER_PERMANENT_CREATURES_CONTROLLED).calculate(game, source, this); if (amount > 0) { targetOpponent.loseLife(amount, game, false); controller.gainLife(amount, game, source); diff --git a/Mage.Sets/src/mage/cards/g/GateColossus.java b/Mage.Sets/src/mage/cards/g/GateColossus.java index e2387b7ccd..000c9e2692 100644 --- a/Mage.Sets/src/mage/cards/g/GateColossus.java +++ b/Mage.Sets/src/mage/cards/g/GateColossus.java @@ -5,9 +5,11 @@ import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; import mage.abilities.common.SimpleEvasionAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.GateYouControlCount; import mage.abilities.effects.common.PutOnLibrarySourceEffect; import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesSourceEffect; import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.hint.common.GateYouControlHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -39,8 +41,10 @@ public final class GateColossus extends CardImpl { this.toughness = new MageInt(8); // This spell costs {1} less to cast for each Gate you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new GateColossusCostReductionEffect()) - .addHint(GateYouControlHint.instance)); + this.addAbility(new SimpleStaticAbility(Zone.ALL, + new SpellCostReductionForEachSourceEffect(1, GateYouControlCount.instance)) + .addHint(GateYouControlHint.instance) + ); // Gate Colossus can't be blocked by creatures with power 2 or less. this.addAbility(new SimpleEvasionAbility(new CantBeBlockedByCreaturesSourceEffect(filter, Duration.WhileOnBattlefield))); diff --git a/Mage.Sets/src/mage/cards/g/GatekeeperGargoyle.java b/Mage.Sets/src/mage/cards/g/GatekeeperGargoyle.java index 43a3218777..b112771820 100644 --- a/Mage.Sets/src/mage/cards/g/GatekeeperGargoyle.java +++ b/Mage.Sets/src/mage/cards/g/GatekeeperGargoyle.java @@ -29,7 +29,7 @@ public final class GatekeeperGargoyle extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); - // Gargoyle Guardian enters the battlefield with a +1/+1 counter on it for each Gate you control. + // Gatekeeper Gargoyle enters the battlefield with a +1/+1 counter on it for each Gate you control. this.addAbility(new EntersBattlefieldAbility( new AddCountersSourceEffect( CounterType.P1P1.createInstance(), diff --git a/Mage.Sets/src/mage/cards/g/GearseekerSerpent.java b/Mage.Sets/src/mage/cards/g/GearseekerSerpent.java index 6d0991c962..8b955be607 100644 --- a/Mage.Sets/src/mage/cards/g/GearseekerSerpent.java +++ b/Mage.Sets/src/mage/cards/g/GearseekerSerpent.java @@ -1,18 +1,19 @@ package mage.cards.g; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.ArtifactYouControlCount; import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.common.ArtifactYouControlHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.filter.common.FilterControlledPermanent; -import mage.game.Game; -import mage.util.CardUtil; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.Zone; import java.util.UUID; @@ -28,7 +29,9 @@ public final class GearseekerSerpent extends CardImpl { this.toughness = new MageInt(6); // Gearseeker Serpent costs {1} less to cast for each artifact you control - this.addAbility(new SimpleStaticAbility(Zone.ALL, new GearseekerSerpentCostReductionEffect())); + this.addAbility(new SimpleStaticAbility(Zone.ALL, + new SpellCostReductionForEachSourceEffect(1, ArtifactYouControlCount.instance) + ).addHint(ArtifactYouControlHint.instance)); // 5U: Gearseeker Serpent can't be blocked this turn. this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, @@ -45,38 +48,3 @@ public final class GearseekerSerpent extends CardImpl { return new GearseekerSerpent(this); } } - -class GearseekerSerpentCostReductionEffect extends CostModificationEffectImpl { - - private static final FilterControlledPermanent filter = new FilterControlledPermanent(); - - static { - filter.add(CardType.ARTIFACT.getPredicate()); - } - - public GearseekerSerpentCostReductionEffect() { - super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST); - staticText = "This spell costs {1} less to cast for each artifact you control"; - } - - protected GearseekerSerpentCostReductionEffect(final GearseekerSerpentCostReductionEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - int count = game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), game).size(); - CardUtil.reduceCost(abilityToModify, count); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - return abilityToModify.getSourceId().equals(source.getSourceId()); - } - - @Override - public GearseekerSerpentCostReductionEffect copy() { - return new GearseekerSerpentCostReductionEffect(this); - } -} diff --git a/Mage.Sets/src/mage/cards/g/Ghoultree.java b/Mage.Sets/src/mage/cards/g/Ghoultree.java index 63a9982da0..ae65b24830 100644 --- a/Mage.Sets/src/mage/cards/g/Ghoultree.java +++ b/Mage.Sets/src/mage/cards/g/Ghoultree.java @@ -1,9 +1,12 @@ package mage.cards.g; -import java.util.UUID; import mage.MageInt; +import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.cost.SourceCostReductionForEachCardInGraveyardEffect; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -11,8 +14,9 @@ import mage.constants.SubType; import mage.constants.Zone; import mage.filter.StaticFilters; +import java.util.UUID; + /** - * * @author North */ public final class Ghoultree extends CardImpl { @@ -26,7 +30,11 @@ public final class Ghoultree extends CardImpl { this.toughness = new MageInt(10); // Ghoultree costs {1} less to cast for each creature card in your graveyard. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SourceCostReductionForEachCardInGraveyardEffect(StaticFilters.FILTER_CARD_CREATURE))); + DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_CREATURE); + Ability ability = new SimpleStaticAbility(Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue)); + ability.setRuleAtTheTop(true); + ability.addHint(new ValueHint("Creature card in your graveyard", xValue)); + this.addAbility(ability); } public Ghoultree(final Ghoultree card) { diff --git a/Mage.Sets/src/mage/cards/g/GlaiveOfTheGuildpact.java b/Mage.Sets/src/mage/cards/g/GlaiveOfTheGuildpact.java index 160e3ddda0..69eaad0167 100644 --- a/Mage.Sets/src/mage/cards/g/GlaiveOfTheGuildpact.java +++ b/Mage.Sets/src/mage/cards/g/GlaiveOfTheGuildpact.java @@ -29,10 +29,7 @@ public final class GlaiveOfTheGuildpact extends CardImpl { this.subtype.add(SubType.EQUIPMENT); // Equipped creature gets +1/+0 for each Gate you control and has vigilance and menace. - Ability ability = new SimpleStaticAbility(new BoostEquippedEffect( - GateYouControlCount.instance, - StaticValue.get(0) - ).setText("Equipped creature gets +1/+0 for each Gate you control")); + Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(GateYouControlCount.instance, StaticValue.get(0))); ability.addEffect(new GainAbilityAttachedEffect( VigilanceAbility.getInstance(), AttachmentType.EQUIPMENT ).setText("and has vigilance")); diff --git a/Mage.Sets/src/mage/cards/h/HoldTheGates.java b/Mage.Sets/src/mage/cards/h/HoldTheGates.java index 83d95b9eaf..1cdda2a3b1 100644 --- a/Mage.Sets/src/mage/cards/h/HoldTheGates.java +++ b/Mage.Sets/src/mage/cards/h/HoldTheGates.java @@ -13,7 +13,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Zone; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.StaticFilters; import java.util.UUID; @@ -25,13 +25,14 @@ public final class HoldTheGates extends CardImpl { public HoldTheGates(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); - // Creatures you control get +0/+1 for each Gate you control and have vigilance. Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostControlledEffect(StaticValue.get(0), GateYouControlCount.instance, Duration.WhileOnBattlefield) - .setText("Creatures you control get +0/+1 for each Gate you control ")); - ability.addEffect(new GainAbilityControlledEffect(VigilanceAbility.getInstance(), Duration.WhileOnBattlefield, new FilterControlledCreaturePermanent("Creatures")) - .setText("and have vigilance")); + ); + ability.addEffect( + new GainAbilityControlledEffect(VigilanceAbility.getInstance(), Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_CREATURES_CONTROLLED) + .setText("and have vigilance") + ); ability.addHint(GateYouControlHint.instance); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/k/KhalniHydra.java b/Mage.Sets/src/mage/cards/k/KhalniHydra.java index b0d7f0a841..f37225ddcf 100644 --- a/Mage.Sets/src/mage/cards/k/KhalniHydra.java +++ b/Mage.Sets/src/mage/cards/k/KhalniHydra.java @@ -1,48 +1,52 @@ - package mage.cards.k; -import java.util.Iterator; -import java.util.UUID; import mage.MageInt; import mage.ObjectColor; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.costs.mana.ManaCosts; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.ValueHint; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.mageobject.ColorPredicate; -import mage.game.Game; + +import java.util.UUID; /** - * * @author maurer.it_at_gmail.com */ public final class KhalniHydra extends CardImpl { - private static final FilterControlledCreaturePermanent filter; + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("green creature you control"); static { - filter = new FilterControlledCreaturePermanent(); filter.add(new ColorPredicate(ObjectColor.GREEN)); } public KhalniHydra(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{G}{G}{G}{G}{G}{G}{G}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{G}{G}{G}{G}{G}{G}{G}"); this.subtype.add(SubType.HYDRA); this.power = new MageInt(8); this.toughness = new MageInt(8); - + // This spell costs {G} less to cast for each green creature you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new KhalniHydraCostReductionEffect())); - + ManaCosts manaReduce = new ManaCostsImpl<>("{G}"); + DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + this.addAbility(new SimpleStaticAbility(Zone.ALL, + new SpellCostReductionForEachSourceEffect(manaReduce, xValue)) + .addHint(new ValueHint("Green creature you control", xValue)) + ); + // Trample this.addAbility(TrampleAbility.getInstance()); } @@ -51,47 +55,8 @@ public final class KhalniHydra extends CardImpl { super(card); } - @Override - public void adjustCosts(Ability ability, Game game) { - super.adjustCosts(ability, game); - int reductionAmount = game.getBattlefield().count(filter, ability.getSourceId(), ability.getControllerId(), game); - Iterator iter = ability.getManaCostsToPay().iterator(); - - while ( reductionAmount > 0 && iter.hasNext() ) { - ManaCost manaCostEntry = iter.next(); - if (manaCostEntry.getMana().getGreen() > 0) { // in case another effect adds additional mana cost - iter.remove(); - reductionAmount--; - } - } - } - @Override public KhalniHydra copy() { return new KhalniHydra(this); } } - -class KhalniHydraCostReductionEffect extends OneShotEffect { - private static final String effectText = "{this} costs {G} less to cast for each green creature you control"; - - KhalniHydraCostReductionEffect ( ) { - super(Outcome.Benefit); - this.staticText = effectText; - } - - KhalniHydraCostReductionEffect ( KhalniHydraCostReductionEffect effect ) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return false; - } - - @Override - public KhalniHydraCostReductionEffect copy() { - return new KhalniHydraCostReductionEffect(this); - } - -} diff --git a/Mage.Sets/src/mage/cards/m/Molderhulk.java b/Mage.Sets/src/mage/cards/m/Molderhulk.java index 9f65632312..9f38fec52a 100644 --- a/Mage.Sets/src/mage/cards/m/Molderhulk.java +++ b/Mage.Sets/src/mage/cards/m/Molderhulk.java @@ -1,25 +1,28 @@ package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; -import mage.abilities.effects.common.cost.SourceCostReductionForEachCardInGraveyardEffect; -import mage.constants.SubType; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.AbilityWord; import mage.constants.CardType; +import mage.constants.SubType; import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.filter.common.FilterLandCard; import mage.target.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class Molderhulk extends CardImpl { @@ -36,8 +39,11 @@ public final class Molderhulk extends CardImpl { this.toughness = new MageInt(6); // Undergrowth — This spell costs {1} less to cast for each creature card in your graveyard. - Ability ability = new SimpleStaticAbility(Zone.ALL, new SourceCostReductionForEachCardInGraveyardEffect(StaticFilters.FILTER_CARD_CREATURE)); + DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_CREATURE); + Ability ability = new SimpleStaticAbility(Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue)); ability.setAbilityWord(AbilityWord.UNDERGROWTH); + ability.setRuleAtTheTop(true); + ability.addHint(new ValueHint("Creature card in your graveyard", xValue)); this.addAbility(ability); // When Molderhulk enters the battlefield, return target land card from your graveyard to the battlefield. diff --git a/Mage.Sets/src/mage/cards/n/NemesisOfMortals.java b/Mage.Sets/src/mage/cards/n/NemesisOfMortals.java index 45bcc9b02d..4f96bbbc4c 100644 --- a/Mage.Sets/src/mage/cards/n/NemesisOfMortals.java +++ b/Mage.Sets/src/mage/cards/n/NemesisOfMortals.java @@ -1,12 +1,14 @@ package mage.cards.n; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; import mage.abilities.effects.Effect; import mage.abilities.effects.common.cost.CostModificationEffectImpl; -import mage.abilities.effects.common.cost.SourceCostReductionForEachCardInGraveyardEffect; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.ValueHint; import mage.abilities.keyword.MonstrosityAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -16,6 +18,8 @@ import mage.game.Game; import mage.players.Player; import mage.util.CardUtil; +import java.util.UUID; + /** * @author LevelX2 */ @@ -29,8 +33,10 @@ public final class NemesisOfMortals extends CardImpl { this.toughness = new MageInt(5); // Nemesis of Mortals costs {1} less to cast for each creature card in your graveyard. - Ability ability = new SimpleStaticAbility(Zone.ALL, new SourceCostReductionForEachCardInGraveyardEffect(StaticFilters.FILTER_CARD_CREATURE)); + DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_CREATURE); + Ability ability = new SimpleStaticAbility(Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue)); ability.setRuleAtTheTop(true); + ability.addHint(new ValueHint("Creature card in your graveyard", xValue)); this.addAbility(ability); // {7}{G}{G}: Monstrosity 5. This ability costs {1} less to activate for each creature card in your graveyard. diff --git a/Mage.Sets/src/mage/cards/o/OreScaleGuardian.java b/Mage.Sets/src/mage/cards/o/OreScaleGuardian.java index cb8c54a0f6..95d09d2f8e 100644 --- a/Mage.Sets/src/mage/cards/o/OreScaleGuardian.java +++ b/Mage.Sets/src/mage/cards/o/OreScaleGuardian.java @@ -3,7 +3,10 @@ package mage.cards.o; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.cost.SourceCostReductionForEachCardInGraveyardEffect; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.ValueHint; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.HasteAbility; import mage.cards.CardImpl; @@ -28,10 +31,10 @@ public final class OreScaleGuardian extends CardImpl { this.toughness = new MageInt(4); // This spell costs {1} less to cast for each land card in your graveyard. - Ability ability = new SimpleStaticAbility( - Zone.ALL, new SourceCostReductionForEachCardInGraveyardEffect(StaticFilters.FILTER_CARD_LAND) - ); + DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_LAND); + Ability ability = new SimpleStaticAbility(Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue)); ability.setRuleAtTheTop(true); + ability.addHint(new ValueHint("Land card in your graveyard", xValue)); this.addAbility(ability); // Flying diff --git a/Mage.Sets/src/mage/cards/t/TheCauldronOfEternity.java b/Mage.Sets/src/mage/cards/t/TheCauldronOfEternity.java index e4a8026e2e..a1d627c1de 100644 --- a/Mage.Sets/src/mage/cards/t/TheCauldronOfEternity.java +++ b/Mage.Sets/src/mage/cards/t/TheCauldronOfEternity.java @@ -1,25 +1,25 @@ package mage.cards.t; -import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.common.ActivateAsSorceryActivatedAbility; import mage.abilities.common.DiesCreatureTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.common.PayLifeCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; import mage.abilities.effects.common.PutOnLibraryTargetEffect; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SuperType; +import mage.constants.Zone; import mage.filter.StaticFilters; -import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetCardInYourGraveyard; -import mage.util.CardUtil; import java.util.UUID; @@ -34,7 +34,11 @@ public final class TheCauldronOfEternity extends CardImpl { this.addSuperType(SuperType.LEGENDARY); // This spell costs {2} less to cast for each creature card in your graveyard. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new TheCauldronOfEternityCostReductionEffect())); + DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_CREATURE); + Ability ability = new SimpleStaticAbility(Zone.ALL, new SpellCostReductionForEachSourceEffect(2, xValue)); + ability.setRuleAtTheTop(true); + ability.addHint(new ValueHint("Creature card in your graveyard", xValue)); + this.addAbility(ability); // Whenever a creature you control dies, put it on the bottom of its owner's library. this.addAbility(new DiesCreatureTriggeredAbility( @@ -43,7 +47,7 @@ public final class TheCauldronOfEternity extends CardImpl { )); // {2}{B}, {T}, Pay 2 life: Return target creature card from your graveyard to the battlefield. Activate this ability only any time you could cast a sorcery. - Ability ability = new ActivateAsSorceryActivatedAbility( + ability = new ActivateAsSorceryActivatedAbility( Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect(), new ManaCostsImpl("{2}{B}") ); ability.addCost(new TapSourceCost()); @@ -61,44 +65,3 @@ public final class TheCauldronOfEternity extends CardImpl { return new TheCauldronOfEternity(this); } } - -class TheCauldronOfEternityCostReductionEffect extends CostModificationEffectImpl { - - TheCauldronOfEternityCostReductionEffect() { - super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST); - staticText = "This spell costs {2} less to cast for each creature card in your graveyard"; - } - - private TheCauldronOfEternityCostReductionEffect(final TheCauldronOfEternityCostReductionEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { - return false; - } - int reductionAmount = player - .getGraveyard() - .getCards(game) - .stream() - .filter(MageObject::isCreature) - .mapToInt(card -> 2) - .sum(); - CardUtil.reduceCost(abilityToModify, reductionAmount); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - return abilityToModify instanceof SpellAbility - && abilityToModify.getSourceId().equals(source.getSourceId()) - && game.getCard(abilityToModify.getSourceId()) != null; - } - - @Override - public TheCauldronOfEternityCostReductionEffect copy() { - return new TheCauldronOfEternityCostReductionEffect(this); - } -} diff --git a/Mage.Sets/src/mage/cards/t/TheCircleOfLoyalty.java b/Mage.Sets/src/mage/cards/t/TheCircleOfLoyalty.java index ef80797958..6acd774073 100644 --- a/Mage.Sets/src/mage/cards/t/TheCircleOfLoyalty.java +++ b/Mage.Sets/src/mage/cards/t/TheCircleOfLoyalty.java @@ -1,24 +1,23 @@ package mage.cards.t; import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.continuous.BoostControlledEffect; -import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.filter.FilterPermanent; import mage.filter.FilterSpell; import mage.filter.common.FilterControlledPermanent; -import mage.game.Game; import mage.game.permanent.token.KnightToken; -import mage.util.CardUtil; import java.util.UUID; @@ -27,10 +26,16 @@ import java.util.UUID; */ public final class TheCircleOfLoyalty extends CardImpl { - private static final FilterSpell filter = new FilterSpell("a legendary spell"); + private static final FilterSpell filterLegendary = new FilterSpell("a legendary spell"); static { - filter.add(SuperType.LEGENDARY.getPredicate()); + filterLegendary.add(SuperType.LEGENDARY.getPredicate()); + } + + static final FilterControlledPermanent filterKnight = new FilterControlledPermanent("Knight you control"); + + static { + filterKnight.add(SubType.KNIGHT.getPredicate()); } public TheCircleOfLoyalty(UUID ownerId, CardSetInfo setInfo) { @@ -39,7 +44,10 @@ public final class TheCircleOfLoyalty extends CardImpl { this.addSuperType(SuperType.LEGENDARY); // This spell costs {1} less to cast for each Knight you control. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new TheCircleOfLoyaltyCostReductionEffect())); + DynamicValue xValue = new PermanentsOnBattlefieldCount(filterKnight); + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue) + ).addHint(new ValueHint("Knight you control", xValue))); // Creatures you control get +1/+1. this.addAbility(new SimpleStaticAbility( @@ -48,7 +56,7 @@ public final class TheCircleOfLoyalty extends CardImpl { // Whenever you cast a legendary spell, create a 2/2 white Knight creature token with vigilance. this.addAbility(new SpellCastControllerTriggeredAbility( - new CreateTokenEffect(new KnightToken()), filter, false + new CreateTokenEffect(new KnightToken()), filterLegendary, false )); // {3}{W}, {T}: Create a 2/2 white Knight creature token with vigilance. @@ -67,37 +75,4 @@ public final class TheCircleOfLoyalty extends CardImpl { public TheCircleOfLoyalty copy() { return new TheCircleOfLoyalty(this); } -} - -class TheCircleOfLoyaltyCostReductionEffect extends CostModificationEffectImpl { - - private static final FilterPermanent filter = new FilterControlledPermanent(SubType.KNIGHT); - - TheCircleOfLoyaltyCostReductionEffect() { - super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST); - staticText = "This spell costs {1} less to cast for each Knight you control"; - } - - private TheCircleOfLoyaltyCostReductionEffect(final TheCircleOfLoyaltyCostReductionEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - int reductionAmount = game.getBattlefield().count(filter, source.getSourceId(), source.getControllerId(), game); - CardUtil.reduceCost(abilityToModify, reductionAmount); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - return abilityToModify instanceof SpellAbility - && abilityToModify.getSourceId().equals(source.getSourceId()) - && game.getCard(abilityToModify.getSourceId()) != null; - } - - @Override - public TheCircleOfLoyaltyCostReductionEffect copy() { - return new TheCircleOfLoyaltyCostReductionEffect(this); - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/TheMagicMirror.java b/Mage.Sets/src/mage/cards/t/TheMagicMirror.java index 9090a80225..2238d81cfc 100644 --- a/Mage.Sets/src/mage/cards/t/TheMagicMirror.java +++ b/Mage.Sets/src/mage/cards/t/TheMagicMirror.java @@ -4,11 +4,13 @@ import mage.abilities.Ability; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; import mage.abilities.dynamicvalue.common.CountersSourceCount; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect; -import mage.abilities.effects.common.cost.SourceCostReductionForEachCardInGraveyardEffect; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -30,10 +32,11 @@ public final class TheMagicMirror extends CardImpl { this.addSuperType(SuperType.LEGENDARY); // This spell costs {1} less to cast for each instant and sorcery card in your graveyard. - this.addAbility(new SimpleStaticAbility( - Zone.ALL, new SourceCostReductionForEachCardInGraveyardEffect( - StaticFilters.FILTER_CARD_INSTANT_AND_SORCERY - )).setRuleAtTheTop(true)); + DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_INSTANT_AND_SORCERY); + Ability ability = new SimpleStaticAbility(Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue)); + ability.setRuleAtTheTop(true); + ability.addHint(new ValueHint("Instant and sorcery card in your graveyard", xValue)); + this.addAbility(ability); // You have no maximum hand size. this.addAbility(new SimpleStaticAbility(new MaximumHandSizeControllerEffect( @@ -42,7 +45,7 @@ public final class TheMagicMirror extends CardImpl { ))); // At the beginning of your upkeep, put a knowledge counter on The Magic Mirror, then draw a card for each knowledge counter on The Magic Mirror. - Ability ability = new BeginningOfUpkeepTriggeredAbility( + ability = new BeginningOfUpkeepTriggeredAbility( new AddCountersSourceEffect(CounterType.KNOWLEDGE.createInstance()) .setText("put a knowledge counter on {this},"), TargetController.YOU, false diff --git a/Mage.Sets/src/mage/cards/t/TorgaarFamineIncarnate.java b/Mage.Sets/src/mage/cards/t/TorgaarFamineIncarnate.java index 9cdaf57aba..18238ef627 100644 --- a/Mage.Sets/src/mage/cards/t/TorgaarFamineIncarnate.java +++ b/Mage.Sets/src/mage/cards/t/TorgaarFamineIncarnate.java @@ -1,7 +1,5 @@ - package mage.cards.t; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.SpellAbility; @@ -13,21 +11,16 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.cost.CostModificationEffectImpl; 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.SuperType; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; import mage.util.CardUtil; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class TorgaarFamineIncarnate extends CardImpl { @@ -107,8 +100,16 @@ class TorgaarFamineIncarnateEffectCostReductionEffect extends CostModificationEf SpellAbility spellAbility = (SpellAbility) abilityToModify; for (Cost cost : spellAbility.getCosts()) { if (cost instanceof SacrificeXTargetCost) { - int reduction = ((SacrificeXTargetCost) cost).getAmount(); - CardUtil.adjustCost(spellAbility, reduction * 2); + if (game.inCheckPlayableState()) { + // allows to cast in getPlayable + int reduction = ((SacrificeXTargetCost) cost).getMaxValue(spellAbility, game); + CardUtil.adjustCost(spellAbility, reduction * 2); + } else { + // real cast + int reduction = ((SacrificeXTargetCost) cost).getAmount(); + CardUtil.adjustCost(spellAbility, reduction * 2); + } + break; } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/CostReduceForEachTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/CostReduceForEachTest.java new file mode 100644 index 0000000000..8303f02800 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/CostReduceForEachTest.java @@ -0,0 +1,140 @@ +package org.mage.test.cards.cost.modification; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +/** + * @author JayDi85 + */ +public class CostReduceForEachTest extends CardTestPlayerBaseWithAIHelps { + + @Test + public void test_AncientStoneIdol_Attacking() { + // {10} + // Flash + // This spell costs {1} less to cast for each attacking creature. + addCard(Zone.HAND, playerA, "Ancient Stone Idol", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10 - 2); + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 2); // give 2 cost reduction + + // before + checkPlayableAbility("before attack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Ancient Stone Idol", false); + + // prepare for attack + attack(1, playerA, "Balduvian Bears"); + attack(1, playerA, "Balduvian Bears"); + + // on attack + checkPlayableAbility("on attack", 1, PhaseStep.DECLARE_BLOCKERS, playerA, "Cast Ancient Stone Idol", true); + castSpell(1, PhaseStep.DECLARE_BLOCKERS, playerA, "Ancient Stone Idol"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Ancient Stone Idol", 1); + } + + @Test + public void test_AncientStoneIdol_AttackingWithSacrifice() { + // The total cost to cast a spell is locked in before you pay that cost. For example, if you control five attacking + // creatures, including one you can sacrifice to add {C} to your mana pool, Ancient Stone Idol costs {5} to cast. + // Then you can sacrifice the creature when you activate mana abilities just before paying the cost, and it still + // costs only {5} to cast. + // (2018-07-13) + + // {10} + // Flash + // This spell costs {1} less to cast for each attacking creature. + addCard(Zone.HAND, playerA, "Ancient Stone Idol", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10 - 4); + // + // Sacrifice Blood Pet: Add {B}. + addCard(Zone.BATTLEFIELD, playerA, "Blood Pet", 2); // give 2 cost reduction + can be sacrificed as 2 mana + + // before + checkPlayableAbility("before attack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Ancient Stone Idol", false); + + // prepare for attack + attack(1, playerA, "Blood Pet"); + attack(1, playerA, "Blood Pet"); + + // on attack (must automaticly sacrifice creatures as mana pay) + checkPlayableAbility("on attack", 1, PhaseStep.DECLARE_BLOCKERS, playerA, "Cast Ancient Stone Idol", true); + castSpell(1, PhaseStep.DECLARE_BLOCKERS, playerA, "Ancient Stone Idol"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Ancient Stone Idol", 1); + assertGraveyardCount(playerA, "Blood Pet", 2); + } + + @Test + public void test_KhalniHydra_ColorReduce() { + // {G}{G}{G}{G}{G}{G}{G}{G} + // This spell costs {G} less to cast for each green creature you control. + addCard(Zone.HAND, playerA, "Khalni Hydra", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 8 - 2); + addCard(Zone.HAND, playerA, "Balduvian Bears", 2); // give 2 cost reduction + + checkPlayableAbility("no cost reduction 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Khalni Hydra", false); + + // prepare creatures for reduce + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + checkPlayableAbility("no cost reduction 2", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Khalni Hydra", false); + + // can cast on next turn + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Khalni Hydra"); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Khalni Hydra", 1); + } + + @Test + public void test_TorgaarFamineIncarnate_SacrificeXTargets() { + // {6}{B}{B} + // As an additional cost to cast this spell, you may sacrifice any number of creatures. + // This spell costs {2} less to cast for each creature sacrificed this way. + // When Torgaar, Famine Incarnate enters the battlefield, up to one target player's life total becomes half their starting life total, rounded down. + addCard(Zone.HAND, playerA, "Torgaar, Famine Incarnate", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 8 - 4 - 2); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.HAND, playerA, "Balduvian Bears", 2); // give 4 cost reduction on sacrifice + + checkPlayableAbility("no cost reduction 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Torgaar, Famine Incarnate", false); + + // prepare creatures for reduce + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + checkPlayableAbility("no cost reduction 2", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Torgaar, Famine Incarnate", false); + + // can cast on next turn + checkPlayableAbility("must reduce", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Torgaar, Famine Incarnate", true); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Torgaar, Famine Incarnate"); + setChoice(playerA, "X=2"); // two creatures sacrifice + setChoice(playerA, "Balduvian Bears"); + setChoice(playerA, "Balduvian Bears"); + addTarget(playerA, playerB); // target player for half life + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Torgaar, Famine Incarnate", 1); + assertLife(playerB, 20 / 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index 71ff178235..c3ae20419e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -1663,16 +1663,20 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement public void attack(int turnNum, TestPlayer player, String attacker) { //Assert.assertNotEquals("", attacker); + assertAliaseSupportInActivateCommand(attacker, false); // it uses old special notation like card_name:index player.addAction(turnNum, PhaseStep.DECLARE_ATTACKERS, "attack:" + attacker); } public void attack(int turnNum, TestPlayer player, String attacker, TestPlayer defendingPlayer) { //Assert.assertNotEquals("", attacker); + assertAliaseSupportInActivateCommand(attacker, false); // it uses old special notation like card_name:index player.addAction(turnNum, PhaseStep.DECLARE_ATTACKERS, "attack:" + attacker + "$defendingPlayer=" + defendingPlayer.getName()); } public void attack(int turnNum, TestPlayer player, String attacker, String planeswalker) { //Assert.assertNotEquals("", attacker); + assertAliaseSupportInActivateCommand(attacker, false); // it uses old special notation like card_name:index + assertAliaseSupportInActivateCommand(planeswalker, false); player.addAction(turnNum, PhaseStep.DECLARE_ATTACKERS, new StringBuilder("attack:").append(attacker).append("$planeswalker=").append(planeswalker).toString()); } @@ -1683,6 +1687,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement public void block(int turnNum, TestPlayer player, String blocker, String attacker) { //Assert.assertNotEquals("", blocker); //Assert.assertNotEquals("", attacker); + assertAliaseSupportInActivateCommand(blocker, false); // it uses old special notation like card_name:index + assertAliaseSupportInActivateCommand(attacker, false); player.addAction(turnNum, PhaseStep.DECLARE_BLOCKERS, "block:" + blocker + '$' + attacker); } diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttackingCreatureCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttackingCreatureCount.java index fa4ba981a5..5b19df74a5 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttackingCreatureCount.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttackingCreatureCount.java @@ -1,38 +1,60 @@ - package mage.abilities.dynamicvalue.common; import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; +import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.combat.CombatGroup; +import mage.game.permanent.Permanent; + +import java.util.UUID; /** - * * @author LevelX2 */ public class AttackingCreatureCount implements DynamicValue { private String message; + private FilterCreaturePermanent filter; public AttackingCreatureCount() { this("attacking creature"); } + public AttackingCreatureCount(FilterCreaturePermanent filter) { + this(filter, "attacking " + filter.getMessage()); + } + public AttackingCreatureCount(String message) { + this(null, message); + } + + public AttackingCreatureCount(FilterCreaturePermanent filter, String message) { this.message = message; + this.filter = filter; } public AttackingCreatureCount(final AttackingCreatureCount dynamicValue) { super(); this.message = dynamicValue.message; + this.filter = dynamicValue.filter; } @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { int count = 0; for (CombatGroup combatGroup : game.getCombat().getGroups()) { - count += combatGroup.getAttackers().size(); + for (UUID permId : combatGroup.getAttackers()) { + if (filter != null) { + Permanent attacker = game.getPermanent(permId); + if (attacker != null && filter.match(attacker, sourceAbility.getSourceId(), sourceAbility.getControllerId(), game)) { + count++; + } + } else { + count++; + } + } } return count; } diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttackingFilterCreatureCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttackingFilterCreatureCount.java deleted file mode 100644 index 5cee8d7544..0000000000 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttackingFilterCreatureCount.java +++ /dev/null @@ -1,65 +0,0 @@ - -package mage.abilities.dynamicvalue.common; - -import java.util.UUID; -import mage.abilities.Ability; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.Effect; -import mage.filter.common.FilterCreaturePermanent; -import mage.game.Game; -import mage.game.combat.CombatGroup; -import mage.game.permanent.Permanent; - -/** - * - * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) - */ -public class AttackingFilterCreatureCount implements DynamicValue { - - private FilterCreaturePermanent filter; - private String message; - - public AttackingFilterCreatureCount(FilterCreaturePermanent filter) { - this(filter, "attacking creature"); - } - - public AttackingFilterCreatureCount(FilterCreaturePermanent filter, String message) { - this.filter = filter; - this.message = message; - } - - public AttackingFilterCreatureCount(final AttackingFilterCreatureCount dynamicValue) { - super(); - this.message = dynamicValue.message; - this.filter = dynamicValue.filter; - } - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - int count = 0; - for (CombatGroup combatGroup : game.getCombat().getGroups()) { - for (UUID permId : combatGroup.getAttackers()) { - Permanent attacker = game.getPermanent(permId); - if (filter.match(attacker, sourceAbility.getSourceId(), sourceAbility.getControllerId(), game)) { - count++; - } - } - } - return count; - } - - @Override - public AttackingFilterCreatureCount copy() { - return new AttackingFilterCreatureCount(this); - } - - @Override - public String getMessage() { - return message; - } - - @Override - public String toString() { - return "X"; - } -} diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/GateYouControlCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/GateYouControlCount.java index 2045cdfac2..61a06c39c9 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/GateYouControlCount.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/GateYouControlCount.java @@ -32,11 +32,11 @@ public enum GateYouControlCount implements DynamicValue { @Override public String toString() { - return "X"; + return "1"; // uses "for each" effects, so must be 1, not X } @Override public String getMessage() { - return "gate you control"; + return "Gate you control"; } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostControlledEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostControlledEffect.java index 901fd63335..8466445ba7 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostControlledEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BoostControlledEffect.java @@ -1,7 +1,5 @@ - package mage.abilities.effects.common.continuous; -import java.util.Iterator; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; @@ -16,8 +14,9 @@ import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; +import java.util.Iterator; + /** - * * @author BetaSteward_at_googlemail.com */ public class BoostControlledEffect extends ContinuousEffectImpl { @@ -56,10 +55,10 @@ public class BoostControlledEffect extends ContinuousEffectImpl { * @param power * @param toughness * @param duration - * @param filter AnotherPredicate is not working, you need to use the - * excludeSource option - * @param lockedIn if true, power and toughness will be calculated only - * once, when the ability resolves + * @param filter AnotherPredicate is not working, you need to use the + * excludeSource option + * @param lockedIn if true, power and toughness will be calculated only + * once, when the ability resolves * @param excludeSource */ public BoostControlledEffect(DynamicValue power, DynamicValue toughness, Duration duration, FilterCreaturePermanent filter, boolean excludeSource, boolean lockedIn) { @@ -105,7 +104,7 @@ public class BoostControlledEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { if (this.affectedObjectsSet) { - for (Iterator it = affectedObjectList.iterator(); it.hasNext();) { + for (Iterator it = affectedObjectList.iterator(); it.hasNext(); ) { Permanent permanent = it.next().getPermanent(game); if (permanent != null) { permanent.addPower(power.calculate(game, source, this)); @@ -126,7 +125,6 @@ public class BoostControlledEffect extends ContinuousEffectImpl { } private void setText() { - String message = null; StringBuilder sb = new StringBuilder(); if (excludeSource) { sb.append("other "); @@ -150,6 +148,9 @@ public class BoostControlledEffect extends ContinuousEffectImpl { sb.append(t); sb.append((duration == Duration.EndOfTurn ? " until end of turn" : "")); + + // where X + String message = null; if (t.equals("X")) { message = toughness.getMessage(); } else if (p.equals("X")) { @@ -158,6 +159,17 @@ public class BoostControlledEffect extends ContinuousEffectImpl { if (message != null && !message.isEmpty()) { sb.append(", where X is ").append(message); } + + // for each + if (message == null) { + message = toughness.getMessage(); + if (message.isEmpty()) { + message = power.getMessage(); + } + if (!message.isEmpty()) { + sb.append(" for each " + message); + } + } staticText = sb.toString(); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/CostModificationEffectImpl.java b/Mage/src/main/java/mage/abilities/effects/common/cost/CostModificationEffectImpl.java index 53af0aca6b..a24c93acf8 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/cost/CostModificationEffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/common/cost/CostModificationEffectImpl.java @@ -12,6 +12,12 @@ import mage.game.Game; /** * Simple implementation of a {@link CostModificationEffect} offering simplified * construction to setup the object for use by the mage framework. + *

+ * WARNING, if you implement custom effect and it can works on stack only (e.g. it need spell's targets to check) then + * use different apply code: + * - one for get playable mode before spell puts on stack (apply maximum possible cost reduction, use game.inCheckPlayableState()). + * - one for normal mode after spell puts on stack (apply real cost reduction) + * Example: TorgaarFamineIncarnate * * @author maurer.it_at_gmail.com */ diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/SourceCostReductionForEachCardInGraveyardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/SourceCostReductionForEachCardInGraveyardEffect.java deleted file mode 100644 index ab50288e85..0000000000 --- a/Mage/src/main/java/mage/abilities/effects/common/cost/SourceCostReductionForEachCardInGraveyardEffect.java +++ /dev/null @@ -1,59 +0,0 @@ -package mage.abilities.effects.common.cost; - -import mage.abilities.Ability; -import mage.abilities.SpellAbility; -import mage.constants.CostModificationType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.filter.FilterCard; -import mage.filter.StaticFilters; -import mage.game.Game; -import mage.players.Player; -import mage.util.CardUtil; - -/** - * @author Styxo - */ -public class SourceCostReductionForEachCardInGraveyardEffect extends CostModificationEffectImpl { - - private FilterCard filter; - - public SourceCostReductionForEachCardInGraveyardEffect() { - this(StaticFilters.FILTER_CARD); - } - - public SourceCostReductionForEachCardInGraveyardEffect(FilterCard filter) { - super(Duration.WhileOnStack, Outcome.Benefit, CostModificationType.REDUCE_COST); - this.filter = filter; - staticText = "{this} costs {1} less to cast for each " + filter.getMessage() + " in your graveyard"; - } - - private SourceCostReductionForEachCardInGraveyardEffect(SourceCostReductionForEachCardInGraveyardEffect effect) { - super(effect); - this.filter = effect.filter.copy(); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - int reductionAmount = player.getGraveyard().count(filter, game); - CardUtil.reduceCost(abilityToModify, reductionAmount); - return true; - } - return false; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - if ((abilityToModify instanceof SpellAbility) && abilityToModify.getSourceId().equals(source.getSourceId())) { - return game.getCard(abilityToModify.getSourceId()) != null; - } - return false; - } - - @Override - public SourceCostReductionForEachCardInGraveyardEffect copy() { - return new SourceCostReductionForEachCardInGraveyardEffect(this); - } -} diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionForEachSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionForEachSourceEffect.java new file mode 100644 index 0000000000..0719d6aee7 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionForEachSourceEffect.java @@ -0,0 +1,93 @@ +package mage.abilities.effects.common.cost; + +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.costs.mana.ManaCosts; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.constants.CostModificationType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.util.CardUtil; + +/** + * @author JayDi85 + */ +public class SpellCostReductionForEachSourceEffect extends CostModificationEffectImpl { + + private final DynamicValue eachAmount; + private ManaCosts reduceManaCosts; + private final int reduceGenericMana; + + public SpellCostReductionForEachSourceEffect(int reduceGenericMana, DynamicValue eachAmount) { + super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST); + this.eachAmount = eachAmount; + this.reduceManaCosts = null; + this.reduceGenericMana = reduceGenericMana; + + StringBuilder sb = new StringBuilder(); + sb.append("this spell costs {") + .append(this.reduceGenericMana) + .append("} less to cast for each ") + .append(this.eachAmount.getMessage()); + this.staticText = sb.toString(); + } + + public SpellCostReductionForEachSourceEffect(ManaCosts reduceManaCosts, DynamicValue eachAmount) { + super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST); + this.eachAmount = eachAmount; + this.reduceManaCosts = reduceManaCosts; + this.reduceGenericMana = 0; + + StringBuilder sb = new StringBuilder(); + sb.append("this spell costs "); + for (String manaSymbol : reduceManaCosts.getSymbols()) { + sb.append(manaSymbol); + } + sb.append(" less to cast for each ").append(this.eachAmount.getMessage()); + this.staticText = sb.toString(); + } + + + protected SpellCostReductionForEachSourceEffect(final SpellCostReductionForEachSourceEffect effect) { + super(effect); + this.eachAmount = effect.eachAmount; + this.reduceManaCosts = effect.reduceManaCosts; + this.reduceGenericMana = effect.reduceGenericMana; + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + int needReduceAmount = eachAmount.calculate(game, source, this); + if (needReduceAmount > 0) { + if (reduceManaCosts != null) { + // color reduce + ManaCosts needReduceMana = new ManaCostsImpl<>(); + for (int i = 0; i <= needReduceAmount; i++) { + needReduceMana.add(reduceManaCosts.copy()); + } + CardUtil.adjustCost((SpellAbility) abilityToModify, needReduceMana, false); + } else { + // generic reduce + CardUtil.reduceCost(abilityToModify, needReduceAmount * this.reduceGenericMana); + } + } + + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + if (abilityToModify.getSourceId().equals(source.getSourceId()) && (abilityToModify instanceof SpellAbility)) { + return true; + } + return false; + } + + @Override + public SpellCostReductionForEachSourceEffect copy() { + return new SpellCostReductionForEachSourceEffect(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionSourceEffect.java index a1910b5116..c6bf32504e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionSourceEffect.java @@ -37,11 +37,10 @@ public class SpellCostReductionSourceEffect extends CostModificationEffectImpl { for (String manaSymbol : manaCostsToReduce.getSymbols()) { sb.append(manaSymbol); } - sb.append(" less"); + sb.append(" less to cast"); if (this.condition != null) { - sb.append(" to if ").append(this.condition.toString()); + sb.append(" if ").append(this.condition.toString()); } - this.staticText = sb.toString(); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionSourceForOpponentsEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionSourceForOpponentsEffect.java deleted file mode 100644 index 6b6509870a..0000000000 --- a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellCostReductionSourceForOpponentsEffect.java +++ /dev/null @@ -1,42 +0,0 @@ -package mage.abilities.effects.common.cost; - -import mage.abilities.Ability; -import mage.abilities.SpellAbility; -import mage.constants.CostModificationType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.game.Game; -import mage.util.CardUtil; - -public class SpellCostReductionSourceForOpponentsEffect extends CostModificationEffectImpl { - - public SpellCostReductionSourceForOpponentsEffect() { - this("undaunted (This spell costs {1} less to cast for each opponent.)"); - } - - public SpellCostReductionSourceForOpponentsEffect(String newStaticText) { - super(Duration.Custom, Outcome.Benefit, CostModificationType.REDUCE_COST); - staticText = newStaticText; - } - - public SpellCostReductionSourceForOpponentsEffect(final SpellCostReductionSourceForOpponentsEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source, Ability abilityToModify) { - int count = game.getOpponents(source.getControllerId()).size(); - CardUtil.reduceCost(abilityToModify, count); - return true; - } - - @Override - public boolean applies(Ability abilityToModify, Ability source, Game game) { - return abilityToModify instanceof SpellAbility && abilityToModify.getSourceId().equals(source.getSourceId()); - } - - @Override - public SpellCostReductionSourceForOpponentsEffect copy() { - return new SpellCostReductionSourceForOpponentsEffect(this); - } -} diff --git a/Mage/src/main/java/mage/abilities/keyword/UndauntedAbility.java b/Mage/src/main/java/mage/abilities/keyword/UndauntedAbility.java index 52061608d3..72f76d60f0 100644 --- a/Mage/src/main/java/mage/abilities/keyword/UndauntedAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/UndauntedAbility.java @@ -1,12 +1,8 @@ -/* - * 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.abilities.keyword; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.cost.SpellCostReductionSourceForOpponentsEffect; +import mage.abilities.dynamicvalue.common.OpponentsCount; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; import mage.constants.Zone; /** @@ -15,7 +11,7 @@ import mage.constants.Zone; public class UndauntedAbility extends SimpleStaticAbility { public UndauntedAbility() { - super(Zone.ALL, new SpellCostReductionSourceForOpponentsEffect("undaunted (This spell costs {1} less to cast for each opponent.)")); + super(Zone.ALL, new SpellCostReductionForEachSourceEffect(1, OpponentsCount.instance)); setRuleAtTheTop(true); } @@ -28,4 +24,8 @@ public class UndauntedAbility extends SimpleStaticAbility { return new UndauntedAbility(this); } + @Override + public String getRule() { + return "undaunted (This spell costs {1} less to cast for each opponent.)"; + } } \ No newline at end of file