diff --git a/Mage.Sets/src/mage/cards/m/MoltenDisaster.java b/Mage.Sets/src/mage/cards/m/MoltenDisaster.java
index 87c2d23db38..d5ed2814f18 100644
--- a/Mage.Sets/src/mage/cards/m/MoltenDisaster.java
+++ b/Mage.Sets/src/mage/cards/m/MoltenDisaster.java
@@ -3,25 +3,19 @@ package mage.cards.m;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.KickedCondition;
-import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
-import mage.abilities.effects.OneShotEffect;
+import mage.abilities.dynamicvalue.common.ManacostVariableValue;
+import mage.abilities.effects.common.DamageEverythingEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.KickerAbility;
+import mage.abilities.keyword.SplitSecondAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
-import mage.constants.Duration;
-import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.AbilityPredicate;
-import mage.game.Game;
-import mage.game.events.GameEvent;
-import mage.game.permanent.Permanent;
-import mage.players.Player;
-import java.util.Optional;
import java.util.UUID;
/**
@@ -29,18 +23,29 @@ import java.util.UUID;
*/
public final class MoltenDisaster extends CardImpl {
+ private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature without flying");
+
+ static {
+ filter.add(Predicates.not(new AbilityPredicate(FlyingAbility.class)));
+ }
+
+ private static final String rule = "if this spell was kicked, it has split second. " +
+ "(As long as this spell is on the stack, players can't cast spells or activate abilities that aren't mana abilities.)";
+
public MoltenDisaster(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{R}{R}");
-
// If Molten Disaster was kicked, it has split second.
- Ability ability = new SimpleStaticAbility(Zone.STACK, new MoltenDisasterSplitSecondEffect());
+ Ability ability = new SimpleStaticAbility(Zone.STACK, SplitSecondAbility.getSplitSecondEffectWithCondition(KickedCondition.ONCE)
+ .setText(rule));
ability.setRuleAtTheTop(true);
this.addAbility(ability);
+
// Kicker {R}
this.addAbility(new KickerAbility("{R}"));
+
// Molten Disaster deals X damage to each creature without flying and each player.
- this.getSpellAbility().addEffect(new MoltenDisasterEffect());
+ this.getSpellAbility().addEffect(new DamageEverythingEffect(ManacostVariableValue.REGULAR, filter));
}
private MoltenDisaster(final MoltenDisaster card) {
@@ -52,84 +57,3 @@ public final class MoltenDisaster extends CardImpl {
return new MoltenDisaster(this);
}
}
-
-class MoltenDisasterSplitSecondEffect extends ContinuousRuleModifyingEffectImpl {
-
- MoltenDisasterSplitSecondEffect() {
- super(Duration.WhileOnStack, Outcome.Detriment);
- staticText = "if this spell was kicked, it has split second. (As long as this spell is on the stack, players can't cast spells or activate abilities that aren't mana abilities.)";
- }
-
- private MoltenDisasterSplitSecondEffect(final MoltenDisasterSplitSecondEffect effect) {
- super(effect);
- }
-
- @Override
- public String getInfoMessage(Ability source, GameEvent event, Game game) {
- return "You can't cast spells or activate abilities that aren't mana abilities (Split second).";
- }
-
- @Override
- public boolean checksEventType(GameEvent event, Game game) {
- return event.getType() == GameEvent.EventType.CAST_SPELL || event.getType() == GameEvent.EventType.ACTIVATE_ABILITY;
- }
-
- @Override
- public boolean applies(GameEvent event, Ability source, Game game) {
- if (event.getType() == GameEvent.EventType.CAST_SPELL) {
- if (KickedCondition.ONCE.apply(game, source)) {
- return true;
- }
- }
- if (event.getType() == GameEvent.EventType.ACTIVATE_ABILITY) {
- Optional ability = game.getAbility(event.getTargetId(), event.getSourceId());
- return ability.isPresent() && !ability.get().isManaActivatedAbility()
- && KickedCondition.ONCE.apply(game, source);
- }
- return false;
- }
-
- @Override
- public MoltenDisasterSplitSecondEffect copy() {
- return new MoltenDisasterSplitSecondEffect(this);
- }
-}
-
-class MoltenDisasterEffect extends OneShotEffect {
-
- private static final FilterCreaturePermanent filter = new FilterCreaturePermanent();
-
- static {
- filter.add(Predicates.not(new AbilityPredicate(FlyingAbility.class)));
- }
-
- public MoltenDisasterEffect() {
- super(Outcome.Damage);
- staticText = "{this} deals X damage to each creature without flying and each player";
- }
-
- private MoltenDisasterEffect(final MoltenDisasterEffect effect) {
- super(effect);
- }
-
- @Override
- public MoltenDisasterEffect copy() {
- return new MoltenDisasterEffect(this);
- }
-
- @Override
- public boolean apply(Game game, Ability source) {
- int amount = source.getManaCostsToPay().getX();
- for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) {
- permanent.damage(amount, source.getSourceId(), source, game, false, true);
- }
- for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
- Player player = game.getPlayer(playerId);
- if (player != null) {
- player.damage(amount, source.getSourceId(), source, game);
- }
- }
- return true;
- }
-
-}
diff --git a/Mage.Sets/src/mage/cards/s/ScourgeOfTheSkyclaves.java b/Mage.Sets/src/mage/cards/s/ScourgeOfTheSkyclaves.java
index 3109a8fca19..f2af217e91f 100644
--- a/Mage.Sets/src/mage/cards/s/ScourgeOfTheSkyclaves.java
+++ b/Mage.Sets/src/mage/cards/s/ScourgeOfTheSkyclaves.java
@@ -3,7 +3,7 @@ package mage.cards.s;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
-import mage.abilities.condition.Condition;
+import mage.abilities.condition.common.KickedCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
@@ -37,7 +37,7 @@ public final class ScourgeOfTheSkyclaves extends CardImpl {
// When you cast this spell, if it was kicked, each player loses half their life, rounded up.
this.addAbility(new ConditionalInterveningIfTriggeredAbility(
- new CastSourceTriggeredAbility(new ScourgeOfTheSkyclavesEffect()), ScourgeOfTheSkyclavesCondition.instance,
+ new CastSourceTriggeredAbility(new ScourgeOfTheSkyclavesEffect()), KickedCondition.ONCE,
"When you cast this spell, if it was kicked, each player loses half their life, rounded up."
));
@@ -58,15 +58,6 @@ public final class ScourgeOfTheSkyclaves extends CardImpl {
}
}
-enum ScourgeOfTheSkyclavesCondition implements Condition {
- instance;
-
- @Override
- public boolean apply(Game game, Ability source) {
- return KickerAbility.getSpellKickedCount(game, source.getSourceId()) > 0;
- }
-}
-
enum ScourgeOfTheSkyclavesValue implements DynamicValue {
instance;
diff --git a/Mage.Sets/src/mage/cards/s/SowingMycospawn.java b/Mage.Sets/src/mage/cards/s/SowingMycospawn.java
index b35237e4011..dc43d599d5f 100644
--- a/Mage.Sets/src/mage/cards/s/SowingMycospawn.java
+++ b/Mage.Sets/src/mage/cards/s/SowingMycospawn.java
@@ -2,7 +2,7 @@ package mage.cards.s;
import mage.MageInt;
import mage.abilities.Ability;
-import mage.abilities.condition.Condition;
+import mage.abilities.condition.common.KickedCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.common.CastSourceTriggeredAbility;
import mage.abilities.effects.common.ExileTargetEffect;
@@ -14,7 +14,6 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.StaticFilters;
-import mage.game.Game;
import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetLandPermanent;
@@ -47,7 +46,7 @@ public final class SowingMycospawn extends CardImpl {
// When you cast this spell, if it was kicked, exile target land.
Ability ability = new ConditionalInterveningIfTriggeredAbility(
new CastSourceTriggeredAbility(new ExileTargetEffect()),
- SowingMycospawnCondition.instance, "When you cast this spell, " +
+ KickedCondition.ONCE, "When you cast this spell, " +
"if it was kicked, exile target land."
);
ability.addTarget(new TargetLandPermanent());
@@ -63,12 +62,3 @@ public final class SowingMycospawn extends CardImpl {
return new SowingMycospawn(this);
}
}
-
-enum SowingMycospawnCondition implements Condition {
- instance;
-
- @Override
- public boolean apply(Game game, Ability source) {
- return KickerAbility.getSpellKickedCount(game, source.getSourceId()) > 0;
- }
-}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerTest.java
index 062b522cd9c..d69e26c04db 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerTest.java
@@ -13,7 +13,7 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
*/
public class KickerTest extends CardTestPlayerBase {
- /**
+ /*
* 702.32. Kicker 702.32a Kicker is a static ability that functions while
* the spell with kicker is on the stack. “Kicker [cost]” means “You may pay
* an additional [cost] as you cast this spell.” Paying a spell's kicker
@@ -722,4 +722,49 @@ public class KickerTest extends CardTestPlayerBase {
assertPermanentCount(playerA, "Brain in a Jar", 1);
}
+
+ @Test
+ public void test_ConditionOnStackNotKicked() {
+ String scourge = "Scourge of the Skyclaves"; // 1B Creature
+ /* Kicker {4}{B}
+ When you cast this spell, if it was kicked, each player loses half their life, rounded up.
+ Scourge of the Skyclaves’s power and toughness are each equal to 20 minus the highest life total among players.
+ */
+ addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
+ addCard(Zone.HAND, playerA, scourge);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, scourge);
+ setChoice(playerA, false); // no kicker
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+
+ assertLife(playerA, 20);
+ assertLife(playerB, 20);
+ assertGraveyardCount(playerA, scourge, 1);
+ }
+
+ @Test
+ public void test_ConditionOnStackKicked() {
+ String scourge = "Scourge of the Skyclaves"; // 1B Creature
+ /* Kicker {4}{B}
+ When you cast this spell, if it was kicked, each player loses half their life, rounded up.
+ Scourge of the Skyclaves’s power and toughness are each equal to 20 minus the highest life total among players.
+ */
+ addCard(Zone.BATTLEFIELD, playerA, "Swamp", 7);
+ addCard(Zone.HAND, playerA, scourge);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, scourge);
+ setChoice(playerA, true); // kicked
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+
+ assertLife(playerA, 10);
+ assertLife(playerB, 10);
+ assertPowerToughness(playerA, scourge, 10, 10);
+ }
+
}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/SplitSecondTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/SplitSecondTest.java
index 8e93a91864b..e526b45979b 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/SplitSecondTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/SplitSecondTest.java
@@ -59,4 +59,90 @@ public class SplitSecondTest extends CardTestPlayerBase {
assertLife(playerB, 20 - 2 - 2);
assertPermanentCount(playerA, "Raging Goblin", 1);
}
+
+ private static final String molten = "Molten Disaster";
+ /* {X}{R}{R} Sorcery
+ Kicker {R}
+ If this spell was kicked, it has split second.
+ Molten Disaster deals X damage to each creature without flying and each player.
+ */
+ private static final String shock = "Shock";
+ private static final String crab = "Fortress Crab"; // 1/6
+ private static final String gnomes = "Bottle Gnomes"; // Sacrifice Bottle Gnomes: You gain 3 life.
+ private static final String bear = "Runeclaw Bear"; // 2/2
+ private static final String drake = "Seacoast Drake"; // 1/3 flying
+
+ public void setupMoltenDisaster() {
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
+ addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1);
+ addCard(Zone.HAND, playerA, molten);
+ addCard(Zone.HAND, playerB, shock);
+ addCard(Zone.BATTLEFIELD, playerA, crab);
+ addCard(Zone.BATTLEFIELD, playerA, gnomes);
+ addCard(Zone.BATTLEFIELD, playerB, bear);
+ addCard(Zone.BATTLEFIELD, playerB, drake);
+ }
+
+ @Test
+ public void testMoltenDisasterUnkicked() {
+ setupMoltenDisaster();
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, molten);
+ setChoice(playerA, false); // no kicker
+ setChoice(playerA, "X=1");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, shock, crab);
+ activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice");
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+
+ assertLife(playerA, 20 - 1 + 3);
+ assertLife(playerB, 20 - 1);
+ assertDamageReceived(playerA, crab, 1 + 2);
+ assertGraveyardCount(playerA, gnomes, 1);
+ assertDamageReceived(playerB, bear, 1);
+ assertDamageReceived(playerB, drake, 0);
+ }
+
+ @Test
+ public void testMoltenDisasterKickedNoSpell() {
+ setupMoltenDisaster();
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, molten);
+ setChoice(playerA, true); // no kicker
+ setChoice(playerA, "X=1");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, shock, crab);
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+
+ try {
+ execute();
+ throw new AssertionError("expected failure to cast Shock");
+ } catch (AssertionError e) {
+ Assert.assertTrue(e.getMessage().contains("Can't find ability to activate command: Cast Shock$target=Fortress Crab"));
+ }
+
+
+ }
+
+ @Test
+ public void testMoltenDisasterKickedNoAbility() {
+ setupMoltenDisaster();
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, molten);
+ setChoice(playerA, true); // no kicker
+ setChoice(playerA, "X=1");
+ activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice");
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+
+ try {
+ execute();
+ throw new AssertionError("expected failure to activate sacrifice ability");
+ } catch (AssertionError e) {
+ Assert.assertTrue(e.getMessage().contains("Can't find ability to activate command: Sacrifice"));
+ }
+
+ }
+
}
diff --git a/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java b/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java
index e0b40c02e34..740409f3a14 100644
--- a/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java
+++ b/Mage/src/main/java/mage/abilities/condition/common/KickedCondition.java
@@ -24,7 +24,8 @@ public enum KickedCondition implements Condition {
@Override
public boolean apply(Game game, Ability source) {
- return KickerAbility.getKickedCounter(game, source) >= kickedCount;
+ return KickerAbility.getKickedCounter(game, source) >= kickedCount // for on battlefield
+ || KickerAbility.getSpellKickedCount(game, source.getSourceId()) >= kickedCount; // for on stack
}
@Override
diff --git a/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java b/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java
index d19f0da7da6..fb9577dc9df 100644
--- a/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java
+++ b/Mage/src/main/java/mage/abilities/keyword/KickerAbility.java
@@ -105,9 +105,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
private void addKickerCostAndSetup(OptionalAdditionalCost newCost) {
this.kickerCosts.add(newCost);
- this.kickerCosts.forEach(cost -> {
- cost.setCostType(VariableCostType.ADDITIONAL);
- });
+ this.kickerCosts.forEach(cost -> cost.setCostType(VariableCostType.ADDITIONAL));
}
private void resetKicker() {
@@ -124,10 +122,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
* Return total kicker activations with the specified Cost (blank for all kickers/multikickers)
* Checks the start of the tags, to work for that blank method, which requires direct access
*
- * @param game
- * @param source
* @param needKickerCost use cost.getText(true)
- * @return
*/
public static int getKickedCounterStrict(Game game, Ability source, String needKickerCost) {
Map costsTags = CardUtil.getSourceCostsTagsMap(game, source);
@@ -148,10 +143,6 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
/**
* Return total kicker activations (kicker + multikicker)
- *
- * @param game
- * @param source
- * @return
*/
public static int getKickedCounter(Game game, Ability source) {
return getKickedCounterStrict(game, source, "");
@@ -159,10 +150,6 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
/**
* If spell was kicked
- *
- * @param game
- * @param source
- * @return
*/
public boolean isKicked(Game game, Ability source) {
return isKicked(game, source, "");
@@ -171,10 +158,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
/**
* If spell was kicked by specific kicker cost
*
- * @param game
- * @param source
* @param needKickerCost use cost.getText(true)
- * @return
*/
public boolean isKicked(Game game, Ability source, String needKickerCost) {
return getKickedCounterStrict(game, source, needKickerCost) > 0;
@@ -287,10 +271,6 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
/**
* Find spell's kicked stats. Must be used on stack only, e.g. for SPELL_CAST events
- *
- * @param game
- * @param spellId
- * @return
*/
public static int getSpellKickedCount(Game game, UUID spellId) {
Spell spell = game.getSpellOrLKIStack(spellId);
diff --git a/Mage/src/main/java/mage/abilities/keyword/SplitSecondAbility.java b/Mage/src/main/java/mage/abilities/keyword/SplitSecondAbility.java
index 900a6641b8b..899b652fd0d 100644
--- a/Mage/src/main/java/mage/abilities/keyword/SplitSecondAbility.java
+++ b/Mage/src/main/java/mage/abilities/keyword/SplitSecondAbility.java
@@ -2,6 +2,8 @@ package mage.abilities.keyword;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
+import mage.abilities.condition.Condition;
+import mage.abilities.decorator.ConditionalContinuousRuleModifyingEffect;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
@@ -37,9 +39,13 @@ public class SplitSecondAbility extends SimpleStaticAbility {
public SplitSecondAbility copy() {
return new SplitSecondAbility(this);
}
-}
-// Molten Disaster has a copy of this effect in it's class, so in case this effect has to be changed check also there
+ // For abilities that need the effect conditionally. Must set text manually.
+ public static ConditionalContinuousRuleModifyingEffect getSplitSecondEffectWithCondition(Condition condition) {
+ return new ConditionalContinuousRuleModifyingEffect(new SplitSecondEffect(), condition);
+ }
+
+}
class SplitSecondEffect extends ContinuousRuleModifyingEffectImpl {