mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 19:58:00 +00:00
PlayEffect: support AltCosts (#4649)
This commit is contained in:
@@ -1539,8 +1539,7 @@ public class AiController {
|
|||||||
boolean mustRespond = false;
|
boolean mustRespond = false;
|
||||||
if (top != null) {
|
if (top != null) {
|
||||||
mustRespond = top.hasParam("AIRespondsToOwnAbility"); // Forced combos (currently defined for Sensei's Divining Top)
|
mustRespond = top.hasParam("AIRespondsToOwnAbility"); // Forced combos (currently defined for Sensei's Divining Top)
|
||||||
mustRespond |= top.isTrigger() && top.getTrigger().getKeyword() != null
|
mustRespond |= top.isTrigger() && top.getTrigger().isKeyword(Keyword.EVOKE); // Evoke sacrifice trigger
|
||||||
&& top.getTrigger().getKeyword().getKeyword() == Keyword.EVOKE; // Evoke sacrifice trigger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topOwnedByAI) {
|
if (topOwnedByAI) {
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ public class ComputerUtilAbility {
|
|||||||
|
|
||||||
for (SpellAbility sa : originListWithAddCosts) {
|
for (SpellAbility sa : originListWithAddCosts) {
|
||||||
// determine which alternative costs are cheaper than the original and prioritize them
|
// 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> priorityAltSa = Lists.newArrayList();
|
||||||
List<SpellAbility> otherAltSa = Lists.newArrayList();
|
List<SpellAbility> otherAltSa = Lists.newArrayList();
|
||||||
for (SpellAbility altSa : saAltCosts) {
|
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
|
// 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)) {
|
if (!combat.getAttackers().isEmpty() && !combat.getAttackers().contains(attacker)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import forge.ai.AiPlayDecision;
|
|||||||
import forge.ai.ComputerUtil;
|
import forge.ai.ComputerUtil;
|
||||||
import forge.ai.PlayerControllerAi;
|
import forge.ai.PlayerControllerAi;
|
||||||
import forge.ai.SpellAbilityAi;
|
import forge.ai.SpellAbilityAi;
|
||||||
import forge.card.CardStateName;
|
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -45,7 +44,7 @@ public class DiscoverAi extends SpellAbilityAi {
|
|||||||
@Override
|
@Override
|
||||||
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
|
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
|
||||||
Card c = (Card)params.get("Card");
|
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) {
|
if (s instanceof LandAbility) {
|
||||||
// return false or we get a ClassCastException later if the AI encounters MDFC with land backside
|
// return false or we get a ClassCastException later if the AI encounters MDFC with land backside
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ public class PlayAi extends SpellAbilityAi {
|
|||||||
public boolean apply(final Card c) {
|
public boolean apply(final Card c) {
|
||||||
// TODO needs to be aligned for MDFC along with getAbilityToPlay so the knowledge
|
// 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
|
// 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)) {
|
if (!sa.matchesValidParam("ValidSA", s)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import forge.game.card.CardPredicates;
|
|||||||
import forge.game.card.CardState;
|
import forge.game.card.CardState;
|
||||||
import forge.game.card.CardView;
|
import forge.game.card.CardView;
|
||||||
import forge.game.card.IHasCardView;
|
import forge.game.card.IHasCardView;
|
||||||
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.keyword.KeywordInterface;
|
import forge.game.keyword.KeywordInterface;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -145,6 +146,9 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
|||||||
this.hostCard = c;
|
this.hostCard = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isKeyword(Keyword kw) {
|
||||||
|
return this.keyword != null && this.keyword.getKeyword() == kw;
|
||||||
|
}
|
||||||
public KeywordInterface getKeyword() {
|
public KeywordInterface getKeyword() {
|
||||||
return this.keyword;
|
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
|
// 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)
|
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;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ public final class GameActionUtil {
|
|||||||
* a possible alternative cost the provided activator can use to pay
|
* a possible alternative cost the provided activator can use to pay
|
||||||
* the provided {@link SpellAbility}.
|
* 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();
|
final List<SpellAbility> alternatives = Lists.newArrayList();
|
||||||
|
|
||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
@@ -134,6 +134,9 @@ public final class GameActionUtil {
|
|||||||
newSA.setBasicSpell(false);
|
newSA.setBasicSpell(false);
|
||||||
changedManaCost = true;
|
changedManaCost = true;
|
||||||
} else {
|
} else {
|
||||||
|
if (altCostOnly) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
newSA = sa.copy(activator);
|
newSA = sa.copy(activator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2898,21 +2898,17 @@ public class AbilityUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final List<SpellAbility> getBasicSpellsFromPlayEffect(final Card tgtCard, final Player controller) {
|
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> 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);
|
CardState original = tgtCard.getState(state);
|
||||||
|
|
||||||
if (tgtCard.isFaceDown()) {
|
if (tgtCard.isFaceDown()) {
|
||||||
Iterables.addAll(list, tgtCard.getBasicSpells(original));
|
collectSpellsForPlayEffect(list, original, controller, withAltCost);
|
||||||
} else {
|
} 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()) {
|
if (state == CardStateName.Transformed && tgtCard.isPermanent() && !tgtCard.isAura()) {
|
||||||
// casting defeated battle
|
// casting defeated battle
|
||||||
Spell sp = new SpellPermanent(tgtCard, original);
|
Spell sp = new SpellPermanent(tgtCard, original);
|
||||||
@@ -2920,13 +2916,7 @@ public class AbilityUtils {
|
|||||||
list.add(sp);
|
list.add(sp);
|
||||||
}
|
}
|
||||||
if (tgtCard.isModal()) {
|
if (tgtCard.isModal()) {
|
||||||
CardState modal = tgtCard.getState(CardStateName.Modal);
|
collectSpellsForPlayEffect(list, tgtCard.getState(CardStateName.Modal), controller, withAltCost);
|
||||||
Iterables.addAll(list, tgtCard.getBasicSpells(modal));
|
|
||||||
if (modal.getType().isLand()) {
|
|
||||||
LandAbility la = new LandAbility(tgtCard, controller, null);
|
|
||||||
la.setCardState(modal);
|
|
||||||
list.add(la);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2949,6 +2939,7 @@ public class AbilityUtils {
|
|||||||
if (res.checkTimingRestrictions(tgtCard, newSA)
|
if (res.checkTimingRestrictions(tgtCard, newSA)
|
||||||
// still need to check the other restrictions like Aftermath
|
// still need to check the other restrictions like Aftermath
|
||||||
&& res.checkOtherRestrictions(tgtCard, newSA, controller)) {
|
&& res.checkOtherRestrictions(tgtCard, newSA, controller)) {
|
||||||
|
newSA.setCastFromPlayEffect(true);
|
||||||
sas.add(newSA);
|
sas.add(newSA);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2956,6 +2947,27 @@ public class AbilityUtils {
|
|||||||
return sas;
|
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) {
|
public static final String applyAbilityTextChangeEffects(final String def, final CardTraitBase ability) {
|
||||||
if (ability == null || !ability.isIntrinsic() || ability.hasParam("LockInText")) {
|
if (ability == null || !ability.isIntrinsic() || ability.hasParam("LockInText")) {
|
||||||
return def;
|
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)) {
|
if (!decider.getController().confirmAction(tgtSA, null, Localizer.getInstance().getMessage("lblDoYouWantPlayCard", CardTranslation.getTranslatedName(tgtCard.getName())), null)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tgtSA.setSVar("IsCastFromPlayEffect", "True");
|
|
||||||
// if played, that card cannot be found
|
// if played, that card cannot be found
|
||||||
if (decider.getController().playSaFromPlayEffect(tgtSA)) {
|
if (decider.getController().playSaFromPlayEffect(tgtSA)) {
|
||||||
fetchList.remove(tgtCard);
|
fetchList.remove(tgtCard);
|
||||||
|
|||||||
@@ -125,8 +125,6 @@ public class DiscoverEffect extends SpellAbilityEffect {
|
|||||||
tgtSA.getTargetRestrictions().setMandatory(true);
|
tgtSA.getTargetRestrictions().setMandatory(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
tgtSA.setSVar("IsCastFromPlayEffect", "True");
|
|
||||||
|
|
||||||
if (p.getController().playSaFromPlayEffect(tgtSA)) {
|
if (p.getController().playSaFromPlayEffect(tgtSA)) {
|
||||||
final Card played = tgtSA.getHostCard();
|
final Card played = tgtSA.getHostCard();
|
||||||
// add remember successfully played here if ever needed
|
// 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 imprint = sa.hasParam("ImprintPlayed");
|
||||||
final boolean forget = sa.hasParam("ForgetPlayed");
|
final boolean forget = sa.hasParam("ForgetPlayed");
|
||||||
final boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC");
|
final boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC");
|
||||||
|
final boolean altCost = sa.hasParam("WithoutManaCost") || sa.hasParam("PlayCost");
|
||||||
int totalCMCLimit = Integer.MAX_VALUE;
|
int totalCMCLimit = Integer.MAX_VALUE;
|
||||||
final Player controller;
|
final Player controller;
|
||||||
if (sa.hasParam("Controller")) {
|
if (sa.hasParam("Controller")) {
|
||||||
@@ -298,9 +299,7 @@ public class PlayEffect extends SpellAbilityEffect {
|
|||||||
state = CardStateName.Transformed;
|
state = CardStateName.Transformed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO if cost isn't replaced should include alternative ones
|
List<SpellAbility> sas = AbilityUtils.getSpellsFromPlayEffect(tgtCard, controller, state, !altCost);
|
||||||
// get basic spells (no flashback, etc.)
|
|
||||||
List<SpellAbility> sas = AbilityUtils.getBasicSpellsFromPlayEffect(tgtCard, controller, state);
|
|
||||||
if (sa.hasParam("ValidSA")) {
|
if (sa.hasParam("ValidSA")) {
|
||||||
final String valid[] = sa.getParam("ValidSA").split(",");
|
final String valid[] = sa.getParam("ValidSA").split(",");
|
||||||
sas.removeIf(sp -> !sp.isValid(valid, controller , source, sa));
|
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();
|
final int tgtCMC = tgtSA.getPayCosts().getTotalMana().getCMC();
|
||||||
|
|
||||||
// illegal action, cancel early
|
// illegal action, cancel early
|
||||||
if ((sa.hasParam("WithoutManaCost") || sa.hasParam("PlayCost")) && tgtSA.costHasManaX() &&
|
if (altCost && tgtSA.costHasManaX() && tgtSA.getPayCosts().getCostMana().getXMin() > 0) {
|
||||||
tgtSA.getPayCosts().getCostMana().getXMin() > 0) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,7 +382,7 @@ public class PlayEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
abCost = new Cost(source.getManaCost(), false);
|
abCost = new Cost(source.getManaCost(), false);
|
||||||
} else if (cost.equals("SuspendCost")) {
|
} 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 {
|
} else {
|
||||||
if (cost.contains("ConvertedManaCost")) {
|
if (cost.contains("ConvertedManaCost")) {
|
||||||
if (unpayableCost) {
|
if (unpayableCost) {
|
||||||
@@ -453,8 +451,6 @@ public class PlayEffect extends SpellAbilityEffect {
|
|||||||
addIllusionaryMaskReplace(tgtCard, sa, moveParams);
|
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
|
// Add controlled by player to target SA so when the spell is resolving, the controller would be changed again
|
||||||
if (controlledByPlayer != null) {
|
if (controlledByPlayer != null) {
|
||||||
tgtSA.setControlledByPlayer(controlledByTimeStamp, controlledByPlayer);
|
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) {
|
public List<SpellAbility> getAllPossibleAbilities(final Player player, final boolean removeUnplayable) {
|
||||||
CardState oState = getState(CardStateName.Original);
|
CardState oState = getState(CardStateName.Original);
|
||||||
// this can only be called by the Human
|
|
||||||
final List<SpellAbility> abilities = Lists.newArrayList();
|
final List<SpellAbility> abilities = Lists.newArrayList();
|
||||||
for (SpellAbility sa : getSpellAbilities()) {
|
for (SpellAbility sa : getSpellAbilities()) {
|
||||||
//adventure spell check
|
//adventure spell check
|
||||||
@@ -7105,12 +7104,12 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
}
|
}
|
||||||
//add alternative costs as additional spell abilities
|
//add alternative costs as additional spell abilities
|
||||||
abilities.add(sa);
|
abilities.add(sa);
|
||||||
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
|
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFaceDown() && isInZone(ZoneType.Exile)) {
|
if (isFaceDown() && isInZone(ZoneType.Exile)) {
|
||||||
for (final SpellAbility sa : oState.getSpellAbilities()) {
|
for (final SpellAbility sa : oState.getSpellAbilities()) {
|
||||||
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
|
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add Modal Spells
|
// Add Modal Spells
|
||||||
@@ -7120,7 +7119,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
|||||||
// only add Spells there
|
// only add Spells there
|
||||||
if (sa.isSpell()) {
|
if (sa.isSpell()) {
|
||||||
abilities.add(sa);
|
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);
|
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>
|
* <p>
|
||||||
* parseMath.
|
* parseMath.
|
||||||
|
|||||||
@@ -521,7 +521,7 @@ public class Combat {
|
|||||||
* @param blocker the blocking creature.
|
* @param blocker the blocking creature.
|
||||||
*/
|
*/
|
||||||
public void addBlockerToDamageAssignmentOrder(Card attacker, Card blocker) {
|
public void addBlockerToDamageAssignmentOrder(Card attacker, Card blocker) {
|
||||||
final CardCollection oldBlockers = blockersOrderedForDamageAssignment.get(attacker);
|
final CardCollection oldBlockers = blockersOrderedForDamageAssignment.get(attacker);
|
||||||
if (oldBlockers == null || oldBlockers.isEmpty()) {
|
if (oldBlockers == null || oldBlockers.isEmpty()) {
|
||||||
blockersOrderedForDamageAssignment.put(attacker, new CardCollection(blocker));
|
blockersOrderedForDamageAssignment.put(attacker, new CardCollection(blocker));
|
||||||
} else {
|
} else {
|
||||||
@@ -815,24 +815,21 @@ public class Combat {
|
|||||||
defender = getDefenderPlayerByAttacker(attacker);
|
defender = getDefenderPlayerByAttacker(attacker);
|
||||||
}
|
}
|
||||||
|
|
||||||
assignCombatDamageToCreature = !attacker.getGame().getCombat().isBlocked(attacker) &&
|
assignCombatDamageToCreature = !attacker.getGame().getCombat().isBlocked(attacker) && getDefendersCreatures().size() > 0 &&
|
||||||
getDefendersCreatures().size() > 0 &&
|
attacker.hasKeyword("If CARDNAME is unblocked, you may have it assign its combat damage to a creature defending player controls.") &&
|
||||||
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,
|
assigningPlayer.getController().confirmStaticApplication(attacker, PlayerActionConfirmMode.AlternativeDamageAssignment,
|
||||||
Localizer.getInstance().getMessage("lblAssignCombatDamageToCreature",
|
Localizer.getInstance().getMessage("lblAssignCombatDamageToCreature", CardTranslation.getTranslatedName(attacker.getName())), null);
|
||||||
CardTranslation.getTranslatedName(attacker.getName())), null);
|
if (divideCombatDamageAsChoose) {
|
||||||
if (divideCombatDamageAsChoose) {
|
if (orderedBlockers == null || orderedBlockers.isEmpty()) {
|
||||||
if (orderedBlockers == null || orderedBlockers.isEmpty()) {
|
orderedBlockers = getDefendersCreatures();
|
||||||
orderedBlockers = getDefendersCreatures();
|
} else {
|
||||||
} else {
|
for (Card c : getDefendersCreatures()) {
|
||||||
for (Card c : getDefendersCreatures()) {
|
if (!orderedBlockers.contains(c)) {
|
||||||
if (!orderedBlockers.contains(c)) {
|
orderedBlockers.add(c);
|
||||||
orderedBlockers.add(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assignedDamage = true;
|
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)
|
// for uncastables like lotus bloom, check if manaCost is blank (except for morph spells)
|
||||||
// but ignore if it comes from PlayEffect
|
// but ignore if it comes from PlayEffect
|
||||||
if (!isCastFaceDown()
|
if (!isCastFaceDown()
|
||||||
&& !hasSVar("IsCastFromPlayEffect")
|
&& !isCastFromPlayEffect()
|
||||||
&& isBasicSpell()
|
&& isBasicSpell()
|
||||||
&& origCost.isNoCost()) {
|
&& origCost.isNoCost()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -166,6 +166,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
private boolean isCopied = false;
|
private boolean isCopied = false;
|
||||||
private boolean mayChooseNewTargets = false;
|
private boolean mayChooseNewTargets = false;
|
||||||
|
|
||||||
|
private boolean isCastFromPlayEffect = false;
|
||||||
|
|
||||||
private EnumSet<OptionalCost> optionalCosts = EnumSet.noneOf(OptionalCost.class);
|
private EnumSet<OptionalCost> optionalCosts = EnumSet.noneOf(OptionalCost.class);
|
||||||
private TargetRestrictions targetRestrictions;
|
private TargetRestrictions targetRestrictions;
|
||||||
private TargetChoices targetChosen = new TargetChoices();
|
private TargetChoices targetChosen = new TargetChoices();
|
||||||
@@ -1645,6 +1647,13 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
undoable = b;
|
undoable = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCastFromPlayEffect() {
|
||||||
|
return isCastFromPlayEffect;
|
||||||
|
}
|
||||||
|
public void setCastFromPlayEffect(boolean b) {
|
||||||
|
isCastFromPlayEffect = b;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isCopied() {
|
public boolean isCopied() {
|
||||||
return isCopied;
|
return isCopied;
|
||||||
}
|
}
|
||||||
@@ -2509,7 +2518,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
if (getRestrictions().isInstantSpeed()) {
|
if (getRestrictions().isInstantSpeed()) {
|
||||||
return true;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import forge.game.card.CardLists;
|
|||||||
import forge.game.card.CardPlayOption;
|
import forge.game.card.CardPlayOption;
|
||||||
import forge.game.card.CardUtil;
|
import forge.game.card.CardUtil;
|
||||||
import forge.game.cost.IndividualCostPaymentInstance;
|
import forge.game.cost.IndividualCostPaymentInstance;
|
||||||
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.staticability.StaticAbilityCastWithFlash;
|
import forge.game.staticability.StaticAbilityCastWithFlash;
|
||||||
@@ -249,7 +250,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
|
|||||||
}
|
}
|
||||||
if (sa.isSpell()) {
|
if (sa.isSpell()) {
|
||||||
final CardPlayOption o = c.mayPlay(sa.getMayPlay());
|
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()));
|
return this.getZone() == null || (cardZone != null && cardZone.is(this.getZone()));
|
||||||
} else if (o.getPlayer() == activator) {
|
} else if (o.getPlayer() == activator) {
|
||||||
Map<String,String> params = sa.getMayPlay().getMapParams();
|
Map<String,String> params = sa.getMayPlay().getMapParams();
|
||||||
@@ -377,6 +378,10 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sa.isKeyword(Keyword.FUSE) && !c.isInZone(ZoneType.Hand)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (getCardsInHand() != -1) {
|
if (getCardsInHand() != -1) {
|
||||||
int h = activator.getCardsIn(ZoneType.Hand).size();
|
int h = activator.getCardsIn(ZoneType.Hand).size();
|
||||||
if (getCardsInHand2() != -1) {
|
if (getCardsInHand2() != -1) {
|
||||||
@@ -596,7 +601,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sa.hasSVar("IsCastFromPlayEffect")) {
|
if (!sa.isCastFromPlayEffect()) {
|
||||||
if (!checkTimingRestrictions(c, sa)) {
|
if (!checkTimingRestrictions(c, sa)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ ManaCost:3 U
|
|||||||
Types:Enchantment Saga
|
Types:Enchantment Saga
|
||||||
K:Chapter:3:DBReturn1,DBReturn2,DBTransform
|
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: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: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: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
|
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
|
ManaCost:4
|
||||||
Types:Legendary Planeswalker Karn
|
Types:Legendary Planeswalker Karn
|
||||||
Loyalty:5
|
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
|
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.
|
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
|
SVar:X:Targeted$CardManaCost
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ ManaCost:2 W W
|
|||||||
Types:Legendary Creature Angel
|
Types:Legendary Creature Angel
|
||||||
PT:3/4
|
PT:3/4
|
||||||
K:Flying
|
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
|
SVar:PlayMain1:TRUE
|
||||||
Oracle:Flying\nActivated abilities of creatures your opponents control can't be activated.
|
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
|
ManaCost:2 U U
|
||||||
Types:Instant
|
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.
|
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
|
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||||
DeckHints:Type$Sorcery|Instant
|
DeckHints:Type$Sorcery|Instant
|
||||||
SVar:X:SpellTargeted$CardManaCostLKI
|
SVar:X:SpellTargeted$CardManaCostLKI
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ public abstract class InputPayMana extends InputSyncronizedBase {
|
|||||||
List<SpellAbility> result = Lists.newArrayList();
|
List<SpellAbility> result = Lists.newArrayList();
|
||||||
for (SpellAbility sa : card.getManaAbilities()) {
|
for (SpellAbility sa : card.getManaAbilities()) {
|
||||||
result.add(sa);
|
result.add(sa);
|
||||||
result.addAll(GameActionUtil.getAlternativeCosts(sa, player));
|
result.addAll(GameActionUtil.getAlternativeCosts(sa, player, false));
|
||||||
}
|
}
|
||||||
final Collection<SpellAbility> toRemove = Lists.newArrayListWithCapacity(result.size());
|
final Collection<SpellAbility> toRemove = Lists.newArrayListWithCapacity(result.size());
|
||||||
for (final SpellAbility sa : result) {
|
for (final SpellAbility sa : result) {
|
||||||
|
|||||||
@@ -851,7 +851,7 @@ public class HumanCostDecision extends CostDecisionMakerBase {
|
|||||||
return PaymentDecision.number(0);
|
return PaymentDecision.number(0);
|
||||||
}
|
}
|
||||||
// player might not want to pay if from a trigger
|
// 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);
|
return PaymentDecision.card(hand);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2910,7 +2910,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
|||||||
// Human player is choosing targets for an ability
|
// Human player is choosing targets for an ability
|
||||||
// controlled by chosen player.
|
// controlled by chosen player.
|
||||||
sa.setActivatingPlayer(p);
|
sa.setActivatingPlayer(p);
|
||||||
sa.setSVar("IsCastFromPlayEffect", "True");
|
sa.setCastFromPlayEffect(true);
|
||||||
HumanPlay.playSaWithoutPayingManaCost(PlayerControllerHuman.this, getGame(), sa, true);
|
HumanPlay.playSaWithoutPayingManaCost(PlayerControllerHuman.this, getGame(), sa, true);
|
||||||
}
|
}
|
||||||
// playSa could fire some triggers
|
// playSa could fire some triggers
|
||||||
|
|||||||
Reference in New Issue
Block a user