mirror of
https://github.com/correl/mage.git
synced 2024-12-25 19:25:41 +00:00
Implementing Blitz mechanic (WIP) (#8835)
* added blitz mechanic (mostly copy/paste of dash) * renamed class * reworked alt cost abilities, greatly reduced redundant code * updated text generation * removed all skips * added test for blitz * changed blitz implementation * [SNC] Implemented Tenacious Underdog
This commit is contained in:
parent
76daf4bd5a
commit
0e3252d256
31 changed files with 620 additions and 722 deletions
|
@ -42,7 +42,7 @@ public final class CaldaiaGuardian extends CardImpl {
|
||||||
));
|
));
|
||||||
|
|
||||||
// Blitz {2}{G}
|
// Blitz {2}{G}
|
||||||
this.addAbility(new BlitzAbility("{2}{G}"));
|
this.addAbility(new BlitzAbility(this, "{2}{G}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private CaldaiaGuardian(final CaldaiaGuardian card) {
|
private CaldaiaGuardian(final CaldaiaGuardian card) {
|
||||||
|
|
|
@ -35,7 +35,7 @@ public final class CaldaiaStrongarm extends CardImpl {
|
||||||
this.addAbility(ability);
|
this.addAbility(ability);
|
||||||
|
|
||||||
// Blitz {3}{G}
|
// Blitz {3}{G}
|
||||||
this.addAbility(new BlitzAbility("{3}{G}"));
|
this.addAbility(new BlitzAbility(this, "{3}{G}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private CaldaiaStrongarm(final CaldaiaStrongarm card) {
|
private CaldaiaStrongarm(final CaldaiaStrongarm card) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ public final class GirderGoons extends CardImpl {
|
||||||
)));
|
)));
|
||||||
|
|
||||||
// Blitz {3}{B}
|
// Blitz {3}{B}
|
||||||
this.addAbility(new BlitzAbility("{3}{B}"));
|
this.addAbility(new BlitzAbility(this, "{3}{B}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private GirderGoons(final GirderGoons card) {
|
private GirderGoons(final GirderGoons card) {
|
||||||
|
|
|
@ -48,7 +48,7 @@ public final class JaxisTheTroublemaker extends CardImpl {
|
||||||
this.addAbility(ability);
|
this.addAbility(ability);
|
||||||
|
|
||||||
// Blitz {1}{R}
|
// Blitz {1}{R}
|
||||||
this.addAbility(new BlitzAbility("{1}{R}"));
|
this.addAbility(new BlitzAbility(this, "{1}{R}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private JaxisTheTroublemaker(final JaxisTheTroublemaker card) {
|
private JaxisTheTroublemaker(final JaxisTheTroublemaker card) {
|
||||||
|
|
|
@ -36,7 +36,7 @@ public final class MayhemPatrol extends CardImpl {
|
||||||
this.addAbility(ability);
|
this.addAbility(ability);
|
||||||
|
|
||||||
// Blitz {1}{R}
|
// Blitz {1}{R}
|
||||||
this.addAbility(new BlitzAbility("{1}{R}"));
|
this.addAbility(new BlitzAbility(this, "{1}{R}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private MayhemPatrol(final MayhemPatrol card) {
|
private MayhemPatrol(final MayhemPatrol card) {
|
||||||
|
|
|
@ -33,7 +33,7 @@ public final class NightClubber extends CardImpl {
|
||||||
)));
|
)));
|
||||||
|
|
||||||
// Blitz {2}{B}
|
// Blitz {2}{B}
|
||||||
this.addAbility(new BlitzAbility("{2}{B}"));
|
this.addAbility(new BlitzAbility(this, "{2}{B}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private NightClubber(final NightClubber card) {
|
private NightClubber(final NightClubber card) {
|
||||||
|
|
|
@ -33,7 +33,7 @@ public final class PlasmaJockey extends CardImpl {
|
||||||
this.addAbility(ability);
|
this.addAbility(ability);
|
||||||
|
|
||||||
// Blitz {2}{R}
|
// Blitz {2}{R}
|
||||||
this.addAbility(new BlitzAbility("{2}{R}"));
|
this.addAbility(new BlitzAbility(this, "{2}{R}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlasmaJockey(final PlasmaJockey card) {
|
private PlasmaJockey(final PlasmaJockey card) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ public final class PugnaciousPugilist extends CardImpl {
|
||||||
)));
|
)));
|
||||||
|
|
||||||
// Blitz {3}{R}
|
// Blitz {3}{R}
|
||||||
this.addAbility(new BlitzAbility("{3}{R}"));
|
this.addAbility(new BlitzAbility(this, "{3}{R}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PugnaciousPugilist(final PugnaciousPugilist card) {
|
private PugnaciousPugilist(final PugnaciousPugilist card) {
|
||||||
|
|
|
@ -28,7 +28,7 @@ public final class RiveteersDecoy extends CardImpl {
|
||||||
this.addAbility(new SimpleStaticAbility(new MustBeBlockedByAtLeastOneSourceEffect()));
|
this.addAbility(new SimpleStaticAbility(new MustBeBlockedByAtLeastOneSourceEffect()));
|
||||||
|
|
||||||
// Blitz {3}{G}
|
// Blitz {3}{G}
|
||||||
this.addAbility(new BlitzAbility("{3}{G}"));
|
this.addAbility(new BlitzAbility(this, "{3}{G}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private RiveteersDecoy(final RiveteersDecoy card) {
|
private RiveteersDecoy(final RiveteersDecoy card) {
|
||||||
|
|
|
@ -29,7 +29,7 @@ public final class RiveteersRequisitioner extends CardImpl {
|
||||||
this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect(new TreasureToken())));
|
this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect(new TreasureToken())));
|
||||||
|
|
||||||
// Blitz {2}{R}
|
// Blitz {2}{R}
|
||||||
this.addAbility(new BlitzAbility("{2}{R}"));
|
this.addAbility(new BlitzAbility(this, "{2}{R}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private RiveteersRequisitioner(final RiveteersRequisitioner card) {
|
private RiveteersRequisitioner(final RiveteersRequisitioner card) {
|
||||||
|
|
82
Mage.Sets/src/mage/cards/t/TenaciousUnderdog.java
Normal file
82
Mage.Sets/src/mage/cards/t/TenaciousUnderdog.java
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package mage.cards.t;
|
||||||
|
|
||||||
|
import mage.MageInt;
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
|
import mage.abilities.costs.common.PayLifeCost;
|
||||||
|
import mage.abilities.effects.AsThoughEffectImpl;
|
||||||
|
import mage.abilities.keyword.BlitzAbility;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.*;
|
||||||
|
import mage.game.Game;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class TenaciousUnderdog extends CardImpl {
|
||||||
|
|
||||||
|
public TenaciousUnderdog(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.HUMAN);
|
||||||
|
this.subtype.add(SubType.WARRIOR);
|
||||||
|
this.power = new MageInt(3);
|
||||||
|
this.toughness = new MageInt(2);
|
||||||
|
|
||||||
|
// Blitz—{2}{B}{B}, Pay 2 life.
|
||||||
|
Ability ability = new BlitzAbility(this, "{2}{B}{B}");
|
||||||
|
ability.addCost(new PayLifeCost(2));
|
||||||
|
this.addAbility(ability);
|
||||||
|
|
||||||
|
// You may cast Tenacious Underdog from your graveyard using its blitz ability.
|
||||||
|
this.addAbility(new SimpleStaticAbility(Zone.ALL, new TenaciousUnderdogEffect()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private TenaciousUnderdog(final TenaciousUnderdog card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TenaciousUnderdog copy() {
|
||||||
|
return new TenaciousUnderdog(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TenaciousUnderdogEffect extends AsThoughEffectImpl {
|
||||||
|
|
||||||
|
TenaciousUnderdogEffect() {
|
||||||
|
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.PutCreatureInPlay);
|
||||||
|
staticText = "You may cast {this} from your graveyard";
|
||||||
|
}
|
||||||
|
|
||||||
|
private TenaciousUnderdogEffect(final TenaciousUnderdogEffect effect) {
|
||||||
|
super(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Game game, Ability source) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TenaciousUnderdogEffect copy() {
|
||||||
|
return new TenaciousUnderdogEffect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
|
||||||
|
return objectId.equals(source.getSourceId())
|
||||||
|
&& source.isControlledBy(playerId)
|
||||||
|
&& affectedAbility instanceof BlitzAbility
|
||||||
|
&& game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD
|
||||||
|
&& game.getCard(source.getSourceId()) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,7 +38,7 @@ public final class WorkshopWarchief extends CardImpl {
|
||||||
this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect(new RhinoWarriorToken())));
|
this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect(new RhinoWarriorToken())));
|
||||||
|
|
||||||
// Blitz {4}{G}{G}
|
// Blitz {4}{G}{G}
|
||||||
this.addAbility(new BlitzAbility("{4}{G}{G}"));
|
this.addAbility(new BlitzAbility(this, "{4}{G}{G}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private WorkshopWarchief(final WorkshopWarchief card) {
|
private WorkshopWarchief(final WorkshopWarchief card) {
|
||||||
|
|
|
@ -4,16 +4,11 @@ import mage.cards.ExpansionSet;
|
||||||
import mage.constants.Rarity;
|
import mage.constants.Rarity;
|
||||||
import mage.constants.SetType;
|
import mage.constants.SetType;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author TheElk801
|
* @author TheElk801
|
||||||
*/
|
*/
|
||||||
public final class NewCapennaCommander extends ExpansionSet {
|
public final class NewCapennaCommander extends ExpansionSet {
|
||||||
|
|
||||||
private static final List<String> unfinished = Arrays.asList("Caldaia Guardian", "Henzie \"Toolbox\" Torre", "Mezzio Mugger", "Wave of Rats");
|
|
||||||
|
|
||||||
private static final NewCapennaCommander instance = new NewCapennaCommander();
|
private static final NewCapennaCommander instance = new NewCapennaCommander();
|
||||||
|
|
||||||
public static NewCapennaCommander getInstance() {
|
public static NewCapennaCommander getInstance() {
|
||||||
|
@ -302,7 +297,5 @@ public final class NewCapennaCommander extends ExpansionSet {
|
||||||
cards.add(new SetCardInfo("Writ of Return", 42, Rarity.RARE, mage.cards.w.WritOfReturn.class));
|
cards.add(new SetCardInfo("Writ of Return", 42, Rarity.RARE, mage.cards.w.WritOfReturn.class));
|
||||||
cards.add(new SetCardInfo("Zndrsplt's Judgment", 240, Rarity.RARE, mage.cards.z.ZndrspltsJudgment.class));
|
cards.add(new SetCardInfo("Zndrsplt's Judgment", 240, Rarity.RARE, mage.cards.z.ZndrspltsJudgment.class));
|
||||||
cards.add(new SetCardInfo("Zurzoth, Chaos Rider", 278, Rarity.RARE, mage.cards.z.ZurzothChaosRider.class));
|
cards.add(new SetCardInfo("Zurzoth, Chaos Rider", 278, Rarity.RARE, mage.cards.z.ZurzothChaosRider.class));
|
||||||
|
|
||||||
cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); // remove when shield counters are implemented
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,11 @@ import mage.cards.ExpansionSet;
|
||||||
import mage.constants.Rarity;
|
import mage.constants.Rarity;
|
||||||
import mage.constants.SetType;
|
import mage.constants.SetType;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author TheElk801
|
* @author TheElk801
|
||||||
*/
|
*/
|
||||||
public final class StreetsOfNewCapenna extends ExpansionSet {
|
public final class StreetsOfNewCapenna extends ExpansionSet {
|
||||||
|
|
||||||
private static final List<String> unfinished = Arrays.asList("Caldaia Strongarm", "Girder Goons", "Jaxis, the Troublemaker", "Mayhem Patrol", "Night Clubber", "Plasma Jockey", "Pugnacious Pugilist", "Riveteers Decoy", "Riveteers Requisitioner", "Tenacious Underdog", "Workshop Warchief", "Ziatora's Envoy");
|
|
||||||
|
|
||||||
private static final StreetsOfNewCapenna instance = new StreetsOfNewCapenna();
|
private static final StreetsOfNewCapenna instance = new StreetsOfNewCapenna();
|
||||||
|
|
||||||
public static StreetsOfNewCapenna getInstance() {
|
public static StreetsOfNewCapenna getInstance() {
|
||||||
|
@ -260,6 +255,7 @@ public final class StreetsOfNewCapenna extends ExpansionSet {
|
||||||
cards.add(new SetCardInfo("Tainted Indulgence", 227, Rarity.UNCOMMON, mage.cards.t.TaintedIndulgence.class));
|
cards.add(new SetCardInfo("Tainted Indulgence", 227, Rarity.UNCOMMON, mage.cards.t.TaintedIndulgence.class));
|
||||||
cards.add(new SetCardInfo("Take to the Streets", 158, Rarity.UNCOMMON, mage.cards.t.TakeToTheStreets.class));
|
cards.add(new SetCardInfo("Take to the Streets", 158, Rarity.UNCOMMON, mage.cards.t.TakeToTheStreets.class));
|
||||||
cards.add(new SetCardInfo("Tavern Swindler", 96, Rarity.UNCOMMON, mage.cards.t.TavernSwindler.class));
|
cards.add(new SetCardInfo("Tavern Swindler", 96, Rarity.UNCOMMON, mage.cards.t.TavernSwindler.class));
|
||||||
|
cards.add(new SetCardInfo("Tenacious Underdog", 97, Rarity.RARE, mage.cards.t.TenaciousUnderdog.class));
|
||||||
cards.add(new SetCardInfo("Titan of Industry", 159, Rarity.MYTHIC, mage.cards.t.TitanOfIndustry.class));
|
cards.add(new SetCardInfo("Titan of Industry", 159, Rarity.MYTHIC, mage.cards.t.TitanOfIndustry.class));
|
||||||
cards.add(new SetCardInfo("Toluz, Clever Conductor", 228, Rarity.RARE, mage.cards.t.ToluzCleverConductor.class));
|
cards.add(new SetCardInfo("Toluz, Clever Conductor", 228, Rarity.RARE, mage.cards.t.ToluzCleverConductor.class));
|
||||||
cards.add(new SetCardInfo("Topiary Stomper", 160, Rarity.RARE, mage.cards.t.TopiaryStomper.class));
|
cards.add(new SetCardInfo("Topiary Stomper", 160, Rarity.RARE, mage.cards.t.TopiaryStomper.class));
|
||||||
|
@ -288,7 +284,5 @@ public final class StreetsOfNewCapenna extends ExpansionSet {
|
||||||
cards.add(new SetCardInfo("Xander's Lounge", 260, Rarity.RARE, mage.cards.x.XandersLounge.class));
|
cards.add(new SetCardInfo("Xander's Lounge", 260, Rarity.RARE, mage.cards.x.XandersLounge.class));
|
||||||
cards.add(new SetCardInfo("Ziatora's Proving Ground", 261, Rarity.RARE, mage.cards.z.ZiatorasProvingGround.class));
|
cards.add(new SetCardInfo("Ziatora's Proving Ground", 261, Rarity.RARE, mage.cards.z.ZiatorasProvingGround.class));
|
||||||
cards.add(new SetCardInfo("Ziatora, the Incinerator", 231, Rarity.MYTHIC, mage.cards.z.ZiatoraTheIncinerator.class));
|
cards.add(new SetCardInfo("Ziatora, the Incinerator", 231, Rarity.MYTHIC, mage.cards.z.ZiatoraTheIncinerator.class));
|
||||||
|
|
||||||
cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); // remove when shield counters are implemented
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
package org.mage.test.cards.abilities.keywords;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.keyword.HasteAbility;
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import mage.game.permanent.Permanent;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public class BlitzTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
|
private static final String withBlitz = " with Blitz";
|
||||||
|
private static final String decoy = "Riveteers Decoy";
|
||||||
|
private static final String underdog = "Tenacious Underdog";
|
||||||
|
private static final String will = "Yawgmoth's Will";
|
||||||
|
|
||||||
|
private void assertBlitzed(String cardName, boolean isBlitzed) {
|
||||||
|
assertPermanentCount(playerA, cardName, 1);
|
||||||
|
Permanent permanent = getPermanent(cardName);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Permanent should " + (isBlitzed ? "" : "not ") + "have haste", isBlitzed,
|
||||||
|
permanent.hasAbility(HasteAbility.getInstance(), currentGame)
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Permanent should " + (isBlitzed ? "" : "not ") + "have card draw trigger", isBlitzed,
|
||||||
|
permanent
|
||||||
|
.getAbilities(currentGame)
|
||||||
|
.stream()
|
||||||
|
.map(Ability::getRule)
|
||||||
|
.anyMatch("When this creature dies, draw a card."::equals)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBlitz() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4);
|
||||||
|
addCard(Zone.HAND, playerA, decoy);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, decoy + withBlitz);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, decoy, 1);
|
||||||
|
assertBlitzed(decoy, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBlitzSacrificed() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4);
|
||||||
|
addCard(Zone.HAND, playerA, decoy);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, decoy + withBlitz);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, decoy, 0);
|
||||||
|
assertGraveyardCount(playerA, decoy, 1);
|
||||||
|
assertHandCount(playerA, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoBlitz() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
||||||
|
addCard(Zone.HAND, playerA, decoy);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, decoy);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, decoy, 1);
|
||||||
|
assertBlitzed(decoy, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTenaciousUnderdogNormal() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
|
||||||
|
addCard(Zone.GRAVEYARD, playerA, underdog);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, underdog);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||||
|
try {
|
||||||
|
execute();
|
||||||
|
} catch (AssertionError e) {
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Shouldn't be able to cast normally from graveyard",
|
||||||
|
"Missing CAST/ACTIVATE def for turn 1, step PRECOMBAT_MAIN, PlayerA\n" +
|
||||||
|
"Can't find available command - activate:Cast Tenacious Underdog " +
|
||||||
|
"(use checkPlayableAbility for \"non available\" checks)", e.getMessage()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertGraveyardCount(playerA, underdog, 1);
|
||||||
|
assertLife(playerA, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTenaciousUnderdogBlitz() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4);
|
||||||
|
addCard(Zone.GRAVEYARD, playerA, underdog);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, underdog + withBlitz);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertBlitzed(underdog, true);
|
||||||
|
assertLife(playerA, 20 - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTenaciousUnderdogYawgmothsWill() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5);
|
||||||
|
addCard(Zone.HAND, playerA, will);
|
||||||
|
addCard(Zone.GRAVEYARD, playerA, underdog);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, will);
|
||||||
|
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, underdog);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertBlitzed(underdog, false);
|
||||||
|
assertLife(playerA, 20);
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,6 @@ import org.junit.Test;
|
||||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author BetaSteward
|
* @author BetaSteward
|
||||||
*/
|
*/
|
||||||
public class DashTest extends CardTestPlayerBase {
|
public class DashTest extends CardTestPlayerBase {
|
||||||
|
@ -31,7 +30,6 @@ public class DashTest extends CardTestPlayerBase {
|
||||||
* may cast this spell for its dash cost. If you do, it gains haste, and
|
* may cast this spell for its dash cost. If you do, it gains haste, and
|
||||||
* it's returned from the battlefield to its owner's hand at the beginning
|
* it's returned from the battlefield to its owner's hand at the beginning
|
||||||
* of the next end step.)
|
* of the next end step.)
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDash() {
|
public void testDash() {
|
||||||
|
@ -133,4 +131,21 @@ public class DashTest extends CardTestPlayerBase {
|
||||||
assertPermanentCount(playerA, "Warbringer", 2);
|
assertPermanentCount(playerA, "Warbringer", 2);
|
||||||
assertHandCount(playerA, "Warbringer", 0);
|
assertHandCount(playerA, "Warbringer", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRegularCostReduction() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Ruby Medallion");
|
||||||
|
addCard(Zone.HAND, playerA, "Screamreach Brawler");
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Screamreach Brawler");
|
||||||
|
setChoice(playerA, true);
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Ruby Medallion", 1);
|
||||||
|
assertPermanentCount(playerA, "Screamreach Brawler", 1);
|
||||||
|
assertHandCount(playerA, "Screamreach Brawler", 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
|
|
||||||
package mage;
|
package mage;
|
||||||
|
|
||||||
|
import mage.constants.ColoredManaSymbol;
|
||||||
|
import mage.util.Copyable;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import mage.constants.ColoredManaSymbol;
|
|
||||||
import mage.util.Copyable;
|
|
||||||
|
|
||||||
public class ObjectColor implements Serializable, Copyable<ObjectColor>, Comparable<ObjectColor> {
|
public class ObjectColor implements Serializable, Copyable<ObjectColor>, Comparable<ObjectColor> {
|
||||||
|
|
||||||
public static final ObjectColor WHITE = new ObjectColor("W");
|
public static final ObjectColor WHITE = new ObjectColor("W");
|
||||||
|
@ -182,13 +182,13 @@ public class ObjectColor implements Serializable, Copyable<ObjectColor>, Compara
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setColor(ObjectColor color) {
|
public void setColor(ObjectColor color) {
|
||||||
this.setBlack(color.isBlack());
|
this.setBlack(color != null && color.isBlack());
|
||||||
this.setBlue(color.isBlue());
|
this.setBlue(color != null && color.isBlue());
|
||||||
this.setGreen(color.isGreen());
|
this.setGreen(color != null && color.isGreen());
|
||||||
this.setRed(color.isRed());
|
this.setRed(color != null && color.isRed());
|
||||||
this.setWhite(color.isWhite());
|
this.setWhite(color != null && color.isWhite());
|
||||||
|
|
||||||
this.setGold(color.isGold());
|
this.setGold(color != null && color.isGold());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addColor(ObjectColor color) {
|
public void addColor(ObjectColor color) {
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package mage.abilities.condition.common;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.condition.Condition;
|
||||||
|
import mage.abilities.keyword.BlitzAbility;
|
||||||
|
import mage.game.Game;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public enum BlitzedCondition implements Condition {
|
||||||
|
instance;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Game game, Ability source) {
|
||||||
|
List<Integer> blitzActivations = (List<Integer>) game.getState().getValue(BlitzAbility.BLITZ_ACTIVATION_VALUE_KEY + source.getSourceId());
|
||||||
|
return blitzActivations != null && blitzActivations.contains(game.getState().getZoneChangeCounter(source.getSourceId()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
package mage.abilities.condition.common;
|
package mage.abilities.condition.common;
|
||||||
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
|
@ -6,24 +5,21 @@ import mage.abilities.condition.Condition;
|
||||||
import mage.abilities.keyword.DashAbility;
|
import mage.abilities.keyword.DashAbility;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author LevelX2
|
* @author LevelX2
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public enum DashedCondition implements Condition {
|
public enum DashedCondition implements Condition {
|
||||||
|
|
||||||
instance;
|
instance;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
Card card = game.getCard(source.getSourceId());
|
Card card = game.getCard(source.getSourceId());
|
||||||
if (card != null) {
|
return card != null
|
||||||
return card.getAbilities(game).stream()
|
&& CardUtil.castStream(card
|
||||||
.filter(DashAbility.class::isInstance)
|
.getAbilities(game)
|
||||||
.anyMatch(d -> ((DashAbility) d).isActivated(source, game));
|
.stream(), DashAbility.class)
|
||||||
|
.anyMatch(ability -> ability.isActivated(source, game));
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@ import mage.game.Game;
|
||||||
*
|
*
|
||||||
* @author LevelX2
|
* @author LevelX2
|
||||||
*/
|
*/
|
||||||
public interface AlternativeCost2 extends Cost {
|
public interface AlternativeCost extends Cost {
|
||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,13 +41,11 @@ public interface AlternativeCost2 extends Cost {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the player intends to pay the alternate cost, the cost will be activated
|
* If the player intends to pay the alternate cost, the cost will be activated
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
void activate();
|
void activate();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the activate
|
* Reset the activate
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
|
@ -61,4 +59,6 @@ public interface AlternativeCost2 extends Cost {
|
||||||
|
|
||||||
Cost getCost();
|
Cost getCost();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
AlternativeCost copy();
|
||||||
}
|
}
|
|
@ -1,43 +1,38 @@
|
||||||
|
|
||||||
package mage.abilities.costs;
|
package mage.abilities.costs;
|
||||||
|
|
||||||
|
import mage.abilities.costs.mana.ManaCost;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alternative costs
|
* Alternative costs
|
||||||
*
|
*
|
||||||
* @author LevelX2
|
|
||||||
*
|
|
||||||
* @param <T>
|
* @param <T>
|
||||||
|
* @author LevelX2
|
||||||
*/
|
*/
|
||||||
public class AlternativeCost2Impl<T extends AlternativeCost2Impl<T>> extends CostsImpl<Cost> implements AlternativeCost2 {
|
public class AlternativeCostImpl<T extends AlternativeCostImpl<T>> extends CostsImpl<Cost> implements AlternativeCost {
|
||||||
|
|
||||||
protected String name;
|
protected String name;
|
||||||
protected String reminderText;
|
protected String reminderText;
|
||||||
protected String delimiter;
|
protected boolean isMana;
|
||||||
|
|
||||||
protected boolean activated;
|
protected boolean activated;
|
||||||
|
|
||||||
public AlternativeCost2Impl(String name, String reminderText, Cost cost) {
|
public AlternativeCostImpl(String name, String reminderText, Cost cost) {
|
||||||
this(name, " ", reminderText, cost);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AlternativeCost2Impl(String name, String delimiter, String reminderText, Cost cost) {
|
|
||||||
this.activated = false;
|
this.activated = false;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.delimiter = delimiter;
|
this.isMana = cost instanceof ManaCost;
|
||||||
if (reminderText != null) {
|
if (reminderText != null) {
|
||||||
this.reminderText = "<i>" + reminderText + "</i>";
|
this.reminderText = "<i>(" + reminderText + ")</i>";
|
||||||
}
|
}
|
||||||
this.add(cost);
|
this.add(cost);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlternativeCost2Impl(final AlternativeCost2Impl cost) {
|
public AlternativeCostImpl(final AlternativeCostImpl<?> cost) {
|
||||||
super(cost);
|
super(cost);
|
||||||
this.name = cost.name;
|
this.name = cost.name;
|
||||||
this.reminderText = cost.reminderText;
|
this.reminderText = cost.reminderText;
|
||||||
this.activated = cost.activated;
|
this.activated = cost.activated;
|
||||||
this.delimiter = cost.delimiter;
|
this.isMana = cost.isMana;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -57,7 +52,7 @@ public class AlternativeCost2Impl<T extends AlternativeCost2Impl<T>> extends Cos
|
||||||
if (onlyCost) {
|
if (onlyCost) {
|
||||||
return getText();
|
return getText();
|
||||||
} else {
|
} else {
|
||||||
return (name != null ? name : "") + (delimiter != null ? delimiter : "") + getText();
|
return (name != null ? name : "") + (isMana ? " " : "—") + getText() + (isMana ? "" : '.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +75,7 @@ public class AlternativeCost2Impl<T extends AlternativeCost2Impl<T>> extends Cos
|
||||||
* message.
|
* message.
|
||||||
*
|
*
|
||||||
* @param position - if there are multiple costs, it's the postion the cost
|
* @param position - if there are multiple costs, it's the postion the cost
|
||||||
* is set (starting with 0)
|
* is set (starting with 0)
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -92,7 +87,6 @@ public class AlternativeCost2Impl<T extends AlternativeCost2Impl<T>> extends Cos
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the player intends to pay the cost, the cost will be activated
|
* If the player intends to pay the cost, the cost will be activated
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void activate() {
|
public void activate() {
|
||||||
|
@ -101,7 +95,6 @@ public class AlternativeCost2Impl<T extends AlternativeCost2Impl<T>> extends Cos
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the activate and count information
|
* Reset the activate and count information
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
|
@ -120,8 +113,8 @@ public class AlternativeCost2Impl<T extends AlternativeCost2Impl<T>> extends Cos
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AlternativeCost2Impl copy() {
|
public AlternativeCostImpl<?> copy() {
|
||||||
return new AlternativeCost2Impl(this);
|
return new AlternativeCostImpl<>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -131,5 +124,4 @@ public class AlternativeCost2Impl<T extends AlternativeCost2Impl<T>> extends Cos
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -24,7 +24,7 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
||||||
|
|
||||||
private static final String ALTERNATIVE_COST_ACTIVATION_KEY = "AlternativeCostActivated";
|
private static final String ALTERNATIVE_COST_ACTIVATION_KEY = "AlternativeCostActivated";
|
||||||
|
|
||||||
private Costs<AlternativeCost2> alternateCosts = new CostsImpl<>();
|
private Costs<AlternativeCost> alternateCosts = new CostsImpl<>();
|
||||||
protected Condition condition;
|
protected Condition condition;
|
||||||
protected String rule;
|
protected String rule;
|
||||||
protected FilterCard filter;
|
protected FilterCard filter;
|
||||||
|
@ -88,15 +88,15 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCost(Cost cost) {
|
public void addCost(Cost cost) {
|
||||||
AlternativeCost2 alternativeCost = convertToAlternativeCost(cost);
|
AlternativeCost alternativeCost = convertToAlternativeCost(cost);
|
||||||
if (alternativeCost != null) {
|
if (alternativeCost != null) {
|
||||||
this.alternateCosts.add(alternativeCost);
|
this.alternateCosts.add(alternativeCost);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AlternativeCost2 convertToAlternativeCost(Cost cost) {
|
private AlternativeCost convertToAlternativeCost(Cost cost) {
|
||||||
//return cost != null ? new AlternativeCost2Impl(null, cost.getText(), cost) : null;
|
//return cost != null ? new AlternativeCost2Impl(null, cost.getText(), cost) : null;
|
||||||
return cost != null ? new AlternativeCost2Impl(null, "", "", cost) : null;
|
return cost != null ? new AlternativeCostImpl(null, "", cost) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -123,7 +123,7 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
||||||
}
|
}
|
||||||
Player player = game.getPlayer(ability.getControllerId());
|
Player player = game.getPlayer(ability.getControllerId());
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
Costs<AlternativeCost2> alternativeCostsToCheck;
|
Costs<AlternativeCost> alternativeCostsToCheck;
|
||||||
if (dynamicCost != null) {
|
if (dynamicCost != null) {
|
||||||
alternativeCostsToCheck = new CostsImpl<>();
|
alternativeCostsToCheck = new CostsImpl<>();
|
||||||
alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(ability, game)));
|
alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(ability, game)));
|
||||||
|
@ -149,7 +149,7 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
||||||
if (!onlyMana) {
|
if (!onlyMana) {
|
||||||
ability.getCosts().clear();
|
ability.getCosts().clear();
|
||||||
}
|
}
|
||||||
for (AlternativeCost2 alternateCost : alternativeCostsToCheck) {
|
for (AlternativeCost alternateCost : alternativeCostsToCheck) {
|
||||||
alternateCost.activate();
|
alternateCost.activate();
|
||||||
for (Iterator it = ((Costs) alternateCost).iterator(); it.hasNext(); ) {
|
for (Iterator it = ((Costs) alternateCost).iterator(); it.hasNext(); ) {
|
||||||
Cost costDetailed = (Cost) it.next();
|
Cost costDetailed = (Cost) it.next();
|
||||||
|
@ -207,14 +207,14 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActivated(Ability source, Game game) {
|
public boolean isActivated(Ability source, Game game) {
|
||||||
Costs<AlternativeCost2> alternativeCostsToCheck;
|
Costs<AlternativeCost> alternativeCostsToCheck;
|
||||||
if (dynamicCost != null) {
|
if (dynamicCost != null) {
|
||||||
alternativeCostsToCheck = new CostsImpl<>();
|
alternativeCostsToCheck = new CostsImpl<>();
|
||||||
alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(source, game)));
|
alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(source, game)));
|
||||||
} else {
|
} else {
|
||||||
alternativeCostsToCheck = this.alternateCosts;
|
alternativeCostsToCheck = this.alternateCosts;
|
||||||
}
|
}
|
||||||
for (AlternativeCost2 cost : alternativeCostsToCheck) {
|
for (AlternativeCost cost : alternativeCostsToCheck) {
|
||||||
if (cost.isActivated(game)) {
|
if (cost.isActivated(game)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -227,6 +227,11 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
||||||
return alternateCosts.isEmpty() ? " without paying its mana costs" : " using alternative casting costs";
|
return alternateCosts.isEmpty() ? " without paying its mana costs" : " using alternative casting costs";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetCost() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRule() {
|
public String getRule() {
|
||||||
if (rule != null) {
|
if (rule != null) {
|
||||||
|
@ -245,7 +250,7 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
||||||
}
|
}
|
||||||
int numberCosts = 0;
|
int numberCosts = 0;
|
||||||
String remarkText = "";
|
String remarkText = "";
|
||||||
for (AlternativeCost2 alternativeCost : alternateCosts) {
|
for (AlternativeCost alternativeCost : alternateCosts) {
|
||||||
if (numberCosts == 0) {
|
if (numberCosts == 0) {
|
||||||
if (alternativeCost.getCost() instanceof ManaCost) {
|
if (alternativeCost.getCost() instanceof ManaCost) {
|
||||||
sb.append("pay ");
|
sb.append("pay ");
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
package mage.abilities.costs;
|
package mage.abilities.costs;
|
||||||
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
|
@ -6,7 +5,7 @@ import mage.game.Game;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for abilities that add alternative costs to the source.
|
* Interface for abilities that add alternative costs to the source.
|
||||||
*
|
* <p>
|
||||||
* Example of such additional source costs: {@link mage.abilities.keyword.KickerAbility}
|
* Example of such additional source costs: {@link mage.abilities.keyword.KickerAbility}
|
||||||
*
|
*
|
||||||
* @author LevelX2
|
* @author LevelX2
|
||||||
|
@ -15,35 +14,38 @@ public interface AlternativeSourceCosts {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ask the player if they want to use the alternative costs
|
* Ask the player if they want to use the alternative costs
|
||||||
*
|
*
|
||||||
* @param ability ability the alternative cost is activated for
|
* @param ability ability the alternative cost is activated for
|
||||||
* @param game
|
* @param game
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
boolean askToActivateAlternativeCosts(Ability ability, Game game);
|
boolean askToActivateAlternativeCosts(Ability ability, Game game);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the alternative spell cost currently available
|
* Is the alternative spell cost currently available
|
||||||
*
|
*
|
||||||
* @param source spell ability the alternative costs can be paid for
|
* @param source spell ability the alternative costs can be paid for
|
||||||
* @param game
|
* @param game
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
boolean isAvailable(Ability source, Game game);
|
boolean isAvailable(Ability source, Game game);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Was the alternative cost activated
|
* Was the alternative cost activated
|
||||||
|
*
|
||||||
* @param game
|
* @param game
|
||||||
* @param source
|
* @param source
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
boolean isActivated(Ability source, Game game);
|
boolean isActivated(Ability source, Game game);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Suffix string to use for game log
|
* Suffix string to use for game log
|
||||||
|
*
|
||||||
* @param game
|
* @param game
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
String getCastMessageSuffix(Game game);
|
String getCastMessageSuffix(Game game);
|
||||||
|
|
||||||
|
void resetCost();
|
||||||
}
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
package mage.abilities.costs;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.SpellAbility;
|
||||||
|
import mage.abilities.StaticAbility;
|
||||||
|
import mage.abilities.costs.mana.ManaCost;
|
||||||
|
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
|
import mage.cards.Card;
|
||||||
|
import mage.constants.Outcome;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.players.Player;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public abstract class AlternativeSourceCostsImpl extends StaticAbility implements AlternativeSourceCosts {
|
||||||
|
|
||||||
|
protected final AlternativeCost alternativeCost;
|
||||||
|
protected final String reminderText;
|
||||||
|
private int zoneChangeCounter = 0;
|
||||||
|
|
||||||
|
protected AlternativeSourceCostsImpl(String name, String reminderText, String manaString) {
|
||||||
|
this(name, reminderText, new ManaCostsImpl<>(manaString));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AlternativeSourceCostsImpl(String name, String reminderText, Cost cost) {
|
||||||
|
super(Zone.ALL, null);
|
||||||
|
this.name = name;
|
||||||
|
this.reminderText = reminderText;
|
||||||
|
this.alternativeCost = new AlternativeCostImpl<>(name, reminderText, cost);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AlternativeSourceCostsImpl(final AlternativeSourceCostsImpl ability) {
|
||||||
|
super(ability);
|
||||||
|
this.alternativeCost = ability.alternativeCost.copy();
|
||||||
|
this.reminderText = ability.reminderText;
|
||||||
|
this.zoneChangeCounter = ability.zoneChangeCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean askToActivateAlternativeCosts(Ability ability, Game game) {
|
||||||
|
if (ability instanceof SpellAbility) {
|
||||||
|
handleActivatingAlternativeCosts(ability, game);
|
||||||
|
}
|
||||||
|
return isActivated(ability, game);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean handleActivatingAlternativeCosts(Ability ability, Game game) {
|
||||||
|
Player player = game.getPlayer(ability.getControllerId());
|
||||||
|
if (player == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.resetCost();
|
||||||
|
if (!alternativeCost.canPay(ability, this, player.getId(), game)
|
||||||
|
|| !player.chooseUse(Outcome.Benefit, "Cast this for its " + this.name + " cost? (" + alternativeCost.getText(true) + ')', ability, game)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
alternativeCost.activate();
|
||||||
|
if (zoneChangeCounter == 0) {
|
||||||
|
Card card = game.getCard(getSourceId());
|
||||||
|
if (card != null) {
|
||||||
|
zoneChangeCounter = card.getZoneChangeCounter(game);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("source card not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ability.getManaCostsToPay().clear();
|
||||||
|
ability.getCosts().clear();
|
||||||
|
for (Iterator<Cost> it = ((Costs<Cost>) alternativeCost).iterator(); it.hasNext(); ) {
|
||||||
|
Cost cost = it.next();
|
||||||
|
if (cost instanceof ManaCost) {
|
||||||
|
ability.getManaCostsToPay().add((ManaCost) cost.copy());
|
||||||
|
} else {
|
||||||
|
ability.getCosts().add(cost.copy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActivated(Ability ability, Game game) {
|
||||||
|
Card card = game.getCard(sourceId);
|
||||||
|
if (card != null && card.getZoneChangeCounter(game) <= zoneChangeCounter + 1) {
|
||||||
|
return alternativeCost.isActivated(game);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Costs<Cost> getCosts() {
|
||||||
|
return (Costs<Cost>) alternativeCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRule() {
|
||||||
|
return alternativeCost.getText(false) + ' ' + alternativeCost.getReminderText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetCost() {
|
||||||
|
alternativeCost.reset();
|
||||||
|
this.zoneChangeCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable(Ability source, Game game) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCastMessageSuffix(Game game) {
|
||||||
|
return alternativeCost.getCastSuffixMessage(0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,49 @@
|
||||||
package mage.abilities.keyword;
|
package mage.abilities.keyword;
|
||||||
|
|
||||||
import mage.abilities.StaticAbility;
|
import mage.abilities.Ability;
|
||||||
import mage.constants.Zone;
|
import mage.abilities.SpellAbility;
|
||||||
|
import mage.abilities.common.DiesSourceTriggeredAbility;
|
||||||
|
import mage.abilities.common.EntersBattlefieldAbility;
|
||||||
|
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
|
||||||
|
import mage.abilities.condition.common.BlitzedCondition;
|
||||||
|
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
|
import mage.abilities.effects.OneShotEffect;
|
||||||
|
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||||
|
import mage.abilities.effects.common.SacrificeTargetEffect;
|
||||||
|
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
|
||||||
|
import mage.cards.Card;
|
||||||
|
import mage.constants.Duration;
|
||||||
|
import mage.constants.Outcome;
|
||||||
|
import mage.constants.SpellAbilityType;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.target.targetpointer.FixedTarget;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author TheElk801
|
* @author TheElk801
|
||||||
*/
|
*/
|
||||||
public class BlitzAbility extends StaticAbility {
|
public class BlitzAbility extends SpellAbility {
|
||||||
|
|
||||||
public BlitzAbility(String manaString) {
|
public static final String BLITZ_ACTIVATION_VALUE_KEY = "blitzActivation";
|
||||||
// TODO: Implement this
|
protected static final String KEYWORD = "Blitz";
|
||||||
super(Zone.ALL, null);
|
protected static final String REMINDER_TEXT = "If you cast this spell for its blitz cost, it gains haste " +
|
||||||
|
"and \"When this creature dies, draw a card.\" Sacrifice it at the beginning of the next end step.";
|
||||||
|
|
||||||
|
public BlitzAbility(Card card, String manaString) {
|
||||||
|
super(new ManaCostsImpl<>(manaString), card.getName() + " with Blitz");
|
||||||
|
this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE;
|
||||||
|
Ability ability = new EntersBattlefieldAbility(
|
||||||
|
new GainAbilitySourceEffect(HasteAbility.getInstance(), Duration.Custom, false),
|
||||||
|
BlitzedCondition.instance, "", ""
|
||||||
|
);
|
||||||
|
ability.addEffect(new GainAbilitySourceEffect(new DiesSourceTriggeredAbility(
|
||||||
|
new DrawCardSourceControllerEffect(1)
|
||||||
|
).setTriggerPhrase("When this creature dies, ")));
|
||||||
|
ability.addEffect(new BlitzAddDelayedTriggeredAbilityEffect());
|
||||||
|
ability.setRuleVisible(false);
|
||||||
|
addSubAbility(ability);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BlitzAbility(final BlitzAbility ability) {
|
private BlitzAbility(final BlitzAbility ability) {
|
||||||
|
@ -21,4 +54,56 @@ public class BlitzAbility extends StaticAbility {
|
||||||
public BlitzAbility copy() {
|
public BlitzAbility copy() {
|
||||||
return new BlitzAbility(this);
|
return new BlitzAbility(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRule() {
|
||||||
|
return "Blitz";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean activate(Game game, boolean noMana) {
|
||||||
|
if (!super.activate(game, noMana)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Object obj = game.getState().getValue(BLITZ_ACTIVATION_VALUE_KEY + getSourceId());
|
||||||
|
List<Integer> blitzActivations;
|
||||||
|
if (obj != null) {
|
||||||
|
blitzActivations = (List<Integer>) obj;
|
||||||
|
} else {
|
||||||
|
blitzActivations = new ArrayList<>();
|
||||||
|
game.getState().setValue(BLITZ_ACTIVATION_VALUE_KEY + getSourceId(), blitzActivations);
|
||||||
|
}
|
||||||
|
blitzActivations.add(game.getState().getZoneChangeCounter(getSourceId()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BlitzAddDelayedTriggeredAbilityEffect extends OneShotEffect {
|
||||||
|
|
||||||
|
BlitzAddDelayedTriggeredAbilityEffect() {
|
||||||
|
super(Outcome.Benefit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlitzAddDelayedTriggeredAbilityEffect(final BlitzAddDelayedTriggeredAbilityEffect effect) {
|
||||||
|
super(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlitzAddDelayedTriggeredAbilityEffect copy() {
|
||||||
|
return new BlitzAddDelayedTriggeredAbilityEffect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Game game, Ability source) {
|
||||||
|
if (game.getPermanentEntering(source.getSourceId()) == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// init target pointer now because the Blitzed creature will only be returned from battlefield zone (now in entering state so zone change counter is not raised yet)
|
||||||
|
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(
|
||||||
|
new SacrificeTargetEffect()
|
||||||
|
.setText("sacrifice the blitzed creature")
|
||||||
|
.setTargetPointer(new FixedTarget(source.getSourceId(), game.getState().getZoneChangeCounter(source.getSourceId()) + 1))
|
||||||
|
), source);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,195 +1,55 @@
|
||||||
package mage.abilities.keyword;
|
package mage.abilities.keyword;
|
||||||
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.DelayedTriggeredAbility;
|
|
||||||
import mage.abilities.SpellAbility;
|
|
||||||
import mage.abilities.StaticAbility;
|
|
||||||
import mage.abilities.common.EntersBattlefieldAbility;
|
import mage.abilities.common.EntersBattlefieldAbility;
|
||||||
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
|
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
|
||||||
import mage.abilities.condition.common.DashedCondition;
|
import mage.abilities.condition.common.DashedCondition;
|
||||||
import mage.abilities.costs.*;
|
import mage.abilities.costs.AlternativeSourceCostsImpl;
|
||||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
|
||||||
import mage.abilities.decorator.ConditionalOneShotEffect;
|
|
||||||
import mage.abilities.effects.OneShotEffect;
|
import mage.abilities.effects.OneShotEffect;
|
||||||
import mage.abilities.effects.common.ReturnToHandTargetEffect;
|
import mage.abilities.effects.common.ReturnToHandTargetEffect;
|
||||||
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
|
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
|
||||||
import mage.cards.Card;
|
|
||||||
import mage.constants.Duration;
|
import mage.constants.Duration;
|
||||||
import mage.constants.Outcome;
|
import mage.constants.Outcome;
|
||||||
import mage.constants.Zone;
|
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.players.Player;
|
|
||||||
import mage.target.targetpointer.FixedTarget;
|
import mage.target.targetpointer.FixedTarget;
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author LevelX2
|
* @author LevelX2
|
||||||
*/
|
*/
|
||||||
public class DashAbility extends StaticAbility implements AlternativeSourceCosts {
|
public class DashAbility extends AlternativeSourceCostsImpl {
|
||||||
|
|
||||||
protected static final String KEYWORD = "Dash";
|
protected static final String KEYWORD = "Dash";
|
||||||
protected static final String REMINDER_TEXT = "(You may cast this spell for its dash cost. "
|
protected static final String REMINDER_TEXT = "You may cast this spell for its dash cost. "
|
||||||
+ "If you do, it gains haste, and it's returned from the battlefield to its owner's "
|
+ "If you do, it gains haste, and it's returned from the battlefield to its owner's "
|
||||||
+ "hand at the beginning of the next end step.)";
|
+ "hand at the beginning of the next end step.";
|
||||||
|
|
||||||
protected List<AlternativeCost2> alternativeSourceCosts = new LinkedList<>();
|
|
||||||
|
|
||||||
// needed to check activation status, if card changes zone after casting it
|
|
||||||
private int zoneChangeCounter = 0;
|
|
||||||
|
|
||||||
public DashAbility(String manaString) {
|
public DashAbility(String manaString) {
|
||||||
super(Zone.ALL, null);
|
super(KEYWORD, REMINDER_TEXT, manaString);
|
||||||
name = KEYWORD;
|
|
||||||
this.addDashCost(manaString);
|
|
||||||
Ability ability = new EntersBattlefieldAbility(
|
Ability ability = new EntersBattlefieldAbility(
|
||||||
new GainAbilitySourceEffect(HasteAbility.getInstance(), Duration.Custom, false),
|
new GainAbilitySourceEffect(HasteAbility.getInstance(), Duration.Custom, false),
|
||||||
DashedCondition.instance, "", "");
|
DashedCondition.instance, "", "");
|
||||||
ability.addEffect(new DashAddDelayedTriggeredAbilityEffect());
|
ability.addEffect(new DashAddDelayedTriggeredAbilityEffect());
|
||||||
ability.setRuleVisible(false);
|
ability.setRuleVisible(false);
|
||||||
addSubAbility(ability);
|
addSubAbility(ability);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DashAbility(final DashAbility ability) {
|
private DashAbility(final DashAbility ability) {
|
||||||
super(ability);
|
super(ability);
|
||||||
this.alternativeSourceCosts.addAll(ability.alternativeSourceCosts);
|
|
||||||
this.zoneChangeCounter = ability.zoneChangeCounter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DashAbility copy() {
|
public DashAbility copy() {
|
||||||
return new DashAbility(this);
|
return new DashAbility(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final AlternativeCost2 addDashCost(String manaString) {
|
|
||||||
AlternativeCost2 evokeCost = new AlternativeCost2Impl(KEYWORD, REMINDER_TEXT, new ManaCostsImpl(manaString));
|
|
||||||
alternativeSourceCosts.add(evokeCost);
|
|
||||||
return evokeCost;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetDash() {
|
|
||||||
for (AlternativeCost2 cost : alternativeSourceCosts) {
|
|
||||||
cost.reset();
|
|
||||||
}
|
|
||||||
zoneChangeCounter = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isActivated(Ability ability, Game game) {
|
|
||||||
Card card = game.getCard(sourceId);
|
|
||||||
if (card != null
|
|
||||||
&& card.getZoneChangeCounter(game) <= zoneChangeCounter + 1) {
|
|
||||||
for (AlternativeCost2 cost : alternativeSourceCosts) {
|
|
||||||
if (cost.isActivated(game)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable(Ability source, Game game) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean askToActivateAlternativeCosts(Ability ability, Game game) {
|
|
||||||
if (ability instanceof SpellAbility) {
|
|
||||||
// we must use the controller of the ability here IE: Hedonist's Trove (play from not own hand when you aren't the owner)
|
|
||||||
Player player = game.getPlayer(ability.getControllerId());
|
|
||||||
if (player != null) {
|
|
||||||
this.resetDash();
|
|
||||||
for (AlternativeCost2 dashCost : alternativeSourceCosts) {
|
|
||||||
if (dashCost.canPay(ability, this, player.getId(), game)
|
|
||||||
&& player.chooseUse(Outcome.Benefit, KEYWORD
|
|
||||||
+ " the creature for " + dashCost.getText(true) + " ?", ability, game)) {
|
|
||||||
activateDash(dashCost, game);
|
|
||||||
ability.getManaCostsToPay().clear();
|
|
||||||
ability.getCosts().clear();
|
|
||||||
for (Iterator it = ((Costs) dashCost).iterator(); it.hasNext(); ) {
|
|
||||||
Cost cost = (Cost) it.next();
|
|
||||||
if (cost instanceof ManaCostsImpl) {
|
|
||||||
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
|
|
||||||
} else {
|
|
||||||
ability.getCosts().add(cost.copy());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isActivated(ability, game);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activateDash(AlternativeCost2 cost, Game game) {
|
|
||||||
cost.activate();
|
|
||||||
// remember zone change counter
|
|
||||||
if (zoneChangeCounter == 0) {
|
|
||||||
Card card = game.getCard(getSourceId());
|
|
||||||
if (card != null) {
|
|
||||||
zoneChangeCounter = card.getZoneChangeCounter(game);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Dash source card not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRule() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
int numberCosts = 0;
|
|
||||||
String remarkText = "";
|
|
||||||
for (AlternativeCost2 dashCost : alternativeSourceCosts) {
|
|
||||||
if (numberCosts == 0) {
|
|
||||||
sb.append(dashCost.getText(false));
|
|
||||||
remarkText = dashCost.getReminderText();
|
|
||||||
} else {
|
|
||||||
sb.append(" and/or ").append(dashCost.getText(true));
|
|
||||||
}
|
|
||||||
++numberCosts;
|
|
||||||
}
|
|
||||||
if (numberCosts == 1) {
|
|
||||||
sb.append(' ').append(remarkText);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCastMessageSuffix(Game game) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
int position = 0;
|
|
||||||
for (AlternativeCost2 cost : alternativeSourceCosts) {
|
|
||||||
if (cost.isActivated(game)) {
|
|
||||||
sb.append(cost.getCastSuffixMessage(position));
|
|
||||||
++position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Costs<Cost> getCosts() {
|
|
||||||
Costs<Cost> alterCosts = new CostsImpl<>();
|
|
||||||
for (AlternativeCost2 aCost : alternativeSourceCosts) {
|
|
||||||
alterCosts.add(aCost.getCost());
|
|
||||||
}
|
|
||||||
return alterCosts;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DashAddDelayedTriggeredAbilityEffect extends OneShotEffect {
|
class DashAddDelayedTriggeredAbilityEffect extends OneShotEffect {
|
||||||
|
|
||||||
public DashAddDelayedTriggeredAbilityEffect() {
|
DashAddDelayedTriggeredAbilityEffect() {
|
||||||
super(Outcome.Benefit);
|
super(Outcome.Benefit);
|
||||||
this.staticText = "return the dashed creature from the battlefield to its owner's hand";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DashAddDelayedTriggeredAbilityEffect(final DashAddDelayedTriggeredAbilityEffect effect) {
|
private DashAddDelayedTriggeredAbilityEffect(final DashAddDelayedTriggeredAbilityEffect effect) {
|
||||||
super(effect);
|
super(effect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,20 +60,15 @@ class DashAddDelayedTriggeredAbilityEffect extends OneShotEffect {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
if (game.getPermanentEntering(source.getSourceId()) != null) {
|
if (game.getPermanentEntering(source.getSourceId()) == null) {
|
||||||
OneShotEffect returnToHandEffect = new ReturnToHandTargetEffect();
|
return false;
|
||||||
ConditionalOneShotEffect mustBeOnBattlefieldToReturn = new ConditionalOneShotEffect(returnToHandEffect, DashAddDelayedTriggeredAbilityEffect::check);
|
|
||||||
mustBeOnBattlefieldToReturn.setText("return the dashed creature from the battlefield to its owner's hand");
|
|
||||||
// init target pointer now because the dashed creature will only be returned from battlefield zone (now in entering state so zone change counter is not raised yet)
|
|
||||||
mustBeOnBattlefieldToReturn.setTargetPointer(new FixedTarget(source.getSourceId(), game.getState().getZoneChangeCounter(source.getSourceId()) + 1));
|
|
||||||
DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(mustBeOnBattlefieldToReturn);
|
|
||||||
game.addDelayedTriggeredAbility(delayedAbility, source);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
// init target pointer now because the dashed creature will only be returned from battlefield zone (now in entering state so zone change counter is not raised yet)
|
||||||
}
|
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(
|
||||||
|
new ReturnToHandTargetEffect()
|
||||||
static boolean check(Game game, Ability source) {
|
.setText("return the dashed creature from the battlefield to its owner's hand")
|
||||||
return game.getState().getZoneChangeCounter(source.getSourceId()) == source.getSourceObjectZoneChangeCounter() + 1;
|
.setTargetPointer(new FixedTarget(source.getSourceId(), game.getState().getZoneChangeCounter(source.getSourceId()) + 1))
|
||||||
|
), source);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,29 @@
|
||||||
package mage.abilities.keyword;
|
package mage.abilities.keyword;
|
||||||
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.SpellAbility;
|
|
||||||
import mage.abilities.StaticAbility;
|
|
||||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||||
import mage.abilities.condition.common.EvokedCondition;
|
import mage.abilities.condition.common.EvokedCondition;
|
||||||
import mage.abilities.costs.*;
|
import mage.abilities.costs.AlternativeSourceCostsImpl;
|
||||||
|
import mage.abilities.costs.Cost;
|
||||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
|
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
|
||||||
import mage.abilities.effects.common.SacrificeSourceEffect;
|
import mage.abilities.effects.common.SacrificeSourceEffect;
|
||||||
import mage.cards.Card;
|
|
||||||
import mage.constants.Outcome;
|
|
||||||
import mage.constants.Zone;
|
|
||||||
import mage.game.Game;
|
|
||||||
import mage.players.Player;
|
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author LevelX2
|
* @author LevelX2
|
||||||
*/
|
*/
|
||||||
public class EvokeAbility extends StaticAbility implements AlternativeSourceCosts {
|
public class EvokeAbility extends AlternativeSourceCostsImpl {
|
||||||
|
|
||||||
protected static final String EVOKE_KEYWORD = "Evoke";
|
protected static final String EVOKE_KEYWORD = "Evoke";
|
||||||
protected static final String REMINDER_TEXT = "(You may cast this spell for its evoke cost. "
|
protected static final String REMINDER_TEXT = "You may cast this spell for its evoke cost. "
|
||||||
+ "If you do, it's sacrificed when it enters the battlefield.)";
|
+ "If you do, it's sacrificed when it enters the battlefield.";
|
||||||
|
|
||||||
protected List<AlternativeCost2> evokeCosts = new LinkedList<>();
|
|
||||||
|
|
||||||
// needed to check activation status, if card changes zone after casting it
|
|
||||||
private int zoneChangeCounter = 0;
|
|
||||||
|
|
||||||
public EvokeAbility(String manaString) {
|
public EvokeAbility(String manaString) {
|
||||||
this(new ManaCostsImpl<>(manaString));
|
this(new ManaCostsImpl<>(manaString));
|
||||||
}
|
}
|
||||||
|
|
||||||
public EvokeAbility(Cost cost) {
|
public EvokeAbility(Cost cost) {
|
||||||
super(Zone.ALL, null);
|
super(EVOKE_KEYWORD, REMINDER_TEXT, cost);
|
||||||
name = EVOKE_KEYWORD;
|
|
||||||
this.addEvokeCost(cost);
|
|
||||||
Ability ability = new ConditionalInterveningIfTriggeredAbility(
|
Ability ability = new ConditionalInterveningIfTriggeredAbility(
|
||||||
new EntersBattlefieldTriggeredAbility(new SacrificeSourceEffect()),
|
new EntersBattlefieldTriggeredAbility(new SacrificeSourceEffect()),
|
||||||
EvokedCondition.instance, "Sacrifice {this} when it enters the battlefield and was evoked.");
|
EvokedCondition.instance, "Sacrifice {this} when it enters the battlefield and was evoked.");
|
||||||
|
@ -50,128 +33,10 @@ public class EvokeAbility extends StaticAbility implements AlternativeSourceCost
|
||||||
|
|
||||||
private EvokeAbility(final EvokeAbility ability) {
|
private EvokeAbility(final EvokeAbility ability) {
|
||||||
super(ability);
|
super(ability);
|
||||||
this.evokeCosts.addAll(ability.evokeCosts);
|
|
||||||
this.zoneChangeCounter = ability.zoneChangeCounter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EvokeAbility copy() {
|
public EvokeAbility copy() {
|
||||||
return new EvokeAbility(this);
|
return new EvokeAbility(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final AlternativeCost2 addEvokeCost(Cost cost) {
|
|
||||||
AlternativeCost2 evokeCost = new AlternativeCost2Impl<>(EVOKE_KEYWORD, REMINDER_TEXT, cost);
|
|
||||||
evokeCosts.add(evokeCost);
|
|
||||||
return evokeCost;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetEvoke() {
|
|
||||||
for (AlternativeCost2 cost : evokeCosts) {
|
|
||||||
cost.reset();
|
|
||||||
}
|
|
||||||
zoneChangeCounter = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isActivated(Ability ability, Game game) {
|
|
||||||
Card card = game.getCard(sourceId);
|
|
||||||
if (card != null
|
|
||||||
&& card.getZoneChangeCounter(game) <= zoneChangeCounter + 1) {
|
|
||||||
for (AlternativeCost2 cost : evokeCosts) {
|
|
||||||
if (cost.isActivated(game)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable(Ability source, Game game) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean askToActivateAlternativeCosts(Ability ability, Game game) {
|
|
||||||
if (ability instanceof SpellAbility) {
|
|
||||||
// we must use the controller of the ability here IE: Hedonist's Trove (play from not own hand when you aren't the owner)
|
|
||||||
Player player = game.getPlayer(ability.getControllerId());
|
|
||||||
if (player != null) {
|
|
||||||
this.resetEvoke();
|
|
||||||
for (AlternativeCost2 evokeCost : evokeCosts) {
|
|
||||||
if (evokeCost.canPay(ability, this, player.getId(), game)
|
|
||||||
&& player.chooseUse(Outcome.Benefit, new StringBuilder(EVOKE_KEYWORD).append(" the creature for ").append(evokeCost.getText(true)).append(" ?").toString(), ability, game)) {
|
|
||||||
activateEvoke(evokeCost, game);
|
|
||||||
ability.getManaCostsToPay().clear();
|
|
||||||
ability.getCosts().clear();
|
|
||||||
for (Iterator it = ((Costs) evokeCost).iterator(); it.hasNext();) {
|
|
||||||
Cost cost = (Cost) it.next();
|
|
||||||
if (cost instanceof ManaCostsImpl) {
|
|
||||||
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
|
|
||||||
} else {
|
|
||||||
ability.getCosts().add(cost.copy());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isActivated(ability, game);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activateEvoke(AlternativeCost2 cost, Game game) {
|
|
||||||
cost.activate();
|
|
||||||
// remember zone change counter
|
|
||||||
if (zoneChangeCounter == 0) {
|
|
||||||
Card card = game.getCard(getSourceId());
|
|
||||||
if (card != null) {
|
|
||||||
zoneChangeCounter = card.getZoneChangeCounter(game);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Evoke source card not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRule() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
int numberCosts = 0;
|
|
||||||
String remarkText = "";
|
|
||||||
for (AlternativeCost2 evokeCost : evokeCosts) {
|
|
||||||
if (numberCosts == 0) {
|
|
||||||
sb.append(evokeCost.getText(false));
|
|
||||||
remarkText = evokeCost.getReminderText();
|
|
||||||
} else {
|
|
||||||
sb.append(" and/or ").append(evokeCost.getText(true));
|
|
||||||
}
|
|
||||||
++numberCosts;
|
|
||||||
}
|
|
||||||
if (numberCosts == 1) {
|
|
||||||
sb.append(' ').append(remarkText);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCastMessageSuffix(Game game) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
int position = 0;
|
|
||||||
for (AlternativeCost2 cost : evokeCosts) {
|
|
||||||
if (cost.isActivated(game)) {
|
|
||||||
sb.append(cost.getCastSuffixMessage(position));
|
|
||||||
++position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Costs<Cost> getCosts() {
|
|
||||||
Costs<Cost> alterCosts = new CostsImpl<>();
|
|
||||||
for (AlternativeCost2 aCost : evokeCosts) {
|
|
||||||
alterCosts.add(aCost.getCost());
|
|
||||||
}
|
|
||||||
return alterCosts;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,33 @@
|
||||||
package mage.abilities.keyword;
|
package mage.abilities.keyword;
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import mage.MageObject;
|
import mage.MageObject;
|
||||||
import mage.ObjectColor;
|
import mage.ObjectColor;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.StaticAbility;
|
|
||||||
import mage.abilities.common.SimpleStaticAbility;
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
import mage.abilities.costs.AlternativeCost2Impl;
|
import mage.abilities.costs.AlternativeSourceCostsImpl;
|
||||||
import mage.abilities.costs.AlternativeSourceCosts;
|
|
||||||
import mage.abilities.costs.Cost;
|
import mage.abilities.costs.Cost;
|
||||||
import mage.abilities.costs.Costs;
|
import mage.abilities.costs.Costs;
|
||||||
import mage.abilities.costs.CostsImpl;
|
import mage.abilities.costs.CostsImpl;
|
||||||
import mage.abilities.costs.mana.GenericManaCost;
|
import mage.abilities.costs.mana.GenericManaCost;
|
||||||
import mage.abilities.costs.mana.ManaCost;
|
import mage.abilities.costs.mana.ManaCost;
|
||||||
import mage.abilities.costs.mana.ManaCosts;
|
|
||||||
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
|
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
|
||||||
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect.FaceDownType;
|
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect.FaceDownType;
|
||||||
import mage.cards.Card;
|
|
||||||
import mage.constants.AbilityType;
|
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.Outcome;
|
|
||||||
import mage.constants.Rarity;
|
import mage.constants.Rarity;
|
||||||
import mage.constants.Zone;
|
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.game.permanent.Permanent;
|
import mage.game.permanent.Permanent;
|
||||||
import mage.game.stack.Spell;
|
import mage.game.stack.Spell;
|
||||||
import mage.players.Player;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 702.36. Morph
|
* 702.36. Morph
|
||||||
*
|
* <p>
|
||||||
* 702.36a Morph is a static ability that functions in any zone from which you
|
* 702.36a Morph is a static ability that functions in any zone from which you
|
||||||
* could play the card its on, and the morph effect works any time the card is
|
* could play the card its on, and the morph effect works any time the card is
|
||||||
* face down. "Morph [cost]" means "You may cast this card as a 2/2 face-down
|
* face down. "Morph [cost]" means "You may cast this card as a 2/2 face-down
|
||||||
* creature, with no text, no name, no subtypes, and no mana cost by paying {3}
|
* creature, with no text, no name, no subtypes, and no mana cost by paying {3}
|
||||||
* rather than paying its mana cost." (See rule 707, "Face-Down Spells and
|
* rather than paying its mana cost." (See rule 707, "Face-Down Spells and
|
||||||
* Permanents.")
|
* Permanents.")
|
||||||
*
|
* <p>
|
||||||
* 702.36b To cast a card using its morph ability, turn it face down. It becomes
|
* 702.36b To cast a card using its morph ability, turn it face down. It becomes
|
||||||
* a 2/2 face-down creature card, with no text, no name, no subtypes, and no
|
* a 2/2 face-down creature card, with no text, no name, no subtypes, and no
|
||||||
* mana cost. Any effects or prohibitions that would apply to casting a card
|
* mana cost. Any effects or prohibitions that would apply to casting a card
|
||||||
|
@ -50,9 +41,9 @@ import mage.players.Player;
|
||||||
* spell resolves, it enters the battlefield with the same characteristics the
|
* spell resolves, it enters the battlefield with the same characteristics the
|
||||||
* spell had. The morph effect applies to the face-down object wherever it is,
|
* spell had. The morph effect applies to the face-down object wherever it is,
|
||||||
* and it ends when the permanent is turned face up. #
|
* and it ends when the permanent is turned face up. #
|
||||||
*
|
* <p>
|
||||||
* 702.36c You can't cast a card face down if it doesn't have morph.
|
* 702.36c You can't cast a card face down if it doesn't have morph.
|
||||||
*
|
* <p>
|
||||||
* 702.36d If you have priority, you may turn a face-down permanent you control
|
* 702.36d If you have priority, you may turn a face-down permanent you control
|
||||||
* face up. This is a special action; it doesn't use the stack (see rule 115).
|
* face up. This is a special action; it doesn't use the stack (see rule 115).
|
||||||
* To do this, show all players what the permanents morph cost would be if it
|
* To do this, show all players what the permanents morph cost would be if it
|
||||||
|
@ -62,223 +53,84 @@ import mage.players.Player;
|
||||||
* characteristics. Any abilities relating to the permanent entering the
|
* characteristics. Any abilities relating to the permanent entering the
|
||||||
* battlefield dont trigger when its turned face up and dont have any effect,
|
* battlefield dont trigger when its turned face up and dont have any effect,
|
||||||
* because the permanent has already entered the battlefield.
|
* because the permanent has already entered the battlefield.
|
||||||
*
|
* <p>
|
||||||
* 702.36e See rule 707, "Face-Down Spells and Permanents," for more information
|
* 702.36e See rule 707, "Face-Down Spells and Permanents," for more information
|
||||||
* on how to cast cards with morph.
|
* on how to cast cards with morph.
|
||||||
*
|
*
|
||||||
* @author LevelX2
|
* @author LevelX2
|
||||||
*/
|
*/
|
||||||
public class MorphAbility extends StaticAbility implements AlternativeSourceCosts {
|
public class MorphAbility extends AlternativeSourceCostsImpl {
|
||||||
|
|
||||||
protected static final String ABILITY_KEYWORD = "Morph";
|
protected static final String ABILITY_KEYWORD = "Morph";
|
||||||
protected static final String ABILITY_KEYWORD_MEGA = "Megamorph";
|
protected static final String ABILITY_KEYWORD_MEGA = "Megamorph";
|
||||||
protected static final String REMINDER_TEXT = "<i>(You may cast this card face down as a "
|
protected static final String REMINDER_TEXT = "You may cast this card face down as a "
|
||||||
+ "2/2 creature for {3}. Turn it face up any time for its morph cost.)</i>";
|
+ "2/2 creature for {3}. Turn it face up any time for its morph cost.";
|
||||||
protected static final String REMINDER_TEXT_MEGA = "<i>(You may cast this card face down "
|
protected static final String REMINDER_TEXT_MEGA = "You may cast this card face down "
|
||||||
+ "as a 2/2 creature for {3}. Turn it face up any time for its megamorph "
|
+ "as a 2/2 creature for {3}. Turn it face up any time for its megamorph "
|
||||||
+ "cost and put a +1/+1 counter on it.)</i>";
|
+ "cost and put a +1/+1 counter on it.";
|
||||||
protected String ruleText;
|
|
||||||
protected AlternativeCost2Impl alternateCosts = new AlternativeCost2Impl(
|
|
||||||
ABILITY_KEYWORD, REMINDER_TEXT, new GenericManaCost(3));
|
|
||||||
protected Costs<Cost> morphCosts;
|
protected Costs<Cost> morphCosts;
|
||||||
// needed to check activation status, if card changes zone after casting it
|
// needed to check activation status, if card changes zone after casting it
|
||||||
private int zoneChangeCounter = 0;
|
private final boolean megamorph;
|
||||||
private boolean megamorph;
|
|
||||||
|
|
||||||
public MorphAbility(Cost morphCost) {
|
public MorphAbility(Cost morphCost) {
|
||||||
this(createCosts(morphCost));
|
this(morphCost, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MorphAbility(Cost morphCost, boolean megamorph) {
|
public MorphAbility(Cost morphCost, boolean megamorph) {
|
||||||
this(createCosts(morphCost), megamorph);
|
super(megamorph ? ABILITY_KEYWORD_MEGA : ABILITY_KEYWORD, megamorph ? REMINDER_TEXT_MEGA : REMINDER_TEXT, new GenericManaCost(3));
|
||||||
}
|
this.morphCosts = new CostsImpl<>();
|
||||||
|
this.morphCosts.add(morphCost);
|
||||||
public MorphAbility(Costs<Cost> morphCosts) {
|
|
||||||
this(morphCosts, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MorphAbility(Costs<Cost> morphCosts, boolean megamorph) {
|
|
||||||
super(Zone.HAND, null);
|
|
||||||
this.morphCosts = morphCosts;
|
|
||||||
this.megamorph = megamorph;
|
this.megamorph = megamorph;
|
||||||
this.setWorksFaceDown(true);
|
this.setWorksFaceDown(true);
|
||||||
StringBuilder sb = new StringBuilder();
|
Ability ability = new SimpleStaticAbility(new BecomesFaceDownCreatureEffect(
|
||||||
if (megamorph) {
|
|
||||||
sb.append(ABILITY_KEYWORD_MEGA).append(' ');
|
|
||||||
} else {
|
|
||||||
sb.append(ABILITY_KEYWORD).append(' ');
|
|
||||||
}
|
|
||||||
name = ABILITY_KEYWORD;
|
|
||||||
for (Cost cost : morphCosts) {
|
|
||||||
if (!(cost instanceof ManaCosts)) {
|
|
||||||
sb.setLength(sb.length() - 1);
|
|
||||||
sb.append("—");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sb.append(morphCosts.getText());
|
|
||||||
if (!(morphCosts.get(morphCosts.size() - 1) instanceof ManaCosts)) {
|
|
||||||
sb.append('.');
|
|
||||||
}
|
|
||||||
sb.append(' ');
|
|
||||||
if (megamorph) {
|
|
||||||
sb.append(REMINDER_TEXT_MEGA);
|
|
||||||
} else {
|
|
||||||
sb.append(REMINDER_TEXT);
|
|
||||||
}
|
|
||||||
|
|
||||||
ruleText = sb.toString();
|
|
||||||
|
|
||||||
Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BecomesFaceDownCreatureEffect(
|
|
||||||
morphCosts, (megamorph ? FaceDownType.MEGAMORPHED : FaceDownType.MORPHED)));
|
morphCosts, (megamorph ? FaceDownType.MEGAMORPHED : FaceDownType.MORPHED)));
|
||||||
ability.setWorksFaceDown(true);
|
ability.setWorksFaceDown(true);
|
||||||
ability.setRuleVisible(false);
|
ability.setRuleVisible(false);
|
||||||
addSubAbility(ability);
|
addSubAbility(ability);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MorphAbility(final MorphAbility ability) {
|
public MorphAbility(final MorphAbility ability) {
|
||||||
super(ability);
|
super(ability);
|
||||||
this.zoneChangeCounter = ability.zoneChangeCounter;
|
|
||||||
this.ruleText = ability.ruleText;
|
|
||||||
this.alternateCosts = ability.alternateCosts.copy();
|
|
||||||
this.morphCosts = ability.morphCosts; // can't be changed
|
this.morphCosts = ability.morphCosts; // can't be changed
|
||||||
this.megamorph = ability.megamorph;
|
this.megamorph = ability.megamorph;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Costs<Cost> createCosts(Cost cost) {
|
|
||||||
Costs<Cost> costs = new CostsImpl<>();
|
|
||||||
costs.add(cost);
|
|
||||||
return costs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MorphAbility copy() {
|
public MorphAbility copy() {
|
||||||
return new MorphAbility(this);
|
return new MorphAbility(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetMorph() {
|
@Override
|
||||||
alternateCosts.reset();
|
public boolean askToActivateAlternativeCosts(Ability ability, Game game) {
|
||||||
zoneChangeCounter = 0;
|
switch (ability.getAbilityType()) {
|
||||||
|
case SPELL:
|
||||||
|
Spell spell = game.getStack().getSpell(ability.getId());
|
||||||
|
if (spell != null) {
|
||||||
|
spell.setFaceDown(true, game);
|
||||||
|
if (handleActivatingAlternativeCosts(ability, game)) {
|
||||||
|
game.getState().setValue("MorphAbility" + ability.getSourceId(), "activated");
|
||||||
|
spell.getColor(game).setColor(null);
|
||||||
|
game.getState().getCreateMageObjectAttribute(spell.getCard(), game).getSubtype().clear();
|
||||||
|
} else {
|
||||||
|
spell.setFaceDown(false, game);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PLAY_LAND:
|
||||||
|
handleActivatingAlternativeCosts(ability, game);
|
||||||
|
}
|
||||||
|
return isActivated(ability, game);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Costs<Cost> getMorphCosts() {
|
public Costs<Cost> getMorphCosts() {
|
||||||
return morphCosts;
|
return morphCosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isActivated(Ability ability, Game game) {
|
|
||||||
Card card = game.getCard(sourceId);
|
|
||||||
if (card != null
|
|
||||||
&& card.getZoneChangeCounter(game) <= zoneChangeCounter + 1) {
|
|
||||||
return alternateCosts.isActivated(game);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable(Ability source, Game game) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean askToActivateAlternativeCosts(Ability ability, Game game) {
|
|
||||||
if (ability.getAbilityType() == AbilityType.SPELL) {
|
|
||||||
Player player = game.getPlayer(ability.getControllerId());
|
|
||||||
Spell spell = game.getStack().getSpell(ability.getId());
|
|
||||||
if (player != null
|
|
||||||
&& spell != null) {
|
|
||||||
this.resetMorph();
|
|
||||||
spell.setFaceDown(true, game); // so only the back is visible
|
|
||||||
if (alternateCosts.canPay(ability, this, ability.getControllerId(), game)) {
|
|
||||||
if (player.chooseUse(Outcome.Benefit, "Cast this card as a 2/2 "
|
|
||||||
+ "face-down creature for " + getCosts().getText() + " ?", ability, game)) {
|
|
||||||
game.getState().setValue("MorphAbility"
|
|
||||||
+ ability.getSourceId(), "activated"); // Gift of Doom
|
|
||||||
activateMorph(game);
|
|
||||||
// change mana costs
|
|
||||||
ability.getManaCostsToPay().clear();
|
|
||||||
ability.getCosts().clear();
|
|
||||||
for (Iterator it = this.alternateCosts.iterator(); it.hasNext();) {
|
|
||||||
Cost cost = (Cost) it.next();
|
|
||||||
if (cost instanceof ManaCost) {
|
|
||||||
ability.getManaCostsToPay().add((ManaCost) cost.copy());
|
|
||||||
} else {
|
|
||||||
ability.getCosts().add(cost.copy());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// change spell colors and subtype *TODO probably this needs to be done by continuous effect (while on the stack)
|
|
||||||
ObjectColor spellColor = spell.getColor(game);
|
|
||||||
spellColor.setBlack(false);
|
|
||||||
spellColor.setRed(false);
|
|
||||||
spellColor.setGreen(false);
|
|
||||||
spellColor.setWhite(false);
|
|
||||||
spellColor.setBlue(false);
|
|
||||||
game.getState().getCreateMageObjectAttribute(spell.getCard(), game).getSubtype().clear();
|
|
||||||
} else {
|
|
||||||
spell.setFaceDown(false, game);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ability.getAbilityType() == AbilityType.PLAY_LAND) {
|
|
||||||
Player player = game.getPlayer(ability.getControllerId());
|
|
||||||
if (player != null) {
|
|
||||||
this.resetMorph();
|
|
||||||
if (alternateCosts.canPay(ability, this, ability.getControllerId(), game)) {
|
|
||||||
if (player.chooseUse(Outcome.Benefit, "Cast this card as a 2/2 "
|
|
||||||
+ "face-down creature for " + getCosts().getText() + " ?", ability, game)) {
|
|
||||||
activateMorph(game);
|
|
||||||
// change mana costs
|
|
||||||
ability.getManaCostsToPay().clear();
|
|
||||||
ability.getCosts().clear();
|
|
||||||
for (Iterator it = this.alternateCosts.iterator(); it.hasNext();) {
|
|
||||||
Cost cost = (Cost) it.next();
|
|
||||||
if (cost instanceof ManaCost) {
|
|
||||||
ability.getManaCostsToPay().add((ManaCost) cost.copy());
|
|
||||||
} else {
|
|
||||||
ability.getCosts().add(cost.copy());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isActivated(ability, game);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activateMorph(Game game) {
|
|
||||||
alternateCosts.activate();
|
|
||||||
// remember zone change counter
|
|
||||||
if (zoneChangeCounter == 0) {
|
|
||||||
Card card = game.getCard(getSourceId());
|
|
||||||
if (card != null) {
|
|
||||||
zoneChangeCounter = card.getZoneChangeCounter(game);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Morph source card not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRule(boolean all) {
|
|
||||||
return getRule();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRule() {
|
public String getRule() {
|
||||||
return ruleText;
|
boolean isMana = morphCosts.get(0) instanceof ManaCost;
|
||||||
}
|
return alternativeCost.getName() + (isMana ? " " : "—") +
|
||||||
|
morphCosts.getText() + (isMana ? ' ' : ". ") + alternativeCost.getReminderText();
|
||||||
@Override
|
|
||||||
public String getCastMessageSuffix(Game game) {
|
|
||||||
return alternateCosts.getCastSuffixMessage(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings({"unchecked"})
|
|
||||||
public Costs<Cost> getCosts() {
|
|
||||||
return alternateCosts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setPermanentToFaceDownCreature(MageObject mageObject, Game game) {
|
public static void setPermanentToFaceDownCreature(MageObject mageObject, Game game) {
|
||||||
|
@ -296,6 +148,5 @@ public class MorphAbility extends StaticAbility implements AlternativeSourceCost
|
||||||
((Permanent) mageObject).setExpansionSetCode("");
|
((Permanent) mageObject).setExpansionSetCode("");
|
||||||
((Permanent) mageObject).setRarity(Rarity.SPECIAL);
|
((Permanent) mageObject).setRarity(Rarity.SPECIAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,13 @@
|
||||||
package mage.abilities.keyword;
|
package mage.abilities.keyword;
|
||||||
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.SpellAbility;
|
|
||||||
import mage.abilities.StaticAbility;
|
|
||||||
import mage.abilities.condition.common.ProwlCondition;
|
import mage.abilities.condition.common.ProwlCondition;
|
||||||
import mage.abilities.costs.*;
|
import mage.abilities.costs.AlternativeSourceCostsImpl;
|
||||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
|
||||||
import mage.abilities.hint.common.ProwlHint;
|
import mage.abilities.hint.common.ProwlHint;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.constants.Outcome;
|
|
||||||
import mage.constants.Zone;
|
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
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>
|
* <p>
|
||||||
|
@ -30,26 +20,21 @@ import java.util.List;
|
||||||
*
|
*
|
||||||
* @author LevelX2
|
* @author LevelX2
|
||||||
*/
|
*/
|
||||||
public class ProwlAbility extends StaticAbility implements AlternativeSourceCosts {
|
public class ProwlAbility extends AlternativeSourceCostsImpl {
|
||||||
|
|
||||||
private static final String PROWL_KEYWORD = "Prowl";
|
private static final String PROWL_KEYWORD = "Prowl";
|
||||||
private final List<AlternativeCost2> prowlCosts = new LinkedList<>();
|
private static final String reminderText = "You may cast this for its prowl cost if you dealt combat damage to a "
|
||||||
private String reminderText;
|
+ "player this turn with a creature that shared a creature type with {this}";
|
||||||
|
|
||||||
public ProwlAbility(Card card, String manaString) {
|
public ProwlAbility(Card card, String manaString) {
|
||||||
super(Zone.ALL, null);
|
super(PROWL_KEYWORD, reminderText, manaString);
|
||||||
this.setRuleAtTheTop(true);
|
this.setRuleAtTheTop(true);
|
||||||
this.name = PROWL_KEYWORD;
|
|
||||||
this.setReminderText(card);
|
|
||||||
this.addProwlCost(manaString);
|
|
||||||
this.addWatcher(new ProwlWatcher());
|
this.addWatcher(new ProwlWatcher());
|
||||||
this.addHint(ProwlHint.instance);
|
this.addHint(ProwlHint.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProwlAbility(final ProwlAbility ability) {
|
private ProwlAbility(final ProwlAbility ability) {
|
||||||
super(ability);
|
super(ability);
|
||||||
this.prowlCosts.addAll(ability.prowlCosts);
|
|
||||||
this.reminderText = ability.reminderText;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -57,112 +42,8 @@ public class ProwlAbility extends StaticAbility implements AlternativeSourceCost
|
||||||
return new ProwlAbility(this);
|
return new ProwlAbility(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final AlternativeCost2 addProwlCost(String manaString) {
|
|
||||||
AlternativeCost2 prowlCost = new AlternativeCost2Impl(PROWL_KEYWORD,
|
|
||||||
reminderText, new ManaCostsImpl(manaString));
|
|
||||||
prowlCosts.add(prowlCost);
|
|
||||||
return prowlCost;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetProwl() {
|
|
||||||
for (AlternativeCost2 cost : prowlCosts) {
|
|
||||||
cost.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isActivated(Ability ability, Game game) {
|
|
||||||
for (AlternativeCost2 cost : prowlCosts) {
|
|
||||||
if (cost.isActivated(game)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAvailable(Ability source, Game game) {
|
public boolean isAvailable(Ability source, Game game) {
|
||||||
return ProwlCondition.instance.apply(game, source);
|
return ProwlCondition.instance.apply(game, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean askToActivateAlternativeCosts(Ability ability, Game game) {
|
|
||||||
if (ability instanceof SpellAbility) {
|
|
||||||
Player player = game.getPlayer(ability.getControllerId());
|
|
||||||
if (player == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (ProwlCondition.instance.apply(game, ability)) {
|
|
||||||
this.resetProwl();
|
|
||||||
for (AlternativeCost2 prowlCost : prowlCosts) {
|
|
||||||
if (prowlCost.canPay(ability, this, ability.getControllerId(), game)
|
|
||||||
&& player.chooseUse(Outcome.Benefit, "Cast for "
|
|
||||||
+ PROWL_KEYWORD + " cost " + prowlCost.getText(true)
|
|
||||||
+ " ?", ability, game)) {
|
|
||||||
prowlCost.activate();
|
|
||||||
ability.getManaCostsToPay().clear();
|
|
||||||
ability.getCosts().clear();
|
|
||||||
for (Iterator it = ((Costs) prowlCost).iterator(); it.hasNext();) {
|
|
||||||
Cost cost = (Cost) it.next();
|
|
||||||
if (cost instanceof ManaCostsImpl) {
|
|
||||||
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
|
|
||||||
} else {
|
|
||||||
ability.getCosts().add(cost.copy());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isActivated(ability, game);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRule() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
int numberCosts = 0;
|
|
||||||
String remarkText = "";
|
|
||||||
for (AlternativeCost2 prowlCost : prowlCosts) {
|
|
||||||
if (numberCosts == 0) {
|
|
||||||
sb.append(prowlCost.getText(false));
|
|
||||||
remarkText = prowlCost.getReminderText();
|
|
||||||
} else {
|
|
||||||
sb.append(" and/or ").append(prowlCost.getText(true));
|
|
||||||
}
|
|
||||||
++numberCosts;
|
|
||||||
}
|
|
||||||
if (numberCosts == 1) {
|
|
||||||
sb.append(' ').append(remarkText);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCastMessageSuffix(Game game) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
int position = 0;
|
|
||||||
for (AlternativeCost2 cost : prowlCosts) {
|
|
||||||
if (cost.isActivated(game)) {
|
|
||||||
sb.append(cost.getCastSuffixMessage(position));
|
|
||||||
++position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setReminderText(Card card) {
|
|
||||||
reminderText
|
|
||||||
= "(You may cast this for its prowl cost if you dealt combat damage to a "
|
|
||||||
+ "player this turn with a creature that shared a creature type with {this})";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Costs<Cost> getCosts() {
|
|
||||||
Costs<Cost> alterCosts = new CostsImpl<>();
|
|
||||||
for (AlternativeCost2 aCost : prowlCosts) {
|
|
||||||
alterCosts.add(aCost.getCost());
|
|
||||||
}
|
|
||||||
return alterCosts;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3607,9 +3607,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
ManaCostsImpl manaCosts = new ManaCostsImpl();
|
ManaCostsImpl manaCosts = new ManaCostsImpl();
|
||||||
for (Cost cost : alternateSourceCostsAbility.getCosts()) {
|
for (Cost cost : alternateSourceCostsAbility.getCosts()) {
|
||||||
// AlternativeCost2 replaced by real cost on activate, so getPlayable need to extract that costs here
|
// AlternativeCost2 replaced by real cost on activate, so getPlayable need to extract that costs here
|
||||||
if (cost instanceof AlternativeCost2) {
|
if (cost instanceof AlternativeCost) {
|
||||||
if (((AlternativeCost2) cost).getCost() instanceof ManaCost) {
|
if (((AlternativeCost) cost).getCost() instanceof ManaCost) {
|
||||||
manaCosts.add((ManaCost) ((AlternativeCost2) cost).getCost());
|
manaCosts.add((ManaCost) ((AlternativeCost) cost).getCost());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (cost instanceof ManaCost) {
|
if (cost instanceof ManaCost) {
|
||||||
|
@ -3656,9 +3656,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
ManaCostsImpl manaCosts = new ManaCostsImpl();
|
ManaCostsImpl manaCosts = new ManaCostsImpl();
|
||||||
for (Cost cost : ((Ability) alternateSourceCosts).getCosts()) {
|
for (Cost cost : ((Ability) alternateSourceCosts).getCosts()) {
|
||||||
// AlternativeCost2 replaced by real cost on activate, so getPlayable need to extract that costs here
|
// AlternativeCost2 replaced by real cost on activate, so getPlayable need to extract that costs here
|
||||||
if (cost instanceof AlternativeCost2) {
|
if (cost instanceof AlternativeCost) {
|
||||||
if (((AlternativeCost2) cost).getCost() instanceof ManaCost) {
|
if (((AlternativeCost) cost).getCost() instanceof ManaCost) {
|
||||||
manaCosts.add((ManaCost) ((AlternativeCost2) cost).getCost());
|
manaCosts.add((ManaCost) ((AlternativeCost) cost).getCost());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (cost instanceof ManaCost) {
|
if (cost instanceof ManaCost) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ Assist|new|
|
||||||
Basic landcycling|cost|
|
Basic landcycling|cost|
|
||||||
Battle cry|new|
|
Battle cry|new|
|
||||||
Bestow|card, manaString|
|
Bestow|card, manaString|
|
||||||
Blitz|manaString|
|
Blitz|card, manaString|
|
||||||
Bloodthirst|number|
|
Bloodthirst|number|
|
||||||
Bushido|number|
|
Bushido|number|
|
||||||
Buyback|manaString|
|
Buyback|manaString|
|
||||||
|
|
Loading…
Reference in a new issue