mirror of
https://github.com/correl/mage.git
synced 2024-12-25 03:00:15 +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}
|
||||
this.addAbility(new BlitzAbility("{2}{G}"));
|
||||
this.addAbility(new BlitzAbility(this, "{2}{G}"));
|
||||
}
|
||||
|
||||
private CaldaiaGuardian(final CaldaiaGuardian card) {
|
||||
|
|
|
@ -35,7 +35,7 @@ public final class CaldaiaStrongarm extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// Blitz {3}{G}
|
||||
this.addAbility(new BlitzAbility("{3}{G}"));
|
||||
this.addAbility(new BlitzAbility(this, "{3}{G}"));
|
||||
}
|
||||
|
||||
private CaldaiaStrongarm(final CaldaiaStrongarm card) {
|
||||
|
|
|
@ -31,7 +31,7 @@ public final class GirderGoons extends CardImpl {
|
|||
)));
|
||||
|
||||
// Blitz {3}{B}
|
||||
this.addAbility(new BlitzAbility("{3}{B}"));
|
||||
this.addAbility(new BlitzAbility(this, "{3}{B}"));
|
||||
}
|
||||
|
||||
private GirderGoons(final GirderGoons card) {
|
||||
|
|
|
@ -48,7 +48,7 @@ public final class JaxisTheTroublemaker extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// Blitz {1}{R}
|
||||
this.addAbility(new BlitzAbility("{1}{R}"));
|
||||
this.addAbility(new BlitzAbility(this, "{1}{R}"));
|
||||
}
|
||||
|
||||
private JaxisTheTroublemaker(final JaxisTheTroublemaker card) {
|
||||
|
|
|
@ -36,7 +36,7 @@ public final class MayhemPatrol extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// Blitz {1}{R}
|
||||
this.addAbility(new BlitzAbility("{1}{R}"));
|
||||
this.addAbility(new BlitzAbility(this, "{1}{R}"));
|
||||
}
|
||||
|
||||
private MayhemPatrol(final MayhemPatrol card) {
|
||||
|
|
|
@ -33,7 +33,7 @@ public final class NightClubber extends CardImpl {
|
|||
)));
|
||||
|
||||
// Blitz {2}{B}
|
||||
this.addAbility(new BlitzAbility("{2}{B}"));
|
||||
this.addAbility(new BlitzAbility(this, "{2}{B}"));
|
||||
}
|
||||
|
||||
private NightClubber(final NightClubber card) {
|
||||
|
|
|
@ -33,7 +33,7 @@ public final class PlasmaJockey extends CardImpl {
|
|||
this.addAbility(ability);
|
||||
|
||||
// Blitz {2}{R}
|
||||
this.addAbility(new BlitzAbility("{2}{R}"));
|
||||
this.addAbility(new BlitzAbility(this, "{2}{R}"));
|
||||
}
|
||||
|
||||
private PlasmaJockey(final PlasmaJockey card) {
|
||||
|
|
|
@ -31,7 +31,7 @@ public final class PugnaciousPugilist extends CardImpl {
|
|||
)));
|
||||
|
||||
// Blitz {3}{R}
|
||||
this.addAbility(new BlitzAbility("{3}{R}"));
|
||||
this.addAbility(new BlitzAbility(this, "{3}{R}"));
|
||||
}
|
||||
|
||||
private PugnaciousPugilist(final PugnaciousPugilist card) {
|
||||
|
|
|
@ -28,7 +28,7 @@ public final class RiveteersDecoy extends CardImpl {
|
|||
this.addAbility(new SimpleStaticAbility(new MustBeBlockedByAtLeastOneSourceEffect()));
|
||||
|
||||
// Blitz {3}{G}
|
||||
this.addAbility(new BlitzAbility("{3}{G}"));
|
||||
this.addAbility(new BlitzAbility(this, "{3}{G}"));
|
||||
}
|
||||
|
||||
private RiveteersDecoy(final RiveteersDecoy card) {
|
||||
|
|
|
@ -29,7 +29,7 @@ public final class RiveteersRequisitioner extends CardImpl {
|
|||
this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect(new TreasureToken())));
|
||||
|
||||
// Blitz {2}{R}
|
||||
this.addAbility(new BlitzAbility("{2}{R}"));
|
||||
this.addAbility(new BlitzAbility(this, "{2}{R}"));
|
||||
}
|
||||
|
||||
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())));
|
||||
|
||||
// Blitz {4}{G}{G}
|
||||
this.addAbility(new BlitzAbility("{4}{G}{G}"));
|
||||
this.addAbility(new BlitzAbility(this, "{4}{G}{G}"));
|
||||
}
|
||||
|
||||
private WorkshopWarchief(final WorkshopWarchief card) {
|
||||
|
|
|
@ -4,16 +4,11 @@ import mage.cards.ExpansionSet;
|
|||
import mage.constants.Rarity;
|
||||
import mage.constants.SetType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
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();
|
||||
|
||||
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("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.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.SetType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
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();
|
||||
|
||||
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("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("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("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));
|
||||
|
@ -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("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.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;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward
|
||||
*/
|
||||
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
|
||||
* it's returned from the battlefield to its owner's hand at the beginning
|
||||
* of the next end step.)
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testDash() {
|
||||
|
@ -133,4 +131,21 @@ public class DashTest extends CardTestPlayerBase {
|
|||
assertPermanentCount(playerA, "Warbringer", 2);
|
||||
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;
|
||||
|
||||
import mage.constants.ColoredManaSymbol;
|
||||
import mage.util.Copyable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import mage.constants.ColoredManaSymbol;
|
||||
import mage.util.Copyable;
|
||||
|
||||
public class ObjectColor implements Serializable, Copyable<ObjectColor>, Comparable<ObjectColor> {
|
||||
|
||||
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) {
|
||||
this.setBlack(color.isBlack());
|
||||
this.setBlue(color.isBlue());
|
||||
this.setGreen(color.isGreen());
|
||||
this.setRed(color.isRed());
|
||||
this.setWhite(color.isWhite());
|
||||
this.setBlack(color != null && color.isBlack());
|
||||
this.setBlue(color != null && color.isBlue());
|
||||
this.setGreen(color != null && color.isGreen());
|
||||
this.setRed(color != null && color.isRed());
|
||||
this.setWhite(color != null && color.isWhite());
|
||||
|
||||
this.setGold(color.isGold());
|
||||
this.setGold(color != null && color.isGold());
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
|
@ -6,24 +5,21 @@ import mage.abilities.condition.Condition;
|
|||
import mage.abilities.keyword.DashAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.game.Game;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
*/
|
||||
|
||||
public enum DashedCondition implements Condition {
|
||||
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Card card = game.getCard(source.getSourceId());
|
||||
if (card != null) {
|
||||
return card.getAbilities(game).stream()
|
||||
.filter(DashAbility.class::isInstance)
|
||||
.anyMatch(d -> ((DashAbility) d).isActivated(source, game));
|
||||
|
||||
}
|
||||
return false;
|
||||
return card != null
|
||||
&& CardUtil.castStream(card
|
||||
.getAbilities(game)
|
||||
.stream(), DashAbility.class)
|
||||
.anyMatch(ability -> ability.isActivated(source, game));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ import mage.game.Game;
|
|||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public interface AlternativeCost2 extends Cost {
|
||||
|
||||
public interface AlternativeCost extends Cost {
|
||||
|
||||
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
|
||||
*
|
||||
*/
|
||||
void activate();
|
||||
|
||||
/**
|
||||
* Reset the activate
|
||||
*
|
||||
* Reset the activate
|
||||
*/
|
||||
void reset();
|
||||
|
||||
|
@ -61,4 +59,6 @@ public interface AlternativeCost2 extends Cost {
|
|||
|
||||
Cost getCost();
|
||||
|
||||
@Override
|
||||
AlternativeCost copy();
|
||||
}
|
|
@ -1,43 +1,38 @@
|
|||
|
||||
package mage.abilities.costs;
|
||||
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.game.Game;
|
||||
|
||||
/**
|
||||
* Alternative costs
|
||||
*
|
||||
* @author LevelX2
|
||||
*
|
||||
* @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 reminderText;
|
||||
protected String delimiter;
|
||||
protected boolean isMana;
|
||||
|
||||
protected boolean activated;
|
||||
|
||||
public AlternativeCost2Impl(String name, String reminderText, Cost cost) {
|
||||
this(name, " ", reminderText, cost);
|
||||
}
|
||||
|
||||
public AlternativeCost2Impl(String name, String delimiter, String reminderText, Cost cost) {
|
||||
public AlternativeCostImpl(String name, String reminderText, Cost cost) {
|
||||
this.activated = false;
|
||||
this.name = name;
|
||||
this.delimiter = delimiter;
|
||||
this.isMana = cost instanceof ManaCost;
|
||||
if (reminderText != null) {
|
||||
this.reminderText = "<i>" + reminderText + "</i>";
|
||||
this.reminderText = "<i>(" + reminderText + ")</i>";
|
||||
}
|
||||
this.add(cost);
|
||||
}
|
||||
|
||||
public AlternativeCost2Impl(final AlternativeCost2Impl cost) {
|
||||
public AlternativeCostImpl(final AlternativeCostImpl<?> cost) {
|
||||
super(cost);
|
||||
this.name = cost.name;
|
||||
this.reminderText = cost.reminderText;
|
||||
this.activated = cost.activated;
|
||||
this.delimiter = cost.delimiter;
|
||||
this.isMana = cost.isMana;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -57,7 +52,7 @@ public class AlternativeCost2Impl<T extends AlternativeCost2Impl<T>> extends Cos
|
|||
if (onlyCost) {
|
||||
return getText();
|
||||
} 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.
|
||||
*
|
||||
* @param position - if there are multiple costs, it's the postion the cost
|
||||
* is set (starting with 0)
|
||||
* is set (starting with 0)
|
||||
* @return
|
||||
*/
|
||||
@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
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void activate() {
|
||||
|
@ -101,7 +95,6 @@ public class AlternativeCost2Impl<T extends AlternativeCost2Impl<T>> extends Cos
|
|||
|
||||
/**
|
||||
* Reset the activate and count information
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void reset() {
|
||||
|
@ -120,8 +113,8 @@ public class AlternativeCost2Impl<T extends AlternativeCost2Impl<T>> extends Cos
|
|||
}
|
||||
|
||||
@Override
|
||||
public AlternativeCost2Impl copy() {
|
||||
return new AlternativeCost2Impl(this);
|
||||
public AlternativeCostImpl<?> copy() {
|
||||
return new AlternativeCostImpl<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -131,5 +124,4 @@ public class AlternativeCost2Impl<T extends AlternativeCost2Impl<T>> extends Cos
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -24,7 +24,7 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
|||
|
||||
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 String rule;
|
||||
protected FilterCard filter;
|
||||
|
@ -88,15 +88,15 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
|||
|
||||
@Override
|
||||
public void addCost(Cost cost) {
|
||||
AlternativeCost2 alternativeCost = convertToAlternativeCost(cost);
|
||||
AlternativeCost alternativeCost = convertToAlternativeCost(cost);
|
||||
if (alternativeCost != null) {
|
||||
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) : null;
|
||||
return cost != null ? new AlternativeCostImpl(null, "", cost) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -123,7 +123,7 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
|||
}
|
||||
Player player = game.getPlayer(ability.getControllerId());
|
||||
if (player != null) {
|
||||
Costs<AlternativeCost2> alternativeCostsToCheck;
|
||||
Costs<AlternativeCost> alternativeCostsToCheck;
|
||||
if (dynamicCost != null) {
|
||||
alternativeCostsToCheck = new CostsImpl<>();
|
||||
alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(ability, game)));
|
||||
|
@ -149,7 +149,7 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
|||
if (!onlyMana) {
|
||||
ability.getCosts().clear();
|
||||
}
|
||||
for (AlternativeCost2 alternateCost : alternativeCostsToCheck) {
|
||||
for (AlternativeCost alternateCost : alternativeCostsToCheck) {
|
||||
alternateCost.activate();
|
||||
for (Iterator it = ((Costs) alternateCost).iterator(); it.hasNext(); ) {
|
||||
Cost costDetailed = (Cost) it.next();
|
||||
|
@ -207,14 +207,14 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
|||
|
||||
@Override
|
||||
public boolean isActivated(Ability source, Game game) {
|
||||
Costs<AlternativeCost2> alternativeCostsToCheck;
|
||||
Costs<AlternativeCost> alternativeCostsToCheck;
|
||||
if (dynamicCost != null) {
|
||||
alternativeCostsToCheck = new CostsImpl<>();
|
||||
alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(source, game)));
|
||||
} else {
|
||||
alternativeCostsToCheck = this.alternateCosts;
|
||||
}
|
||||
for (AlternativeCost2 cost : alternativeCostsToCheck) {
|
||||
for (AlternativeCost cost : alternativeCostsToCheck) {
|
||||
if (cost.isActivated(game)) {
|
||||
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";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetCost() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
if (rule != null) {
|
||||
|
@ -245,7 +250,7 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
|||
}
|
||||
int numberCosts = 0;
|
||||
String remarkText = "";
|
||||
for (AlternativeCost2 alternativeCost : alternateCosts) {
|
||||
for (AlternativeCost alternativeCost : alternateCosts) {
|
||||
if (numberCosts == 0) {
|
||||
if (alternativeCost.getCost() instanceof ManaCost) {
|
||||
sb.append("pay ");
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.abilities.costs;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
|
@ -6,7 +5,7 @@ import mage.game.Game;
|
|||
|
||||
/**
|
||||
* Interface for abilities that add alternative costs to the source.
|
||||
*
|
||||
* <p>
|
||||
* Example of such additional source costs: {@link mage.abilities.keyword.KickerAbility}
|
||||
*
|
||||
* @author LevelX2
|
||||
|
@ -15,35 +14,38 @@ public interface AlternativeSourceCosts {
|
|||
|
||||
/**
|
||||
* Ask the player if they want to use the alternative costs
|
||||
*
|
||||
*
|
||||
* @param ability ability the alternative cost is activated for
|
||||
* @param game
|
||||
* @return
|
||||
* @return
|
||||
*/
|
||||
boolean askToActivateAlternativeCosts(Ability ability, Game game);
|
||||
|
||||
|
||||
/**
|
||||
* Is the alternative spell cost currently available
|
||||
*
|
||||
*
|
||||
* @param source spell ability the alternative costs can be paid for
|
||||
* @param game
|
||||
* @return
|
||||
* @return
|
||||
*/
|
||||
boolean isAvailable(Ability source, Game game);
|
||||
|
||||
|
||||
/**
|
||||
* Was the alternative cost activated
|
||||
*
|
||||
* @param game
|
||||
* @param source
|
||||
* @return
|
||||
*/
|
||||
boolean isActivated(Ability source, Game game);
|
||||
|
||||
|
||||
/**
|
||||
* Suffix string to use for game log
|
||||
*
|
||||
* @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;
|
||||
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.constants.Zone;
|
||||
import mage.abilities.Ability;
|
||||
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
|
||||
*/
|
||||
public class BlitzAbility extends StaticAbility {
|
||||
public class BlitzAbility extends SpellAbility {
|
||||
|
||||
public BlitzAbility(String manaString) {
|
||||
// TODO: Implement this
|
||||
super(Zone.ALL, null);
|
||||
public static final String BLITZ_ACTIVATION_VALUE_KEY = "blitzActivation";
|
||||
protected static final String KEYWORD = "Blitz";
|
||||
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) {
|
||||
|
@ -21,4 +54,56 @@ public class BlitzAbility extends StaticAbility {
|
|||
public BlitzAbility copy() {
|
||||
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;
|
||||
|
||||
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.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
|
||||
import mage.abilities.condition.common.DashedCondition;
|
||||
import mage.abilities.costs.*;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.decorator.ConditionalOneShotEffect;
|
||||
import mage.abilities.costs.AlternativeSourceCostsImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.ReturnToHandTargetEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class DashAbility extends StaticAbility implements AlternativeSourceCosts {
|
||||
public class DashAbility extends AlternativeSourceCostsImpl {
|
||||
|
||||
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 "
|
||||
+ "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;
|
||||
+ "hand at the beginning of the next end step.";
|
||||
|
||||
public DashAbility(String manaString) {
|
||||
super(Zone.ALL, null);
|
||||
name = KEYWORD;
|
||||
this.addDashCost(manaString);
|
||||
super(KEYWORD, REMINDER_TEXT, manaString);
|
||||
Ability ability = new EntersBattlefieldAbility(
|
||||
new GainAbilitySourceEffect(HasteAbility.getInstance(), Duration.Custom, false),
|
||||
DashedCondition.instance, "", "");
|
||||
ability.addEffect(new DashAddDelayedTriggeredAbilityEffect());
|
||||
ability.setRuleVisible(false);
|
||||
addSubAbility(ability);
|
||||
|
||||
}
|
||||
|
||||
private DashAbility(final DashAbility ability) {
|
||||
super(ability);
|
||||
this.alternativeSourceCosts.addAll(ability.alternativeSourceCosts);
|
||||
this.zoneChangeCounter = ability.zoneChangeCounter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DashAbility copy() {
|
||||
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 {
|
||||
|
||||
public DashAddDelayedTriggeredAbilityEffect() {
|
||||
DashAddDelayedTriggeredAbilityEffect() {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -200,20 +60,15 @@ class DashAddDelayedTriggeredAbilityEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
if (game.getPermanentEntering(source.getSourceId()) != null) {
|
||||
OneShotEffect returnToHandEffect = new ReturnToHandTargetEffect();
|
||||
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;
|
||||
if (game.getPermanentEntering(source.getSourceId()) == null) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static boolean check(Game game, Ability source) {
|
||||
return game.getState().getZoneChangeCounter(source.getSourceId()) == source.getSourceObjectZoneChangeCounter() + 1;
|
||||
// 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()
|
||||
.setText("return the dashed creature from the battlefield to its owner's hand")
|
||||
.setTargetPointer(new FixedTarget(source.getSourceId(), game.getState().getZoneChangeCounter(source.getSourceId()) + 1))
|
||||
), source);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,46 +1,29 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
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.decorator.ConditionalInterveningIfTriggeredAbility;
|
||||
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
|
||||
*/
|
||||
public class EvokeAbility extends StaticAbility implements AlternativeSourceCosts {
|
||||
public class EvokeAbility extends AlternativeSourceCostsImpl {
|
||||
|
||||
protected static final String EVOKE_KEYWORD = "Evoke";
|
||||
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.)";
|
||||
|
||||
protected List<AlternativeCost2> evokeCosts = new LinkedList<>();
|
||||
|
||||
// needed to check activation status, if card changes zone after casting it
|
||||
private int zoneChangeCounter = 0;
|
||||
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.";
|
||||
|
||||
public EvokeAbility(String manaString) {
|
||||
this(new ManaCostsImpl<>(manaString));
|
||||
}
|
||||
|
||||
public EvokeAbility(Cost cost) {
|
||||
super(Zone.ALL, null);
|
||||
name = EVOKE_KEYWORD;
|
||||
this.addEvokeCost(cost);
|
||||
super(EVOKE_KEYWORD, REMINDER_TEXT, cost);
|
||||
Ability ability = new ConditionalInterveningIfTriggeredAbility(
|
||||
new EntersBattlefieldTriggeredAbility(new SacrificeSourceEffect()),
|
||||
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) {
|
||||
super(ability);
|
||||
this.evokeCosts.addAll(ability.evokeCosts);
|
||||
this.zoneChangeCounter = ability.zoneChangeCounter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EvokeAbility copy() {
|
||||
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;
|
||||
|
||||
import java.util.Iterator;
|
||||
import mage.MageObject;
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.AlternativeCost2Impl;
|
||||
import mage.abilities.costs.AlternativeSourceCosts;
|
||||
import mage.abilities.costs.AlternativeSourceCostsImpl;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.Costs;
|
||||
import mage.abilities.costs.CostsImpl;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
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.FaceDownType;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.AbilityType;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Rarity;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.players.Player;
|
||||
|
||||
/**
|
||||
* 702.36. Morph
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
* 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}
|
||||
* rather than paying its mana cost." (See rule 707, "Face-Down Spells and
|
||||
* Permanents.")
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
* 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 had. The morph effect applies to the face-down object wherever it is,
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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).
|
||||
* 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
|
||||
* battlefield dont trigger when its turned face up and dont have any effect,
|
||||
* because the permanent has already entered the battlefield.
|
||||
*
|
||||
* <p>
|
||||
* 702.36e See rule 707, "Face-Down Spells and Permanents," for more information
|
||||
* on how to cast cards with morph.
|
||||
*
|
||||
* @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_MEGA = "Megamorph";
|
||||
protected static final String REMINDER_TEXT = "<i>(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>";
|
||||
protected static final String REMINDER_TEXT_MEGA = "<i>(You may cast this card face down "
|
||||
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.";
|
||||
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 "
|
||||
+ "cost and put a +1/+1 counter on it.)</i>";
|
||||
protected String ruleText;
|
||||
protected AlternativeCost2Impl alternateCosts = new AlternativeCost2Impl(
|
||||
ABILITY_KEYWORD, REMINDER_TEXT, new GenericManaCost(3));
|
||||
+ "cost and put a +1/+1 counter on it.";
|
||||
protected Costs<Cost> morphCosts;
|
||||
// needed to check activation status, if card changes zone after casting it
|
||||
private int zoneChangeCounter = 0;
|
||||
private boolean megamorph;
|
||||
private final boolean megamorph;
|
||||
|
||||
public MorphAbility(Cost morphCost) {
|
||||
this(createCosts(morphCost));
|
||||
this(morphCost, false);
|
||||
}
|
||||
|
||||
public MorphAbility(Cost morphCost, boolean megamorph) {
|
||||
this(createCosts(morphCost), megamorph);
|
||||
}
|
||||
|
||||
public MorphAbility(Costs<Cost> morphCosts) {
|
||||
this(morphCosts, false);
|
||||
}
|
||||
|
||||
public MorphAbility(Costs<Cost> morphCosts, boolean megamorph) {
|
||||
super(Zone.HAND, null);
|
||||
this.morphCosts = morphCosts;
|
||||
super(megamorph ? ABILITY_KEYWORD_MEGA : ABILITY_KEYWORD, megamorph ? REMINDER_TEXT_MEGA : REMINDER_TEXT, new GenericManaCost(3));
|
||||
this.morphCosts = new CostsImpl<>();
|
||||
this.morphCosts.add(morphCost);
|
||||
this.megamorph = megamorph;
|
||||
this.setWorksFaceDown(true);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
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(
|
||||
Ability ability = new SimpleStaticAbility(new BecomesFaceDownCreatureEffect(
|
||||
morphCosts, (megamorph ? FaceDownType.MEGAMORPHED : FaceDownType.MORPHED)));
|
||||
ability.setWorksFaceDown(true);
|
||||
ability.setRuleVisible(false);
|
||||
addSubAbility(ability);
|
||||
|
||||
}
|
||||
|
||||
public MorphAbility(final MorphAbility 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.megamorph = ability.megamorph;
|
||||
}
|
||||
|
||||
private static Costs<Cost> createCosts(Cost cost) {
|
||||
Costs<Cost> costs = new CostsImpl<>();
|
||||
costs.add(cost);
|
||||
return costs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MorphAbility copy() {
|
||||
return new MorphAbility(this);
|
||||
}
|
||||
|
||||
public void resetMorph() {
|
||||
alternateCosts.reset();
|
||||
zoneChangeCounter = 0;
|
||||
@Override
|
||||
public boolean askToActivateAlternativeCosts(Ability ability, Game game) {
|
||||
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() {
|
||||
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
|
||||
public String getRule() {
|
||||
return ruleText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCastMessageSuffix(Game game) {
|
||||
return alternateCosts.getCastSuffixMessage(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Costs<Cost> getCosts() {
|
||||
return alternateCosts;
|
||||
boolean isMana = morphCosts.get(0) instanceof ManaCost;
|
||||
return alternativeCost.getName() + (isMana ? " " : "—") +
|
||||
morphCosts.getText() + (isMana ? ' ' : ". ") + alternativeCost.getReminderText();
|
||||
}
|
||||
|
||||
public static void setPermanentToFaceDownCreature(MageObject mageObject, Game game) {
|
||||
|
@ -296,6 +148,5 @@ public class MorphAbility extends StaticAbility implements AlternativeSourceCost
|
|||
((Permanent) mageObject).setExpansionSetCode("");
|
||||
((Permanent) mageObject).setRarity(Rarity.SPECIAL);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,13 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.abilities.condition.common.ProwlCondition;
|
||||
import mage.abilities.costs.*;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.costs.AlternativeSourceCostsImpl;
|
||||
import mage.abilities.hint.common.ProwlHint;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.watchers.common.ProwlWatcher;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 702.74. Prowl #
|
||||
* <p>
|
||||
|
@ -30,26 +20,21 @@ import java.util.List;
|
|||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class ProwlAbility extends StaticAbility implements AlternativeSourceCosts {
|
||||
public class ProwlAbility extends AlternativeSourceCostsImpl {
|
||||
|
||||
private static final String PROWL_KEYWORD = "Prowl";
|
||||
private final List<AlternativeCost2> prowlCosts = new LinkedList<>();
|
||||
private String reminderText;
|
||||
private static final String 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}";
|
||||
|
||||
public ProwlAbility(Card card, String manaString) {
|
||||
super(Zone.ALL, null);
|
||||
super(PROWL_KEYWORD, reminderText, manaString);
|
||||
this.setRuleAtTheTop(true);
|
||||
this.name = PROWL_KEYWORD;
|
||||
this.setReminderText(card);
|
||||
this.addProwlCost(manaString);
|
||||
this.addWatcher(new ProwlWatcher());
|
||||
this.addHint(ProwlHint.instance);
|
||||
}
|
||||
|
||||
public ProwlAbility(final ProwlAbility ability) {
|
||||
private ProwlAbility(final ProwlAbility ability) {
|
||||
super(ability);
|
||||
this.prowlCosts.addAll(ability.prowlCosts);
|
||||
this.reminderText = ability.reminderText;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -57,112 +42,8 @@ public class ProwlAbility extends StaticAbility implements AlternativeSourceCost
|
|||
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
|
||||
public boolean isAvailable(Ability source, Game game) {
|
||||
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();
|
||||
for (Cost cost : alternateSourceCostsAbility.getCosts()) {
|
||||
// AlternativeCost2 replaced by real cost on activate, so getPlayable need to extract that costs here
|
||||
if (cost instanceof AlternativeCost2) {
|
||||
if (((AlternativeCost2) cost).getCost() instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) ((AlternativeCost2) cost).getCost());
|
||||
if (cost instanceof AlternativeCost) {
|
||||
if (((AlternativeCost) cost).getCost() instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) ((AlternativeCost) cost).getCost());
|
||||
}
|
||||
} else {
|
||||
if (cost instanceof ManaCost) {
|
||||
|
@ -3656,9 +3656,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
ManaCostsImpl manaCosts = new ManaCostsImpl();
|
||||
for (Cost cost : ((Ability) alternateSourceCosts).getCosts()) {
|
||||
// AlternativeCost2 replaced by real cost on activate, so getPlayable need to extract that costs here
|
||||
if (cost instanceof AlternativeCost2) {
|
||||
if (((AlternativeCost2) cost).getCost() instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) ((AlternativeCost2) cost).getCost());
|
||||
if (cost instanceof AlternativeCost) {
|
||||
if (((AlternativeCost) cost).getCost() instanceof ManaCost) {
|
||||
manaCosts.add((ManaCost) ((AlternativeCost) cost).getCost());
|
||||
}
|
||||
} else {
|
||||
if (cost instanceof ManaCost) {
|
||||
|
|
|
@ -6,7 +6,7 @@ Assist|new|
|
|||
Basic landcycling|cost|
|
||||
Battle cry|new|
|
||||
Bestow|card, manaString|
|
||||
Blitz|manaString|
|
||||
Blitz|card, manaString|
|
||||
Bloodthirst|number|
|
||||
Bushido|number|
|
||||
Buyback|manaString|
|
||||
|
|
Loading…
Reference in a new issue