From eef8f508e4cc1e791158c5c591f78665f1c1c173 Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Sat, 12 Aug 2023 21:49:06 +0200 Subject: [PATCH] [CMM] Implement Demon of Fate's Design (#10737) * refactor SacrificeCostManaValue to be an enum. * [CMM] Implement Demon of Fates Design * Add Unit Tests, including one bug on alternative cost. * fix alternativeCosts made from dynamicCost returning that they were not activated when paid. * fix small issues, add hint * cleanup tests and add a couple * Capitalize enum instances * Minor fixes * simplify the ContinuousEffect * use the ConditionPermanentHint made for the Demon * fix text --- .../mage/cards/a/AyaraWidowOfTheRealm.java | 4 +- Mage.Sets/src/mage/cards/b/BoshIronGolem.java | 12 +- Mage.Sets/src/mage/cards/b/BurntOffering.java | 9 +- .../src/mage/cards/d/DemonOfFatesDesign.java | 251 +++++++++++++++++ Mage.Sets/src/mage/cards/f/FaithHealer.java | 12 +- Mage.Sets/src/mage/cards/f/ForgeArmor.java | 5 +- .../src/mage/cards/h/HomaridSpawningBed.java | 8 +- .../src/mage/cards/i/IlluminorSzeras.java | 4 +- .../src/mage/cards/m/MorbidCuriosity.java | 4 +- .../src/mage/cards/p/PriestOfYawgmoth.java | 10 +- Mage.Sets/src/mage/cards/s/Sacrifice.java | 9 +- Mage.Sets/src/mage/cards/s/SoldeviAdnate.java | 12 +- Mage.Sets/src/mage/sets/CommanderMasters.java | 1 + .../single/cmm/DemonOfFatesDesignTest.java | 262 ++++++++++++++++++ .../common/IsBeingCastFromHandCondition.java | 37 +++ .../costs/AlternativeCostSourceAbility.java | 32 ++- .../abilities/costs/common/PayLifeCost.java | 2 +- ...dMana.java => SacrificeCostManaValue.java} | 24 +- ...stFromHandWithoutPayingManaCostEffect.java | 36 +-- .../mage/abilities/hint/ConditionHint.java | 2 +- .../hint/common/ConditionPermanentHint.java | 47 ++++ .../main/java/mage/players/PlayerImpl.java | 35 ++- 22 files changed, 707 insertions(+), 111 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/d/DemonOfFatesDesign.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/cmm/DemonOfFatesDesignTest.java create mode 100644 Mage/src/main/java/mage/abilities/condition/common/IsBeingCastFromHandCondition.java rename Mage/src/main/java/mage/abilities/dynamicvalue/common/{SacrificeCostConvertedMana.java => SacrificeCostManaValue.java} (64%) create mode 100644 Mage/src/main/java/mage/abilities/hint/common/ConditionPermanentHint.java diff --git a/Mage.Sets/src/mage/cards/a/AyaraWidowOfTheRealm.java b/Mage.Sets/src/mage/cards/a/AyaraWidowOfTheRealm.java index 71e25ff68e3..b8bda045a3b 100644 --- a/Mage.Sets/src/mage/cards/a/AyaraWidowOfTheRealm.java +++ b/Mage.Sets/src/mage/cards/a/AyaraWidowOfTheRealm.java @@ -8,7 +8,7 @@ import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.dynamicvalue.common.SacrificeCostConvertedMana; +import mage.abilities.dynamicvalue.common.SacrificeCostManaValue; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.TransformSourceEffect; @@ -30,7 +30,7 @@ import java.util.UUID; */ public final class AyaraWidowOfTheRealm extends CardImpl { - private static final DynamicValue xValue = new SacrificeCostConvertedMana("permanent"); + private static final DynamicValue xValue = SacrificeCostManaValue.PERMANENT; private static final FilterPermanentOrPlayer filter = new FilterPermanentOrPlayer( "opponent or battle", StaticFilters.FILTER_PERMANENT_BATTLE, new FilterOpponent() ); diff --git a/Mage.Sets/src/mage/cards/b/BoshIronGolem.java b/Mage.Sets/src/mage/cards/b/BoshIronGolem.java index 7fdae74828d..924b84a1984 100644 --- a/Mage.Sets/src/mage/cards/b/BoshIronGolem.java +++ b/Mage.Sets/src/mage/cards/b/BoshIronGolem.java @@ -1,13 +1,12 @@ package mage.cards.b; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.dynamicvalue.common.SacrificeCostConvertedMana; +import mage.abilities.dynamicvalue.common.SacrificeCostManaValue; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.keyword.TrampleAbility; @@ -18,17 +17,18 @@ import mage.constants.SubType; import mage.constants.SuperType; import mage.constants.Zone; import mage.filter.common.FilterControlledArtifactPermanent; -import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetAnyTarget; +import mage.target.common.TargetControlledPermanent; + +import java.util.UUID; /** - * * @author jeffwadsworth */ public final class BoshIronGolem extends CardImpl { public BoshIronGolem(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT,CardType.CREATURE},"{8}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{8}"); this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.GOLEM); @@ -39,7 +39,7 @@ public final class BoshIronGolem extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // {3}{R}, Sacrifice an artifact: Bosh, Iron Golem deals damage equal to the sacrificed artifact's converted mana cost to any target. - Effect effect = new DamageTargetEffect(new SacrificeCostConvertedMana("artifact")); + Effect effect = new DamageTargetEffect(SacrificeCostManaValue.ARTIFACT); effect.setText("{this} deals damage equal to the sacrificed artifact's mana value to any target"); Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new ManaCostsImpl<>("{3}{R}")); ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent(new FilterControlledArtifactPermanent("an artifact")))); diff --git a/Mage.Sets/src/mage/cards/b/BurntOffering.java b/Mage.Sets/src/mage/cards/b/BurntOffering.java index 2695188be5f..f680f71df01 100644 --- a/Mage.Sets/src/mage/cards/b/BurntOffering.java +++ b/Mage.Sets/src/mage/cards/b/BurntOffering.java @@ -1,19 +1,18 @@ package mage.cards.b; -import java.util.UUID; import mage.abilities.costs.common.SacrificeTargetCost; -import mage.abilities.dynamicvalue.common.SacrificeCostConvertedMana; +import mage.abilities.dynamicvalue.common.SacrificeCostManaValue; import mage.abilities.effects.mana.AddManaInAnyCombinationEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.ColoredManaSymbol; import mage.filter.StaticFilters; -import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; /** - * * @author Topher */ public final class BurntOffering extends CardImpl { @@ -24,7 +23,7 @@ public final class BurntOffering extends CardImpl { //As an additional cost to cast Burnt Offering, sacrifice a creature. this.getSpellAbility().addCost(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT)); //Add an amount of {B} and/or {R} equal to the sacrificed creature's converted mana cost. - SacrificeCostConvertedMana xValue = new SacrificeCostConvertedMana("creature"); + SacrificeCostManaValue xValue = SacrificeCostManaValue.CREATURE; this.getSpellAbility().addEffect(new AddManaInAnyCombinationEffect( xValue, xValue, ColoredManaSymbol.B, ColoredManaSymbol.R )); diff --git a/Mage.Sets/src/mage/cards/d/DemonOfFatesDesign.java b/Mage.Sets/src/mage/cards/d/DemonOfFatesDesign.java new file mode 100644 index 00000000000..28458bb7cf1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DemonOfFatesDesign.java @@ -0,0 +1,251 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.CompoundCondition; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceIsSpellCondition; +import mage.abilities.costs.AlternativeCostSourceAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.DynamicCost; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.SacrificeCostManaValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.hint.common.ConditionPermanentHint; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author Susucr + */ +public final class DemonOfFatesDesign extends CardImpl { + + public DemonOfFatesDesign(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{4}{B}{B}"); + + this.subtype.add(SubType.DEMON); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Once during each of your turns, you may cast an enchantment spell by paying life equal to its mana value rather than paying its mana cost. + this.addAbility( + new SimpleStaticAbility(new DemonOfFatesDesignCastEffect()) + .addHint(new ConditionPermanentHint( + DemonOfFatesDesignCondition.instance, + "Can cast with alternative cost this turn.", + null, + "Cannot cast with alternative cost this turn.", + null, + true + )), + new DemonOfFatesDesignWatcher() + ); + + // {2}{B}, Sacrifice another enchantment: Demon of Fate's Design gets +X/+0 until end of turn, where X is the sacrificed enchantment's mana value. + Ability ability = new SimpleActivatedAbility(new BoostSourceEffect( + SacrificeCostManaValue.ENCHANTMENT, + StaticValue.get(0), Duration.EndOfTurn + ), new ManaCostsImpl<>("{2}{B}")); + ability.addCost(new SacrificeTargetCost( + StaticFilters.FILTER_CONTROLLED_ANOTHER_ENCHANTMENT_SHORT_TEXT + )); + this.addAbility(ability); + } + + private DemonOfFatesDesign(final DemonOfFatesDesign card) { + super(card); + } + + @Override + public DemonOfFatesDesign copy() { + return new DemonOfFatesDesign(this); + } +} + +class DemonOfFatesDesignPayLifeCost extends PayLifeCost { + + private MageObjectReference mor; + + DemonOfFatesDesignPayLifeCost(int amount) { + super(amount); + } + + private DemonOfFatesDesignPayLifeCost(final DemonOfFatesDesignPayLifeCost cost) { + super(cost); + this.mor = cost.mor; + } + + @Override + public DemonOfFatesDesignPayLifeCost copy() { + return new DemonOfFatesDesignPayLifeCost(this); + } + + MageObjectReference getMor() { + return this.mor; + } + + void setMor(MageObjectReference mor) { + this.mor = mor; + } + +} + +enum DemonOfFatesDesignCost implements DynamicCost { + instance; + + @Override + public Cost getCost(Ability ability, Game game) { + return new DemonOfFatesDesignPayLifeCost(ability.getManaCosts().manaValue()); + } + + @Override + public String getText(Ability ability, Game game) { + return "Pay " + ability.getManaCosts().manaValue() + " life rather than " + + ability.getManaCosts().getText() + '?'; + } +} + +class DemonOfFatesDesignAlternativeCostSourceAbility extends AlternativeCostSourceAbility { + + private MageObjectReference mor; + + DemonOfFatesDesignAlternativeCostSourceAbility() { + super( + new CompoundCondition(SourceIsSpellCondition.instance, IsBeingCastFromHandCondition.instance), + null, StaticFilters.FILTER_CARD_ENCHANTMENT, + true, DemonOfFatesDesignCost.instance + ); + } + + private DemonOfFatesDesignAlternativeCostSourceAbility(final DemonOfFatesDesignAlternativeCostSourceAbility effect) { + super(effect); + } + + @Override + public DemonOfFatesDesignAlternativeCostSourceAbility copy() { + return new DemonOfFatesDesignAlternativeCostSourceAbility(this); + } + + @Override + protected void doActivate(Game game, Ability ability) { + super.doActivate(game, ability); + for (Cost cost : ability.getCosts()) { + if (cost != null && cost instanceof DemonOfFatesDesignPayLifeCost) { + ((DemonOfFatesDesignPayLifeCost) cost).setMor(getMor(game)); + } + } + } + + MageObjectReference getMor(Game game) { + return new MageObjectReference(getSourceObject(game), game); + } +} + +class DemonOfFatesDesignCastEffect extends ContinuousEffectImpl { + + private final DemonOfFatesDesignAlternativeCostSourceAbility alternativeCastingCostAbility + = new DemonOfFatesDesignAlternativeCostSourceAbility(); + + DemonOfFatesDesignCastEffect() { + super(Duration.WhileOnBattlefield, Layer.RulesEffects, SubLayer.NA, Outcome.Neutral); + this.staticText = "once during each of your turns, you may cast an enchantment spell by paying life " + + "equal to its mana value rather than paying its mana cost."; + } + + private DemonOfFatesDesignCastEffect(final DemonOfFatesDesignCastEffect effect) { + super(effect); + } + + @Override + public DemonOfFatesDesignCastEffect copy() { + return new DemonOfFatesDesignCastEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + DemonOfFatesDesignWatcher watcher = game.getState().getWatcher(DemonOfFatesDesignWatcher.class); + if (controller == null || watcher == null) { + return false; + } + + alternativeCastingCostAbility.setSourceId(source.getSourceId()); + if (!watcher.canAbilityBeUsed(game, source, alternativeCastingCostAbility.getMor(game))) { + return false; + } + + controller.getAlternativeSourceCosts().add(alternativeCastingCostAbility); + return true; + } +} + +class DemonOfFatesDesignWatcher extends Watcher { + + private final Set usedFrom = new HashSet<>(); + + public DemonOfFatesDesignWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.SPELL_CAST) { + Spell spell = game.getSpell(event.getTargetId()); + if (spell != null) { + for (Cost cost : spell.getStackAbility().getCosts()) { + if (cost != null && cost instanceof DemonOfFatesDesignPayLifeCost) { + usedFrom.add(((DemonOfFatesDesignPayLifeCost) cost).getMor()); + } + } + } + } + } + + @Override + public void reset() { + super.reset(); + usedFrom.clear(); + } + + public boolean canAbilityBeUsed(Game game, Ability source, MageObjectReference mor) { + return game.isActivePlayer(source.getControllerId()) && !usedFrom.contains(mor); + } +} + + +enum DemonOfFatesDesignCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + DemonOfFatesDesignWatcher watcher = game.getState().getWatcher(DemonOfFatesDesignWatcher.class); + return watcher != null + && watcher.canAbilityBeUsed(game, source, new MageObjectReference(source.getSourceId(), game)); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/f/FaithHealer.java b/Mage.Sets/src/mage/cards/f/FaithHealer.java index 8ee77650013..132cb1d6d82 100644 --- a/Mage.Sets/src/mage/cards/f/FaithHealer.java +++ b/Mage.Sets/src/mage/cards/f/FaithHealer.java @@ -1,11 +1,10 @@ package mage.cards.f; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeTargetCost; -import mage.abilities.dynamicvalue.common.SacrificeCostConvertedMana; +import mage.abilities.dynamicvalue.common.SacrificeCostManaValue; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -15,22 +14,23 @@ import mage.constants.Zone; import mage.filter.common.FilterControlledEnchantmentPermanent; import mage.target.common.TargetControlledPermanent; +import java.util.UUID; + /** - * * @author LoneFox */ public final class FaithHealer extends CardImpl { public FaithHealer(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.CLERIC); this.power = new MageInt(1); this.toughness = new MageInt(1); // Sacrifice an enchantment: You gain life equal to the sacrificed enchantment's converted mana cost. - this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainLifeEffect(new SacrificeCostConvertedMana("enchantment")), - new SacrificeTargetCost(new TargetControlledPermanent(new FilterControlledEnchantmentPermanent())))); + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainLifeEffect(SacrificeCostManaValue.ENCHANTMENT), + new SacrificeTargetCost(new TargetControlledPermanent(new FilterControlledEnchantmentPermanent())))); } private FaithHealer(final FaithHealer card) { diff --git a/Mage.Sets/src/mage/cards/f/ForgeArmor.java b/Mage.Sets/src/mage/cards/f/ForgeArmor.java index 41d7ff37cbc..c383c32f672 100644 --- a/Mage.Sets/src/mage/cards/f/ForgeArmor.java +++ b/Mage.Sets/src/mage/cards/f/ForgeArmor.java @@ -2,7 +2,7 @@ package mage.cards.f; import mage.abilities.costs.common.SacrificeTargetCost; -import mage.abilities.dynamicvalue.common.SacrificeCostConvertedMana; +import mage.abilities.dynamicvalue.common.SacrificeCostManaValue; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -15,7 +15,6 @@ import mage.target.common.TargetCreaturePermanent; import java.util.UUID; /** - * * @author LoneFox */ public final class ForgeArmor extends CardImpl { @@ -27,7 +26,7 @@ public final class ForgeArmor extends CardImpl { this.getSpellAbility().addCost(new SacrificeTargetCost(new TargetControlledPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT_AN))); // Put X +1/+1 counters on target creature, where X is the sacrificed artifact's converted mana cost. this.getSpellAbility().addEffect(new AddCountersTargetEffect( - CounterType.P1P1.createInstance(), new SacrificeCostConvertedMana("artifact"))); + CounterType.P1P1.createInstance(), SacrificeCostManaValue.ARTIFACT)); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } diff --git a/Mage.Sets/src/mage/cards/h/HomaridSpawningBed.java b/Mage.Sets/src/mage/cards/h/HomaridSpawningBed.java index 72b77411e1e..71596173a4e 100644 --- a/Mage.Sets/src/mage/cards/h/HomaridSpawningBed.java +++ b/Mage.Sets/src/mage/cards/h/HomaridSpawningBed.java @@ -1,13 +1,12 @@ package mage.cards.h; -import java.util.UUID; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.dynamicvalue.common.SacrificeCostConvertedMana; +import mage.abilities.dynamicvalue.common.SacrificeCostManaValue; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -18,8 +17,9 @@ import mage.filter.predicate.mageobject.ColorPredicate; import mage.game.permanent.token.CamaridToken; import mage.target.common.TargetControlledCreaturePermanent; +import java.util.UUID; + /** - * * @author fireshoes */ public final class HomaridSpawningBed extends CardImpl { @@ -34,7 +34,7 @@ public final class HomaridSpawningBed extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{U}{U}"); // {1}{U}{U}, Sacrifice a blue creature: create X 1/1 blue Camarid creature tokens, where X is the sacrificed creature's converted mana cost. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CreateTokenEffect(new CamaridToken(), new SacrificeCostConvertedMana("creature")), + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CreateTokenEffect(new CamaridToken(), SacrificeCostManaValue.CREATURE), new ManaCostsImpl<>("{1}{U}{U}")); ability.addCost(new SacrificeTargetCost(new TargetControlledCreaturePermanent(filter))); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/i/IlluminorSzeras.java b/Mage.Sets/src/mage/cards/i/IlluminorSzeras.java index 2a2553be0e4..03a334df08f 100644 --- a/Mage.Sets/src/mage/cards/i/IlluminorSzeras.java +++ b/Mage.Sets/src/mage/cards/i/IlluminorSzeras.java @@ -7,7 +7,7 @@ import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.HighestCMCOfPermanentValue; -import mage.abilities.dynamicvalue.common.SacrificeCostConvertedMana; +import mage.abilities.dynamicvalue.common.SacrificeCostManaValue; import mage.abilities.mana.DynamicManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -23,7 +23,7 @@ import java.util.UUID; */ public final class IlluminorSzeras extends CardImpl { - private static final DynamicValue xValue = new SacrificeCostConvertedMana("creature"); + private static final DynamicValue xValue = SacrificeCostManaValue.CREATURE; private static final DynamicValue netValue = new HighestCMCOfPermanentValue( StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE, true ); diff --git a/Mage.Sets/src/mage/cards/m/MorbidCuriosity.java b/Mage.Sets/src/mage/cards/m/MorbidCuriosity.java index 64b2020bb1f..d2faa1d5b81 100644 --- a/Mage.Sets/src/mage/cards/m/MorbidCuriosity.java +++ b/Mage.Sets/src/mage/cards/m/MorbidCuriosity.java @@ -2,7 +2,7 @@ package mage.cards.m; import mage.abilities.costs.common.SacrificeTargetCost; -import mage.abilities.dynamicvalue.common.SacrificeCostConvertedMana; +import mage.abilities.dynamicvalue.common.SacrificeCostManaValue; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -24,7 +24,7 @@ public final class MorbidCuriosity extends CardImpl { // Draw cards equal to the converted mana cost of the sacrificed permanent. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect( - new SacrificeCostConvertedMana("permanent") + SacrificeCostManaValue.PERMANENT ).setText("draw cards equal to the mana value of the sacrificed permanent")); } diff --git a/Mage.Sets/src/mage/cards/p/PriestOfYawgmoth.java b/Mage.Sets/src/mage/cards/p/PriestOfYawgmoth.java index fcf8f65ba77..c2835ddcc1f 100644 --- a/Mage.Sets/src/mage/cards/p/PriestOfYawgmoth.java +++ b/Mage.Sets/src/mage/cards/p/PriestOfYawgmoth.java @@ -1,14 +1,13 @@ package mage.cards.p; -import java.util.UUID; import mage.MageInt; import mage.Mana; import mage.abilities.Ability; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.dynamicvalue.common.HighestCMCOfPermanentValue; -import mage.abilities.dynamicvalue.common.SacrificeCostConvertedMana; +import mage.abilities.dynamicvalue.common.SacrificeCostManaValue; import mage.abilities.mana.DynamicManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -17,14 +16,15 @@ import mage.constants.SubType; import mage.filter.StaticFilters; import mage.target.common.TargetControlledPermanent; +import java.util.UUID; + /** - * * @author LoneFox */ public final class PriestOfYawgmoth extends CardImpl { public PriestOfYawgmoth(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); this.subtype.add(SubType.PHYREXIAN); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.CLERIC); @@ -32,7 +32,7 @@ public final class PriestOfYawgmoth extends CardImpl { this.toughness = new MageInt(2); // {T}, Sacrifice an artifact: Add an amount of {B} equal to the sacrificed artifact's converted mana cost. - Ability ability = new DynamicManaAbility(Mana.BlackMana(1), new SacrificeCostConvertedMana("artifact"), + Ability ability = new DynamicManaAbility(Mana.BlackMana(1), SacrificeCostManaValue.ARTIFACT, new TapSourceCost(), "add an amount of {B} equal to the sacrificed artifact's mana value", false, diff --git a/Mage.Sets/src/mage/cards/s/Sacrifice.java b/Mage.Sets/src/mage/cards/s/Sacrifice.java index 8ce45589ca7..4ffa628ccdb 100644 --- a/Mage.Sets/src/mage/cards/s/Sacrifice.java +++ b/Mage.Sets/src/mage/cards/s/Sacrifice.java @@ -1,19 +1,18 @@ package mage.cards.s; -import java.util.UUID; import mage.Mana; import mage.abilities.costs.common.SacrificeTargetCost; -import mage.abilities.dynamicvalue.common.SacrificeCostConvertedMana; +import mage.abilities.dynamicvalue.common.SacrificeCostManaValue; import mage.abilities.effects.mana.DynamicManaEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.StaticFilters; -import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; /** - * * @author LoneFox */ public final class Sacrifice extends CardImpl { @@ -24,7 +23,7 @@ public final class Sacrifice extends CardImpl { // As an additional cost to cast Sacrifice, sacrifice a creature. this.getSpellAbility().addCost(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT)); // Add an amount of {B} equal to the sacrificed creature's converted mana cost. - this.getSpellAbility().addEffect(new DynamicManaEffect(Mana.BlackMana(1), new SacrificeCostConvertedMana("creature"), + this.getSpellAbility().addEffect(new DynamicManaEffect(Mana.BlackMana(1), SacrificeCostManaValue.CREATURE, "add an amount of {B} equal to the sacrificed creature's mana value")); } diff --git a/Mage.Sets/src/mage/cards/s/SoldeviAdnate.java b/Mage.Sets/src/mage/cards/s/SoldeviAdnate.java index f3efd81b6a4..f521f14b71a 100644 --- a/Mage.Sets/src/mage/cards/s/SoldeviAdnate.java +++ b/Mage.Sets/src/mage/cards/s/SoldeviAdnate.java @@ -1,7 +1,6 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.Mana; import mage.ObjectColor; @@ -9,7 +8,7 @@ import mage.abilities.Ability; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.dynamicvalue.common.HighestCMCOfPermanentValue; -import mage.abilities.dynamicvalue.common.SacrificeCostConvertedMana; +import mage.abilities.dynamicvalue.common.SacrificeCostManaValue; import mage.abilities.mana.DynamicManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -20,8 +19,9 @@ import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorPredicate; import mage.target.common.TargetControlledPermanent; +import java.util.UUID; + /** - * * @author LoneFox */ public final class SoldeviAdnate extends CardImpl { @@ -33,15 +33,15 @@ public final class SoldeviAdnate extends CardImpl { } public SoldeviAdnate(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.CLERIC); this.power = new MageInt(1); this.toughness = new MageInt(2); // {T}, Sacrifice a black or artifact creature: Add an amount of {B} equal to the sacrificed creature's converted mana cost. - Ability ability = new DynamicManaAbility(Mana.BlackMana(1), new SacrificeCostConvertedMana("creature"), new TapSourceCost(), - "add an amount of {B} equal to the sacrificed creature's mana value" , false, + Ability ability = new DynamicManaAbility(Mana.BlackMana(1), SacrificeCostManaValue.CREATURE, new TapSourceCost(), + "add an amount of {B} equal to the sacrificed creature's mana value", false, new HighestCMCOfPermanentValue(filter, true)); ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent(filter))); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/sets/CommanderMasters.java b/Mage.Sets/src/mage/sets/CommanderMasters.java index e9670a37e1b..cce04546a50 100644 --- a/Mage.Sets/src/mage/sets/CommanderMasters.java +++ b/Mage.Sets/src/mage/sets/CommanderMasters.java @@ -158,6 +158,7 @@ public final class CommanderMasters extends ExpansionSet { cards.add(new SetCardInfo("Deep Analysis", 86, Rarity.COMMON, mage.cards.d.DeepAnalysis.class)); cards.add(new SetCardInfo("Deepglow Skate", 844, Rarity.RARE, mage.cards.d.DeepglowSkate.class)); cards.add(new SetCardInfo("Deflecting Swat", 214, Rarity.RARE, mage.cards.d.DeflectingSwat.class)); + cards.add(new SetCardInfo("Demon of Fate's Design", 731, Rarity.RARE, mage.cards.d.DemonOfFatesDesign.class)); cards.add(new SetCardInfo("Demon's Disciple", 149, Rarity.COMMON, mage.cards.d.DemonsDisciple.class)); cards.add(new SetCardInfo("Demonic Tutor", 150, Rarity.MYTHIC, mage.cards.d.DemonicTutor.class)); cards.add(new SetCardInfo("Demonlord Belzenlok", 151, Rarity.RARE, mage.cards.d.DemonlordBelzenlok.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/cmm/DemonOfFatesDesignTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/cmm/DemonOfFatesDesignTest.java new file mode 100644 index 00000000000..b98e9239d71 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/cmm/DemonOfFatesDesignTest.java @@ -0,0 +1,262 @@ +package org.mage.test.cards.single.cmm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class DemonOfFatesDesignTest extends CardTestPlayerBase { + + // Demon of Fate's Design + // {4}{B}{B} + // Enchantment Creature — Demon + // + // Flying, trample + // Once during each of your turns, you may cast an enchantment spell by paying life equal to its mana value rather than paying its mana cost. + // {2}{B}, Sacrifice another enchantment: Demon of Fate’s Design gets +X/+0 until end of turn, where X is the sacrificed enchantment’s mana value. + private static final String demon = "Demon of Fate's Design"; + + @Test + public void CastForLife() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, demon); + + addCard(Zone.HAND, playerA, "Glorious Anthem"); // Enchantment {1}{W}{W} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Glorious Anthem"); + setChoice(playerA, true); // yes to alt cast + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Glorious Anthem", 1); + assertLife(playerA, 20 - 3); + } + + @Test + public void SayNoToTheDemon() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, demon); + + addCard(Zone.HAND, playerA, "Glorious Anthem"); // Enchantment {1}{W}{W} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Glorious Anthem"); + setChoice(playerA, false); // no to alt cast + + boolean hadError = false; + try { + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + } catch (AssertionError e) { + hadError = true; + assert e.getMessage().equals("Can't find ability to activate command: Cast Glorious Anthem"); + } finally { + assert hadError; + } + } + + @Test + public void CantCastBothInSameTurn() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, demon); + + addCard(Zone.HAND, playerA, "Glorious Anthem"); // Enchantment {1}{W}{W} + addCard(Zone.HAND, playerA, "Absolute Law"); // Enchantment {1}{W} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Glorious Anthem"); + setChoice(playerA, true); // yes to alt cast + + checkPlayableAbility("playable", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast Absolute Law", false); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Glorious Anthem", 1); + assertLife(playerA, 20 - 3); + } + + @Test + public void CantCastDuringOpponentTurn() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, demon); + + addCard(Zone.HAND, playerA, "Dictate of Kruphix"); // Enchantment {1}{U}{U} Flash + + checkPlayableAbility("playable", 2, PhaseStep.UPKEEP, playerA, "Cast Dictate of Kruphix", false); + + setStopAt(2, PhaseStep.DRAW); + execute(); + + assertLife(playerA, 20); + } + + @Test + public void CantCastIfOpponentHasDemon() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, demon); + + addCard(Zone.HAND, playerA, "Glorious Anthem"); // Enchantment {1}{W}{W} + + checkPlayableAbility("playable", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Glorious Anthem", false); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertHandCount(playerA, "Glorious Anthem", 1); + assertLife(playerA, 20); + } + + @Test + public void CanCastBothInDifferentTurn() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, demon, 1); + + addCard(Zone.HAND, playerA, "Glorious Anthem"); // Enchantment {1}{W}{W} + addCard(Zone.HAND, playerA, "Absolute Law"); // Enchantment {1}{W} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Glorious Anthem"); + setChoice(playerA, true); // yes to alt cast + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Glorious Anthem", 1); + assertLife(playerA, 20 - 3); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Absolute Law"); + setChoice(playerA, true); // yes to alt cast + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Absolute Law", 1); + assertLife(playerA, 20 - 3 - 2); + } + + @Test + public void BlinkTest() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, demon); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + + // Instant {W} + // Exile target creature you control, then return that card to the battlefield under your control. + addCard(Zone.HAND, playerA, "Cloudshift"); + addCard(Zone.HAND, playerA, "Glorious Anthem"); // Enchantment {1}{W}{W} + addCard(Zone.HAND, playerA, "Absolute Law"); // Enchantment {1}{W} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Glorious Anthem", true); + setChoice(playerA, true); // yes to alt cast + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cloudshift", demon, true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Absolute Law"); + setChoice(playerA, true); // yes to alt cast + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Glorious Anthem", 1); + assertPermanentCount(playerA, "Absolute Law", 1); + assertLife(playerA, 20 - 3 - 2); + } + + @Test + public void UnsubstantiateTest() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, demon, 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + // Instant {U}{U} + // Return target spell or creature to its owner’s hand. + addCard(Zone.HAND, playerA, "Unsubstantiate"); + addCard(Zone.HAND, playerA, "Glorious Anthem"); // Enchantment {1}{W}{W} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Glorious Anthem"); + setChoice(playerA, true); // yes to alt cast + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Unsubstantiate", "Glorious Anthem"); + + // Did not keep the alt cost from the first cast + checkPlayableAbility("playable", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Glorious Anthem", false); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertHandCount(playerA, "Glorious Anthem", 1); + assertLife(playerA, 20 - 3); + } + + @Test + public void DoubleDemonDoubleCast() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, demon, 2); + + addCard(Zone.HAND, playerA, "Glorious Anthem"); // Enchantment {1}{W}{W} + addCard(Zone.HAND, playerA, "Absolute Law"); // Enchantment {1}{W} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Glorious Anthem", true); + setChoice(playerA, false); // no to the first one + setChoice(playerA, true); // yes to second one + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Absolute Law"); + setChoice(playerA, true); // yes to first one + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Glorious Anthem", 1); + assertPermanentCount(playerA, "Absolute Law", 1); + assertLife(playerA, 20 - 3 - 2); + } + + //118.9a Only one alternative cost can be applied to any one spell as it’s being cast. + @Test + public void DoubleDemon() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, demon, 2); + + addCard(Zone.HAND, playerA, "Glorious Anthem"); // Enchantment {1}{W}{W} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Glorious Anthem"); + setChoice(playerA, true); // yes to alt cast of first demon + // second demon has no choice to make. + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Glorious Anthem", 1); + assertLife(playerA, 20 - 3); + } + + @Test + public void DoubleDemonDoubleCast2() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, demon, 2); + + addCard(Zone.HAND, playerA, "Glorious Anthem"); // Enchantment {1}{W}{W} + addCard(Zone.HAND, playerA, "Absolute Law"); // Enchantment {1}{W} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Glorious Anthem", true); + setChoice(playerA, true); // yes to the first one + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Absolute Law"); + setChoice(playerA, true); // yes to second one + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Glorious Anthem", 1); + assertPermanentCount(playerA, "Absolute Law", 1); + assertLife(playerA, 20 - 3 - 2); + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/IsBeingCastFromHandCondition.java b/Mage/src/main/java/mage/abilities/condition/common/IsBeingCastFromHandCondition.java new file mode 100644 index 00000000000..fe3ca1e23ce --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/IsBeingCastFromHandCondition.java @@ -0,0 +1,37 @@ +package mage.abilities.condition.common; + + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.cards.AdventureCardSpell; +import mage.cards.Card; +import mage.cards.ModalDoubleFacedCardHalf; +import mage.cards.SplitCardHalf; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.stack.Spell; + +import java.util.UUID; + +public enum IsBeingCastFromHandCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + MageObject object = game.getObject(source); + if (object instanceof SplitCardHalf || object instanceof AdventureCardSpell || object instanceof ModalDoubleFacedCardHalf) { + UUID mainCardId = ((Card) object).getMainCard().getId(); + object = game.getObject(mainCardId); + } + if (object instanceof Spell) { // needed to check if it can be cast by alternate cost + Spell spell = (Spell) object; + return Zone.HAND.equals(spell.getFromZone()); + } + if (object instanceof Card) { // needed for the check what's playable + Card card = (Card) object; + return game.getPlayer(card.getOwnerId()).getHand().get(card.getId(), game) != null; + } + return false; + } +} diff --git a/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java b/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java index 41b8f228473..c50c1a2f56f 100644 --- a/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java +++ b/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java @@ -24,6 +24,7 @@ import java.util.stream.Collectors; public class AlternativeCostSourceAbility extends StaticAbility implements AlternativeSourceCosts { private static final String ALTERNATIVE_COST_ACTIVATION_KEY = "AlternativeCostActivated"; + private static final String ALTERNATIVE_DYNAMIC_COST_ACTIVATED_KEY = "AlternativeDynamicCostActivated"; private Costs alternateCosts = new CostsImpl<>(); protected Condition condition; @@ -162,8 +163,13 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter } } + // Those cost have been paid, we want to store them. + if (dynamicCost != null) { + rememberDynamicCost(game, ability, alternativeCostsToCheck); + } + // save activated status - game.getState().setValue(getActivatedKey(ability), Boolean.TRUE); + doActivate(game, ability); } else { return false; } @@ -174,6 +180,10 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter return isActivated(ability, game); } + protected void doActivate(Game game, Ability ability) { + game.getState().setValue(getActivatedKey(ability), Boolean.TRUE); + } + private String getActivatedKey(Ability source) { return getActivatedKey(this.getOriginalId(), source.getSourceId(), source.getSourceObjectZoneChangeCounter()); } @@ -184,6 +194,20 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter return ALTERNATIVE_COST_ACTIVATION_KEY + "_" + alternativeCostOriginalId + "_" /*+ sourceId + "_"*/ + sourceZCC; } + private void rememberDynamicCost(Game game, Ability ability, Costs costs) { + game.getState().setValue(getDynamicCostActivatedKey(ability), costs); + } + + private String getDynamicCostActivatedKey(Ability source) { + return getDynamicCostActivatedKey(this.getOriginalId(), source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + } + + private static String getDynamicCostActivatedKey(UUID alternativeCostOriginalId, UUID sourceId, int sourceZCC) { + // can't use sourceId cause copied cards are different... + // TODO: enable sourceId after copy card fix (it must copy cards with all related game state values) + return ALTERNATIVE_DYNAMIC_COST_ACTIVATED_KEY + "_" + alternativeCostOriginalId + "_" /*+ sourceId + "_"*/ + sourceZCC; + } + /** * Search activated status of alternative cost. *

@@ -210,8 +234,10 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter public boolean isActivated(Ability source, Game game) { Costs alternativeCostsToCheck; if (dynamicCost != null) { - alternativeCostsToCheck = new CostsImpl<>(); - alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(source, game))); + alternativeCostsToCheck = (Costs) game.getState().getValue(getDynamicCostActivatedKey(source)); + if (alternativeCostsToCheck == null) { + return false; + } } else { alternativeCostsToCheck = this.alternateCosts; } diff --git a/Mage/src/main/java/mage/abilities/costs/common/PayLifeCost.java b/Mage/src/main/java/mage/abilities/costs/common/PayLifeCost.java index 25f910b5ad3..a0ea7366138 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/PayLifeCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/PayLifeCost.java @@ -28,7 +28,7 @@ public class PayLifeCost extends CostImpl { this.text = "pay " + text; } - public PayLifeCost(PayLifeCost cost) { + protected PayLifeCost(final PayLifeCost cost) { super(cost); this.amount = cost.amount.copy(); } diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/SacrificeCostConvertedMana.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/SacrificeCostManaValue.java similarity index 64% rename from Mage/src/main/java/mage/abilities/dynamicvalue/common/SacrificeCostConvertedMana.java rename to Mage/src/main/java/mage/abilities/dynamicvalue/common/SacrificeCostManaValue.java index d12a859e251..b57f1de04b3 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/SacrificeCostConvertedMana.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/SacrificeCostManaValue.java @@ -10,27 +10,27 @@ import mage.game.Game; import mage.game.permanent.Permanent; /** - * @author LoneFox + * @author LoneFox, Susucr */ -public class SacrificeCostConvertedMana implements DynamicValue { +public enum SacrificeCostManaValue implements DynamicValue { + CREATURE("creature"), + ENCHANTMENT("enchantment"), + ARTIFACT("artifact"), + PERMANENT("permanent"); private final String type; - public SacrificeCostConvertedMana(String type) { + private SacrificeCostManaValue(String type) { this.type = type; } - public SacrificeCostConvertedMana(SacrificeCostConvertedMana value) { - this.type = value.type; - } - @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - for(Cost cost : sourceAbility.getCosts()) { - if(cost instanceof SacrificeTargetCost) { + for (Cost cost : sourceAbility.getCosts()) { + if (cost instanceof SacrificeTargetCost) { SacrificeTargetCost sacrificeCost = (SacrificeTargetCost) cost; int totalCMC = 0; - for(Permanent permanent : sacrificeCost.getPermanents()) { + for (Permanent permanent : sacrificeCost.getPermanents()) { totalCMC += permanent.getManaValue(); } return totalCMC; @@ -40,8 +40,8 @@ public class SacrificeCostConvertedMana implements DynamicValue { } @Override - public SacrificeCostConvertedMana copy() { - return new SacrificeCostConvertedMana(this); + public SacrificeCostManaValue copy() { + return this; } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/CastFromHandWithoutPayingManaCostEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/CastFromHandWithoutPayingManaCostEffect.java index da6b9ddfdb9..3370b3143b7 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/CastFromHandWithoutPayingManaCostEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/CastFromHandWithoutPayingManaCostEffect.java @@ -1,21 +1,19 @@ package mage.abilities.effects.common.continuous; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.condition.CompoundCondition; import mage.abilities.condition.Condition; +import mage.abilities.condition.common.IsBeingCastFromHandCondition; import mage.abilities.condition.common.SourceIsSpellCondition; import mage.abilities.costs.AlternativeCostSourceAbility; import mage.abilities.effects.ContinuousEffectImpl; -import mage.cards.AdventureCardSpell; -import mage.cards.Card; -import mage.cards.ModalDoubleFacedCardHalf; -import mage.cards.SplitCardHalf; -import mage.constants.*; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.SubLayer; import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.game.Game; -import mage.game.stack.Spell; import mage.players.Player; import java.util.UUID; @@ -81,26 +79,4 @@ public class CastFromHandWithoutPayingManaCostEffect extends ContinuousEffectImp public boolean hasLayer(Layer layer) { return layer == Layer.RulesEffects; } -} - -enum IsBeingCastFromHandCondition implements Condition { - instance; - - @Override - public boolean apply(Game game, Ability source) { - MageObject object = game.getObject(source); - if (object instanceof SplitCardHalf || object instanceof AdventureCardSpell || object instanceof ModalDoubleFacedCardHalf) { - UUID mainCardId = ((Card) object).getMainCard().getId(); - object = game.getObject(mainCardId); - } - if (object instanceof Spell) { // needed to check if it can be cast by alternate cost - Spell spell = (Spell) object; - return Zone.HAND.equals(spell.getFromZone()); - } - if (object instanceof Card) { // needed for the check what's playable - Card card = (Card) object; - return game.getPlayer(card.getOwnerId()).getHand().get(card.getId(), game) != null; - } - return false; - } -} +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/hint/ConditionHint.java b/Mage/src/main/java/mage/abilities/hint/ConditionHint.java index 6347054b2f1..6e20561bb41 100644 --- a/Mage/src/main/java/mage/abilities/hint/ConditionHint.java +++ b/Mage/src/main/java/mage/abilities/hint/ConditionHint.java @@ -36,7 +36,7 @@ public class ConditionHint implements Hint { this.useIcons = useIcons; } - private ConditionHint(final ConditionHint hint) { + protected ConditionHint(final ConditionHint hint) { this.condition = hint.condition; this.trueText = hint.trueText; this.trueColor = hint.trueColor; diff --git a/Mage/src/main/java/mage/abilities/hint/common/ConditionPermanentHint.java b/Mage/src/main/java/mage/abilities/hint/common/ConditionPermanentHint.java new file mode 100644 index 00000000000..d45c3b25bc3 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/hint/common/ConditionPermanentHint.java @@ -0,0 +1,47 @@ +package mage.abilities.hint.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.game.Game; + +import java.awt.*; + +/** + * ConditionHint that is only shown on permanents. + * + * @author Susucr + */ +public class ConditionPermanentHint extends ConditionHint { + + public ConditionPermanentHint(Condition condition) { + super(condition); + } + + public ConditionPermanentHint(Condition condition, String textWithIcons) { + super(condition, textWithIcons); + } + + public ConditionPermanentHint(Condition condition, String trueText, Color trueColor, String falseText, Color falseColor, Boolean useIcons) { + super(condition, trueText, trueColor, falseText, falseColor, useIcons); + } + + private ConditionPermanentHint(final ConditionPermanentHint hint) { + super(hint); + } + + @Override + public String getText(Game game, Ability ability) { + if (game.getPermanent(ability.getSourceId()) == null) { + return ""; + } + + return super.getText(game, ability); + } + + @Override + public Hint copy() { + return new ConditionPermanentHint(this); + } +} diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 08fc43e6240..c804e7b5539 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -1212,7 +1212,7 @@ public abstract class PlayerImpl implements Player, Serializable { game.fireEvent(castEvent); if (spell.activate(game, noMana)) { GameEvent castedEvent = GameEvent.getEvent(GameEvent.EventType.SPELL_CAST, - spell.getSpellAbility().getId(), spell.getSpellAbility(), playerId, approvingObject); + ability.getId(), ability, playerId, approvingObject); castedEvent.setZone(fromZone); game.fireEvent(castedEvent); if (!game.isSimulation()) { @@ -4721,7 +4721,7 @@ public abstract class PlayerImpl implements Player, Serializable { boolean chooseOrder = false; if (userData.askMoveToGraveOrder() && (cards.size() > 1)) { chooseOrder = choosingPlayer.chooseUse(Outcome.Neutral, - "Choose the order in which the cards go to the graveyard?", source, game); + "Choose the order in which the cards go to the graveyard?", source, game); } if (chooseOrder) { TargetCard target = new TargetCard(fromZone, @@ -5145,13 +5145,13 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public Permanent getRingBearer(Game game) { return game.getBattlefield() - .getActivePermanents( - StaticFilters.FILTER_CONTROLLED_RINGBEARER, - getId(),null, game) - .stream() - .filter(Objects::nonNull) - .findFirst() - .orElse(null); + .getActivePermanents( + StaticFilters.FILTER_CONTROLLED_RINGBEARER, + getId(), null, game) + .stream() + .filter(Objects::nonNull) + .findFirst() + .orElse(null); } // 701.52a Certain spells and abilities have the text “the Ring tempts you.” Each time the Ring tempts @@ -5163,11 +5163,11 @@ public abstract class PlayerImpl implements Player, Serializable { UUID currentBearerId = currentBearer == null ? null : currentBearer.getId(); List ids = game.getBattlefield() - .getActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURE, getId(), null, game) - .stream() - .filter(Objects::nonNull) - .map(p -> p.getId()) - .collect(Collectors.toList()); + .getActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURE, getId(), null, game) + .stream() + .filter(Objects::nonNull) + .map(p -> p.getId()) + .collect(Collectors.toList()); if (ids.isEmpty()) { game.informPlayers(getLogName() + " has no creature to be Ring-bearer."); @@ -5196,8 +5196,7 @@ public abstract class PlayerImpl implements Player, Serializable { choose(Outcome.Neutral, target, null, game); newBearerId = target.getFirstTarget(); - } - else { + } else { newBearerId = currentBearerId; } } @@ -5211,11 +5210,11 @@ public abstract class PlayerImpl implements Player, Serializable { // or abilities that care about which creature was chosen as your Ring-bearer. // (2023-06-16) game.informPlayers(getLogName() + " did not choose a new Ring-bearer. " + - "It is still " + (currentBearer == null ? "" : currentBearer.getLogName()) + "."); + "It is still " + (currentBearer == null ? "" : currentBearer.getLogName()) + "."); game.fireEvent(GameEvent.getEvent(GameEvent.EventType.RING_BEARER_CHOSEN, currentBearerId, null, getId())); } else { Permanent ringBearer = game.getPermanent(newBearerId); - if(ringBearer != null){ + if (ringBearer != null) { // The setRingBearer method is taking care of removing // the status from the current ring bearer, if existing. ringBearer.setRingBearer(game, true);