Alternative cost - fixed that it doesn't allow to cast cards that was affected by cost modification effects (example: Prowl ability, see #6698);

This commit is contained in:
Oleg Agafonov 2020-07-05 23:11:47 +04:00
parent f9a9a55f7b
commit 1e744a0aae
12 changed files with 218 additions and 114 deletions

View file

@ -1,21 +1,19 @@
package mage.cards.e; package mage.cards.e;
import java.util.List;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.condition.common.ProwlCondition; import mage.abilities.condition.common.ProwlCostWasPaidCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.hint.common.ProwlCostWasPaidHint;
import mage.abilities.keyword.ProwlAbility; import mage.abilities.keyword.ProwlAbility;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.game.Game; import mage.game.Game;
@ -23,14 +21,16 @@ import mage.players.Player;
import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetOpponent; import mage.target.common.TargetOpponent;
import java.util.List;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class EarwigSquad extends CardImpl { public final class EarwigSquad extends CardImpl {
public EarwigSquad(UUID ownerId, CardSetInfo setInfo) { public EarwigSquad(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{B}{B}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}");
this.subtype.add(SubType.GOBLIN); this.subtype.add(SubType.GOBLIN);
this.subtype.add(SubType.ROGUE); this.subtype.add(SubType.ROGUE);
@ -39,11 +39,13 @@ public final class EarwigSquad extends CardImpl {
// Prowl {2}{B} // Prowl {2}{B}
this.addAbility(new ProwlAbility(this, "{2}{B}")); this.addAbility(new ProwlAbility(this, "{2}{B}"));
// When Earwig Squad enters the battlefield, if its prowl cost was paid, search target opponent's library for three cards and exile them. Then that player shuffles their library. // When Earwig Squad enters the battlefield, if its prowl cost was paid, search target opponent's library for three cards and exile them. Then that player shuffles their library.
EntersBattlefieldTriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new EarwigSquadEffect(), false); EntersBattlefieldTriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new EarwigSquadEffect(), false);
ability.addTarget(new TargetOpponent()); ability.addTarget(new TargetOpponent());
this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, ProwlCondition.instance, this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, ProwlCostWasPaidCondition.instance,
"When {this} enters the battlefield, if its prowl cost was paid, search target opponent's library for three cards and exile them. Then that player shuffles their library.")); "When {this} enters the battlefield, if its prowl cost was paid, search target opponent's library for three cards and exile them. Then that player shuffles their library.")
.addHint(ProwlCostWasPaidHint.instance));
} }

View file

@ -1,12 +1,11 @@
package mage.cards.l; package mage.cards.l;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.condition.common.ProwlCondition; import mage.abilities.condition.common.ProwlCostWasPaidCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.hint.common.ProwlCostWasPaidHint;
import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.ProwlAbility; import mage.abilities.keyword.ProwlAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -14,14 +13,15 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class LatchkeyFaerie extends CardImpl { public final class LatchkeyFaerie extends CardImpl {
public LatchkeyFaerie(UUID ownerId, CardSetInfo setInfo) { public LatchkeyFaerie(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{U}"); super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}");
this.subtype.add(SubType.FAERIE); this.subtype.add(SubType.FAERIE);
this.subtype.add(SubType.ROGUE); this.subtype.add(SubType.ROGUE);
@ -36,8 +36,9 @@ public final class LatchkeyFaerie extends CardImpl {
// When Latchkey Faerie enters the battlefield, if its prowl cost was paid, draw a card. // When Latchkey Faerie enters the battlefield, if its prowl cost was paid, draw a card.
EntersBattlefieldTriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1), false); EntersBattlefieldTriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1), false);
this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, ProwlCondition.instance, this.addAbility(new ConditionalInterveningIfTriggeredAbility(ability, ProwlCostWasPaidCondition.instance,
"When {this} enters the battlefield, if its prowl cost was paid, draw a card.")); "When {this} enters the battlefield, if its prowl cost was paid, draw a card.")
.addHint(ProwlCostWasPaidHint.instance));
} }

View file

