mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
PlayEffect: support AltCosts (#4649)
This commit is contained in:
@@ -1539,8 +1539,7 @@ public class AiController {
|
||||
boolean mustRespond = false;
|
||||
if (top != null) {
|
||||
mustRespond = top.hasParam("AIRespondsToOwnAbility"); // Forced combos (currently defined for Sensei's Divining Top)
|
||||
mustRespond |= top.isTrigger() && top.getTrigger().getKeyword() != null
|
||||
&& top.getTrigger().getKeyword().getKeyword() == Keyword.EVOKE; // Evoke sacrifice trigger
|
||||
mustRespond |= top.isTrigger() && top.getTrigger().isKeyword(Keyword.EVOKE); // Evoke sacrifice trigger
|
||||
}
|
||||
|
||||
if (topOwnedByAI) {
|
||||
|
||||
@@ -106,7 +106,7 @@ public class ComputerUtilAbility {
|
||||
|
||||
for (SpellAbility sa : originListWithAddCosts) {
|
||||
// determine which alternative costs are cheaper than the original and prioritize them
|
||||
List<SpellAbility> saAltCosts = GameActionUtil.getAlternativeCosts(sa, player);
|
||||
List<SpellAbility> saAltCosts = GameActionUtil.getAlternativeCosts(sa, player, false);
|
||||
List<SpellAbility> priorityAltSa = Lists.newArrayList();
|
||||
List<SpellAbility> otherAltSa = Lists.newArrayList();
|
||||
for (SpellAbility altSa : saAltCosts) {
|
||||
|
||||
@@ -1231,7 +1231,7 @@ public class ComputerUtilCombat {
|
||||
}
|
||||
|
||||
// Extra check for the Exalted trigger in case we're declaring more than one attacker
|
||||
if (combat != null && trigger.getKeyword() != null && trigger.getKeyword().getKeyword() == Keyword.EXALTED) {
|
||||
if (combat != null && trigger.isKeyword(Keyword.EXALTED)) {
|
||||
if (!combat.getAttackers().isEmpty() && !combat.getAttackers().contains(attacker)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import forge.ai.AiPlayDecision;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
@@ -45,7 +44,7 @@ public class DiscoverAi extends SpellAbilityAi {
|
||||
@Override
|
||||
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
|
||||
Card c = (Card)params.get("Card");
|
||||
for (SpellAbility s : AbilityUtils.getBasicSpellsFromPlayEffect(c, ai, CardStateName.Original)) { // TODO: other states for split cards and MDFC?
|
||||
for (SpellAbility s : AbilityUtils.getBasicSpellsFromPlayEffect(c, ai)) {
|
||||
if (s instanceof LandAbility) {
|
||||
// return false or we get a ClassCastException later if the AI encounters MDFC with land backside
|
||||
return false;
|
||||
|
||||
@@ -153,7 +153,7 @@ public class PlayAi extends SpellAbilityAi {
|
||||
public boolean apply(final Card c) {
|
||||
// TODO needs to be aligned for MDFC along with getAbilityToPlay so the knowledge
|
||||
// of which spell was the reason for the choice can be used there
|
||||
for (SpellAbility s : AbilityUtils.getBasicSpellsFromPlayEffect(c, ai, state)) {
|
||||
for (SpellAbility s : AbilityUtils.getSpellsFromPlayEffect(c, ai, state, false)) {
|
||||
if (!sa.matchesValidParam("ValidSA", s)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.card.CardView;
|
||||
import forge.game.card.IHasCardView;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -145,6 +146,9 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
this.hostCard = c;
|
||||
}
|
||||
|
||||
public boolean isKeyword(Keyword kw) {
|
||||
return this.keyword != null && this.keyword.getKeyword() == kw;
|
||||
}
|
||||
public KeywordInterface getKeyword() {
|
||||
return this.keyword;
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ public class GameAction {
|
||||
|
||||
// Rule 111.8: A token that has left the battlefield can't move to another zone
|
||||
if (!c.isSpell() && c.isToken() && !fromBattlefield && zoneFrom != null && !zoneFrom.is(ZoneType.Stack)
|
||||
&& (cause == null || !(cause instanceof SpellPermanent) || !cause.hasSVar("IsCastFromPlayEffect"))) {
|
||||
&& (cause == null || !(cause instanceof SpellPermanent) || !cause.isCastFromPlayEffect())) {
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ public final class GameActionUtil {
|
||||
* a possible alternative cost the provided activator can use to pay
|
||||
* the provided {@link SpellAbility}.
|
||||
*/
|
||||
public static final List<SpellAbility> getAlternativeCosts(final SpellAbility sa, final Player activator) {
|
||||
public static final List<SpellAbility> getAlternativeCosts(final SpellAbility sa, final Player activator, boolean altCostOnly) {
|
||||
final List<SpellAbility> alternatives = Lists.newArrayList();
|
||||
|
||||
Card source = sa.getHostCard();
|
||||
@@ -134,6 +134,9 @@ public final class GameActionUtil {
|
||||
newSA.setBasicSpell(false);
|
||||
changedManaCost = true;
|
||||
} else {
|
||||
if (altCostOnly) {
|
||||
continue;
|
||||
}
|
||||
newSA = sa.copy(activator);
|
||||
}
|
||||
|
||||
|
||||
@@ -2898,21 +2898,17 @@ public class AbilityUtils {
|
||||
}
|
||||
|
||||
public static final List<SpellAbility> getBasicSpellsFromPlayEffect(final Card tgtCard, final Player controller) {
|
||||
return getBasicSpellsFromPlayEffect(tgtCard, controller, CardStateName.Original);
|
||||
return getSpellsFromPlayEffect(tgtCard, controller, CardStateName.Original, false);
|
||||
}
|
||||
public static final List<SpellAbility> getBasicSpellsFromPlayEffect(final Card tgtCard, final Player controller, CardStateName state) {
|
||||
public static final List<SpellAbility> getSpellsFromPlayEffect(final Card tgtCard, final Player controller, CardStateName state, boolean withAltCost) {
|
||||
List<SpellAbility> sas = new ArrayList<>();
|
||||
List<SpellAbility> list = Lists.newArrayList(tgtCard.getBasicSpells());
|
||||
List<SpellAbility> list = new ArrayList<>();
|
||||
collectSpellsForPlayEffect(list, tgtCard.getState(tgtCard.getCurrentStateName()), controller, withAltCost);
|
||||
CardState original = tgtCard.getState(state);
|
||||
|
||||
if (tgtCard.isFaceDown()) {
|
||||
Iterables.addAll(list, tgtCard.getBasicSpells(original));
|
||||
collectSpellsForPlayEffect(list, original, controller, withAltCost);
|
||||
} else {
|
||||
if (tgtCard.isLand()) {
|
||||
LandAbility la = new LandAbility(tgtCard, controller, null);
|
||||
la.setCardState(original);
|
||||
list.add(la);
|
||||
}
|
||||
if (state == CardStateName.Transformed && tgtCard.isPermanent() && !tgtCard.isAura()) {
|
||||
// casting defeated battle
|
||||
Spell sp = new SpellPermanent(tgtCard, original);
|
||||
@@ -2920,13 +2916,7 @@ public class AbilityUtils {
|
||||
list.add(sp);
|
||||
}
|
||||
if (tgtCard.isModal()) {
|
||||
CardState modal = tgtCard.getState(CardStateName.Modal);
|
||||
Iterables.addAll(list, tgtCard.getBasicSpells(modal));
|
||||
if (modal.getType().isLand()) {
|
||||
LandAbility la = new LandAbility(tgtCard, controller, null);
|
||||
la.setCardState(modal);
|
||||
list.add(la);
|
||||
}
|
||||
collectSpellsForPlayEffect(list, tgtCard.getState(CardStateName.Modal), controller, withAltCost);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2949,6 +2939,7 @@ public class AbilityUtils {
|
||||
if (res.checkTimingRestrictions(tgtCard, newSA)
|
||||
// still need to check the other restrictions like Aftermath
|
||||
&& res.checkOtherRestrictions(tgtCard, newSA, controller)) {
|
||||
newSA.setCastFromPlayEffect(true);
|
||||
sas.add(newSA);
|
||||
}
|
||||
}
|
||||
@@ -2956,6 +2947,27 @@ public class AbilityUtils {
|
||||
return sas;
|
||||
}
|
||||
|
||||
private static void collectSpellsForPlayEffect(final List<SpellAbility> result, final CardState state, final Player controller, final boolean withAltCost) {
|
||||
if (state.getType().isLand()) {
|
||||
LandAbility la = new LandAbility(state.getCard(), controller, null);
|
||||
la.setCardState(state);
|
||||
result.add(la);
|
||||
}
|
||||
final Iterable<SpellAbility> spells = state.getSpellAbilities();
|
||||
for (SpellAbility sa : spells) {
|
||||
if (!sa.isSpell()) {
|
||||
continue;
|
||||
}
|
||||
if (!withAltCost && !sa.isBasicSpell()) {
|
||||
continue;
|
||||
}
|
||||
result.add(sa);
|
||||
if (withAltCost) {
|
||||
result.addAll(GameActionUtil.getAlternativeCosts(sa, controller, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final String applyAbilityTextChangeEffects(final String def, final CardTraitBase ability) {
|
||||
if (ability == null || !ability.isIntrinsic() || ability.hasParam("LockInText")) {
|
||||
return def;
|
||||
|
||||
@@ -1058,7 +1058,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
if (!decider.getController().confirmAction(tgtSA, null, Localizer.getInstance().getMessage("lblDoYouWantPlayCard", CardTranslation.getTranslatedName(tgtCard.getName())), null)) {
|
||||
continue;
|
||||
}
|
||||
tgtSA.setSVar("IsCastFromPlayEffect", "True");
|
||||
// if played, that card cannot be found
|
||||
if (decider.getController().playSaFromPlayEffect(tgtSA)) {
|
||||
fetchList.remove(tgtCard);
|
||||
|
||||
@@ -125,8 +125,6 @@ public class DiscoverEffect extends SpellAbilityEffect {
|
||||
tgtSA.getTargetRestrictions().setMandatory(true);
|
||||
}
|
||||
|
||||
tgtSA.setSVar("IsCastFromPlayEffect", "True");
|
||||
|
||||
if (p.getController().playSaFromPlayEffect(tgtSA)) {
|
||||
final Card played = tgtSA.getHostCard();
|
||||
// add remember successfully played here if ever needed
|
||||
|
||||
@@ -91,6 +91,7 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
final boolean imprint = sa.hasParam("ImprintPlayed");
|
||||
final boolean forget = sa.hasParam("ForgetPlayed");
|
||||
final boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC");
|
||||
final boolean altCost = sa.hasParam("WithoutManaCost") || sa.hasParam("PlayCost");
|
||||
int totalCMCLimit = Integer.MAX_VALUE;
|
||||
final Player controller;
|
||||
if (sa.hasParam("Controller")) {
|
||||
@@ -298,9 +299,7 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
state = CardStateName.Transformed;
|
||||
}
|
||||
|
||||
// TODO if cost isn't replaced should include alternative ones
|
||||
// get basic spells (no flashback, etc.)
|
||||
List<SpellAbility> sas = AbilityUtils.getBasicSpellsFromPlayEffect(tgtCard, controller, state);
|
||||
List<SpellAbility> sas = AbilityUtils.getSpellsFromPlayEffect(tgtCard, controller, state, !altCost);
|
||||
if (sa.hasParam("ValidSA")) {
|
||||
final String valid[] = sa.getParam("ValidSA").split(",");
|
||||
sas.removeIf(sp -> !sp.isValid(valid, controller , source, sa));
|
||||
@@ -367,8 +366,7 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
final int tgtCMC = tgtSA.getPayCosts().getTotalMana().getCMC();
|
||||
|
||||
// illegal action, cancel early
|
||||
if ((sa.hasParam("WithoutManaCost") || sa.hasParam("PlayCost")) && tgtSA.costHasManaX() &&
|
||||
tgtSA.getPayCosts().getCostMana().getXMin() > 0) {
|
||||
if (altCost && tgtSA.costHasManaX() && tgtSA.getPayCosts().getCostMana().getXMin() > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -384,7 +382,7 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
}
|
||||
abCost = new Cost(source.getManaCost(), false);
|
||||
} else if (cost.equals("SuspendCost")) {
|
||||
abCost = Iterables.find(tgtCard.getNonManaAbilities(), s -> s.getKeyword() != null && s.getKeyword().getKeyword() == Keyword.SUSPEND).getPayCosts();
|
||||
abCost = Iterables.find(tgtCard.getNonManaAbilities(), s -> s.isKeyword(Keyword.SUSPEND)).getPayCosts();
|
||||
} else {
|
||||
if (cost.contains("ConvertedManaCost")) {
|
||||
if (unpayableCost) {
|
||||
@@ -453,8 +451,6 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
addIllusionaryMaskReplace(tgtCard, sa, moveParams);
|
||||
}
|
||||
|
||||
tgtSA.setSVar("IsCastFromPlayEffect", "True");
|
||||
|
||||
// Add controlled by player to target SA so when the spell is resolving, the controller would be changed again
|
||||
if (controlledByPlayer != null) {
|
||||
tgtSA.setControlledByPlayer(controlledByTimeStamp, controlledByPlayer);
|
||||
|
||||
@@ -7095,7 +7095,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|
||||
public List<SpellAbility> getAllPossibleAbilities(final Player player, final boolean removeUnplayable) {
|
||||
CardState oState = getState(CardStateName.Original);
|
||||
// this can only be called by the Human
|
||||
final List<SpellAbility> abilities = Lists.newArrayList();
|
||||
for (SpellAbility sa : getSpellAbilities()) {
|
||||
//adventure spell check
|
||||
@@ -7105,12 +7104,12 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
//add alternative costs as additional spell abilities
|
||||
abilities.add(sa);
|
||||
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
|
||||
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player, false));
|
||||
}
|
||||
|
||||
if (isFaceDown() && isInZone(ZoneType.Exile)) {
|
||||
for (final SpellAbility sa : oState.getSpellAbilities()) {
|
||||
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
|
||||
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player, false));
|
||||
}
|
||||
}
|
||||
// Add Modal Spells
|
||||
@@ -7120,7 +7119,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
// only add Spells there
|
||||
if (sa.isSpell()) {
|
||||
abilities.add(sa);
|
||||
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
|
||||
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,27 +274,6 @@ public class CardFactoryUtil {
|
||||
return AbilityFactory.getAbility(ab, sourceCard);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* countOccurrences.
|
||||
* </p>
|
||||
*
|
||||
* @param arg1
|
||||
* a {@link java.lang.String} object.
|
||||
* @param arg2
|
||||
* a {@link java.lang.String} object.
|
||||
* @return a int.
|
||||
*/
|
||||
public static int countOccurrences(final String arg1, final String arg2) {
|
||||
int count = 0;
|
||||
int index = 0;
|
||||
while ((index = arg1.indexOf(arg2, index)) != -1) {
|
||||
++index;
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* parseMath.
|
||||
|
||||
@@ -521,7 +521,7 @@ public class Combat {
|
||||
* @param blocker the blocking creature.
|
||||
*/
|
||||
public void addBlockerToDamageAssignmentOrder(Card attacker, Card blocker) {
|
||||
final CardCollection oldBlockers = blockersOrderedForDamageAssignment.get(attacker);
|
||||
final CardCollection oldBlockers = blockersOrderedForDamageAssignment.get(attacker);
|
||||
if (oldBlockers == null || oldBlockers.isEmpty()) {
|
||||
blockersOrderedForDamageAssignment.put(attacker, new CardCollection(blocker));
|
||||
} else {
|
||||
@@ -815,24 +815,21 @@ public class Combat {
|
||||
defender = getDefenderPlayerByAttacker(attacker);
|
||||
}
|
||||
|
||||
assignCombatDamageToCreature = !attacker.getGame().getCombat().isBlocked(attacker) &&
|
||||
getDefendersCreatures().size() > 0 &&
|
||||
attacker.hasKeyword("If CARDNAME is unblocked, you may have it assign its combat damage to " +
|
||||
"a creature defending player controls.") &&
|
||||
assignCombatDamageToCreature = !attacker.getGame().getCombat().isBlocked(attacker) && getDefendersCreatures().size() > 0 &&
|
||||
attacker.hasKeyword("If CARDNAME is unblocked, you may have it assign its combat damage to a creature defending player controls.") &&
|
||||
assigningPlayer.getController().confirmStaticApplication(attacker, PlayerActionConfirmMode.AlternativeDamageAssignment,
|
||||
Localizer.getInstance().getMessage("lblAssignCombatDamageToCreature",
|
||||
CardTranslation.getTranslatedName(attacker.getName())), null);
|
||||
if (divideCombatDamageAsChoose) {
|
||||
if (orderedBlockers == null || orderedBlockers.isEmpty()) {
|
||||
orderedBlockers = getDefendersCreatures();
|
||||
} else {
|
||||
for (Card c : getDefendersCreatures()) {
|
||||
if (!orderedBlockers.contains(c)) {
|
||||
orderedBlockers.add(c);
|
||||
}
|
||||
}
|
||||
Localizer.getInstance().getMessage("lblAssignCombatDamageToCreature", CardTranslation.getTranslatedName(attacker.getName())), null);
|
||||
if (divideCombatDamageAsChoose) {
|
||||
if (orderedBlockers == null || orderedBlockers.isEmpty()) {
|
||||
orderedBlockers = getDefendersCreatures();
|
||||
} else {
|
||||
for (Card c : getDefendersCreatures()) {
|
||||
if (!orderedBlockers.contains(c)) {
|
||||
orderedBlockers.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assignedDamage = true;
|
||||
|
||||
@@ -106,7 +106,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
|
||||
// for uncastables like lotus bloom, check if manaCost is blank (except for morph spells)
|
||||
// but ignore if it comes from PlayEffect
|
||||
if (!isCastFaceDown()
|
||||
&& !hasSVar("IsCastFromPlayEffect")
|
||||
&& !isCastFromPlayEffect()
|
||||
&& isBasicSpell()
|
||||
&& origCost.isNoCost()) {
|
||||
return false;
|
||||
|
||||
@@ -166,6 +166,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
private boolean isCopied = false;
|
||||
private boolean mayChooseNewTargets = false;
|
||||
|
||||
private boolean isCastFromPlayEffect = false;
|
||||
|
||||
private EnumSet<OptionalCost> optionalCosts = EnumSet.noneOf(OptionalCost.class);
|
||||
private TargetRestrictions targetRestrictions;
|
||||
private TargetChoices targetChosen = new TargetChoices();
|
||||
@@ -1645,6 +1647,13 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
undoable = b;
|
||||
}
|
||||
|
||||
public boolean isCastFromPlayEffect() {
|
||||
return isCastFromPlayEffect;
|
||||
}
|
||||
public void setCastFromPlayEffect(boolean b) {
|
||||
isCastFromPlayEffect = b;
|
||||
}
|
||||
|
||||
public boolean isCopied() {
|
||||
return isCopied;
|
||||
}
|
||||
@@ -2509,7 +2518,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
if (getRestrictions().isInstantSpeed()) {
|
||||
return true;
|
||||
}
|
||||
if ((isSpell() || this instanceof LandAbility) && (hasSVar("IsCastFromPlayEffect") || host.isInstant() || host.hasKeyword(Keyword.FLASH))) {
|
||||
if ((isSpell() || this instanceof LandAbility) && (isCastFromPlayEffect() || host.isInstant() || host.hasKeyword(Keyword.FLASH))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPlayOption;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.cost.IndividualCostPaymentInstance;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.staticability.StaticAbilityCastWithFlash;
|
||||
@@ -249,7 +250,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
|
||||
}
|
||||
if (sa.isSpell()) {
|
||||
final CardPlayOption o = c.mayPlay(sa.getMayPlay());
|
||||
if (o == null) {
|
||||
if (o == null || sa.isCastFromPlayEffect()) {
|
||||
return this.getZone() == null || (cardZone != null && cardZone.is(this.getZone()));
|
||||
} else if (o.getPlayer() == activator) {
|
||||
Map<String,String> params = sa.getMayPlay().getMapParams();
|
||||
@@ -377,6 +378,10 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.isKeyword(Keyword.FUSE) && !c.isInZone(ZoneType.Hand)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getCardsInHand() != -1) {
|
||||
int h = activator.getCardsIn(ZoneType.Hand).size();
|
||||
if (getCardsInHand2() != -1) {
|
||||
@@ -596,7 +601,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sa.hasSVar("IsCastFromPlayEffect")) {
|
||||
if (!sa.isCastFromPlayEffect()) {
|
||||
if (!checkTimingRestrictions(c, sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:3 U
|
||||
Types:Enchantment Saga
|
||||
K:Chapter:3:DBReturn1,DBReturn2,DBTransform
|
||||
SVar:DBReturn1:DB$ ChangeZone | ValidTgts$ Creature,Planeswalker | TargetMin$ 0 | TargetMax$ 1 | Origin$ Battlefield | Destination$ Hand | TgtPrompt$ Select target creature or planeswalker | SpellDescription$ Return up to one target creature or planeswalker to its owner's hand.
|
||||
SVar:DBReturn2:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ChangeType$ Artifact | ChangeNum$ 1 | Mandatory$ True | RememberChanged$ True | SubAbility$ DBDraw | SpellDescription$ Return an artifact card from your graveyard to your hand. If you can't, draw a card.
|
||||
SVar:DBReturn2:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ChangeType$ Artifact.YouOwn | Hidden$ True | ChangeNum$ 1 | Mandatory$ True | RememberChanged$ True | SubAbility$ DBDraw | SpellDescription$ Return an artifact card from your graveyard to your hand. If you can't, draw a card.
|
||||
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0 | SubAbility$ DBCleanup
|
||||
SVar:DBTransform:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ DBReturn | SpellDescription$ Exile this Saga, then return it to the battlefield transformed under your control.
|
||||
SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | Transformed$ True | GainControl$ True | SubAbility$ DBCleanup
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Karn, the Great Creator
|
||||
ManaCost:4
|
||||
Types:Legendary Planeswalker Karn
|
||||
Loyalty:5
|
||||
S:Mode$ CantBeActivated | Activator$ Opponent | AffectedZone$ Battlefield | ValidCard$ Artifact | ValidSA$ Activated | Description$ Activated abilities of artifacts your opponents control can't be activated.
|
||||
S:Mode$ CantBeActivated | AffectedZone$ Battlefield | ValidCard$ Artifact.OppCtrl | ValidSA$ Activated | Description$ Activated abilities of artifacts your opponents control can't be activated.
|
||||
SVar:NonStackingEffect:True
|
||||
A:AB$ Animate | Cost$ AddCounter<1/LOYALTY> | TargetMin$ 0 | TargetMax$ 1 | Planeswalker$ True | ValidTgts$ Artifact.nonCreature | TgtPrompt$ Select target noncreature artifact | Power$ X | Toughness$ X | Types$ Artifact,Creature | Duration$ UntilYourNextTurn | AILogic$ PTByCMC | SpellDescription$ Until your next turn, up to one target noncreature artifact becomes an artifact creature with power and toughness each equal to its mana value.
|
||||
SVar:X:Targeted$CardManaCost
|
||||
|
||||
@@ -3,6 +3,6 @@ ManaCost:2 W W
|
||||
Types:Legendary Creature Angel
|
||||
PT:3/4
|
||||
K:Flying
|
||||
S:Mode$ CantBeActivated | Activator$ Opponent | AffectedZone$ Battlefield | ValidCard$ Creature | ValidSA$ Activated | Description$ Activated abilities of creatures your opponents control can't be activated.
|
||||
S:Mode$ CantBeActivated | AffectedZone$ Battlefield | ValidCard$ Creature.OppCtrl | ValidSA$ Activated | Description$ Activated abilities of creatures your opponents control can't be activated.
|
||||
SVar:PlayMain1:TRUE
|
||||
Oracle:Flying\nActivated abilities of creatures your opponents control can't be activated.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Press the Enemy
|
||||
ManaCost:2 U U
|
||||
Types:Instant
|
||||
A:SP$ ChangeZone | ValidTgts$ Permanent.nonLand+OppCtrl,Card.inZoneStack+OppCtrl | TgtZone$ Stack,Battlefield | Origin$ Battlefield,Stack | Fizzle$ True | Destination$ Hand | SubAbility$ DBMayPlay | SpellDescription$ Return target spell or nonland permanent an opponent controls to its owner's hand.
|
||||
SVar:DBMayPlay:DB$ Play | Valid$ Card.YouOwn | ValidZone$ Hand| ValidSA$ Instant.cmcLEZ,Sorcery.cmcLEZ | WithoutManaCost$ True | Optional$ True | SubAbility$ DBCleanup | SpellDescription$ You may cast an instant or sorcery spell with equal or lesser mana value from your hand without paying its mana cost.
|
||||
SVar:DBMayPlay:DB$ Play | Valid$ Card.YouOwn | ValidZone$ Hand | ValidSA$ Instant.cmcLEZ,Sorcery.cmcLEZ | WithoutManaCost$ True | Optional$ True | SubAbility$ DBCleanup | SpellDescription$ You may cast an instant or sorcery spell with equal or lesser mana value from your hand without paying its mana cost.
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
DeckHints:Type$Sorcery|Instant
|
||||
SVar:X:SpellTargeted$CardManaCostLKI
|
||||
|
||||
@@ -116,7 +116,7 @@ public abstract class InputPayMana extends InputSyncronizedBase {
|
||||
List<SpellAbility> result = Lists.newArrayList();
|
||||
for (SpellAbility sa : card.getManaAbilities()) {
|
||||
result.add(sa);
|
||||
result.addAll(GameActionUtil.getAlternativeCosts(sa, player));
|
||||
result.addAll(GameActionUtil.getAlternativeCosts(sa, player, false));
|
||||
}
|
||||
final Collection<SpellAbility> toRemove = Lists.newArrayListWithCapacity(result.size());
|
||||
for (final SpellAbility sa : result) {
|
||||
|
||||
@@ -851,7 +851,7 @@ public class HumanCostDecision extends CostDecisionMakerBase {
|
||||
return PaymentDecision.number(0);
|
||||
}
|
||||
// player might not want to pay if from a trigger
|
||||
if (!ability.hasSVar("IsCastFromPlayEffect") && hand.size() == num) {
|
||||
if (!ability.isCastFromPlayEffect() && hand.size() == num) {
|
||||
return PaymentDecision.card(hand);
|
||||
}
|
||||
|
||||
|
||||
@@ -2910,7 +2910,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
||||
// Human player is choosing targets for an ability
|
||||
// controlled by chosen player.
|
||||
sa.setActivatingPlayer(p);
|
||||
sa.setSVar("IsCastFromPlayEffect", "True");
|
||||
sa.setCastFromPlayEffect(true);
|
||||
HumanPlay.playSaWithoutPayingManaCost(PlayerControllerHuman.this, getGame(), sa, true);
|
||||
}
|
||||
// playSa could fire some triggers
|
||||
|
||||
Reference in New Issue
Block a user