mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 11:18:01 +00:00
Merge remote-tracking branch 'core/master' into AdventureModePort
This commit is contained in:
@@ -153,10 +153,15 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
|
||||
/** Choose opponent for AI to attack here. Expand as necessary. */
|
||||
/**
|
||||
* Choose opponent for AI to attack here. Expand as necessary.
|
||||
* No strategy to secure a second place instead, since Forge has no variant for that
|
||||
*/
|
||||
public static Player choosePreferredDefenderPlayer(Player ai) {
|
||||
Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range
|
||||
|
||||
// TODO connect with evaluateBoardPosition and only fall back to random when no player is the biggest threat by a fair margin
|
||||
|
||||
if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players
|
||||
// TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked
|
||||
return ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
|
||||
@@ -720,7 +725,7 @@ public class AiAttackController {
|
||||
continue;
|
||||
}
|
||||
boolean mustAttack = false;
|
||||
// TODO for nextTurn check if it was temporary
|
||||
// TODO this might result into attacking the wrong player
|
||||
if (attacker.isGoaded()) {
|
||||
mustAttack = true;
|
||||
} else if (attacker.getSVar("MustAttack").equals("True")) {
|
||||
@@ -737,7 +742,7 @@ public class AiAttackController {
|
||||
mustAttack = true;
|
||||
}
|
||||
}
|
||||
if (mustAttack || (attacker.getController().getMustAttackEntity() != null && nextTurn) || (attacker.getController().getMustAttackEntityThisTurn() != null && !nextTurn)) {
|
||||
if (mustAttack ||attacker.getController().getMustAttackEntityThisTurn() != null) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
attackersLeft.remove(attacker);
|
||||
numForcedAttackers++;
|
||||
|
||||
@@ -426,7 +426,6 @@ public class AiBlockController {
|
||||
}
|
||||
|
||||
attackersLeft = new ArrayList<>(currentAttackers);
|
||||
currentAttackers = new ArrayList<>(attackersLeft);
|
||||
|
||||
boolean considerTripleBlock = true;
|
||||
|
||||
@@ -437,6 +436,11 @@ public class AiBlockController {
|
||||
continue;
|
||||
}
|
||||
|
||||
// AI can't handle good blocks with more than three creatures yet
|
||||
if (CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > (considerTripleBlock ? 3 : 2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int evalAttackerValue = ComputerUtilCard.evaluateCreature(attacker);
|
||||
|
||||
blockers = getPossibleBlockers(combat, attacker, blockersLeft, false);
|
||||
@@ -446,11 +450,6 @@ public class AiBlockController {
|
||||
int currentValue; // The value of the creatures in the blockgang
|
||||
boolean foundDoubleBlock = false; // if true, a good double block is found
|
||||
|
||||
// AI can't handle good blocks with more than three creatures yet
|
||||
if (CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > (considerTripleBlock ? 3 : 2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to add blockers that could be destroyed, but are worth less than the attacker
|
||||
// Don't use blockers without First Strike or Double Strike if attacker has it
|
||||
usableBlockers = CardLists.filter(blockers, new Predicate<Card>() {
|
||||
@@ -460,8 +459,7 @@ public class AiBlockController {
|
||||
&& !ComputerUtilCombat.dealsFirstStrikeDamage(c, false, combat)) {
|
||||
return false;
|
||||
}
|
||||
final boolean randomTrade = wouldLikeToRandomlyTrade(attacker, c, combat);
|
||||
return lifeInDanger || randomTrade || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker);
|
||||
return lifeInDanger || wouldLikeToRandomlyTrade(attacker, c, combat) || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker);
|
||||
}
|
||||
});
|
||||
if (usableBlockers.size() < 2) {
|
||||
|
||||
@@ -1343,7 +1343,7 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
final CardCollection typeList =
|
||||
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(","), source.getController(), source, sa);
|
||||
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type, source.getController(), source, sa);
|
||||
for (Card c : typeList) {
|
||||
if (c.getSVar("SacMe").equals("6")) {
|
||||
return true;
|
||||
@@ -1620,7 +1620,7 @@ public class ComputerUtil {
|
||||
objects = AbilityUtils.getDefinedObjects(source, topStack.getParam("Defined"), topStack);
|
||||
} else if (topStack.hasParam("ValidCards")) {
|
||||
CardCollectionView battleField = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||
objects = CardLists.getValidCards(battleField, topStack.getParam("ValidCards").split(","), source.getController(), source, topStack);
|
||||
objects = CardLists.getValidCards(battleField, topStack.getParam("ValidCards"), source.getController(), source, topStack);
|
||||
} else {
|
||||
return threatened;
|
||||
}
|
||||
@@ -2822,8 +2822,6 @@ public class ComputerUtil {
|
||||
pRating /= 5;
|
||||
}
|
||||
|
||||
System.out.println("Board position evaluation for " + p + ": " + pRating);
|
||||
|
||||
if (pRating > bestBoardRating) {
|
||||
bestBoardRating = pRating;
|
||||
bestBoardPosition = p;
|
||||
|
||||
@@ -36,7 +36,7 @@ public class ComputerUtilAbility {
|
||||
public boolean apply(final Card c) {
|
||||
if (!c.getSVar("NeedsToPlay").isEmpty()) {
|
||||
final String needsToPlay = c.getSVar("NeedsToPlay");
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay.split(","), c.getController(), c, null);
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay, c.getController(), c, null);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1951,7 +1951,7 @@ public class ComputerUtilCard {
|
||||
|
||||
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
|
||||
|
||||
list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card, sa);
|
||||
list = CardLists.getValidCards(list, needsToPlay, card.getController(), card, sa);
|
||||
if (list.isEmpty()) {
|
||||
return AiPlayDecision.MissingNeededCards;
|
||||
}
|
||||
|
||||
@@ -830,7 +830,7 @@ public class ComputerUtilCombat {
|
||||
}
|
||||
|
||||
// defender == null means unblocked
|
||||
if ((defender == null) && mode == TriggerType.AttackerUnblocked) {
|
||||
if (defender == null && mode == TriggerType.AttackerUnblocked) {
|
||||
willTrigger = true;
|
||||
if (!trigger.matchesValidParam("ValidCard", attacker)) {
|
||||
return false;
|
||||
|
||||
@@ -144,7 +144,7 @@ public class ComputerUtilCost {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
final CardCollection typeList = CardLists.getValidCards(hand, type.split(","), source.getController(), source, sa);
|
||||
final CardCollection typeList = CardLists.getValidCards(hand, type, source.getController(), source, sa);
|
||||
if (typeList.size() > ai.getMaxHandSize()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -69,14 +69,14 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
final String valid = topStack.getParamOrDefault("SacValid", "Card.Self");
|
||||
String num = topStack.getParamOrDefault("Amount", "1");
|
||||
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
|
||||
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
||||
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid,
|
||||
ai.getWeakestOpponent(), topStack.getHostCard(), topStack);
|
||||
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true));
|
||||
ComputerUtilCard.sortByEvaluateCreature(list);
|
||||
if (!list.isEmpty() && list.size() == nToSac && ComputerUtilCost.canPayCost(sa, ai, sa.isTrigger())) {
|
||||
Card animatedCopy = becomeAnimated(source, sa);
|
||||
list.add(animatedCopy);
|
||||
list = CardLists.getValidCards(list, valid.split(","), ai.getWeakestOpponent(), topStack.getHostCard(),
|
||||
list = CardLists.getValidCards(list, valid, ai.getWeakestOpponent(), topStack.getHostCard(),
|
||||
topStack);
|
||||
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true));
|
||||
if (ComputerUtilCard.evaluateCreature(animatedCopy) < ComputerUtilCard.evaluateCreature(list.get(0))
|
||||
|
||||
@@ -184,7 +184,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
choice = ComputerUtilCard.getBestCreatureAI(options);
|
||||
} else if (logic.equals("Clone")) {
|
||||
final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
|
||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||
CardCollection newOptions = CardLists.getValidCards(options, filter, ctrl, host, sa);
|
||||
if (!newOptions.isEmpty()) {
|
||||
options = newOptions;
|
||||
}
|
||||
@@ -194,7 +194,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
choice = Aggregates.random(options);
|
||||
} else if (logic.equals("Untap")) {
|
||||
final String filter = "Permanent.YouCtrl,Permanent.tapped";
|
||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||
CardCollection newOptions = CardLists.getValidCards(options, filter, ctrl, host, sa);
|
||||
if (!newOptions.isEmpty()) {
|
||||
options = newOptions;
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ public class CloneAi extends SpellAbilityAi {
|
||||
filter = filter.replace(".nonLegendary+", ".").replace(".nonLegendary", "");
|
||||
}
|
||||
|
||||
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||
CardCollection newOptions = CardLists.getValidCards(options, filter, ctrl, host, sa);
|
||||
if (!newOptions.isEmpty()) {
|
||||
options = newOptions;
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
final boolean canCopyLegendary = sa.hasParam("NonLegendary");
|
||||
final String filter = canCopyLegendary ? "Permanent" : "Permanent.YouDontCtrl,Permanent.nonLegendary";
|
||||
// TODO add filter to not select Legendary from Other Player when ai already have a Legendary with that name
|
||||
return CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
|
||||
return CardLists.getValidCards(options, filter, ctrl, host, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -101,6 +101,7 @@ public abstract class CountersAi extends SpellAbilityAi {
|
||||
Card choice = null;
|
||||
|
||||
if (type.equals("P1P1")) {
|
||||
// TODO look for modified
|
||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||
|
||||
if (choice == null) {
|
||||
|
||||
@@ -251,7 +251,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
|
||||
// TODO: X may be something different than X paid
|
||||
CardCollection list =
|
||||
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC.split(","), source.getController(), source, sa);
|
||||
CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC, source.getController(), source, sa);
|
||||
|
||||
final Predicate<Card> filterKillable = new Predicate<Card>() {
|
||||
@Override
|
||||
|
||||
@@ -92,8 +92,8 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
|
||||
// TODO should probably sort results when targeted to use on biggest threat instead of first match
|
||||
for (Player opponent: ai.getOpponents()) {
|
||||
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
|
||||
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
||||
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
|
||||
|
||||
opplist = CardLists.filter(opplist, predicate);
|
||||
ailist = CardLists.filter(ailist, predicate);
|
||||
|
||||
@@ -68,7 +68,7 @@ public class DigUntilAi extends SpellAbilityAi {
|
||||
} else {
|
||||
if (sa.hasParam("Valid")) {
|
||||
final String valid = sa.getParam("Valid");
|
||||
if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid.split(","), source.getController(), source, sa).isEmpty()) {
|
||||
if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid, source.getController(), source, sa).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class RegenerateAllAi extends SpellAbilityAi {
|
||||
final String valid = sa.getParamOrDefault("ValidCards", "");
|
||||
|
||||
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.getValidCards(list, valid.split(","), hostCard.getController(), hostCard, sa);
|
||||
list = CardLists.getValidCards(list, valid, hostCard.getController(), hostCard, sa);
|
||||
list = CardLists.filter(list, CardPredicates.isController(ai));
|
||||
|
||||
if (list.size() == 0) {
|
||||
|
||||
@@ -79,7 +79,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
|
||||
List<Card> list = null;
|
||||
try {
|
||||
list = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), source, sa);
|
||||
list = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa);
|
||||
} catch (NullPointerException e) {
|
||||
return false;
|
||||
} finally {
|
||||
@@ -141,7 +141,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
|
||||
List<Card> humanList = null;
|
||||
try {
|
||||
humanList = CardLists.getValidCards(ai.getStrongestOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), source, sa);
|
||||
humanList = CardLists.getValidCards(ai.getStrongestOpponent().getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa);
|
||||
} catch (NullPointerException e) {
|
||||
return false;
|
||||
} finally {
|
||||
@@ -155,7 +155,7 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
} else if (defined.equals("You")) {
|
||||
List<Card> computerList = null;
|
||||
try {
|
||||
computerList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), source, sa);
|
||||
computerList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa);
|
||||
} catch (NullPointerException e) {
|
||||
return false;
|
||||
} finally {
|
||||
|
||||
@@ -241,14 +241,14 @@ public class TokenAi extends SpellAbilityAi {
|
||||
final String valid = topStack.getParamOrDefault("SacValid", "Card.Self");
|
||||
String num = sa.getParamOrDefault("Amount", "1");
|
||||
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
|
||||
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
|
||||
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid,
|
||||
ai.getWeakestOpponent(), topStack.getHostCard(), sa);
|
||||
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true));
|
||||
// only care about saving single creature for now
|
||||
if (!list.isEmpty() && nTokens > 0 && list.size() == nToSac) {
|
||||
ComputerUtilCard.sortByEvaluateCreature(list);
|
||||
list.add(token);
|
||||
list = CardLists.getValidCards(list, valid.split(","), ai.getWeakestOpponent(), topStack.getHostCard(), sa);
|
||||
list = CardLists.getValidCards(list, valid, ai.getWeakestOpponent(), topStack.getHostCard(), sa);
|
||||
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true));
|
||||
return ComputerUtilCard.evaluateCreature(token) < ComputerUtilCard.evaluateCreature(list.get(0))
|
||||
&& list.contains(token);
|
||||
|
||||
@@ -26,7 +26,7 @@ public class UntapAllAi extends SpellAbilityAi {
|
||||
}
|
||||
CardCollectionView list = CardLists.filter(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.TAPPED);
|
||||
final String valid = sa.getParamOrDefault("ValidCards", "");
|
||||
list = CardLists.getValidCards(list, valid.split(","), source.getController(), source, sa);
|
||||
list = CardLists.getValidCards(list, valid, source.getController(), source, sa);
|
||||
// don't untap if only opponent benefits
|
||||
PlayerCollection goodControllers = aiPlayer.getAllies();
|
||||
goodControllers.add(aiPlayer);
|
||||
@@ -43,7 +43,7 @@ public class UntapAllAi extends SpellAbilityAi {
|
||||
if (sa.hasParam("ValidCards")) {
|
||||
String valid = sa.getParam("ValidCards");
|
||||
CardCollectionView list = CardLists.filter(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.TAPPED);
|
||||
list = CardLists.getValidCards(list, valid.split(","), source.getController(), source, sa);
|
||||
list = CardLists.getValidCards(list, valid, source.getController(), source, sa);
|
||||
return mandatory || !list.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
@@ -368,7 +368,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
list.addAll(p.getCardsIn(presentZone));
|
||||
}
|
||||
}
|
||||
list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), this);
|
||||
list = CardLists.getValidCards(list, sIsPresent, this.getHostCard().getController(), this.getHostCard(), this);
|
||||
|
||||
final String rightString = presentCompare.substring(2);
|
||||
int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this);
|
||||
@@ -397,7 +397,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
|
||||
}
|
||||
}
|
||||
|
||||
list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), this);
|
||||
list = CardLists.getValidCards(list, sIsPresent, this.getHostCard().getController(), this.getHostCard(), this);
|
||||
|
||||
final String rightString = presentCompare.substring(2);
|
||||
int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this);
|
||||
|
||||
@@ -346,7 +346,7 @@ public class AbilityUtils {
|
||||
candidates = game.getCardsIn(ZoneType.smartValueOf(zone));
|
||||
validDefined = s[1];
|
||||
}
|
||||
cards.addAll(CardLists.getValidCards(candidates, validDefined.split(","), hostCard.getController(), hostCard, sa));
|
||||
cards.addAll(CardLists.getValidCards(candidates, validDefined, hostCard.getController(), hostCard, sa));
|
||||
return cards;
|
||||
} else {
|
||||
CardCollection list = null;
|
||||
@@ -977,7 +977,7 @@ public class AbilityUtils {
|
||||
String var = sa.getParam("AbilityCount");
|
||||
valid = TextUtil.fastReplace(valid, var, Integer.toString(calculateAmount(source, var, sa)));
|
||||
}
|
||||
return CardLists.getValidCards(list, valid.split(","), sa.getActivatingPlayer(), source, sa);
|
||||
return CardLists.getValidCards(list, valid, sa.getActivatingPlayer(), source, sa);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1884,7 +1884,7 @@ public class AbilityUtils {
|
||||
return doXMath(0, expr, c, ctb);
|
||||
}
|
||||
}
|
||||
list = CardLists.getValidCards(list, k[1].split(","), sa.getActivatingPlayer(), c, sa);
|
||||
list = CardLists.getValidCards(list, k[1], sa.getActivatingPlayer(), c, sa);
|
||||
if (k[0].contains("TotalToughness")) {
|
||||
return doXMath(Aggregates.sum(list, CardPredicates.Accessors.fnGetNetToughness), expr, c, ctb);
|
||||
}
|
||||
@@ -1911,7 +1911,7 @@ public class AbilityUtils {
|
||||
return doXMath(0, expr, c, ctb);
|
||||
}
|
||||
}
|
||||
list = CardLists.getValidCards(list, k[1].split(","), sa.getActivatingPlayer(), c, sa);
|
||||
list = CardLists.getValidCards(list, k[1], sa.getActivatingPlayer(), c, sa);
|
||||
return doXMath(list.size(), expr, c, ctb);
|
||||
}
|
||||
|
||||
@@ -1940,14 +1940,14 @@ public class AbilityUtils {
|
||||
if (sq[0].startsWith("LastStateBattlefield")) {
|
||||
final String[] k = l[0].split(" ");
|
||||
CardCollection list = new CardCollection(game.getLastStateBattlefield());
|
||||
list = CardLists.getValidCards(list, k[1].split(","), player, c, ctb);
|
||||
list = CardLists.getValidCards(list, k[1], player, c, ctb);
|
||||
return doXMath(list.size(), expr, c, ctb);
|
||||
}
|
||||
|
||||
if (sq[0].startsWith("LastStateGraveyard")) {
|
||||
final String[] k = l[0].split(" ");
|
||||
CardCollection list = new CardCollection(game.getLastStateGraveyard());
|
||||
list = CardLists.getValidCards(list, k[1].split(","), player, c, ctb);
|
||||
list = CardLists.getValidCards(list, k[1], player, c, ctb);
|
||||
return doXMath(list.size(), expr, c, ctb);
|
||||
}
|
||||
|
||||
@@ -2176,7 +2176,7 @@ public class AbilityUtils {
|
||||
|
||||
if (sq[0].startsWith("Devoured")) {
|
||||
final String validDevoured = sq[0].split(" ")[1];
|
||||
CardCollection cl = CardLists.getValidCards(c.getDevouredCards(), validDevoured.split(","), player, c, ctb);
|
||||
CardCollection cl = CardLists.getValidCards(c.getDevouredCards(), validDevoured, player, c, ctb);
|
||||
return doXMath(cl.size(), expr, c, ctb);
|
||||
}
|
||||
|
||||
@@ -2451,8 +2451,7 @@ public class AbilityUtils {
|
||||
|
||||
if (sq[0].startsWith("ColorsCtrl")) {
|
||||
final String restriction = l[0].substring(11);
|
||||
final String[] rest = restriction.split(",");
|
||||
final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb);
|
||||
final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
|
||||
byte n = 0;
|
||||
for (final Card card : list) {
|
||||
n |= card.getColor().getColor();
|
||||
@@ -2711,8 +2710,7 @@ public class AbilityUtils {
|
||||
// Count$SumPower_valid
|
||||
if (sq[0].startsWith("SumPower")) {
|
||||
final String[] restrictions = l[0].split("_");
|
||||
final String[] rest = restrictions[1].split(",");
|
||||
CardCollection filteredCards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb);
|
||||
CardCollection filteredCards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restrictions[1], player, c, ctb);
|
||||
return doXMath(Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetNetPower), expr, c, ctb);
|
||||
}
|
||||
|
||||
@@ -2723,9 +2721,8 @@ public class AbilityUtils {
|
||||
if (sq[0].contains("Graveyard"))
|
||||
zone = ZoneType.Graveyard;
|
||||
final String[] restrictions = l[0].split("_");
|
||||
final String[] rest = restrictions[1].split(",");
|
||||
CardCollectionView cardsonbattlefield = game.getCardsIn(zone);
|
||||
CardCollection filteredCards = CardLists.getValidCards(cardsonbattlefield, rest, player, c, ctb);
|
||||
CardCollection filteredCards = CardLists.getValidCards(cardsonbattlefield, restrictions[1], player, c, ctb);
|
||||
return Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetCmc);
|
||||
}
|
||||
|
||||
@@ -2821,8 +2818,7 @@ public class AbilityUtils {
|
||||
|
||||
if (sq[0].startsWith("GreatestToughness_")) {
|
||||
final String restriction = l[0].substring(18);
|
||||
final String[] rest = restriction.split(",");
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb);
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
|
||||
int highest = 0;
|
||||
for (final Card crd : list) {
|
||||
if (crd.getNetToughness() > highest) {
|
||||
@@ -2834,8 +2830,7 @@ public class AbilityUtils {
|
||||
|
||||
if (sq[0].startsWith("HighestCMC_")) {
|
||||
final String restriction = l[0].substring(11);
|
||||
final String[] rest = restriction.split(",");
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsInGame(), rest, player, c, ctb);
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsInGame(), restriction, player, c, ctb);
|
||||
int highest = 0;
|
||||
for (final Card crd : list) {
|
||||
// dont check for Split card anymore
|
||||
@@ -2874,8 +2869,7 @@ public class AbilityUtils {
|
||||
if (sq[0].startsWith("DifferentCardNames_")) {
|
||||
final List<String> crdname = Lists.newArrayList();
|
||||
final String restriction = l[0].substring(19);
|
||||
final String[] rest = restriction.split(",");
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsInGame(), rest, player, c, ctb);
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsInGame(), restriction, player, c, ctb);
|
||||
for (final Card card : list) {
|
||||
String name = card.getName();
|
||||
// CR 201.2b Those objects have different names only if each of them has at least one name and no two objects in that group have a name in common
|
||||
@@ -2889,8 +2883,7 @@ public class AbilityUtils {
|
||||
if (sq[0].startsWith("DifferentPower_")) {
|
||||
final List<Integer> powers = Lists.newArrayList();
|
||||
final String restriction = l[0].substring(15);
|
||||
final String[] rest = restriction.split(",");
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb);
|
||||
CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
|
||||
for (final Card card : list) {
|
||||
Integer pow = card.getNetPower();
|
||||
if (!powers.contains(pow)) {
|
||||
@@ -2915,8 +2908,7 @@ public class AbilityUtils {
|
||||
|
||||
if (sq[0].startsWith("ColorsCtrl")) {
|
||||
final String restriction = l[0].substring(11);
|
||||
final String[] rest = restriction.split(",");
|
||||
final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb);
|
||||
final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb);
|
||||
byte n = 0;
|
||||
for (final Card card : list) {
|
||||
n |= card.getColor().getColor();
|
||||
@@ -3417,16 +3409,14 @@ public class AbilityUtils {
|
||||
String[] lparts = l[0].split(" ", 2);
|
||||
final List<ZoneType> vZone = ZoneType.listValueOf(lparts[0].split("Valid")[1]);
|
||||
String restrictions = TextUtil.fastReplace(l[0], TextUtil.addSuffix(lparts[0]," "), "");
|
||||
final String[] rest = restrictions.split(",");
|
||||
CardCollection cards = CardLists.getValidCards(game.getCardsIn(vZone), rest, player, source, ctb);
|
||||
CardCollection cards = CardLists.getValidCards(game.getCardsIn(vZone), restrictions, player, source, ctb);
|
||||
return doXMath(cards.size(), m, source, ctb);
|
||||
}
|
||||
|
||||
// count valid cards on the battlefield
|
||||
if (l[0].startsWith("Valid ")) {
|
||||
final String restrictions = l[0].substring(6);
|
||||
final String[] rest = restrictions.split(",");
|
||||
CardCollection cardsonbattlefield = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, source, ctb);
|
||||
CardCollection cardsonbattlefield = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restrictions, player, source, ctb);
|
||||
return doXMath(cardsonbattlefield.size(), m, source, ctb);
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
list = getTargetPlayers(sa).getCardsIn(ZoneType.Battlefield);
|
||||
}
|
||||
|
||||
list = CardLists.getValidCards(list, valid.split(","), host.getController(), host, sa);
|
||||
list = CardLists.getValidCards(list, valid, host.getController(), host, sa);
|
||||
|
||||
for (final Card c : list) {
|
||||
doAnimate(c, sa, power, toughness, types, removeTypes, finalColors,
|
||||
|
||||
@@ -67,7 +67,7 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
} else {
|
||||
num = Math.min(AbilityUtils.calculateAmount(source, sa.getParamOrDefault("CharmNum", "1"), sa), list.size());
|
||||
}
|
||||
final int min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParamOrDefault("MinCharmNum", "1"), sa) : num;
|
||||
final int min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParam("MinCharmNum"), sa) : num;
|
||||
|
||||
boolean repeat = sa.hasParam("CanRepeatModes");
|
||||
boolean random = sa.hasParam("Random");
|
||||
@@ -120,7 +120,7 @@ public class CharmEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
if (additionalDesc) {
|
||||
String addDescS = (sa.getParam("AdditionalDescription"));
|
||||
String addDescS = sa.getParam("AdditionalDescription");
|
||||
if (optional) {
|
||||
sb.append(". ").append(addDescS.trim());
|
||||
} else if (addDescS.startsWith(("."))) {
|
||||
|
||||
@@ -132,7 +132,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
}
|
||||
valid = CardLists.getValidCards(valid, type.split(","), chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa);
|
||||
valid = CardLists.getValidCards(valid, type, chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa);
|
||||
Card originalTarget = Iterables.getFirst(getTargetCards(chosenSA), null);
|
||||
valid.remove(originalTarget);
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
if (changeValid.contains("ChosenType")) {
|
||||
changeValid = changeValid.replace("ChosenType", host.getChosenType());
|
||||
}
|
||||
valid = CardLists.getValidCards(top, changeValid.split(","), cont, host, sa);
|
||||
valid = CardLists.getValidCards(top, changeValid, cont, host, sa);
|
||||
if (totalCMC) {
|
||||
valid = CardLists.getValidCards(valid, "Card.cmcLE" + totcmc, cont, host, sa);
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
"X", Integer.toString(AbilityUtils.calculateAmount(source, "X", sa)));
|
||||
}
|
||||
|
||||
toBeDiscarded = CardLists.getValidCards(dPHand, valid.split(","), source.getController(), source, sa);
|
||||
toBeDiscarded = CardLists.getValidCards(dPHand, valid, source.getController(), source, sa);
|
||||
toBeDiscarded = CardLists.filter(toBeDiscarded, Presets.NON_TOKEN);
|
||||
if (toBeDiscarded.size() > 1) {
|
||||
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
|
||||
@@ -250,8 +250,7 @@ public class DiscardEffect extends SpellAbilityEffect {
|
||||
}
|
||||
|
||||
final String valid = sa.getParamOrDefault("DiscardValid", "Card");
|
||||
String[] dValid = valid.split(",");
|
||||
CardCollection validCards = CardLists.getValidCards(dPHand, dValid, source.getController(), source, sa);
|
||||
CardCollection validCards = CardLists.getValidCards(dPHand, valid, source.getController(), source, sa);
|
||||
|
||||
Player chooser = p;
|
||||
if (mode.equals("RevealYouChoose")) {
|
||||
|
||||
@@ -25,6 +25,9 @@ import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostDiscard;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostReveal;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
@@ -70,7 +73,7 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
Player controlledByPlayer = null;
|
||||
long controlledByTimeStamp = -1;
|
||||
final Game game = activator.getGame();
|
||||
final boolean optional = sa.hasParam("Optional");
|
||||
boolean optional = sa.hasParam("Optional");
|
||||
boolean remember = sa.hasParam("RememberPlayed");
|
||||
int amount = 1;
|
||||
boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC");
|
||||
@@ -329,9 +332,19 @@ public class PlayEffect extends SpellAbilityEffect {
|
||||
tgtSA = tgtSA.copyWithDefinedCost(abCost);
|
||||
}
|
||||
|
||||
if (!optional) {
|
||||
// 118.8c
|
||||
for (CostPart cost : tgtSA.getPayCosts().getCostParts()) {
|
||||
if ((cost instanceof CostDiscard || cost instanceof CostReveal)
|
||||
&& !cost.getType().equals("Card") && !cost.getType().equals("Random")) {
|
||||
optional = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!optional) {
|
||||
tgtSA.getPayCosts().setMandatory(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("PlayReduceCost")) {
|
||||
// for Kefnet only can reduce colorless cost
|
||||
|
||||
@@ -29,7 +29,7 @@ public class RegenerateAllEffect extends RegenerateBaseEffect {
|
||||
final String valid = sa.getParamOrDefault("ValidCards", "");
|
||||
|
||||
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.getValidCards(list, valid.split(","), hostCard.getController(), hostCard, sa);
|
||||
list = CardLists.getValidCards(list, valid, hostCard.getController(), hostCard, sa);
|
||||
|
||||
// create Effect for Regeneration
|
||||
createRegenerationEffect(sa, list);
|
||||
|
||||
@@ -84,7 +84,7 @@ public class RepeatEffect extends SpellAbilityEffect {
|
||||
} else {
|
||||
list = game.getCardsIn(ZoneType.Battlefield);
|
||||
}
|
||||
list = CardLists.getValidCards(list, repeatPresent.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
list = CardLists.getValidCards(list, repeatPresent, sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||
|
||||
final String rightString = repeatCompare.substring(2);
|
||||
int right = AbilityUtils.calculateAmount(sa.getHostCard(), rightString, sa);
|
||||
|
||||
@@ -140,7 +140,7 @@ public class UnattachAllEffect extends SpellAbilityEffect {
|
||||
|
||||
String valid = sa.getParam("UnattachValid");
|
||||
CardCollectionView unattachList = game.getCardsIn(ZoneType.Battlefield);
|
||||
unattachList = CardLists.getValidCards(unattachList, valid.split(","), source.getController(), source, sa);
|
||||
unattachList = CardLists.getValidCards(unattachList, valid, source.getController(), source, sa);
|
||||
for (final Card c : unattachList) {
|
||||
handleUnattachment((GameEntity) o, c);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class UntapAllEffect extends SpellAbilityEffect {
|
||||
}
|
||||
list = list2;
|
||||
}
|
||||
list = CardLists.getValidCards(list, valid.split(","), card.getController(), card, sa);
|
||||
list = CardLists.getValidCards(list, valid, card.getController(), card, sa);
|
||||
|
||||
boolean remember = sa.hasParam("RememberUntapped");
|
||||
for (Card c : list) {
|
||||
|
||||
@@ -3491,6 +3491,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
}
|
||||
|
||||
public final boolean isModified() {
|
||||
if (!isCreature()) {
|
||||
return false;
|
||||
}
|
||||
if (this.isEquipped() || this.hasCounters()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1452,7 +1452,7 @@ public class CardFactoryUtil {
|
||||
final String manacost = k[1];
|
||||
final String abStrReveal = "DB$ Reveal | Defined$ You | RevealDefined$ Self"
|
||||
+ " | MiracleCost$ " + manacost;
|
||||
final String abStrPlay = "DB$ Play | Defined$ Self | PlayCost$ " + manacost;
|
||||
final String abStrPlay = "DB$ Play | Defined$ Self | Optional$ True | PlayCost$ " + manacost;
|
||||
|
||||
String revealed = "DB$ ImmediateTrigger | TriggerDescription$ CARDNAME - Miracle";
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ public class CardZoneTable extends ForwardingTable<ZoneType, ZoneType, CardColle
|
||||
}
|
||||
|
||||
if (valid != null) {
|
||||
allCards = CardLists.getValidCards(allCards, valid.split(","), host.getController(), host, sa);
|
||||
allCards = CardLists.getValidCards(allCards, valid, host.getController(), host, sa);
|
||||
}
|
||||
return allCards;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import forge.card.CardType;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -834,9 +835,14 @@ public class Cost implements Serializable {
|
||||
sb.append(Cost.NUM_NAMES[i]);
|
||||
}
|
||||
|
||||
sb.append(" ").append(type);
|
||||
sb.append(" ");
|
||||
if (1 != i) {
|
||||
sb.append("s");
|
||||
String [] typewords = type.split(" ");
|
||||
String lastWord = typewords[typewords.length - 1];
|
||||
sb.append(CardType.isASubType(lastWord) ? type.replace(lastWord, CardType.getPluralType(lastWord))
|
||||
: type + "s");
|
||||
} else {
|
||||
sb.append(type);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
|
||||
@@ -138,6 +138,7 @@ public class CostAdjustment {
|
||||
count = Integer.parseInt(amount);
|
||||
} else {
|
||||
if (st.hasParam("Relative")) {
|
||||
// grab SVar here already to avoid potential collision when SA has one with same name
|
||||
count = AbilityUtils.calculateAmount(hostCard, st.hasSVar(amount) ? st.getSVar(amount) : amount, sa);
|
||||
} else {
|
||||
count = AbilityUtils.calculateAmount(hostCard, amount, st);
|
||||
@@ -379,7 +380,7 @@ public class CostAdjustment {
|
||||
// TODO: update cards with "This spell costs X less to cast...if you..."
|
||||
// The caster is sa.getActivatingPlayer()
|
||||
// cards like Hostage Taker can cast spells from other players.
|
||||
value = AbilityUtils.calculateAmount(hostCard, amount, sa);
|
||||
value = AbilityUtils.calculateAmount(hostCard, staticAbility.hasSVar(amount) ? staticAbility.getSVar(amount) : amount, sa);
|
||||
} else {
|
||||
value = AbilityUtils.calculateAmount(hostCard, amount, staticAbility);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package forge.game.cost;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import forge.card.CardType;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import forge.game.CardTraitBase;
|
||||
@@ -106,7 +107,11 @@ public abstract class CostPart implements Comparable<CostPart>, Cloneable, Seria
|
||||
|
||||
public final String getDescriptiveType() {
|
||||
String typeDesc = this.getTypeDescription();
|
||||
return typeDesc == null ? this.getType() : typeDesc;
|
||||
if (typeDesc == null) {
|
||||
String typeS = this.getType();
|
||||
typeDesc = CardType.CoreType.isValidEnum(typeS) ? typeS.toLowerCase() : typeS;
|
||||
}
|
||||
return typeDesc;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package forge.game.cost;
|
||||
|
||||
import forge.card.CardType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
@@ -87,21 +88,34 @@ public class CostTapType extends CostPartWithList {
|
||||
@Override
|
||||
public final String toString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Tap ");
|
||||
|
||||
final Integer i = this.convertAmount();
|
||||
final String desc = this.getDescriptiveType();
|
||||
final String type = this.getType();
|
||||
|
||||
if (type.contains("+withTotalPowerGE")) {
|
||||
String num = type.split("\\+withTotalPowerGE")[1];
|
||||
sb.append("Tap any number of untapped creatures you control other than CARDNAME with total power ");
|
||||
sb.append(num).append("or greater");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
sb.append("Tap ");
|
||||
if (type.contains("sharesCreatureTypeWith")) {
|
||||
sb.append("two untapped creatures you control that share a creature type");
|
||||
} else if (type.contains("+withTotalPowerGE")) {
|
||||
String num = type.split("\\+withTotalPowerGE")[1];
|
||||
sb.append("Tap any number of untapped creatures you control other than CARDNAME with total power ").append(num).append("or greater");
|
||||
} else {
|
||||
sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), "untapped " + desc));
|
||||
} else if (type.contains("Other")) {
|
||||
String rep = type.contains(".Other") ? ".Other" : "+Other";
|
||||
String descTrim = desc.replace(rep, "");
|
||||
if (CardType.CoreType.isValidEnum(descTrim)) {
|
||||
descTrim = descTrim.toLowerCase();
|
||||
}
|
||||
sb.append("another untapped ").append(descTrim);
|
||||
if (!descTrim.contains("you control")) {
|
||||
sb.append(" you control");
|
||||
}
|
||||
} else {
|
||||
sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), "untapped " + desc)).append(" you control");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -467,7 +467,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
|
||||
CardCollectionView list = game.getCardsIn(zone);
|
||||
final String present = getParam("IsPresent");
|
||||
|
||||
list = CardLists.getValidCards(list, present.split(","), controller, hostCard, this);
|
||||
list = CardLists.getValidCards(list, present, controller, hostCard, this);
|
||||
|
||||
int right = 1;
|
||||
final String rightString = compare.substring(2);
|
||||
|
||||
@@ -1070,7 +1070,7 @@ public final class StaticAbilityContinuous {
|
||||
affectedCardsOriginal = new CardCollection(affectedCards);
|
||||
}
|
||||
|
||||
affectedCards = CardLists.getValidCards(affectedCards, stAb.getParam("Affected").split(","), controller, hostCard, stAb);
|
||||
affectedCards = CardLists.getValidCards(affectedCards, stAb.getParam("Affected"), controller, hostCard, stAb);
|
||||
|
||||
// Add back all cards that are in other player's graveyard, and meet the restrictions without YouOwn/YouCtrl (treat it as in your graveyard)
|
||||
if (affectedCardsOriginal != null) {
|
||||
|
||||
@@ -74,7 +74,7 @@ public class TriggerAttackersDeclared extends Trigger {
|
||||
|
||||
CardCollection attackers = (CardCollection) runParams.get(AbilityKey.Attackers);
|
||||
if (hasParam("ValidAttackers")) {
|
||||
attackers = CardLists.getValidCards(attackers, getParam("ValidAttackers").split(","), getHostCard().getController(), getHostCard(), this);
|
||||
attackers = CardLists.getValidCards(attackers, getParam("ValidAttackers"), getHostCard().getController(), getHostCard(), this);
|
||||
FCollection<GameEntity> defenders = new FCollection<>();
|
||||
for (Card attacker : attackers) {
|
||||
defenders.add(attacker.getGame().getCombat().getDefenderByAttacker(attacker));
|
||||
|
||||
@@ -74,7 +74,7 @@ public class TriggerDrawn extends Trigger {
|
||||
final String sIsPresent = this.getParam("ValidPlayerControls");
|
||||
final Player p = ((Player)runParams.get(AbilityKey.Player));
|
||||
CardCollection list = (CardCollection) p.getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(),
|
||||
list = CardLists.getValidCards(list, sIsPresent, this.getHostCard().getController(),
|
||||
this.getHostCard(), this);
|
||||
if (list.size() == 0) {
|
||||
return false;
|
||||
|
||||
@@ -64,7 +64,7 @@ public class TriggerLifeGained extends Trigger {
|
||||
final String sIsPresent = this.getParam("ValidPlayerControls");
|
||||
final Player p = ((Player)runParams.get(AbilityKey.Player));
|
||||
CardCollection list = (CardCollection) p.getCardsIn(ZoneType.Battlefield);
|
||||
list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(),
|
||||
list = CardLists.getValidCards(list, sIsPresent, this.getHostCard().getController(),
|
||||
this.getHostCard(), this);
|
||||
if (list.size() == 0) {
|
||||
return false;
|
||||
|
||||
@@ -7,6 +7,7 @@ import forge.Graphics;
|
||||
import forge.assets.FSkinColor;
|
||||
import forge.assets.FSkinColor.Colors;
|
||||
import forge.assets.FSkinTexture;
|
||||
import forge.gui.GuiBase;
|
||||
import forge.screens.FScreen;
|
||||
import forge.toolbox.FContainer;
|
||||
import forge.toolbox.FDisplayObject;
|
||||
@@ -221,18 +222,21 @@ public abstract class FDropDown extends FScrollPane {
|
||||
|
||||
@Override
|
||||
public boolean pan(float x, float y, float deltaX, float deltaY, boolean moreVertical) {
|
||||
if (!GuiBase.isAndroid())
|
||||
hide(); //always hide if backdrop panned
|
||||
return false; //allow pan to pass through to object behind backdrop
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean fling(float velocityX, float velocityY) {
|
||||
if (!GuiBase.isAndroid())
|
||||
hide(); //always hide if backdrop flung
|
||||
return false; //allow fling to pass through to object behind backdrop
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean zoom(float x, float y, float amount) {
|
||||
if (!GuiBase.isAndroid())
|
||||
hide(); //always hide if backdrop zoomed
|
||||
return false; //allow zoom to pass through to object behind backdrop
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@ ManaCost:2 B
|
||||
Types:Creature Zombie Treefolk
|
||||
PT:0/5
|
||||
K:Defender
|
||||
A:AB$ Pump | Cost$ B tapXType<1/Creature.Other/another creature> | CostDesc$ {B}, Tap another untapped creature you control: | Defined$ Self | NumAtt$ +1 | NumDef$ +1 | SpellDescription$ CARDNAME gets +1/+1 until end of turn.
|
||||
A:AB$ Pump | Cost$ B tapXType<1/Creature.Other> | Defined$ Self | NumAtt$ +1 | NumDef$ +1 | SpellDescription$ CARDNAME gets +1/+1 until end of turn.
|
||||
Oracle:Defender\n{B}, Tap another untapped creature you control: Black Oak of Odunos gets +1/+1 until end of turn.
|
||||
|
||||
@@ -6,6 +6,6 @@ K:Flying
|
||||
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDigUntil | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order.
|
||||
SVar:TrigDigUntil:DB$ DigUntil | Defined$ TriggeredTarget | Valid$ Instant,Sorcery | ValidDescription$ instant or sorcery | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | RememberRevealed$ True | IsCurse$ True | SubAbility$ DBPlay | SpellDescription$ Whenever CARDNAME deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order.
|
||||
SVar:DBPlay:DB$ Play | Defined$ Remembered | ValidZone$ Exile | Valid$ Instant.IsRemembered,Sorcery.IsRemembered | ValidSA$ Spell | WithoutManaCost$ True | RememberObjects$ Remembered | Optional$ True | ForgetTargetRemembered$ True | SubAbility$ DBRestRandomOrder
|
||||
SVar:DBRestRandomOrder:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup
|
||||
SVar:DBRestRandomOrder:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Exile | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
Oracle:Flying\nWhenever Dazzling Sphinx deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order.
|
||||
|
||||
@@ -2,9 +2,10 @@ Name:Kumena, Tyrant of Orazca
|
||||
ManaCost:1 G U
|
||||
Types:Legendary Creature Merfolk Shaman
|
||||
PT:2/4
|
||||
A:AB$ Pump | Cost$ tapXType<1/Merfolk.Other> | CostDesc$ Tap another untapped Merfolk you control: | Defined$ Self | KW$ HIDDEN Unblockable | AILogic$ BeforeCombat | SpellDescription$ CARDNAME can't be blocked this turn.
|
||||
A:AB$ Draw | Cost$ tapXType<3/Merfolk> | CostDesc$ Tap three untapped Merfolk you control: | NumCards$ 1 | AILogic$ AtOppEOT | SpellDescription$ Draw a card.
|
||||
A:AB$ PutCounterAll | Cost$ tapXType<5/Merfolk> | CostDesc$ Tap five untapped Merfolk you control: | ValidCards$ Merfolk.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 | AILogic$ AtOppEOT | SpellDescription$ Put a +1/+1 counter on each Merfolk you control.
|
||||
A:AB$ Pump | Cost$ tapXType<1/Merfolk.Other> | Defined$ Self | KW$ HIDDEN Unblockable | AILogic$ BeforeCombat | SpellDescription$ CARDNAME can't be blocked this turn.
|
||||
A:AB$ Draw | Cost$ tapXType<3/Merfolk> | NumCards$ 1 | AILogic$ AtOppEOT | SpellDescription$ Draw a card.
|
||||
A:AB$ PutCounterAll | Cost$ tapXType<5/Merfolk> | ValidCards$ Merfolk.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 | AILogic$ AtOppEOT | SpellDescription$ Put a +1/+1 counter on each Merfolk you control.
|
||||
DeckHints:Type$Merfolk
|
||||
SVar:BuffedBy:Merfolk
|
||||
DeckHas:Ability$Counters
|
||||
Oracle:Tap another untapped Merfolk you control: Kumena, Tyrant of Orazca can't be blocked this turn.\nTap three untapped Merfolk you control: Draw a card.\nTap five untapped Merfolk you control: Put a +1/+1 counter on each Merfolk you control.
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Legendary Creature Angel
|
||||
PT:6/4
|
||||
K:Flying
|
||||
K:Partner
|
||||
A:AB$ Protection | Cost$ tapXType<1/Creature.untapped+withFlying+Other/another creature you control> | CostDesc$ Tap another untapped creature you control with flying: | Gains$ Choice | Choices$ AnyColor | SpellDescription$ CARDNAME gains protection from the color of your choice until end of turn.
|
||||
A:AB$ Protection | Cost$ tapXType<1/Creature.withFlying+Other/creature you control with flying> | Gains$ Choice | Choices$ AnyColor | SpellDescription$ CARDNAME gains protection from the color of your choice until end of turn.
|
||||
SVar:BuffedBy:Creature.withFlying
|
||||
DeckNeeds:Keyword$Flying
|
||||
Oracle:Flying\nTap another untapped creature you control with flying: Radiant, Serra Archangel gains protection from the color of your choice until end of turn.\nPartner (You can have two commanders if both have partner.)
|
||||
|
||||
@@ -3,5 +3,5 @@ ManaCost:W
|
||||
Types:Creature Bird
|
||||
PT:1/1
|
||||
K:Flying
|
||||
A:AB$ Venture | Cost$ 3 T tapXType<1/Creature/creature> | SorcerySpeed$ True | SpellDescription$ Venture into the dungeon. Activate only as a sorcery. (Enter the first room or advance to the next room.)
|
||||
A:AB$ Venture | Cost$ 3 T tapXType<1/Creature.Other> | SorcerySpeed$ True | SpellDescription$ Venture into the dungeon. Activate only as a sorcery. (Enter the first room or advance to the next room.)
|
||||
Oracle:Flying\n{3}, {T}, Tap another untapped creature you control: Venture into the dungeon. Activate only as a sorcery. (Enter the first room or advance to the next room.)
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Shadow Stinger
|
||||
ManaCost:2 B
|
||||
Types:Creature Vampire Rogue
|
||||
PT:1/4
|
||||
A:AB$ Pump | Cost$ tapXType<1/Rogue> | CostDesc$ Tap another untapped Rogue you control: | Defined$ Self | KW$ Deathtouch | SpellDescription$ CARDNAME gains deathtouch until end of turn.
|
||||
A:AB$ Pump | Cost$ tapXType<1/Rogue> | Defined$ Self | KW$ Deathtouch | SpellDescription$ CARDNAME gains deathtouch until end of turn.
|
||||
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigMill | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player mills three cards. (They put the top three cards of their library into their graveyard.)
|
||||
SVar:TrigMill:DB$ Mill | Defined$ TriggeredTarget | NumCards$ 3
|
||||
DeckHas:Ability$Mill
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Sure-Footed Infiltrator
|
||||
ManaCost:3 U
|
||||
Types:Creature Merfolk Rogue
|
||||
PT:2/3
|
||||
A:AB$ Pump | Cost$ tapXType<1/Rogue.Other> | CostDesc$ Tap another untapped Rogue you control: | Defined$ Self | KW$ HIDDEN Unblockable | StackDescription$ SpellDescription | SpellDescription$ CARDNAME can't be blocked this turn.
|
||||
A:AB$ Pump | Cost$ tapXType<1/Rogue.Other> | Defined$ Self | KW$ HIDDEN Unblockable | StackDescription$ SpellDescription | SpellDescription$ CARDNAME can't be blocked this turn.
|
||||
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDraw | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, draw a card.
|
||||
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1
|
||||
DeckNeeds:Type$Rogue
|
||||
|
||||
9
forge-gui/res/cardsfolder/upcoming/iron_apprentice.txt
Normal file
9
forge-gui/res/cardsfolder/upcoming/iron_apprentice.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Name:Iron Apprentice
|
||||
ManaCost:1
|
||||
Types:Artifact Creature Construct
|
||||
PT:0/0
|
||||
K:etbCounter:P1P1:1
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+HasCounters | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME dies, if it had counters on it, put those counters on target creature you control.
|
||||
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ EachFromSource | EachFromSource$ TriggeredCardLKICopy
|
||||
DeckHas:Ability$Counters
|
||||
Oracle:Iron Apprentice enters the battlefield with a +1/+1 counter on it.\nWhen Iron Apprentice dies, if it had counters on it, put those counters on target creature you control.
|
||||
7
forge-gui/res/cardsfolder/upcoming/papercraft_decoy.txt
Normal file
7
forge-gui/res/cardsfolder/upcoming/papercraft_decoy.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Name:Papercraft Decoy
|
||||
ManaCost:2
|
||||
Types:Artifact Creature Frog
|
||||
PT:2/1
|
||||
T:Mode$ ChangesZone | Origin$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw1 | TriggerDescription$ When CARDNAME leaves the battlefield, you may pay {2}. If you do, draw a card.
|
||||
SVar:TrigDraw1:AB$ Draw | Cost$ 2 | Defined$ You | NumCards$ 1
|
||||
Oracle:When Papercraft Decoy leaves the battlefield, you may pay {2}. If you do, draw a card.
|
||||
10
forge-gui/res/cardsfolder/upcoming/patchwork_automaton.txt
Normal file
10
forge-gui/res/cardsfolder/upcoming/patchwork_automaton.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
Name:Patchwork Automaton
|
||||
ManaCost:2
|
||||
Types:Artifact Creature Construct
|
||||
PT:1/1
|
||||
K:Ward:2
|
||||
T:Mode$ SpellCast | ValidCard$ Artifact | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cast an artifact spell, put a +1/+1 counter on CARDNAME.
|
||||
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
|
||||
DeckHas:Ability$Counters
|
||||
DeckHints:Type$Artifact
|
||||
Oracle:Ward {2} (Whenever this creature becomes the target of a spell or ability an opponent controls, counter it unless that player pays {2}.)\nWhenever you cast an artifact spell, put a +1/+1 counter on Patchwork Automaton.
|
||||
10
forge-gui/res/cardsfolder/upcoming/reito_sentinel.txt
Normal file
10
forge-gui/res/cardsfolder/upcoming/reito_sentinel.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
Name:Reito Sentinel
|
||||
ManaCost:3
|
||||
Types:Artifact Creature Construct
|
||||
PT:3/3
|
||||
K:Defender
|
||||
T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMill | TriggerDescription$ When CARDNAME enters the battlefield, target player mills three cards.
|
||||
SVar:TrigMill:DB$ Mill | NumCards$ 3 | ValidTgts$ Player | TgtPrompt$ Select target player
|
||||
A:AB$ ChangeZone | Cost$ 3 | ValidTgts$ Card | TgtPrompt$ Select target card in a graveyard | Origin$ Graveyard | Destination$ Library | LibraryPosition$ -1 | SpellDescription$ Put target card from a graveyard on the bottom of its owner's library.
|
||||
DeckHas:Ability$Mill|Graveyard
|
||||
Oracle:Defender\nWhen Reito Sentinel enters the battlefield, target player mills three cards. (They put the top three cards of their library into their graveyard.)\n{3}: Put target card from a graveyard on the bottom of its owner's library.
|
||||
@@ -0,0 +1,9 @@
|
||||
Name:Searchlight Companion
|
||||
ManaCost:3
|
||||
Types:Artifact Creature Drone
|
||||
PT:1/1
|
||||
K:Flying
|
||||
T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a 1/1 colorless Spirit creature token.
|
||||
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_1_1_spirit
|
||||
DeckHas:Ability$Token & Type$Spirit
|
||||
Oracle:Flying\nWhen Searchlight Companion enters the battlefield, create a 1/1 colorless Spirit creature token.
|
||||
8
forge-gui/res/cardsfolder/upcoming/shrine_steward.txt
Normal file
8
forge-gui/res/cardsfolder/upcoming/shrine_steward.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Name:Shrine Steward
|
||||
ManaCost:5
|
||||
Types:Artifact Creature Construct
|
||||
PT:3/2
|
||||
T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may search your library for an Aura or Shrine card, reveal it, put it into your hand, then shuffle.
|
||||
SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Aura,Shrine | ChangeNum$ 1 | ChangeTypeDesc$ Aura or Shrine card | ShuffleNonMandatory$ True
|
||||
DeckNeeds:Type$Aura|Shrine
|
||||
Oracle:When Shrine Steward enters the battlefield, you may search your library for an Aura or Shrine card, reveal it, put it into your hand, then shuffle.
|
||||
@@ -0,0 +1,8 @@
|
||||
Name:Thundersteel Colossus
|
||||
ManaCost:7
|
||||
Types:Artifact Vehicle
|
||||
PT:7/7
|
||||
K:Trample
|
||||
K:Haste
|
||||
K:Crew:2
|
||||
Oracle:Trample, haste\nCrew 2 (Tap any number of creatures you control with total power 2 or more: This Vehicle becomes an artifact creature until end of turn.)
|
||||
11
forge-gui/res/cardsfolder/upcoming/towashi_guide_bot.txt
Normal file
11
forge-gui/res/cardsfolder/upcoming/towashi_guide_bot.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
Name:Towashi Guide-Bot
|
||||
ManaCost:4
|
||||
Types:Artifact Creature Construct
|
||||
PT:3/2
|
||||
T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME enters the battlefield, put a +1/+1 counter on target creature you control.
|
||||
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ P1P1 | CounterNum$ 1
|
||||
A:AB$ Draw | Cost$ 4 T | NumCards$ 1 | ReduceCost$ X | SpellDescription$ Draw a card. This ability costs {1} less to activate for each modified creature you control.
|
||||
SVar:X:Count$Valid Creature.modified+YouCtrl
|
||||
DeckHas:Ability$Counters
|
||||
DeckHints:Type$Aura|Equipment & Ability$Counters
|
||||
Oracle:When Towashi Guide-Bot enters the battlefield, put a +1/+1 counter on target creature you control.\n{4}, {T}: Draw a card. This ability costs {1} less to activate for each modified creature you control. (Equipment, Auras you control, and counters are modifications.)
|
||||
@@ -4,7 +4,7 @@ Types:Creature Human Soldier Ally
|
||||
PT:*/*
|
||||
S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the number of creatures you control.
|
||||
SVar:X:Count$Valid Creature.YouCtrl
|
||||
A:AB$ GenericChoice | Cost$ tapXType<1/Ally.Other> | CostDesc$ Tap another untapped Ally you control: | Choices$ ChooseFirstStrike,ChooseVigilance,ChooseTrample | SpellDescription$ CARDNAME gains your choice of first strike, vigilance, or trample until end of turn.
|
||||
A:AB$ GenericChoice | Cost$ tapXType<1/Ally.Other> | Choices$ ChooseFirstStrike,ChooseVigilance,ChooseTrample | SpellDescription$ CARDNAME gains your choice of first strike, vigilance, or trample until end of turn.
|
||||
SVar:ChooseFirstStrike:DB$ Pump | Defined$ Self | KW$ First Strike | SpellDescription$ CARDNAME gains first strike until end of turn.
|
||||
SVar:ChooseVigilance:DB$ Pump | Defined$ Self | KW$ Vigilance | SpellDescription$ CARDNAME gains vigilance until end of turn.
|
||||
SVar:ChooseTrample:DB$ Pump | Defined$ Self | KW$ Trample | SpellDescription$ CARDNAME gains trample until end of turn.
|
||||
|
||||
@@ -463,6 +463,7 @@ ScryfallCode=SLD
|
||||
588 R Sphere of Safety @Johannes Voss
|
||||
589 R Arcane Signet @Dan Frazier
|
||||
591 R Crash Through @Tyler Walpole
|
||||
596 R Persistent Petitioners @Crom
|
||||
597 R Persistent Petitioners @Death Burger
|
||||
598 R Persistent Petitioners @Feifei Ruan
|
||||
603 M Eldrazi Monument @Cosmin Podar
|
||||
|
||||
13
forge-gui/res/editions/Year of the Tiger 2022.txt
Normal file
13
forge-gui/res/editions/Year of the Tiger 2022.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
[metadata]
|
||||
Code=PL22
|
||||
Date=2022-02-25
|
||||
Name=Year of the Tiger 2022
|
||||
Type=Promo
|
||||
ScryfallCode=PL22
|
||||
|
||||
[cards]
|
||||
1 R Temur Sabertooth @tswck
|
||||
2 R Jedit Ojanen @
|
||||
3 M Yuriko, the Tiger's Shadow @
|
||||
4 M Snapdax, Apex of the Hunt @
|
||||
5 R Herald's Horn @tswck
|
||||
@@ -760,7 +760,8 @@ public class HumanCostDecision extends CostDecisionMakerBase {
|
||||
if (num == 0) {
|
||||
return PaymentDecision.number(0);
|
||||
}
|
||||
if (hand.size() == num) {
|
||||
// player might not want to pay if from a trigger
|
||||
if (!ability.hasSVar("IsCastFromPlayEffect") && hand.size() == num) {
|
||||
return PaymentDecision.card(hand);
|
||||
}
|
||||
|
||||
|
||||
@@ -472,7 +472,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
||||
return null;
|
||||
}
|
||||
|
||||
String announceTitle = ("X".equals(announce)) ? announce : ability.getParamOrDefault("AnnounceTitle", announce);
|
||||
String announceTitle = "X".equals(announce) ? announce : ability.getParamOrDefault("AnnounceTitle", announce);
|
||||
if (cost.isMandatory()) {
|
||||
return chooseNumber(ability, localizer.getMessage("lblChooseAnnounceForCard", announceTitle,
|
||||
CardTranslation.getTranslatedName(ability.getHostCard().getName())) , min, max);
|
||||
|
||||
Reference in New Issue
Block a user