@ -1,13 +1,12 @@
package mage.cards.m; package mage.cards.m;
import java.util.UUID; import mage.abilities.condition.common.ProwlCostWasPaidCondition;
import mage.abilities.condition.common.ProwlCondition;
import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.LoseLifeTargetEffect; import mage.abilities.effects.common.LoseLifeTargetEffect;
import mage.abilities.hint.common.ProwlCostWasPaidHint;
import mage.abilities.keyword.ProwlAbility; import mage.abilities.keyword.ProwlAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
@ -15,14 +14,15 @@ import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.target.TargetPlayer; import mage.target.TargetPlayer;
import java.util.UUID;
/** /**
*
* @author LevelX2 * @author LevelX2
*/ */
public final class MorselTheft extends CardImpl { public final class MorselTheft extends CardImpl {
public MorselTheft(UUID ownerId, CardSetInfo setInfo) { public MorselTheft(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.TRIBAL,CardType.SORCERY},"{2}{B}{B}"); super(ownerId, setInfo, new CardType[]{CardType.TRIBAL, CardType.SORCERY}, "{2}{B}{B}");
this.subtype.add(SubType.ROGUE); this.subtype.add(SubType.ROGUE);
// Prowl {1}{B} // Prowl {1}{B}
@ -34,7 +34,8 @@ public final class MorselTheft extends CardImpl {
effect.setText("and you gain 3 life"); effect.setText("and you gain 3 life");
getSpellAbility().addEffect(effect); getSpellAbility().addEffect(effect);
getSpellAbility().addTarget(new TargetPlayer()); getSpellAbility().addTarget(new TargetPlayer());
getSpellAbility().addEffect(new ConditionalOneShotEffect(new DrawCardSourceControllerEffect(1), ProwlCondition.instance)); getSpellAbility().addEffect(new ConditionalOneShotEffect(new DrawCardSourceControllerEffect(1), ProwlCostWasPaidCondition.instance));
getSpellAbility().addHint(ProwlCostWasPaidHint.instance);
} }

View file

@ -1,44 +1,47 @@
package mage.cards.n; package mage.cards.n;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.condition.common.ProwlCondition; import mage.abilities.condition.common.ProwlCostWasPaidCondition;
import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.turn.AddExtraTurnControllerEffect; import mage.abilities.effects.common.turn.AddExtraTurnControllerEffect;
import mage.abilities.hint.common.ProwlCostWasPaidHint;
import mage.abilities.keyword.ProwlAbility; import mage.abilities.keyword.ProwlAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.SubType;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.token.FaerieRogueToken; import mage.game.permanent.token.FaerieRogueToken;
import mage.players.Player; import mage.players.Player;
import mage.watchers.common.AmountOfDamageAPlayerReceivedThisTurnWatcher; import mage.watchers.common.AmountOfDamageAPlayerReceivedThisTurnWatcher;
import java.util.UUID;
/** /**
*
* @author LoneFox * @author LoneFox
*/ */
public final class NotoriousThrong extends CardImpl { public final class NotoriousThrong extends CardImpl {
public NotoriousThrong(UUID ownerId, CardSetInfo setInfo) { public NotoriousThrong(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.TRIBAL,CardType.SORCERY},"{3}{U}"); super(ownerId, setInfo, new CardType[]{CardType.TRIBAL, CardType.SORCERY}, "{3}{U}");
this.subtype.add(SubType.ROGUE); this.subtype.add(SubType.ROGUE);
// Prowl {5}{U} // Prowl {5}{U}
this.addAbility(new ProwlAbility(this, "{5}{U}")); this.addAbility(new ProwlAbility(this, "{5}{U}"));
// create X 1/1 black Faerie Rogue creature tokens with flying, where X is the damage dealt to your opponents this turn. // create X 1/1 black Faerie Rogue creature tokens with flying, where X is the damage dealt to your opponents this turn.
this.getSpellAbility().addEffect(new NotoriousThrongEffect()); this.getSpellAbility().addEffect(new NotoriousThrongEffect());
this.getSpellAbility().addWatcher(new AmountOfDamageAPlayerReceivedThisTurnWatcher()); this.getSpellAbility().addWatcher(new AmountOfDamageAPlayerReceivedThisTurnWatcher());
// If Notorious Throng's prowl cost was paid, take an extra turn after this one. // If Notorious Throng's prowl cost was paid, take an extra turn after this one.
Effect effect = new ConditionalOneShotEffect(new AddExtraTurnControllerEffect(), ProwlCondition.instance); Effect effect = new ConditionalOneShotEffect(new AddExtraTurnControllerEffect(), ProwlCostWasPaidCondition.instance);
effect.setText("If {this}'s prowl cost was paid, take an extra turn after this one."); effect.setText("If {this}'s prowl cost was paid, take an extra turn after this one.");
this.getSpellAbility().addEffect(effect); this.getSpellAbility().addEffect(effect);
this.getSpellAbility().addHint(ProwlCostWasPaidHint.instance);
} }
public NotoriousThrong(final NotoriousThrong card) { public NotoriousThrong(final NotoriousThrong card) {
@ -71,12 +74,12 @@ class NotoriousThrongEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
AmountOfDamageAPlayerReceivedThisTurnWatcher watcher = game.getState().getWatcher(AmountOfDamageAPlayerReceivedThisTurnWatcher.class); AmountOfDamageAPlayerReceivedThisTurnWatcher watcher = game.getState().getWatcher(AmountOfDamageAPlayerReceivedThisTurnWatcher.class);
if(controller != null && watcher != null) { if (controller != null && watcher != null) {
int numTokens = 0; int numTokens = 0;
for(UUID opponentId: game.getOpponents(controller.getId())) { for (UUID opponentId : game.getOpponents(controller.getId())) {
numTokens += watcher.getAmountOfDamageReceivedThisTurn(opponentId); numTokens += watcher.getAmountOfDamageReceivedThisTurn(opponentId);
} }
if(numTokens > 0) { if (numTokens > 0) {
new CreateTokenEffect(new FaerieRogueToken(), numTokens).apply(game, source); new CreateTokenEffect(new FaerieRogueToken(), numTokens).apply(game, source);
} }
return true; return true;

View file

@ -1,42 +1,40 @@
/*
* 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 org.mage.test.cards.abilities.keywords; package org.mage.test.cards.abilities.keywords;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
import mage.constants.Zone; import mage.constants.Zone;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase; import org.mage.test.serverside.base.CardTestPlayerBase;
/** /**
*
* @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com)
*/ */
public class ProwlTest extends CardTestPlayerBase { public class ProwlTest extends CardTestPlayerBase {
@Ignore // have not figured out how to have the test API cast a card using Prowl yet
@Test @Test
public void testBasicProwlCasting() { public void test_ProwlNormal() {
// Auntie's Snitch {2}{B} Creature Goblin Rogue (3/1) // Auntie's Snitch {2}{B} Creature Goblin Rogue (3/1)
// Auntie's Snitch can't block. // Auntie's Snitch can't block.
// Prowl {1}{B} (You may cast this for its prowl cost if you dealt combat damage to a player this turn with a Goblin or Rogue.) // Prowl {1}{B} (You may cast this for its prowl cost if you dealt combat damage to a player this turn with a Goblin or Rogue.)
// Whenever a Goblin or Rogue you control deals combat damage to a player, if Auntie's Snitch is in your graveyard, you may return Auntie's Snitch to your hand. // Whenever a Goblin or Rogue you control deals combat damage to a player, if Auntie's Snitch is in your graveyard, you may return Auntie's Snitch to your hand.
addCard(Zone.HAND, playerA, "Auntie's Snitch"); addCard(Zone.HAND, playerA, "Auntie's Snitch");
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
//
// {1}{R} Creature Goblin Warrior 1/1 // {1}{R} Creature Goblin Warrior 1/1
// Red creatures you control have first strike. // Red creatures you control have first strike.
addCard(Zone.BATTLEFIELD, playerA, "Bloodmark Mentor"); addCard(Zone.BATTLEFIELD, playerA, "Bloodmark Mentor");
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
// prepare prowl condition
checkPlayableAbility("can't", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Auntie's Snitch", false);
attack(1, playerA, "Bloodmark Mentor"); attack(1, playerA, "Bloodmark Mentor");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Auntie's Snitch using prowl"); checkPlayableAbility("must play", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast Auntie's Snitch", true);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Auntie's Snitch");
setChoice(playerA, "Yes"); // choosing to pay prowl cost setChoice(playerA, "Yes"); // choosing to pay prowl cost
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN); setStopAt(1, PhaseStep.END_TURN);
execute(); execute();
assertAllCommandsUsed();
assertLife(playerB, 19); assertLife(playerB, 19);
assertPermanentCount(playerA, "Bloodmark Mentor", 1); assertPermanentCount(playerA, "Bloodmark Mentor", 1);
@ -46,29 +44,33 @@ public class ProwlTest extends CardTestPlayerBase {
/* /*
* Reported bug: Prowl is not taking into consideration other cost reducing effects. For instance Goblin Warchief * Reported bug: Prowl is not taking into consideration other cost reducing effects. For instance Goblin Warchief
* does not reduce the Prowl cost of other Goblin cards with Prowl ability. * does not reduce the Prowl cost of other Goblin cards with Prowl ability.
*/ */
@Ignore // have not figured out how to have the test API cast a card using Prowl yet
@Test @Test
public void testProwlWithCostDiscount() { public void test_ProwlWithCostReduce() {
// Auntie's Snitch {2}{B} Creature Goblin Rogue (3/1) // Auntie's Snitch {2}{B} Creature Goblin Rogue (3/1)
// Auntie's Snitch can't block. // Auntie's Snitch can't block.
// Prowl {1}{B} (You may cast this for its prowl cost if you dealt combat damage to a player this turn with a Goblin or Rogue.) // Prowl {1}{B} (You may cast this for its prowl cost if you dealt combat damage to a player this turn with a Goblin or Rogue.)
// Whenever a Goblin or Rogue you control deals combat damage to a player, if Auntie's Snitch is in your graveyard, you may return Auntie's Snitch to your hand. // Whenever a Goblin or Rogue you control deals combat damage to a player, if Auntie's Snitch is in your graveyard, you may return Auntie's Snitch to your hand.
addCard(Zone.HAND, playerA, "Auntie's Snitch"); addCard(Zone.HAND, playerA, "Auntie's Snitch");
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
// Goblin Warchief {1}{R}{R} Creature Goblin Warrior (2/2) // Goblin Warchief {1}{R}{R} Creature Goblin Warrior (2/2)
// Goblin spells you cast cost 1 less to cast. // Goblin spells you cast cost 1 less to cast.
// Goblin creatures you control have haste. // Goblin creatures you control have haste.
addCard(Zone.BATTLEFIELD, playerA, "Goblin Warchief"); addCard(Zone.BATTLEFIELD, playerA, "Goblin Warchief");
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
// prepare prowl condition
checkPlayableAbility("can't", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Auntie's Snitch", false);
attack(1, playerA, "Goblin Warchief"); attack(1, playerA, "Goblin Warchief");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Auntie's Snitch using prowl"); // should only cost {B} with Warchief discount checkPlayableAbility("must play", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast Auntie's Snitch", true);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Auntie's Snitch"); // should only cost {B} with Warchief discount
setChoice(playerA, "Yes"); // choosing to pay prowl cost setChoice(playerA, "Yes"); // choosing to pay prowl cost
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN); setStopAt(1, PhaseStep.END_TURN);
execute(); execute();
assertAllCommandsUsed();
assertLife(playerB, 18); assertLife(playerB, 18);
assertPermanentCount(playerA, "Goblin Warchief", 1); assertPermanentCount(playerA, "Goblin Warchief", 1);

View file

@ -129,10 +129,15 @@ public class PlayFromNonHandZoneTest extends CardTestPlayerBase {
attack(2, playerB, "Narset, Enlightened Master"); attack(2, playerB, "Narset, Enlightened Master");
checkPlayableAbility("must play", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Cast Cathartic Reunion", true);
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Cathartic Reunion"); castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Cathartic Reunion");
setChoice(playerB, "Swamp^Forest"); setChoice(playerB, "Swamp^Forest");
setStrictChooseMode(true);
setStopAt(2, PhaseStep.END_TURN); setStopAt(2, PhaseStep.END_TURN);
execute(); execute();
assertAllCommandsUsed();
assertHandCount(playerB, 3); assertHandCount(playerB, 3);
assertGraveyardCount(playerB, "Forest", 1); assertGraveyardCount(playerB, "Forest", 1);

View file

@ -1,16 +1,16 @@
package mage.abilities.condition.common; package mage.abilities.condition.common;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.condition.Condition; import mage.abilities.condition.Condition;
import mage.abilities.keyword.ProwlAbility;
import mage.cards.Card; import mage.cards.Card;
import mage.constants.SubType;
import mage.game.Game; import mage.game.Game;
import mage.watchers.common.ProwlWatcher;
/** /**
* Checks if a the spell was cast with the alternate prowl costs * Is it able to activate prowl cost (damage was made)
* *
* @author LevelX2 * @author JayDi85
*/ */
public enum ProwlCondition implements Condition { public enum ProwlCondition implements Condition {
@ -18,22 +18,15 @@ public enum ProwlCondition implements Condition {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
ProwlWatcher watcher = game.getState().getWatcher(ProwlWatcher.class);
Card card = game.getCard(source.getSourceId()); Card card = game.getCard(source.getSourceId());
if (card != null) { if (watcher != null && card != null) {
for (Ability ability : card.getAbilities()) { for (SubType subtype : card.getSubtype(game)) {
if (ability instanceof ProwlAbility) { if (watcher.hasSubtypeMadeCombatDamage(source.getControllerId(), subtype)) {
if (((ProwlAbility) ability).isActivated(source, game)) { return true;
return true;
}
} }
} }
} }
return false; return false;
} }
@Override
public String toString() {
return "{source}'s prowl cost was paid";
}
} }

View file

@ -0,0 +1,38 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.keyword.ProwlAbility;
import mage.cards.Card;
import mage.game.Game;
/**
* Checks if a the spell was cast with the alternate prowl costs
*
* @author LevelX2
*/
public enum ProwlCostWasPaidCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
for (Ability ability : card.getAbilities()) {
if (ability instanceof ProwlAbility) {
if (((ProwlAbility) ability).isActivated(source, game)) {
return true;
}
}
}
}
return false;
}
@Override
public String toString() {
return "{source}'s prowl cost was paid";
}
}

View file

@ -0,0 +1,26 @@
package mage.abilities.hint.common;
import mage.abilities.Ability;
import mage.abilities.condition.common.ProwlCostWasPaidCondition;
import mage.abilities.hint.ConditionHint;
import mage.abilities.hint.Hint;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum ProwlCostWasPaidHint implements Hint {
instance;
private static final ConditionHint hint = new ConditionHint(ProwlCostWasPaidCondition.instance, "Prowl cost was paid");
@Override
public String getText(Game game, Ability ability) {
return hint.getText(game, ability);
}
@Override
public Hint copy() {
return instance;
}
}

View file

@ -0,0 +1,26 @@
package mage.abilities.hint.common;
import mage.abilities.Ability;
import mage.abilities.condition.common.ProwlCondition;
import mage.abilities.hint.ConditionHint;
import mage.abilities.hint.Hint;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum ProwlHint implements Hint {
instance;
private static final ConditionHint hint = new ConditionHint(ProwlCondition.instance, "Prowl cost can be activated");
@Override
public String getText(Game game, Ability ability) {
return hint.getText(game, ability);
}
@Override
public Hint copy() {
return instance;
}
}

View file

@ -1,30 +1,26 @@
package mage.abilities.keyword; package mage.abilities.keyword;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility; import mage.abilities.StaticAbility;
import mage.abilities.costs.AlternativeCost2; import mage.abilities.condition.common.ProwlCondition;
import mage.abilities.costs.AlternativeCost2Impl; import mage.abilities.costs.*;
import mage.abilities.costs.AlternativeSourceCosts;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.CostsImpl;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.hint.common.ProwlHint;
import mage.cards.Card; import mage.cards.Card;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.players.Player; import mage.players.Player;
import mage.watchers.common.ProwlWatcher; import mage.watchers.common.ProwlWatcher;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/** /**
* 702.74. Prowl # * 702.74. Prowl #
* * <p>
* 702.74a Prowl is a static ability that functions on the stack. "Prowl [cost]" * 702.74a Prowl is a static ability that functions on the stack. "Prowl [cost]"
* means "You may pay [cost] rather than pay this spell's mana cost if a player * means "You may pay [cost] rather than pay this spell's mana cost if a player
* was dealt combat damage this turn by a source that, at the time it dealt that * was dealt combat damage this turn by a source that, at the time it dealt that
@ -41,13 +37,13 @@ public class ProwlAbility extends StaticAbility implements AlternativeSourceCost
private String reminderText; private String reminderText;
public ProwlAbility(Card card, String manaString) { public ProwlAbility(Card card, String manaString) {
super(Zone.STACK, null); super(Zone.ALL, null);
setRuleAtTheTop(true); this.setRuleAtTheTop(true);
name = PROWL_KEYWORD; this.name = PROWL_KEYWORD;
setReminderText(card); this.setReminderText(card);
this.addProwlCost(manaString); this.addProwlCost(manaString);
addWatcher(new ProwlWatcher()); this.addWatcher(new ProwlWatcher());
this.addHint(ProwlHint.instance);
} }
public ProwlAbility(final ProwlAbility ability) { public ProwlAbility(final ProwlAbility ability) {
@ -85,26 +81,17 @@ public class ProwlAbility extends StaticAbility implements AlternativeSourceCost
@Override @Override
public boolean isAvailable(Ability source, Game game) { public boolean isAvailable(Ability source, Game game) {
return true; return ProwlCondition.instance.apply(game, source);
} }
@Override @Override
public boolean askToActivateAlternativeCosts(Ability ability, Game game) { public boolean askToActivateAlternativeCosts(Ability ability, Game game) {
if (ability instanceof SpellAbility) { if (ability instanceof SpellAbility) {
Player player = game.getPlayer(controllerId); Player player = game.getPlayer(controllerId);
ProwlWatcher prowlWatcher = game.getState().getWatcher(ProwlWatcher.class); if (player == null) {
Card card = game.getCard(ability.getSourceId()); return false;
if (player == null || prowlWatcher == null || card == null) {
throw new IllegalArgumentException("Params can't be null");
} }
boolean canProwl = false; if (ProwlCondition.instance.apply(game, ability)) {
for (SubType subtype : card.getSubtype(game)) {
if (prowlWatcher.hasSubtypeMadeCombatDamage(ability.getControllerId(), subtype)) {
canProwl = true;
break;
}
}
if (canProwl) {
this.resetProwl(); this.resetProwl();
for (AlternativeCost2 prowlCost : prowlCosts) { for (AlternativeCost2 prowlCost : prowlCosts) {
if (prowlCost.canPay(ability, sourceId, controllerId, game) if (prowlCost.canPay(ability, sourceId, controllerId, game)
@ -112,7 +99,7 @@ public class ProwlAbility extends StaticAbility implements AlternativeSourceCost
prowlCost.activate(); prowlCost.activate();
ability.getManaCostsToPay().clear(); ability.getManaCostsToPay().clear();
ability.getCosts().clear(); ability.getCosts().clear();
for (Iterator it = ((Costs) prowlCost).iterator(); it.hasNext();) { for (Iterator it = ((Costs) prowlCost).iterator(); it.hasNext(); ) {
Cost cost = (Cost) it.next(); Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) { if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy()); ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());

View file

@ -3046,6 +3046,7 @@ public abstract class PlayerImpl implements Player, Serializable {
protected boolean canPlayCardByAlternateCost(Card sourceObject, ManaOptions availableMana, Ability ability, Game game) { protected boolean canPlayCardByAlternateCost(Card sourceObject, ManaOptions availableMana, Ability ability, Game game) {
if (sourceObject != null && !(sourceObject instanceof Permanent)) { if (sourceObject != null && !(sourceObject instanceof Permanent)) {
Ability copyAbility; // for alternative cost and reduce tries
for (Ability alternateSourceCostsAbility : sourceObject.getAbilities()) { for (Ability alternateSourceCostsAbility : sourceObject.getAbilities()) {
// if cast for noMana no Alternative costs are allowed // if cast for noMana no Alternative costs are allowed
if (alternateSourceCostsAbility instanceof AlternativeSourceCosts) { if (alternateSourceCostsAbility instanceof AlternativeSourceCosts) {
@ -3064,7 +3065,15 @@ public abstract class PlayerImpl implements Player, Serializable {
if (availableMana == null) { if (availableMana == null) {
return true; return true;
} }
for (Mana mana : manaCosts.getOptions()) {
// alternative cost reduce
copyAbility = ability.copy();
copyAbility.getManaCostsToPay().clear();
copyAbility.getManaCostsToPay().addAll(manaCosts.copy());
sourceObject.adjustCosts(copyAbility, game);
game.getContinuousEffects().costModification(copyAbility, game);
for (Mana mana : copyAbility.getManaCostsToPay().getOptions()) {
for (Mana avail : availableMana) { for (Mana avail : availableMana) {
if (mana.enough(avail)) { if (mana.enough(avail)) {
return true; return true;
@ -3092,7 +3101,18 @@ public abstract class PlayerImpl implements Player, Serializable {
if (manaCosts.isEmpty()) { if (manaCosts.isEmpty()) {
return true; return true;
} else { } else {
for (Mana mana : manaCosts.getOptions()) { if (availableMana == null) {
return true;
}
// alternative cost reduce
copyAbility = ability.copy();
copyAbility.getManaCostsToPay().clear();
copyAbility.getManaCostsToPay().addAll(manaCosts.copy());
sourceObject.adjustCosts(copyAbility, game);
game.getContinuousEffects().costModification(copyAbility, game);
for (Mana mana : copyAbility.getManaCostsToPay().getOptions()) {
for (Mana avail : availableMana) { for (Mana avail : availableMana) {
if (mana.enough(avail)) { if (mana.enough(avail)) {
return true; return true;