diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index f486ff0a99d..c51fac0a205 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -1391,9 +1391,8 @@ private void calculate(final List defenders, final Combat combat) { // used to check that CanKillAllDangerous check makes sense in context where creatures with dangerous abilities are present dangerousBlockersPresent = validBlockers.anyMatch( - CardPredicates.hasKeyword(Keyword.WITHER) - .or(CardPredicates.hasKeyword(Keyword.INFECT)) - .or(CardPredicates.hasKeyword(Keyword.LIFELINK)) + CardPredicates.hasKeyword(Keyword.LIFELINK) + .or(Card::isWitherDamage) ); // total power of the defending creatures, used in predicting whether a gang block can kill the attacker @@ -1422,7 +1421,7 @@ private void calculate(final List defenders, final Combat combat) { canKillAll = false; if (blocker.getSVar("HasCombatEffect").equals("TRUE") || blocker.getSVar("HasBlockEffect").equals("TRUE") - || blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT) || blocker.hasKeyword(Keyword.LIFELINK)) { + || blocker.isWitherDamage() || blocker.hasKeyword(Keyword.LIFELINK)) { canKillAllDangerous = false; // there is a creature that can survive an attack from this creature // and combat will have negative effects diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 8f3838ff2b5..b7350535ae6 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -18,7 +18,6 @@ package forge.ai; import com.esotericsoftware.minlog.Log; -import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import forge.ai.AiCardMemory.MemorySet; @@ -64,6 +63,7 @@ import io.sentry.Sentry; import java.util.*; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.concurrent.CompletableFuture; @@ -717,12 +717,14 @@ public boolean reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean return reserveManaSources(sa, phaseType, enemy, true, null); } public boolean reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean enemy, boolean forNextSpell, SpellAbility exceptForThisSa) { - ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0); + ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa.getPayCosts(), sa, true, 0, false); CardCollection manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player); // used for chained spells where two spells need to be cast in succession if (exceptForThisSa != null) { - manaSources.removeAll(ComputerUtilMana.getManaSourcesToPayCost(ComputerUtilMana.calculateManaCost(exceptForThisSa, true, 0), exceptForThisSa, player)); + manaSources.removeAll(ComputerUtilMana.getManaSourcesToPayCost( + ComputerUtilMana.calculateManaCost(exceptForThisSa.getPayCosts(), exceptForThisSa, true, 0, false), + exceptForThisSa, player)); } if (manaSources.isEmpty()) { @@ -820,7 +822,7 @@ private AiPlayDecision canPlayAndPayForFace(final SpellAbility sa) { } // TODO check for Reduce too, e.g. Battlefield Thaumaturge could make it castable if (!sa.getAllTargetChoices().isEmpty()) { - oldCMC = CostAdjustment.adjust(sa.getPayCosts(), sa).getTotalMana().getCMC(); + oldCMC = CostAdjustment.adjust(sa.getPayCosts(), sa, false).getTotalMana().getCMC(); } } @@ -853,7 +855,7 @@ private AiPlayDecision canPlayAndPayForFace(final SpellAbility sa) { // check if some target raised cost if (!xCost && oldCMC > -1) { - int finalCMC = CostAdjustment.adjust(sa.getPayCosts(), sa).getTotalMana().getCMC(); + int finalCMC = CostAdjustment.adjust(sa.getPayCosts(), sa, false).getTotalMana().getCMC(); if (finalCMC > oldCMC) { xCost = true; } @@ -1013,7 +1015,7 @@ private boolean canPlaySpellWithoutBuyback(Card card, SpellAbility sa) { costWithBuyback.add(opt.getCost()); } } - costWithBuyback = CostAdjustment.adjust(costWithBuyback, sa); + costWithBuyback = CostAdjustment.adjust(costWithBuyback, sa, false); if (costWithBuyback.hasSpecificCostType(CostPayLife.class) || costWithBuyback.hasSpecificCostType(CostDiscard.class) || costWithBuyback.hasSpecificCostType(CostSacrifice.class)) { @@ -2208,8 +2210,6 @@ public List orderPlaySa(List activePlayerSAs) { return activePlayerSAs; } - List result = Lists.newArrayList(); - // filter list by ApiTypes List discard = filterListByApi(activePlayerSAs, ApiType.Discard); List mandatoryDiscard = filterList(discard, SpellAbilityPredicates.isMandatory()); @@ -2225,37 +2225,33 @@ public List orderPlaySa(List activePlayerSAs) { List pump = filterListByApi(activePlayerSAs, ApiType.Pump); List pumpAll = filterListByApi(activePlayerSAs, ApiType.PumpAll); + List result = Lists.newArrayList(activePlayerSAs); + // do mandatory discard early if hand is empty or has DiscardMe card - boolean discardEarly = false; CardCollectionView playerHand = player.getCardsIn(ZoneType.Hand); - if (playerHand.isEmpty() || playerHand.anyMatch(CardPredicates.hasSVar("DiscardMe"))) { - discardEarly = true; + if (!playerHand.isEmpty() && !playerHand.anyMatch(CardPredicates.hasSVar("DiscardMe"))) { result.addAll(mandatoryDiscard); + mandatoryDiscard.clear(); } - // token should be added first so they might get the pump bonus - result.addAll(token); - result.addAll(pump); - result.addAll(pumpAll); + // optional Discard, probably combined with Draw + result.addAll(discard); + // do Draw before Discard + result.addAll(draw); - // do Evolve Trigger before other PutCounter SpellAbilities + result.addAll(putCounterAll); // do putCounter before Draw/Discard because it can cause a Draw Trigger - result.addAll(evolve); result.addAll(putCounter); - result.addAll(putCounterAll); - - // do Draw before Discard - result.addAll(draw); - result.addAll(discard); // optional Discard, probably combined with Draw + // do Evolve Trigger before other PutCounter SpellAbilities + result.addAll(evolve); - if (!discardEarly) { - result.addAll(mandatoryDiscard); - } + // token should be added first so they might get the pump bonus + result.addAll(pumpAll); + result.addAll(pump); + result.addAll(token); - result.addAll(activePlayerSAs); + result.addAll(mandatoryDiscard); - //need to reverse because of magic stack - Collections.reverse(result); return result; } @@ -2267,6 +2263,10 @@ private static List filterList(List input, Predicate pred) } // TODO move to more common place + public static List filterList(List input, Function pred, Object value) { + return filterList(input, trb -> pred.apply(trb.ensureAbility()) == value); + } + public static List filterListByApi(List input, ApiType type) { return filterList(input, SpellAbilityPredicates.isApi(type)); } @@ -2303,12 +2303,11 @@ private boolean checkAiSpecificRestrictions(final SpellAbility sa) { public ReplacementEffect chooseSingleReplacementEffect(List list) { // no need to choose anything if (list.size() <= 1) { - return Iterables.getFirst(list, null); + return list.get(0); } - ReplacementType mode = Iterables.getFirst(list, null).getMode(); + ReplacementType mode = list.get(0).getMode(); - // replace lifegain effects if (mode.equals(ReplacementType.GainLife)) { List noGain = filterListByAiLogic(list, "NoLife"); List loseLife = filterListByAiLogic(list, "LoseLife"); @@ -2317,16 +2316,16 @@ public ReplacementEffect chooseSingleReplacementEffect(List l if (!noGain.isEmpty()) { // no lifegain is better than lose life - return Iterables.getFirst(noGain, null); + return noGain.get(0); } else if (!loseLife.isEmpty()) { // lose life before double life to prevent lose double - return Iterables.getFirst(loseLife, null); + return loseLife.get(0); } else if (!lichDraw.isEmpty()) { // lich draw before double life to prevent to draw to much - return Iterables.getFirst(lichDraw, null); + return lichDraw.get(0); } else if (!doubleLife.isEmpty()) { // other than that, do double life - return Iterables.getFirst(doubleLife, null); + return doubleLife.get(0); } } else if (mode.equals(ReplacementType.DamageDone)) { List prevention = filterList(list, CardTraitPredicates.hasParam("Prevent")); @@ -2334,7 +2333,7 @@ public ReplacementEffect chooseSingleReplacementEffect(List l // TODO when Protection is done as ReplacementEffect do them // before normal prevention if (!prevention.isEmpty()) { - return Iterables.getFirst(prevention, null); + return prevention.get(0); } } else if (mode.equals(ReplacementType.Destroy)) { List shield = filterList(list, CardTraitPredicates.hasParam("ShieldCounter")); @@ -2344,30 +2343,35 @@ public ReplacementEffect chooseSingleReplacementEffect(List l // Indestructible umbra armor is the best if (!umbraArmorIndestructible.isEmpty()) { - return Iterables.getFirst(umbraArmorIndestructible, null); + return umbraArmorIndestructible.get(0); } // then it might be better to remove shield counter if able? if (!shield.isEmpty()) { - return Iterables.getFirst(shield, null); + return shield.get(0); } // TODO get the RunParams for Affected to check if the creature already dealt combat damage for Regeneration effects // is using a Regeneration Effect better than using a Umbra Armor? if (!regeneration.isEmpty()) { - return Iterables.getFirst(regeneration, null); + return regeneration.get(0); } if (!umbraArmor.isEmpty()) { // sort them by cmc umbraArmor.sort(Comparator.comparing(CardTraitBase::getHostCard, Comparator.comparing(Card::getCMC))); - return Iterables.getFirst(umbraArmor, null); + return umbraArmor.get(0); + } + } else if (mode.equals(ReplacementType.Draw)) { + List winGame = filterList(list, SpellAbility::getApi, ApiType.WinsGame); + if (!winGame.isEmpty()) { + return winGame.get(0); } } // TODO always lower counters with Vorinclex first, might turn it from 1 to 0 as final - return Iterables.getFirst(list, null); + return list.get(0); } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 7622f8203b2..b83fd81ae61 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -120,22 +120,13 @@ public static boolean handlePlayingSpellAbility(final Player ai, SpellAbility sa game.getStack().freezeStack(sa); - // TODO: update mana color conversion for Daxos of Meletis - if (cost == null) { - // Is this fork even used for anything anymore? - if (ComputerUtilMana.payManaCost(ai, sa, false)) { - game.getStack().addAndUnfreeze(sa); - return true; - } - } else { - final CostPayment pay = new CostPayment(cost, sa); - if (pay.payComputerCosts(new AiCostDecision(ai, sa, false))) { - game.getStack().addAndUnfreeze(sa); - if (sa.getSplicedCards() != null && !sa.getSplicedCards().isEmpty()) { - game.getAction().reveal(sa.getSplicedCards(), ai, true, "Computer reveals spliced cards from "); - } - return true; + final CostPayment pay = new CostPayment(cost, sa); + if (pay.payComputerCosts(new AiCostDecision(ai, sa, false))) { + game.getStack().addAndUnfreeze(sa); + if (sa.getSplicedCards() != null && !sa.getSplicedCards().isEmpty()) { + game.getAction().reveal(sa.getSplicedCards(), ai, true, "Computer reveals spliced cards from "); } + return true; } // FIXME: Should not arrive here, though the card seems to be stucked on stack zone and invalidated and nowhere to be found, try to put back to original zone and maybe try to cast again if possible at later time? System.out.println("[" + sa.getActivatingPlayer() + "] AI failed to play " + sa.getHostCard() + " [" + sa.getHostCard().getZone() + "]"); @@ -239,10 +230,9 @@ public static final boolean playStack(SpellAbility sa, final Player ai, final Ga if (sa.isSpell() && !source.isCopiedSpell()) { sa.setHostCard(game.getAction().moveToStack(source, sa)); + sa = GameActionUtil.addExtraKeywordCost(sa); } - sa = GameActionUtil.addExtraKeywordCost(sa); - final Cost cost = sa.getPayCosts(); final CostPayment pay = new CostPayment(cost, sa); @@ -252,15 +242,11 @@ public static final boolean playStack(SpellAbility sa, final Player ai, final Ga return false; } - if (cost == null) { - ComputerUtilMana.payManaCost(ai, sa, false); + if (pay.payComputerCosts(new AiCostDecision(ai, sa, false))) { game.getStack().add(sa); - } else { - if (pay.payComputerCosts(new AiCostDecision(ai, sa, false))) { - game.getStack().add(sa); - } + return true; } - return true; + return false; } public static final void playSpellAbilityForFree(final Player ai, final SpellAbility sa) { @@ -324,22 +310,19 @@ public static final boolean playNoStack(final Player ai, SpellAbility sa, final } final Card source = sa.getHostCard(); - if (sa.isSpell() && !source.isCopiedSpell()) { + if (!effect && sa.isSpell() && !source.isCopiedSpell()) { sa.setHostCard(game.getAction().moveToStack(source, sa)); + sa = GameActionUtil.addExtraKeywordCost(sa); } - sa = GameActionUtil.addExtraKeywordCost(sa); - final Cost cost = sa.getPayCosts(); - if (cost == null) { - ComputerUtilMana.payManaCost(ai, sa, effect); - } else { - final CostPayment pay = new CostPayment(cost, sa); - pay.payComputerCosts(new AiCostDecision(ai, sa, effect)); + final CostPayment pay = new CostPayment(cost, sa); + if (pay.payComputerCosts(new AiCostDecision(ai, sa, effect))) { + AbilityUtils.resolve(sa); + return true; } - AbilityUtils.resolve(sa); - return true; + return false; } public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index d1cbe9b5728..154fa0e8ef4 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -1,17 +1,22 @@ package forge.ai; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; + import com.google.common.collect.Lists; import com.google.common.collect.Sets; + import forge.ai.AiCardMemory.MemorySet; import forge.ai.ability.AnimateAi; -import forge.ai.ability.TokenAi; -import forge.card.ColorSet; import forge.game.Game; import forge.game.ability.AbilityUtils; -import forge.game.ability.ApiType; import forge.game.card.*; import forge.game.combat.Combat; -import forge.game.combat.CombatUtil; import forge.game.cost.*; import forge.game.keyword.Keyword; import forge.game.phase.PhaseType; @@ -23,14 +28,6 @@ import forge.util.IterableUtil; import forge.util.MyRandom; import forge.util.TextUtil; -import forge.util.collect.FCollectionView; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; - -import java.util.Collection; -import java.util.List; -import java.util.Set; -import java.util.function.Predicate; public class ComputerUtilCost { @@ -91,7 +88,7 @@ public static boolean checkRemoveCounterCost(final Cost cost, final Card source, continue; } - // even if it can be paid, removing zero counters should not be done. + // even if it can be paid, removing zero counters should not be done. if (part.payCostFromSource() && source.getCounters(type) <= 0) { return false; } @@ -140,21 +137,27 @@ public static boolean checkDiscardCost(final Player ai, final Cost cost, final C final CostDiscard disc = (CostDiscard) part; final String type = disc.getType(); - if (type.equals("CARDNAME")) { - if (source.getAbilityText().contains("Bloodrush")) { + final CardCollection typeList; + int num; + if (type.equals("Hand")) { + typeList = hand; + num = hand.size(); + } else { + if (type.equals("CARDNAME")) { + if (source.getAbilityText().contains("Bloodrush")) { + continue; + } else if (ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN, ai) + && !ai.isUnlimitedHandSize() && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize()) { + // Better do something than just discard stuff + return true; + } + } + typeList = CardLists.getValidCards(hand, type, source.getController(), source, sa); + if (typeList.size() > ai.getMaxHandSize()) { continue; - } else if (ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN, ai) - && !ai.isUnlimitedHandSize() && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize()) { - // Better do something than just discard stuff - return true; } + num = AbilityUtils.calculateAmount(source, disc.getAmount(), sa); } - final CardCollection typeList = CardLists.getValidCards(hand, type, source.getController(), source, sa); - if (typeList.size() > ai.getMaxHandSize()) { - continue; - } - int num = AbilityUtils.calculateAmount(source, disc.getAmount(), sa); - for (int i = 0; i < num; i++) { Card pref = ComputerUtil.getCardPreference(ai, source, "DiscardCost", typeList); if (pref == null) { @@ -220,10 +223,7 @@ public static boolean checkLifeCost(final Player ai, final Cost cost, final Card if (part instanceof CostPayLife) { final CostPayLife payLife = (CostPayLife) part; - Integer amount = payLife.convertAmount(); - if (amount == null) { - amount = AbilityUtils.calculateAmount(source, payLife.getAmount(), sourceAbility); - } + int amount = payLife.getAbilityAmount(sourceAbility); // check if there's override for the remainingLife threshold if (sourceAbility != null && sourceAbility.hasParam("AILifeThreshold")) { @@ -527,6 +527,9 @@ public static boolean checkTapTypeCost(final Player ai, final Cost cost, final C * @return a boolean. */ public static boolean canPayCost(final SpellAbility sa, final Player player, final boolean effect) { + return canPayCost(sa.getPayCosts(), sa, player, effect); + } + public static boolean canPayCost(final Cost cost, final SpellAbility sa, final Player player, final boolean effect) { if (sa.getActivatingPlayer() == null) { sa.setActivatingPlayer(player); // complaints on NPE had came before this line was added. } @@ -535,271 +538,89 @@ public static boolean canPayCost(final SpellAbility sa, final Player player, fin // Check for stuff like Nether Void int extraManaNeeded = 0; - if (sa instanceof Spell) { - cannotBeCountered = !sa.isCounterableBy(null); - for (Card c : player.getGame().getCardsIn(ZoneType.Battlefield)) { - final String snem = c.getSVar("AI_SpellsNeedExtraMana"); - if (!StringUtils.isBlank(snem)) { - if (cannotBeCountered && c.getName().equals("Nether Void")) { - continue; - } - String[] parts = TextUtil.split(snem, ' '); - boolean meetsRestriction = parts.length == 1 || player.isValid(parts[1], c.getController(), c, sa); - if(!meetsRestriction) - continue; - - if (StringUtils.isNumeric(parts[0])) { - extraManaNeeded += Integer.parseInt(parts[0]); - } else { - System.out.println("wrong SpellsNeedExtraMana SVar format on " + c); + if (!effect) { + if (sa instanceof Spell) { + cannotBeCountered = !sa.isCounterableBy(null); + for (Card c : player.getGame().getCardsIn(ZoneType.Battlefield)) { + final String snem = c.getSVar("AI_SpellsNeedExtraMana"); + if (!StringUtils.isBlank(snem)) { + if (cannotBeCountered && c.getName().equals("Nether Void")) { + continue; + } + String[] parts = TextUtil.split(snem, ' '); + boolean meetsRestriction = parts.length == 1 || player.isValid(parts[1], c.getController(), c, sa); + if(!meetsRestriction) + continue; + + if (StringUtils.isNumeric(parts[0])) { + extraManaNeeded += Integer.parseInt(parts[0]); + } else { + System.out.println("wrong SpellsNeedExtraMana SVar format on " + c); + } } } - } - for (Card c : player.getCardsIn(ZoneType.Command)) { - if (cannotBeCountered) { - continue; - } - final String snem = c.getSVar("SpellsNeedExtraManaEffect"); - if (!StringUtils.isBlank(snem)) { - if (StringUtils.isNumeric(snem)) { - extraManaNeeded += Integer.parseInt(snem); - } else { - System.out.println("wrong SpellsNeedExtraManaEffect SVar format on " + c); + for (Card c : player.getCardsIn(ZoneType.Command)) { + if (cannotBeCountered) { + continue; } - } - } - } - - // Try not to lose Planeswalker if not threatened - if (sa.isPwAbility()) { - for (final CostPart part : sa.getPayCosts().getCostParts()) { - if (part instanceof CostRemoveCounter) { - if (part.convertAmount() != null && part.convertAmount() == sa.getHostCard().getCurrentLoyalty()) { - // refuse to pay if opponent has no creature threats or - // 50% chance otherwise - if (player.getOpponents().getCreaturesInPlay().isEmpty() - || MyRandom.getRandom().nextFloat() < .5f) { - return false; + final String snem = c.getSVar("SpellsNeedExtraManaEffect"); + if (!StringUtils.isBlank(snem)) { + if (StringUtils.isNumeric(snem)) { + extraManaNeeded += Integer.parseInt(snem); + } else { + System.out.println("wrong SpellsNeedExtraManaEffect SVar format on " + c); } } } } - } - // Ward - will be accounted for when rechecking a targeted ability - if (!sa.isTrigger() && (!sa.isSpell() || !cannotBeCountered)) { - for (TargetChoices tc : sa.getAllTargetChoices()) { - for (Card tgt : tc.getTargetCards()) { - if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) { - Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt); - if (wardCost.hasManaCost()) { - extraManaNeeded += wardCost.getTotalMana().getCMC(); + // Try not to lose Planeswalker if not threatened + if (sa.isPwAbility()) { + for (final CostPart part : cost.getCostParts()) { + if (part instanceof CostRemoveCounter) { + if (part.convertAmount() != null && part.convertAmount() == sa.getHostCard().getCurrentLoyalty()) { + // refuse to pay if opponent has no creature threats or + // 50% chance otherwise + if (player.getOpponents().getCreaturesInPlay().isEmpty() + || MyRandom.getRandom().nextFloat() < .5f) { + return false; + } } } } } - } - // Bail early on Casualty in case there are no cards that would make sense to pay with - if (sa.getHostCard().hasKeyword(Keyword.CASUALTY)) { - for (final CostPart part : sa.getPayCosts().getCostParts()) { - if (part instanceof CostSacrifice) { - CardCollection valid = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), part.getType().split(";"), - sa.getActivatingPlayer(), sa.getHostCard(), sa); - valid = CardLists.filter(valid, CardPredicates.hasSVar("AIDontSacToCasualty").negate()); - if (valid.isEmpty()) { - return false; - } - } - } - } - - return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded, effect) - && CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa, effect); - } - - public static boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { - final Card source = sa.getHostCard(); - final String aiLogic = sa.getParam("UnlessAI"); - boolean payForOwnOnly = "OnlyOwn".equals(aiLogic); - boolean payOwner = sa.hasParam("UnlessAI") && aiLogic.startsWith("Defined"); - boolean payNever = "Never".equals(aiLogic); - boolean isMine = sa.getActivatingPlayer().equals(payer); - - if (payNever) { return false; } - if (payForOwnOnly && !isMine) { return false; } - if (payOwner) { - final String defined = aiLogic.substring(7); - final Player player = AbilityUtils.getDefinedPlayers(source, defined, sa).get(0); - if (!payer.equals(player)) { - return false; - } - } else if ("OnlyDontControl".equals(aiLogic)) { - if (source == null || payer.equals(source.getController())) { - return false; - } - } else if ("Paralyze".equals(aiLogic)) { - final Card c = source.getEnchantingCard(); - if (c == null || c.isUntapped()) { - return false; - } - } else if ("RiskFactor".equals(aiLogic)) { - final Player activator = sa.getActivatingPlayer(); - if (!activator.canDraw()) { - return false; - } - } else if ("MorePowerful".equals(aiLogic)) { - final int sourceCreatures = sa.getActivatingPlayer().getCreaturesInPlay().size(); - final int payerCreatures = payer.getCreaturesInPlay().size(); - if (payerCreatures > sourceCreatures + 1) { - return false; - } - } else if (aiLogic != null && aiLogic.startsWith("LifeLE")) { - // if payer can't lose life its no need to pay unless - if (!payer.canLoseLife()) - return false; - else if (payer.getLife() <= AbilityUtils.calculateAmount(source, aiLogic.substring(6), sa)) { - return true; - } - } else if ("WillAttack".equals(aiLogic)) { - AiAttackController aiAtk = new AiAttackController(payer); - Combat combat = new Combat(payer); - aiAtk.declareAttackers(combat); - if (combat.getAttackers().isEmpty()) { - return false; - } - } else if ("nonToken".equals(aiLogic) && !AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).isEmpty() - && AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0).isToken()) { - return false; - } else if ("LowPriority".equals(aiLogic) && MyRandom.getRandom().nextInt(100) < 67) { - return false; - } else if (aiLogic != null && aiLogic.startsWith("Fabricate")) { - final int n = Integer.parseInt(aiLogic.substring("Fabricate".length())); - - // if host would leave the play or if host is useless, create tokens - if (source.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(payer, source)) { - return false; - } - - // need a copy for one with extra +1/+1 counter boost, - // without causing triggers to run - final Card copy = CardCopyService.getLKICopy(source); - copy.setCounters(CounterEnumType.P1P1, copy.getCounters(CounterEnumType.P1P1) + n); - copy.setZone(source.getZone()); - - // if host would put into the battlefield attacking - Combat combat = source.getGame().getCombat(); - if (combat != null && combat.isAttacking(source)) { - final Player defender = combat.getDefenderPlayerByAttacker(source); - if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy, true)) { - return true; - } - return false; - } - - // if the host has haste and can attack - if (CombatUtil.canAttack(copy)) { - for (final Player opp : payer.getOpponents()) { - if (CombatUtil.canAttack(copy, opp) && - opp.canLoseLife() && - !ComputerUtilCard.canBeBlockedProfitably(opp, copy, true)) - return true; - } - } - - // TODO check for trigger to turn token ETB into +1/+1 counter for host - // TODO check for trigger to turn token ETB into damage or life loss for opponent - // in this cases Token might be prefered even if they would not survive - final Card tokenCard = TokenAi.spawnToken(payer, sa); - - // Token would not survive - if (!tokenCard.isCreature() || tokenCard.getNetToughness() < 1) { - return true; - } - - // Special Card logic, this one try to median its power with the number of artifacts - if ("Marionette Master".equals(source.getName())) { - CardCollection list = CardLists.filter(payer.getCardsIn(ZoneType.Battlefield), CardPredicates.ARTIFACTS); - return list.size() >= copy.getNetPower(); - } else if ("Cultivator of Blades".equals(source.getName())) { - // Cultivator does try to median with number of Creatures - CardCollection list = payer.getCreaturesInPlay(); - return list.size() >= copy.getNetPower(); - } - - // evaluate Creature with +1/+1 - int evalCounter = ComputerUtilCard.evaluateCreature(copy); - - final CardCollection tokenList = new CardCollection(source); - for (int i = 0; i < n; ++i) { - tokenList.add(TokenAi.spawnToken(payer, sa)); - } - - // evaluate Host with Tokens - int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList); - - return evalToken < evalCounter; - } else if ("Riot".equals(aiLogic)) { - return !SpecialAiLogic.preferHasteForRiot(sa, payer); - } - - // Check for shocklands and similar ETB replacement effects - if (sa.hasParam("ETB") && sa.getApi().equals(ApiType.Tap)) { - for (final CostPart part : cost.getCostParts()) { - if (part instanceof CostPayLife) { - final CostPayLife lifeCost = (CostPayLife) part; - Integer amount = lifeCost.convertAmount(); - if (payer.getLife() > (amount + 1) && payer.canPayLife(amount, true, sa)) { - final int landsize = payer.getLandsInPlay().size() + 1; - for (Card c : payer.getCardsIn(ZoneType.Hand)) { - // Check if the AI has enough lands to play the card - if (landsize != c.getCMC()) { - continue; - } - // Check if the AI intends to play the card and if it can pay for it with the mana it has - boolean willPlay = ComputerUtil.hasReasonToPlayCardThisTurn(payer, c); - boolean canPay = c.getManaCost().canBePaidWithAvailable(ColorSet.fromNames(getAvailableManaColors(payer, source)).getColor()); - if (canPay && willPlay) { - return true; + // Ward - will be accounted for when rechecking a targeted ability + if (!sa.isTrigger() && (!sa.isSpell() || !cannotBeCountered)) { + for (TargetChoices tc : sa.getAllTargetChoices()) { + for (Card tgt : tc.getTargetCards()) { + if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) { + Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt); + if (wardCost.hasManaCost()) { + extraManaNeeded += wardCost.getTotalMana().getCMC(); } } } - return false; } } - } - - // AI will only pay when it's not already payed and only opponents abilities - if (alreadyPaid || (payers.size() > 1 && (isMine && !payForOwnOnly))) { - return false; - } - // ward or human misplay - if (ApiType.Counter.equals(sa.getApi())) { - List spells = AbilityUtils.getDefinedSpellAbilities(source, sa.getParamOrDefault("Defined", "Targeted"), sa); - for (SpellAbility toBeCountered : spells) { - if (!toBeCountered.isCounterableBy(sa)) { - return false; - } - // no reason to pay if we don't plan to confirm - if (toBeCountered.isOptionalTrigger() && !SpellApiToAi.Converter.get(toBeCountered.getApi()).doTriggerNoCostWithSubs(payer, toBeCountered, false)) { - return false; + // Bail early on Casualty in case there are no cards that would make sense to pay with + if (sa.getHostCard().hasKeyword(Keyword.CASUALTY)) { + for (final CostPart part : cost.getCostParts()) { + if (part instanceof CostSacrifice) { + CardCollection valid = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), part.getType().split(";"), + sa.getActivatingPlayer(), sa.getHostCard(), sa); + valid = CardLists.filter(valid, CardPredicates.hasSVar("AIDontSacToCasualty").negate()); + if (valid.isEmpty()) { + return false; + } + } } - // TODO check hasFizzled } } - // AI was crashing because the blank ability used to pay costs - // Didn't have any of the data on the original SA to pay dependant costs - - return checkLifeCost(payer, cost, source, 4, sa) - && checkDamageCost(payer, cost, source, 4, sa) - && (isMine || checkSacrificeCost(payer, cost, source, sa)) - && (isMine || checkDiscardCost(payer, cost, source, sa)) - && (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2) - && (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2) - && (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1) - && (!source.getName().equals("Chain of Vapor") || (payer.getWeakestOpponent().getCreaturesInPlay().size() > 0 && payer.getLandsInPlay().size() > 3)); + return ComputerUtilMana.canPayManaCost(cost, sa, player, extraManaNeeded, effect) + && CostPayment.canPayAdditionalCosts(cost, sa, effect); } public static Set getAvailableManaColors(Player ai, Card additionalLand) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index 94150c423ee..4aa79c74a8d 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -56,25 +56,28 @@ public static boolean canPayManaCost(ManaCostBeingPaid cost, final SpellAbility return payManaCost(cost, sa, ai, true, true, effect); } public static boolean canPayManaCost(final SpellAbility sa, final Player ai, final int extraMana, final boolean effect) { - return payManaCost(sa, ai, true, extraMana, true, effect); + return canPayManaCost(sa.getPayCosts(), sa, ai, extraMana, effect); + } + public static boolean canPayManaCost(final Cost cost, final SpellAbility sa, final Player ai, final int extraMana, final boolean effect) { + return payManaCost(cost, sa, ai, true, extraMana, true, effect); } public static boolean payManaCost(ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean effect) { return payManaCost(cost, sa, ai, false, true, effect); } - public static boolean payManaCost(final Player ai, final SpellAbility sa, final boolean effect) { - return payManaCost(sa, ai, false, 0, true, effect); + public static boolean payManaCost(final Cost cost, final Player ai, final SpellAbility sa, final boolean effect) { + return payManaCost(cost, sa, ai, false, 0, true, effect); } - private static boolean payManaCost(final SpellAbility sa, final Player ai, final boolean test, final int extraMana, boolean checkPlayable, final boolean effect) { - ManaCostBeingPaid cost = calculateManaCost(sa, test, extraMana); - return payManaCost(cost, sa, ai, test, checkPlayable, effect); + private static boolean payManaCost(final Cost cost, final SpellAbility sa, final Player ai, final boolean test, final int extraMana, boolean checkPlayable, final boolean effect) { + ManaCostBeingPaid manaCost = calculateManaCost(cost, sa, test, extraMana, effect); + return payManaCost(manaCost, sa, ai, test, checkPlayable, effect); } /** * Return the number of colors used for payment for Converge */ public static int getConvergeCount(final SpellAbility sa, final Player ai) { - ManaCostBeingPaid cost = calculateManaCost(sa, true, 0); + ManaCostBeingPaid cost = calculateManaCost(sa.getPayCosts(), sa, true, 0, false); if (payManaCost(cost, sa, ai, true, true, false)) { return cost.getSunburst(); } @@ -86,7 +89,7 @@ public static boolean hasEnoughManaSourcesToCast(final SpellAbility sa, final Pl if (ai == null || sa == null) return false; sa.setActivatingPlayer(ai); - return payManaCost(sa, ai, true, 0, false, false); + return payManaCost(sa.getPayCosts(), sa, ai, true, 0, false, false); } private static Integer scoreManaProducingCard(final Card card) { @@ -1269,7 +1272,7 @@ private static ListMultimap groupAndOrderToPayShard * @param extraMana extraMana * @return ManaCost */ - public static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final boolean test, final int extraMana) { + public static ManaCostBeingPaid calculateManaCost(final Cost cost, final SpellAbility sa, final boolean test, final int extraMana, final boolean effect) { Card card = sa.getHostCard(); Zone castFromBackup = null; if (test && sa.isSpell() && !card.isInZone(ZoneType.Stack)) { @@ -1277,16 +1280,16 @@ public static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final b card.setCastFrom(card.getZone() != null ? card.getZone() : null); } - Cost payCosts = CostAdjustment.adjust(sa.getPayCosts(), sa); + Cost payCosts = CostAdjustment.adjust(cost, sa, effect); CostPartMana manapart = payCosts != null ? payCosts.getCostMana() : null; final ManaCost mana = payCosts != null ? ( manapart == null ? ManaCost.ZERO : manapart.getManaCostFor(sa) ) : ManaCost.NO_COST; - ManaCostBeingPaid cost = new ManaCostBeingPaid(mana); + ManaCostBeingPaid manaCost = new ManaCostBeingPaid(mana); // Tack xMana Payments into mana here if X is a set value - if (cost.getXcounter() > 0 || extraMana > 0) { + if (manaCost.getXcounter() > 0 || extraMana > 0) { int manaToAdd = 0; - int xCounter = cost.getXcounter(); + int xCounter = manaCost.getXcounter(); if (test && extraMana > 0) { final int multiplicator = Math.max(xCounter, 1); manaToAdd = extraMana * multiplicator; @@ -1307,9 +1310,9 @@ public static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final b xColor = "WUBRGX"; } if (xCounter > 0) { - cost.setXManaCostPaid(manaToAdd / xCounter, xColor); + manaCost.setXManaCostPaid(manaToAdd / xCounter, xColor); } else { - cost.increaseShard(ManaCostShard.parseNonGeneric(xColor), manaToAdd); + manaCost.increaseShard(ManaCostShard.parseNonGeneric(xColor), manaToAdd); } if (!test) { @@ -1317,7 +1320,7 @@ public static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final b } } - CostAdjustment.adjust(cost, sa, null, test); + CostAdjustment.adjust(manaCost, sa, null, test); if ("NumTimes".equals(sa.getParam("Announce"))) { // e.g. the Adversary cycle ManaCost mkCost = sa.getPayCosts().getTotalMana(); @@ -1336,7 +1339,7 @@ public static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final b sa.getHostCard().setCastFrom(castFromBackup); } - return cost; + return manaCost; } // This method can be used to estimate the total amount of mana available to the player, diff --git a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java index ecd32a12743..b5dd1016310 100644 --- a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java +++ b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java @@ -11,6 +11,8 @@ import forge.game.staticability.StaticAbilityAssignCombatDamageAsUnblocked; import forge.game.staticability.StaticAbilityCantAttackBlock; import forge.game.staticability.StaticAbilityMustAttack; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerType; import java.util.List; import java.util.function.Function; @@ -224,30 +226,53 @@ else if (c.hasKeyword(Keyword.WITHER)) { } else { value -= subValue(10 * c.getCounters(CounterEnumType.STUN), "stunned"); } - if (c.hasSVar("EndOfTurnLeavePlay")) { - value -= subValue(50, "eot-leaves"); - } else if (c.hasKeyword(Keyword.CUMULATIVE_UPKEEP)) { - value -= subValue(30, "cupkeep"); - } else if (c.hasStartOfKeyword("UpkeepCost")) { - value -= subValue(20, "sac-unless"); - } else if (c.hasKeyword(Keyword.ECHO) && c.cameUnderControlSinceLastUpkeep()) { - value -= subValue(10, "echo-unpaid"); - } - if (c.hasKeyword(Keyword.FADING)) { - value -= subValue(20 / (Math.max(1, c.getCounters(CounterEnumType.FADE))), "fading"); - } - if (c.hasKeyword(Keyword.VANISHING)) { - value -= subValue(20 / (Math.max(1, c.getCounters(CounterEnumType.TIME))), "vanishing"); - } // use scaling because the creature is only available halfway if (c.hasKeyword(Keyword.PHASING)) { value -= subValue(Math.max(20, value / 2), "phasing"); } - // TODO no longer a KW - if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) { - value -= subValue(20, "upkeep-dmg"); - } + if (c.hasSVar("EndOfTurnLeavePlay")) { + value -= subValue(50, "eot-leaves"); + } else { + for (Trigger t : c.getTriggers()) { + if (!TriggerType.Phase.equals(t.getMode())) { + continue; + } + if (!"Upkeep".equals(t.getParam("Phase"))) { + continue; + } + + if (t.isKeyword(Keyword.CUMULATIVE_UPKEEP)) { + value -= subValue(30, "cupkeep"); + } else if (t.isKeyword(Keyword.ECHO) && c.cameUnderControlSinceLastUpkeep()) { + value -= subValue(10, "echo-unpaid"); + } + if (t.isKeyword(Keyword.FADING)) { + value -= subValue(20 / (Math.max(1, c.isInPlay() ? c.getCounters(CounterEnumType.FADE) : c.getKeywordMagnitude(Keyword.FADING))), "fading"); + } + if (t.isKeyword(Keyword.VANISHING)) { + value -= subValue(20 / (Math.max(1, c.isInPlay() ? c.getCounters(CounterEnumType.TIME) : c.getKeywordMagnitude(Keyword.VANISHING))), "vanishing"); + } + + SpellAbility ab = t.ensureAbility(); + if (ab == null) { + continue; + } + if (ApiType.DealDamage.equals(ab.getApi())) { + if (!"You".equals(ab.getParamOrDefault("Defined", "You"))) { + continue; + } + if (c.getController().canLoseLife()) { + value -= subValue(20, "upkeep-dmg"); + } + } else if (ApiType.Sacrifice.equals(ab.getApi())) { + if (!ab.hasParam("UnlessCost")) { + continue; + } + value -= subValue(20, "sac-unless"); + } + } + } // card-specific evaluation modifier if (c.hasSVar("AIEvaluationModifier")) { diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index a7f7c54d23f..998b830a4ba 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -21,6 +21,7 @@ import forge.game.cost.CostEnlist; import forge.game.cost.CostPart; import forge.game.cost.CostPartMana; +import forge.game.cost.CostPayment; import forge.game.keyword.Keyword; import forge.game.keyword.KeywordInterface; import forge.game.mana.Mana; @@ -835,14 +836,7 @@ public CardCollection chooseCardsToRevealFromHand(int min, int max, CardCollecti @Override public boolean payManaOptional(Card c, Cost cost, SpellAbility sa, String prompt, ManaPaymentPurpose purpose) { - // TODO replace with EmptySa - final Ability ability = new AbilityStatic(c, cost, null) { @Override public void resolve() {} }; - ability.setActivatingPlayer(c.getController()); - ability.setCardState(sa.getCardState()); - - if (ComputerUtil.playNoStack(c.getController(), ability, getGame(), true)) { - // transfer this info for Balduvian Fallen - sa.setPayingMana(ability.getPayingMana()); + if (ComputerUtil.playNoStack(c.getController(), sa, getGame(), true)) { return true; } return false; @@ -1220,26 +1214,13 @@ public String chooseProtectionType(String string, SpellAbility sa, List @Override public boolean payCostToPreventEffect(Cost cost, SpellAbility sa, boolean alreadyPaid, FCollectionView allPayers) { - final Card source = sa.getHostCard(); - // TODO replace with EmptySa - final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } }; - emptyAbility.setActivatingPlayer(player); - emptyAbility.setTriggeringObjects(sa.getTriggeringObjects()); - emptyAbility.setReplacingObjects(sa.getReplacingObjects()); - emptyAbility.setTrigger(sa.getTrigger()); - emptyAbility.setReplacementEffect(sa.getReplacementEffect()); - emptyAbility.setSVars(sa.getSVars()); - emptyAbility.setCardState(sa.getCardState()); - emptyAbility.setXManaCostPaid(sa.getRootAbility().getXManaCostPaid()); - emptyAbility.setTargets(sa.getTargets().clone()); - - if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers)) { - boolean result = ComputerUtil.playNoStack(player, emptyAbility, getGame(), true); // AI needs something to resolve to pay that cost - if (!emptyAbility.getPaidHash().isEmpty()) { - // report info to original sa (Argentum Masticore) - sa.setPaidHash(emptyAbility.getPaidHash()); + if (SpellApiToAi.Converter.get(sa.getApi()).willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers)) { + if (!ComputerUtilCost.canPayCost(cost, sa, player, true)) { + return false; } - return result; + + final CostPayment pay = new CostPayment(cost, sa); + return pay.payComputerCosts(new AiCostDecision(player, sa, true)); } return false; } @@ -1379,7 +1360,7 @@ public List chooseCardsYouWonToAddToDeck(List losses) { @Override public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt /* ai needs hints as well */, ManaConversionMatrix matrix, boolean effect) { - return ComputerUtilMana.payManaCost(player, sa, effect); + return ComputerUtilMana.payManaCost(new Cost(toPay, effect), player, sa, effect); } @Override diff --git a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java index dbec5250a51..89d77c08663 100644 --- a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java @@ -1,7 +1,12 @@ package forge.ai; +import java.util.Collection; +import java.util.List; +import java.util.Map; + import com.google.common.collect.Iterables; import com.google.common.collect.Lists; + import forge.card.CardStateName; import forge.card.ICardFace; import forge.card.mana.ManaCost; @@ -22,10 +27,7 @@ import forge.game.spellability.SpellAbilityCondition; import forge.game.zone.ZoneType; import forge.util.MyRandom; - -import java.util.Collection; -import java.util.List; -import java.util.Map; +import forge.util.collect.FCollectionView; /** * Base class for API-specific AI logic @@ -153,7 +155,7 @@ protected boolean willPayCosts(final Player ai, final SpellAbility sa, final Cos protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) { return true; } - + protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph, final String logic) { return checkPhaseRestrictions(ai, sa, ph); @@ -167,7 +169,7 @@ protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { } return MyRandom.getRandom().nextFloat() < .8f; // random success } - + public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) { // this evaluation order is currently intentional as it does more stuff that helps avoiding some crashes if (!ComputerUtilCost.canPayCost(sa, aiPlayer, true) && !mandatory) { @@ -247,7 +249,7 @@ public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) { *

* isSorcerySpeed. *

- * + * * @param sa * a {@link forge.game.spellability.SpellAbility} object. * @return a boolean. @@ -263,7 +265,7 @@ protected static boolean isSorcerySpeed(final SpellAbility sa, Player ai) { *

* playReusable. *

- * + * * @param sa * a {@link forge.game.spellability.SpellAbility} object. * @return a boolean. @@ -276,15 +278,15 @@ protected static boolean playReusable(final Player ai, final SpellAbility sa) { if (sa instanceof AbilitySub) { return true; // This is only true for Drawbacks and triggers } - + if (!sa.getPayCosts().isReusuableResource()) { return false; } - + if (ComputerUtil.playImmediately(ai, sa)) { return true; } - + if (sa.isPwAbility() && phase.is(PhaseType.MAIN2)) { return true; } @@ -303,7 +305,7 @@ protected static boolean playReusable(final Player ai, final SpellAbility sa) { */ public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) { final AbilitySub subAb = ab.getSubAbility(); - return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb)); + return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb)); } public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map params) { @@ -311,6 +313,25 @@ public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirm return true; } + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + final Card source = sa.getHostCard(); + final String aiLogic = sa.getParam("UnlessAI"); + boolean payNever = "Never".equals(aiLogic); + boolean isMine = sa.getActivatingPlayer().equals(payer); + + if (payNever) { return false; } + + // AI will only pay when it's not already payed and only opponents abilities + if (alreadyPaid || (payers.size() > 1 && isMine)) { + return false; + } + + return ComputerUtilCost.checkLifeCost(payer, cost, source, 4, sa) + && ComputerUtilCost.checkDamageCost(payer, cost, source, 4, sa) + && (isMine || ComputerUtilCost.checkSacrificeCost(payer, cost, source, sa)) + && (isMine || ComputerUtilCost.checkDiscardCost(payer, cost, source, sa)); + } + @SuppressWarnings("unchecked") public T chooseSingleEntity(Player ai, SpellAbility sa, Collection options, boolean isOptional, Player targetedPlayer, Map params) { boolean hasPlayer = false; @@ -348,7 +369,7 @@ protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable optio System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleCard is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method"); return Iterables.getFirst(options, null); } - + protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options, Map params) { System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayer is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method"); return Iterables.getFirst(options, null); diff --git a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java index b6aa11208cc..0a580319f82 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java @@ -2,6 +2,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; + import forge.ai.*; import forge.card.CardType; import forge.card.ColorSet; @@ -12,7 +13,9 @@ import forge.game.ability.effects.AnimateEffectBase; import forge.game.card.*; import forge.game.combat.Combat; +import forge.game.cost.Cost; import forge.game.cost.CostPutCounter; +import forge.game.keyword.Keyword; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -23,6 +26,7 @@ import forge.game.staticability.StaticAbilityLayer; import forge.game.zone.ZoneType; import forge.util.FileSection; +import forge.util.collect.FCollectionView; import java.util.Arrays; import java.util.List; @@ -32,7 +36,7 @@ *

* AbilityFactoryAnimate class. *

- * + * * @author Forge * @version $Id: AbilityFactoryAnimate.java 17608 2012-10-20 22:27:27Z Max mtg $ */ @@ -169,7 +173,7 @@ protected boolean checkApiLogic(Player aiPlayer, SpellAbility sa) { bFlag |= !c.isCreature() && !c.isTapped() && (!c.hasSickness() || givesHaste || !ph.isPlayerTurn(aiPlayer)) && !c.isEquipping(); - + // for creatures that could be improved (like Figure of Destiny) if (!bFlag && c.isCreature() && ("Permanent".equals(sa.getParam("Duration")) || (!c.isTapped() && !c.isSick()))) { int power = -5; @@ -263,7 +267,7 @@ protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean ma public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map params) { return player.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2); } - + private boolean animateTgtAI(final SpellAbility sa) { final Player ai = sa.getActivatingPlayer(); final PhaseHandler ph = ai.getGame().getPhaseHandler(); @@ -272,7 +276,7 @@ private boolean animateTgtAI(final SpellAbility sa) { && sa.getPayCosts().hasSpecificCostType(CostPutCounter.class) && sa.usesTargeting() && sa.getTargetRestrictions().getMinTargets(sa.getHostCard(), sa) == 0; - + final CardType types = new CardType(true); if (sa.hasParam("Types")) { types.addAll(Arrays.asList(sa.getParam("Types").split(","))); @@ -356,11 +360,11 @@ private boolean animateTgtAI(final SpellAbility sa) { return false; } - // get the best creature to be animated + // get the best creature to be animated List maxList = Lists.newArrayList(); int maxValue = 0; for (final Map.Entry e : data.entrySet()) { - int v = e.getValue(); + int v = e.getValue(); if (v > maxValue) { maxValue = v; maxList.clear(); @@ -576,4 +580,12 @@ private void holdAnimatedTillMain2(Player ai, Card c) { private void releaseHeldTillMain2(Player ai, Card c) { AiCardMemory.forgetCard(ai, c, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2); } + + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + if (sa.isKeyword(Keyword.RIOT)) { + return !SpecialAiLogic.preferHasteForRiot(sa, payer); + } + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index d49516c1a4a..b3e6470c12a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -28,6 +28,8 @@ import forge.game.zone.ZoneType; import forge.util.Aggregates; import forge.util.MyRandom; +import forge.util.collect.FCollectionView; + import org.apache.commons.lang3.StringUtils; import java.util.*; @@ -2139,4 +2141,35 @@ private static Card doBounceOwnTriggerLogic(Player ai, CardCollection choices) { return null; } } + + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + final Card host = sa.getHostCard(); + + int lifeLoss = 0; + if (cost.hasSpecificCostType(CostDamage.class)) { + if (!payer.canLoseLife()) { + return true; + } + CostDamage damageCost = cost.getCostPartByType(CostDamage.class); + lifeLoss = ComputerUtilCombat.predictDamageTo(payer, damageCost.getAbilityAmount(sa), host, false); + if (lifeLoss == 0) { + return true; + } + } else if (cost.hasSpecificCostType(CostPayLife.class)) { + CostPayLife lifeCost = cost.getCostPartByType(CostPayLife.class); + lifeLoss = lifeCost.getAbilityAmount(sa); + } + + for (Card c : AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa)) { + if (c.isToken()) { + return false; + } + if (!c.isCreature() || c.getBasePower() < lifeLoss || payer.getLife() < lifeLoss * 2) { // costs use either pay 3 life or deal 3 damage + return false; + } + } + + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericAi.java index 5ee0ee3db12..dd578c3d128 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericAi.java @@ -75,8 +75,7 @@ protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa } @Override - public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List spells, - Map params) { + public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, List spells, Map params) { Card host = sa.getHostCard(); final Game game = host.getGame(); final String logic = sa.getParam("AILogic"); @@ -94,10 +93,8 @@ public SpellAbility chooseSingleSpellAbility(Player player, SpellAbility sa, Lis String unlessCost = sp.getParam("UnlessCost"); sp.setActivatingPlayer(sa.getActivatingPlayer()); Cost unless = new Cost(unlessCost, false); - SpellAbility paycost = new SpellAbility.EmptySa(sa.getHostCard(), player); - paycost.setPayCosts(unless); - if (ComputerUtilCost.willPayUnlessCost(sp, player, unless, false, new FCollection<>(player)) - && ComputerUtilCost.canPayCost(paycost, player, true)) { + if (SpellApiToAi.Converter.get(sp.getApi()).willPayUnlessCost(sp, player, unless, false, new FCollection<>(player)) + && ComputerUtilCost.canPayCost(unless, sp, player, true)) { return sp; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java index 8a405acaba8..af58595d327 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java @@ -26,6 +26,7 @@ import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; import forge.game.card.CardLists; +import forge.game.cost.Cost; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.player.PlayerCollection; @@ -35,6 +36,7 @@ import forge.game.staticability.StaticAbilityMustTarget; import forge.game.zone.ZoneType; import forge.util.Aggregates; +import forge.util.collect.FCollectionView; import java.util.List; import java.util.Map; @@ -325,4 +327,22 @@ protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable Card chosen = ComputerUtilCard.getBestCreatureAI(cards); return chosen != null ? chosen.getController() : Iterables.getFirst(options, null); } + + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + // Pay to gain Control + if (sa.hasParam("UnlessSwitched")) { + final Card host = sa.getHostCard(); + + final Card gameCard = host.getGame().getCardState(host, null); + if (gameCard == null + || !gameCard.isInPlay() // not in play + || payer.equals(gameCard.getController()) // already in control + ) { + return false; + } + } + + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java index 8ca5fb23402..9ed64ee8db1 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java @@ -3,11 +3,13 @@ import forge.ai.*; import forge.game.Game; import forge.game.ability.ApiType; +import forge.game.cost.Cost; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; import forge.game.spellability.Spell; import forge.game.spellability.SpellAbility; import forge.util.MyRandom; +import forge.util.collect.FCollectionView; import java.util.List; import java.util.Map; @@ -22,7 +24,7 @@ protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { String logic = sa.getParamOrDefault("AILogic", ""); if (game.getStack().isEmpty()) { - return sa.isMandatory(); + return sa.isMandatory() || "Always".equals(logic); } final SpellAbility top = game.getStack().peekAbility(); @@ -142,4 +144,23 @@ public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirm return true; } + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + final String aiLogic = sa.getParam("UnlessAI"); + if ("Never".equals(aiLogic)) { return false; } + + if (sa.hasParam("UnlessSwitched")) { + // TODO try without AI Logic flag + if ("ChainOfVapor".equals(aiLogic)) { + if (payer.getLandsInPlay().size() < 3) { + return false; + } + // TODO make better logic in to pick which opponent + if (payer.getOpponents().getCreaturesInPlay().size() < 0) { + return false; + } + } + } + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java index b83a496c6f0..1a8f4f8385c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java @@ -1,11 +1,20 @@ package forge.ai.ability; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + import forge.ai.*; import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.ability.effects.CounterEffect; import forge.game.card.Card; +import forge.game.card.CardCollectionView; +import forge.game.card.CardPredicates; import forge.game.cost.Cost; import forge.game.cost.CostDiscard; import forge.game.cost.CostExile; @@ -15,11 +24,8 @@ import forge.game.spellability.SpellAbilityStackInstance; import forge.game.zone.ZoneType; import forge.util.MyRandom; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; +import forge.util.collect.FCollectionView; -import java.util.Iterator; public class CounterAi extends SpellAbilityAi { @@ -72,10 +78,17 @@ protected boolean canPlayAI(Player ai, SpellAbility sa) { return false; } - if ("OppDiscardsHand".equals(sa.getParam("AILogic"))) { - if (topSA.getActivatingPlayer().getCardsIn(ZoneType.Hand).size() < 2) { - return false; + if (sa.hasParam("UnlessCost") && "TargetedController".equals(sa.getParamOrDefault("UnlessPayer", "TargetedController"))) { + Cost unlessCost = AbilityUtils.calculateUnlessCost(sa, sa.getParam("UnlessCost"), false); + if (unlessCost.hasSpecificCostType(CostDiscard.class)) { + CostDiscard discardCost = unlessCost.getCostPartByType(CostDiscard.class); + if ("Hand".equals(discardCost.getType())) { + if (topSA.getActivatingPlayer().getCardsIn(ZoneType.Hand).size() < 2) { + return false; + } + } } + // TODO check if Player can pay the unless cost? } sa.resetTargets(); @@ -343,4 +356,51 @@ public Pair chooseTargetSpellAbility(Game game, SpellAbil return new ImmutablePair<>(bestOption != null ? bestOption : leastBadOption, bestOption != null); } + + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + // ward or human misplay + final Card source = sa.getHostCard(); + final Game game = source.getGame(); + List spells = AbilityUtils.getDefinedSpellAbilities(source, sa.getParamOrDefault("Defined", "Targeted"), sa); + for (SpellAbility toBeCountered : spells) { + if (!toBeCountered.isCounterableBy(sa)) { + return false; + } + + if (toBeCountered.isSpell()) { + Card spellHost = toBeCountered.getHostCard(); + Card gameCard = game.getCardState(spellHost, null); + // Spell Host already left the Stack Zone + if (gameCard == null || !gameCard.isInZone(ZoneType.Stack) || !gameCard.equalsWithGameTimestamp(spellHost)) { + return false; + } + } + + // no reason to pay if we don't plan to confirm + if (toBeCountered.isOptionalTrigger() && !SpellApiToAi.Converter.get(toBeCountered.getApi()).doTriggerNoCostWithSubs(payer, toBeCountered, false)) { + return false; + } + // TODO check hasFizzled + } + CardCollectionView hand = payer.getCardsIn(ZoneType.Hand); + if (cost.hasSpecificCostType(CostDiscard.class)) { + CostDiscard discard = cost.getCostPartByType(CostDiscard.class); + String type = discard.getType(); + if (type.equals("Hand")) { + if (hand.isEmpty()) { + return true; + } + + // TODO how to check if the Spell on the Stack is more valuable than the Cards in Hand? + int spellSum = spells.stream().map(SpellAbility::getHostCard).filter(CardPredicates.CREATURES).mapToInt(ComputerUtilCard::evaluateCreature).sum(); + int handSum = hand.stream().filter(CardPredicates.CREATURES).mapToInt(ComputerUtilCard::evaluateCreature).sum(); + if (spellSum <= handSum) { + return false; + } + } + } + + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java index 8064c3329de..cc2b5c7d526 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -27,6 +27,8 @@ import forge.game.staticability.StaticAbilityMustTarget; import forge.game.zone.ZoneType; import forge.util.MyRandom; +import forge.util.collect.FCollectionView; + import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -1123,4 +1125,28 @@ public static Pair getDamagingSAToChain(Player ai, SpellA return null; } + + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, + FCollectionView payers) { + if (!payer.canLoseLife() || payer.cantLoseForZeroOrLessLife()) { + return false; + } + + final Card hostCard = sa.getHostCard(); + + final List definedSources = AbilityUtils.getDefinedCards(hostCard, sa.getParam("DamageSource"), sa); + if (definedSources == null || definedSources.isEmpty()) { + return false; + } + int dmg = AbilityUtils.calculateAmount(hostCard, sa.getParam("NumDmg"), sa); + for (Card source : definedSources) { + int predictedDamage = ComputerUtilCombat.predictDamageTo(payer, dmg, source, false); + if (payer.getLife() < predictedDamage * 1.5) { + return true; + } + } + + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java index 2fed685edb0..d61c89becdb 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java @@ -1,5 +1,7 @@ package forge.ai.ability; +import java.util.function.Predicate; + import forge.ai.*; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; @@ -14,8 +16,7 @@ import forge.game.spellability.SpellAbility; import forge.game.staticability.StaticAbilityMustTarget; import forge.game.zone.ZoneType; - -import java.util.function.Predicate; +import forge.util.collect.FCollectionView; public class DestroyAi extends SpellAbilityAi { @Override @@ -444,4 +445,20 @@ public boolean doLandForLandRemovalLogic(SpellAbility sa, Player ai, Card tgtLan } } + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + final Card host = sa.getHostCard(); + if (alreadyPaid) { + return false; + } + + if (sa.hasParam("Defined")) { + CardCollection cards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa); + if (!cards.anyMatch(CardPredicates.isController(payer))) { + return false; + } + } + + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java index 630876e1d53..78cdfcdb64b 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java @@ -5,11 +5,13 @@ import forge.game.card.*; import forge.game.combat.Combat; import forge.game.cost.Cost; +import forge.game.cost.CostDamage; import forge.game.keyword.Keyword; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; +import forge.util.collect.FCollectionView; import java.util.function.Predicate; @@ -179,4 +181,38 @@ else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCar return false; } + + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + final Card source = sa.getHostCard(); + if (payers.size() > 1) { + if (alreadyPaid) { + return false; + } + } + String valid = sa.getParamOrDefault("ValidCards", ""); + + CardCollection ailist = CardLists.getValidCards(payer.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa); + ailist = CardLists.filter(ailist, predicate); + + if (ailist.isEmpty()) { + return false; + } + + if (cost.hasSpecificCostType(CostDamage.class)) { + if (!payer.canLoseLife()) { + return false; + } + final CostDamage pay = cost.getCostPartByType(CostDamage.class); + int realDamage = ComputerUtilCombat.predictDamageTo(payer, pay.getAbilityAmount(sa), source, false); + if (realDamage > payer.getLife()) { + return false; + } + if (realDamage > ailist.size() * 3) { // three life points per one creature + return false; + } + } + + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } \ No newline at end of file diff --git a/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java b/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java index 5ce68804d0c..d9874f2443b 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java @@ -1,5 +1,9 @@ package forge.ai.ability; +import java.util.Collections; +import java.util.List; +import java.util.Map; + import forge.ai.*; import forge.game.ability.AbilityUtils; import forge.game.card.Card; @@ -7,6 +11,8 @@ import forge.game.card.CardLists; import forge.game.card.CardPredicates; import forge.game.cost.Cost; +import forge.game.cost.CostDamage; +import forge.game.cost.CostDraw; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; @@ -15,10 +21,7 @@ import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; import forge.util.MyRandom; - -import java.util.Collections; -import java.util.List; -import java.util.Map; +import forge.util.collect.FCollectionView; public class DiscardAi extends SpellAbilityAi { @@ -214,4 +217,57 @@ public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirm } return super.confirmAction(player, sa, mode, message, params); } + + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + final Card host = sa.getHostCard(); + final String aiLogic = sa.getParam("UnlessAI"); + if ("Never".equals(aiLogic)) { return false; } + + CardCollectionView hand = payer.getCardsIn(ZoneType.Hand); + + if ("Hand".equals(sa.getParam("Mode"))) { + if (hand.size() <= 2) { + return false; + } + } else { + int amount = AbilityUtils.calculateAmount(host, sa.getParam("NumCards"), sa); + // damage cost with prevention? + if (cost.hasSpecificCostType(CostDamage.class)) { + if (!payer.canLoseLife()) { + return false; + } + final CostDamage pay = cost.getCostPartByType(CostDamage.class); + int realDamage = ComputerUtilCombat.predictDamageTo(payer, pay.getAbilityAmount(sa), host, false); + if (realDamage > payer.getLife()) { + return false; + } + if (realDamage > amount * 2) { // two life points per not discarded card? + return false; + } + } + + boolean isDrawDiscard = cost.hasOnlySpecificCostType(CostDraw.class) && sa.hasParam("UnlessSwitched"); + // TODO should AI do draw + discard effects when hand is empty? + // maybe if deck supports Graveyard or discard effects? + if (hand.isEmpty()) { + return false; + } + // is it always better? + if (isDrawDiscard) { + // check to not deck yourself + int libSize = payer.getCardsIn(ZoneType.Library).size(); + if (amount >= libSize - 3) { + if (payer.isCardInPlay("Laboratory Maniac") && !payer.cantWin()) { + return true; + } + // Don't deck yourself + return false; + } + return true; + } + } + + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java index 874725ea939..736ca53a659 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -18,6 +18,8 @@ */ package forge.ai.ability; +import java.util.Map; + import forge.ai.*; import forge.game.Game; import forge.game.ability.AbilityUtils; @@ -31,8 +33,8 @@ import forge.game.player.*; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; - -import java.util.Map; +import forge.util.MyRandom; +import forge.util.collect.FCollectionView; public class DrawAi extends SpellAbilityAi { @@ -547,4 +549,36 @@ public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirm // except it has Laboratory Maniac return player.isCardInPlay("Laboratory Maniac"); } + + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + final Card host = sa.getHostCard(); + final String aiLogic = sa.getParam("UnlessAI"); + + if ("LowPriority".equals(aiLogic) && MyRandom.getRandom().nextInt(100) < 67) { + return false; + } + + // Risk Factor Effects + for (Player p : AbilityUtils.getDefinedPlayers(host, sa.getParam("Defined"), sa)) { + if (p.isOpponentOf(payer)) { + if (!p.canDraw()) { + return false; + } + if (cost.hasSpecificCostType(CostDamage.class)) { + if (!payer.canLoseLife()) { + continue; + } + final CostDamage pay = cost.getCostPartByType(CostDamage.class); + int realDamage = ComputerUtilCombat.predictDamageTo(payer, pay.getAbilityAmount(sa), host, false); + if (payer.getLife() < realDamage * 2) { + return false; + } + } + } + } + // TODO add logic for Discard + Draw Effects + + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java index 263ce13df5a..13192cdeeb5 100644 --- a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java @@ -11,6 +11,7 @@ import forge.game.card.*; import forge.game.combat.Combat; import forge.game.combat.CombatUtil; +import forge.game.cost.Cost; import forge.game.keyword.Keyword; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; @@ -26,6 +27,7 @@ import forge.game.zone.ZoneType; import forge.util.MyRandom; import forge.util.TextUtil; +import forge.util.collect.FCollectionView; import java.util.ArrayList; import java.util.List; @@ -637,4 +639,19 @@ protected boolean cantRegenerateCheckStack(Card host) { return false; } + + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + final String aiLogic = sa.getParam("UnlessAI"); + if ("WillAttack".equals(aiLogic)) { + // TODO use AiController::getPredictedCombat + AiAttackController aiAtk = new AiAttackController(payer); + Combat combat = new Combat(payer); + aiAtk.declareAttackers(combat); + if (combat.getAttackers().isEmpty()) { + return false; + } + } + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java index a26f360ddee..4c4f2b7096b 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java @@ -13,6 +13,7 @@ import forge.game.player.PlayerPredicates; import forge.game.spellability.SpellAbility; import forge.util.collect.FCollection; +import forge.util.collect.FCollectionView; import java.util.List; @@ -193,6 +194,34 @@ protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, return mandatory || !tgtPlayers.contains(ai) || amount <= 0 || amount + 3 <= ai.getLife(); } + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, + FCollectionView payers) { + if (!payer.canLoseLife() || payer.cantLoseForZeroOrLessLife()) { + return false; + } + + final Card source = sa.getHostCard(); + + // Withercrown should be sacrificed early? + if (source.canBeSacrificedBy(sa, true) && cost.hasOnlySpecificCostType(CostSacrifice.class)) { + CostSacrifice costSac = cost.getCostPartByType(CostSacrifice.class); + if (costSac.payCostFromSource()) { + return true; + } + } + int n = AbilityUtils.calculateAmount(source, sa.getParam("LifeAmount"), sa); + // what should be the limit where AI stops letting it lose life? + // TODO predict lose life modifier + // also check if life loss would trigger life gain for Activating Player + // and that resulting in another life loss + if (payer.getLife() < 2 * n) { + return true; + } + + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } + protected boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) { sa.resetTargets(); PlayerCollection opps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa)); diff --git a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java index 5216bda3118..5313bef01ee 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java @@ -7,6 +7,8 @@ import forge.card.CardStateName; import forge.card.CardType.Supertype; import forge.card.mana.ManaCost; +import forge.game.ability.AbilityUtils; +import forge.game.ability.ApiType; import forge.game.card.*; import forge.game.cost.Cost; import forge.game.keyword.Keyword; @@ -16,6 +18,8 @@ import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerType; import forge.game.zone.ZoneType; import org.apache.commons.lang3.StringUtils; @@ -110,7 +114,7 @@ protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { if ("SacToReduceCost".equals(sa.getParam("AILogic"))) { // reset X to better calculate sa.setXManaCostPaid(0); - ManaCostBeingPaid paidCost = ComputerUtilMana.calculateManaCost(sa, true, 0); + ManaCostBeingPaid paidCost = ComputerUtilMana.calculateManaCost(sa.getPayCosts(), sa, true, 0, false); int generic = paidCost.getGenericManaAmount(); // Set PayX here to maximum value. @@ -181,22 +185,39 @@ protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { } // don't play cards without being able to pay the upkeep for - for (KeywordInterface inst : source.getKeywords()) { - String ability = inst.getOriginal(); - if (ability.startsWith("UpkeepCost")) { - final String[] k = ability.split(":"); - final String costs = k[1]; - - final SpellAbility emptyAbility = new SpellAbility.EmptySa(source, ai); - emptyAbility.setPayCosts(new Cost(costs, true)); - emptyAbility.setTargetRestrictions(sa.getTargetRestrictions()); - emptyAbility.setCardState(sa.getCardState()); - emptyAbility.setActivatingPlayer(ai); + boolean hasUpkeepCost = false; + Cost upkeepCost = new Cost("0", true); + for (Trigger t : source.getTriggers()) { + if (!TriggerType.Phase.equals(t.getMode())) { + continue; + } + if (!"Upkeep".equals(t.getParam("Phase"))) { + continue; + } + SpellAbility ab = t.ensureAbility(); + if (ab == null) { + continue; + } - if (!ComputerUtilCost.canPayCost(emptyAbility, ai, true)) { - // AiPlayDecision.AnotherTime - return false; + if (ApiType.Sacrifice.equals(ab.getApi())) { + if (!ab.hasParam("UnlessCost")) { + continue; } + hasUpkeepCost = true; + upkeepCost.add(AbilityUtils.calculateUnlessCost(ab, ab.getParam("UnlessCost"), true)); + } + } + + if (hasUpkeepCost) { + final SpellAbility emptyAbility = new SpellAbility.EmptySa(source, ai); + emptyAbility.setPayCosts(upkeepCost); + emptyAbility.setTargetRestrictions(sa.getTargetRestrictions()); + emptyAbility.setCardState(sa.getCardState()); + emptyAbility.setActivatingPlayer(ai); + + if (!ComputerUtilCost.canPayCost(emptyAbility, ai, true)) { + // AiPlayDecision.AnotherTime + return false; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java index 515345c5b88..bdcf719dab8 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java @@ -9,6 +9,7 @@ import forge.game.card.CardCollection; import forge.game.card.CardLists; import forge.game.card.CardPredicates; +import forge.game.cost.Cost; import forge.game.keyword.Keyword; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; @@ -16,6 +17,7 @@ import forge.game.player.PlayerPredicates; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; +import forge.util.collect.FCollectionView; import java.util.List; import java.util.Map; @@ -201,4 +203,17 @@ public static boolean doSacOneEachLogic(Player ai, SpellAbility sa) { return true; } + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + // Icy Prison + if (payers.size() > 1) { + final Player p = sa.getActivatingPlayer(); + // not me or team mate + if (!p.sameTeam(payer)) { + return false; + } + } + + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java b/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java index 70e71c05114..958f17bd91e 100644 --- a/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java @@ -1,9 +1,11 @@ package forge.ai.ability; import forge.ai.SpellAbilityAi; +import forge.game.cost.Cost; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.trigger.WrappedAbility; +import forge.util.collect.FCollectionView; public class StoreSVarAi extends SpellAbilityAi { @@ -24,4 +26,17 @@ protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean ma return true; } + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + // Join Forces cards + if (sa.hasParam("UnlessSwitched") && payers.size() > 1) { + final Player p = sa.getActivatingPlayer(); + // not me or team mate + if (!p.sameTeam(payer)) { + return false; + } + } + + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/TapAi.java b/forge-ai/src/main/java/forge/ai/ability/TapAi.java index 9422efdc341..7a2b6c2d8eb 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TapAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TapAi.java @@ -1,16 +1,21 @@ package forge.ai.ability; import forge.ai.*; +import forge.card.ColorSet; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardLists; +import forge.game.combat.CombatUtil; import forge.game.cost.Cost; +import forge.game.cost.CostPart; +import forge.game.cost.CostPayLife; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; +import forge.util.collect.FCollectionView; public class TapAi extends TapAiBase { @Override @@ -84,4 +89,51 @@ protected boolean canPlayAI(Player ai, SpellAbility sa) { } } + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + // Check for shocklands and similar ETB replacement effects + if (sa.hasParam("ETB")) { + final Card source = sa.getHostCard(); + for (final CostPart part : cost.getCostParts()) { + if (part instanceof CostPayLife) { + final CostPayLife lifeCost = (CostPayLife) part; + Integer amount = lifeCost.convertAmount(); + if (payer.getLife() > (amount + 1) && payer.canPayLife(amount, true, sa)) { + final int landsize = payer.getLandsInPlay().size() + 1; + for (Card c : payer.getCardsIn(ZoneType.Hand)) { + // Check if the AI has enough lands to play the card + if (landsize != c.getCMC()) { + continue; + } + // Check if the AI intends to play the card and if it can pay for it with the mana it has + boolean willPlay = ComputerUtil.hasReasonToPlayCardThisTurn(payer, c); + boolean canPay = c.getManaCost().canBePaidWithAvailable(ColorSet.fromNames(ComputerUtilCost.getAvailableManaColors(payer, source)).getColor()); + if (canPay && willPlay) { + return true; + } + } + } + return false; + } + } + } else if (sa.hasParam("UnlessSwitched")) { + // effect is each opponent may sacrifice to tap creature + Card source = sa.getHostCard(); + if (alreadyPaid) { + return false; + } + // if it can't attack the payer, do nothing? + // TODO check if it can attack team mates? + if (!CombatUtil.canAttack(source, payer)) { + return false; + } + + // predict combat damage + int dmg = ComputerUtilCombat.damageIfUnblocked(source, payer, null, false); + if (payer.getLife() < dmg * 1.5) { + return true; + } + } + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java index c2ff7d8d5d7..5916434e971 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java @@ -1,6 +1,11 @@ package forge.ai.ability; + +import java.util.List; +import java.util.Map; + import com.google.common.collect.Iterables; + import forge.ai.*; import forge.game.Game; import forge.game.GameEntity; @@ -10,6 +15,8 @@ import forge.game.card.token.TokenInfo; import forge.game.combat.Combat; import forge.game.combat.CombatUtil; +import forge.game.cost.Cost; +import forge.game.cost.CostDraw; import forge.game.cost.CostPart; import forge.game.cost.CostPutCounter; import forge.game.cost.CostRemoveCounter; @@ -25,15 +32,13 @@ import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType; import forge.util.MyRandom; - -import java.util.List; -import java.util.Map; +import forge.util.collect.FCollectionView; /** *

* AbilityFactory_Token class. *

- * + * * @author Forge * @version $Id: AbilityFactoryToken.java 17656 2012-10-22 19:32:56Z Max mtg $ */ @@ -101,7 +106,7 @@ protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, if (canInterruptSacrifice(ai, sa, actualToken, tokenAmount)) { return true; } - + boolean haste = actualToken.hasKeyword(Keyword.HASTE); boolean oneShot = sa.getSubAbility() != null && sa.getSubAbility().getApi() == ApiType.DelayedTrigger; @@ -127,7 +132,7 @@ protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, } return (!ph.getPhase().isAfter(PhaseType.COMBAT_BEGIN) && ph.isPlayerTurn(ai)) || !oneShot; } - + @Override protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { /* @@ -400,4 +405,106 @@ private boolean tgtRoleAura(final Player ai, final SpellAbility sa, final Card t return false; } + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + final Card source = sa.getHostCard(); + Player p = sa.getActivatingPlayer(); + if (sa.isKeyword(Keyword.FABRICATE)) { + final int n = Integer.parseInt(sa.getParam("TokenAmount")); + + // if host would leave the play or if host is useless, create tokens + if (source.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(payer, source)) { + return false; + } + + // need a copy for one with extra +1/+1 counter boost, + // without causing triggers to run + final Card copy = CardCopyService.getLKICopy(source); + copy.setCounters(CounterEnumType.P1P1, copy.getCounters(CounterEnumType.P1P1) + n); + copy.setZone(source.getZone()); + + // if host would put into the battlefield attacking + Combat combat = source.getGame().getCombat(); + if (combat != null && combat.isAttacking(source)) { + final Player defender = combat.getDefenderPlayerByAttacker(source); + if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy, true)) { + return true; + } + return false; + } + + // if the host has haste and can attack + if (CombatUtil.canAttack(copy)) { + for (final Player opp : payer.getOpponents()) { + if (CombatUtil.canAttack(copy, opp) && + opp.canLoseLife() && + !ComputerUtilCard.canBeBlockedProfitably(opp, copy, true)) + return true; + } + } + + // TODO check for trigger to turn token ETB into +1/+1 counter for host + // TODO check for trigger to turn token ETB into damage or life loss for opponent + // in this cases Token might be prefered even if they would not survive + final Card tokenCard = TokenAi.spawnToken(payer, sa); + + // Token would not survive + if (!tokenCard.isCreature() || tokenCard.getNetToughness() < 1) { + return true; + } + + // Special Card logic, this one try to median its power with the number of artifacts + if ("Marionette Master".equals(source.getName())) { + CardCollection list = CardLists.filter(payer.getCardsIn(ZoneType.Battlefield), CardPredicates.ARTIFACTS); + return list.size() >= copy.getNetPower(); + } else if ("Cultivator of Blades".equals(source.getName())) { + // Cultivator does try to median with number of Creatures + CardCollection list = payer.getCreaturesInPlay(); + return list.size() >= copy.getNetPower(); + } + + // evaluate Creature with +1/+1 + int evalCounter = ComputerUtilCard.evaluateCreature(copy); + + final CardCollection tokenList = new CardCollection(source); + for (int i = 0; i < n; ++i) { + tokenList.add(TokenAi.spawnToken(payer, sa)); + } + + // evaluate Host with Tokens + int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList); + + return evalToken < evalCounter; + } + + // Development effect, Payer can let Opponent draw, or they get a token + if (payer.isOpponentOf(sa.getActivatingPlayer())) { + if (cost.hasSpecificCostType(CostDraw.class)) { + CostDraw draw = cost.getCostPartByType(CostDraw.class); + // try to deck out opponent + if (draw.getPotentialPlayers(payer, sa).contains(p) && p.getCardsIn(ZoneType.Library).size() < 5) { + if (!p.isCardInPlay("Laboratory Maniac") || p.cantWin()) { + return true; + } + } + } + + if (alreadyPaid) { + return false; + } + final Card tokenCard = TokenAi.spawnToken(p, sa); + + // Token would not survive + if (!tokenCard.isCreature() || tokenCard.getNetToughness() < 1) { + return false; + } + int evalActivator = ComputerUtilCard.evaluateCreature(tokenCard) + ComputerUtilCard.evaluateCreatureList(p.getCreaturesInPlay());; + int evalPayerCreatures = ComputerUtilCard.evaluateCreatureList(payer.getCreaturesInPlay()); + + if (evalActivator > evalPayerCreatures) { + return true; + } + } + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java index 72e08815282..8d2267d2a00 100644 --- a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java @@ -11,6 +11,7 @@ import forge.game.card.CardPredicates; import forge.game.combat.Combat; import forge.game.cost.Cost; +import forge.game.cost.CostPartMana; import forge.game.cost.CostTap; import forge.game.mana.ManaCostBeingPaid; import forge.game.phase.PhaseHandler; @@ -21,6 +22,7 @@ import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType; +import forge.util.collect.FCollectionView; import java.util.List; import java.util.Map; @@ -348,7 +350,6 @@ private static Card detectPriorityUntapTargets(final List untapList) { private boolean doPreventCombatDamageLogic(final Player ai, final SpellAbility sa) { // Only Maze of Ith and Maze of Shadows uses this. Feel free to use it aggressively. Game game = ai.getGame(); - Card source = sa.getHostCard(); sa.resetTargets(); if (!game.getPhaseHandler().getPlayerTurn().isOpponentOf(ai)) { @@ -464,4 +465,29 @@ private boolean doPoolExtraManaLogic(final Player ai, final SpellAbility sa) { // haven't found any immediate playable options } + @Override + public boolean willPayUnlessCost(SpellAbility sa, Player payer, Cost cost, boolean alreadyPaid, FCollectionView payers) { + // Paralyze effects + if (sa.hasParam("UnlessSwitched")) { + final Card host = sa.getHostCard(); + final Game game = host.getGame(); + for (Card card : AbilityUtils.getDefinedCards(host, null, sa)) { + final Card gameCard = game.getCardState(card, null); + if (gameCard == null + || !gameCard.isInPlay() // not in play + || gameCard.isUntapped() // already untapped + ) { + return false; + } + + // if the ManaCost would cost more than the creatures CMC, it is not worth it + CostPartMana mana = cost.getCostMana(); + if (mana != null && mana.getManaCostFor(sa).getCMC() > card.getCMC()) { + return false; + } + } + } + + return super.willPayUnlessCost(sa, payer, cost, alreadyPaid, payers); + } } diff --git a/forge-core/src/main/java/forge/card/CardRulesPredicates.java b/forge-core/src/main/java/forge/card/CardRulesPredicates.java index 838587234fc..ed439d0419c 100644 --- a/forge-core/src/main/java/forge/card/CardRulesPredicates.java +++ b/forge-core/src/main/java/forge/card/CardRulesPredicates.java @@ -253,7 +253,7 @@ public static Predicate hasColorIdentity(final int colormask) { } public static Predicate canBePartnerCommanderWith(final CardRules commander) { - return (rules) -> rules.canBePartnerCommanders(commander); + return rules -> rules.canBePartnerCommanders(commander); } private static class LeafString extends PredicateString { diff --git a/forge-core/src/main/java/forge/util/collect/FCollection.java b/forge-core/src/main/java/forge/util/collect/FCollection.java index 1c4b0f90125..824184fec03 100644 --- a/forge-core/src/main/java/forge/util/collect/FCollection.java +++ b/forge-core/src/main/java/forge/util/collect/FCollection.java @@ -549,17 +549,17 @@ public T get(final T obj) { @Override public Stream stream() { - return list.stream(); + return list.stream(); } @Override public boolean anyMatch(Predicate test) { - return set.stream().anyMatch(test); + return set.stream().anyMatch(test); } @Override public boolean allMatch(Predicate test) { - return set.stream().allMatch(test); + return set.stream().allMatch(test); } /** diff --git a/forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java index 4ea52020a48..f2d038a1861 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ActivateAbilityEffect.java @@ -5,7 +5,6 @@ import org.apache.commons.lang3.StringUtils; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; @@ -45,7 +44,7 @@ public void resolve(SpellAbility sa) { List list = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card")); for (Card c : list) { - List possibleAb = Lists.newArrayList(c.getAllPossibleAbilities(p, true)); + List possibleAb = c.getAllPossibleAbilities(p, true); if (isManaAb) { possibleAb.retainAll((FCollection)c.getManaAbilities()); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index d9c0a138c11..024577d3ee1 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -880,13 +880,7 @@ private void changeKnownOriginResolve(final SpellAbility sa) { * a {@link forge.game.spellability.SpellAbility} object. */ private void changeHiddenOriginResolve(final SpellAbility sa) { - List fetchers; - - if (sa.hasParam("DefinedPlayer")) { - fetchers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("DefinedPlayer"), sa); - } else { - fetchers = Lists.newArrayList(sa.getActivatingPlayer()); - } + List fetchers = AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("DefinedPlayer"), sa); Player chooser = null; if (sa.hasParam("Chooser")) { @@ -1059,7 +1053,7 @@ else if (origin.contains(ZoneType.Hand) && player.isOpponentOf(decider)) { handleCastWhileSearching(fetchList, decider); } final Map runParams = AbilityKey.mapFromPlayer(decider); - runParams.put(AbilityKey.Target, Lists.newArrayList(player)); + runParams.put(AbilityKey.Target, player); game.getTriggerHandler().runTrigger(TriggerType.SearchedLibrary, runParams, false); } if (searchedLibrary && sa.hasParam("Searched")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java index 30ac9ee1888..cd2de5f8362 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java @@ -22,7 +22,6 @@ public class ChooseCardNameEffect extends SpellAbilityEffect { @Override protected String getStackDescription(SpellAbility sa) { - return Lang.joinHomogenous(getTargetPlayers(sa)) + " names a card."; } @@ -58,21 +57,6 @@ public void resolve(SpellAbility sa) { continue; } String chosen; - //This section was used for Momir Avatar, which no longer uses it - commented out 7/28/2021 - //if (randomChoice) { - //String numericAmount = "X"; - //final int validAmount = StringUtils.isNumeric(numericAmount) ? Integer.parseInt(numericAmount) : - // AbilityUtils.calculateAmount(host, numericAmount, sa); - // Momir needs PaperCard - //Collection cards = StaticData.instance().getCommonCards().getUniqueCards(); - //Predicate cpp = Predicates.and( - // Predicates.compose(CardRulesPredicates.IS_CREATURE, PaperCard.FN_GET_RULES), - // Predicates.compose(CardRulesPredicates.cmc(ComparableOp.EQUALS, validAmount), PaperCard.FN_GET_RULES)); - //cards = Lists.newArrayList(Iterables.filter(cards, cpp)); - //if (!cards.isEmpty()) { chosen = Aggregates.random(cards).getName(); - //} else { - // chosen = ""; - //} if (chooseFromDefined) { CardCollection choices = AbilityUtils.getDefinedCards(host, sa.getParam("ChooseFromDefinedCards"), sa); choices = CardLists.getValidCards(choices, valid, host.getController(), host, sa); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java index 9e6a7e87c88..65fb4634f6f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java @@ -2,12 +2,11 @@ import java.util.List; -import com.google.common.collect.Lists; - import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; +import forge.game.card.CardCollectionView; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.util.CardTranslation; @@ -23,9 +22,9 @@ public class ControlExchangeEffect extends SpellAbilityEffect { protected String getStackDescription(SpellAbility sa) { Card object1 = null; Card object2 = null; - List tgts = null; + CardCollectionView tgts = null; if (sa.usesTargeting()) { - tgts = Lists.newArrayList(sa.getTargets().getTargetCards()); + tgts = sa.getTargets().getTargetCards(); if (tgts.size() > 0) { object1 = tgts.get(0); } @@ -57,9 +56,9 @@ public void resolve(SpellAbility sa) { Card object1 = null; Card object2 = null; - List tgts = null; + CardCollectionView tgts = null; if (sa.usesTargeting()) { - tgts = Lists.newArrayList(sa.getTargets().getTargetCards()); + tgts = sa.getTargets().getTargetCards(); if (tgts.size() > 0) { object1 = tgts.get(0); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java index c51968e750e..cc926cdd2a8 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java @@ -72,10 +72,7 @@ public void resolve(SpellAbility sa) { return; } - List controllers = Lists.newArrayList(sa.getActivatingPlayer()); - if (sa.hasParam("Controller")) { - controllers = AbilityUtils.getDefinedPlayers(card, sa.getParam("Controller"), sa); - } + List controllers = AbilityUtils.getDefinedPlayers(card, sa.getParam("Controller"), sa); boolean isOptional = sa.hasParam("Optional"); diff --git a/forge-game/src/main/java/forge/game/ability/effects/DrawEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DrawEffect.java index 6f51da53447..cede75c96f2 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DrawEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DrawEffect.java @@ -65,7 +65,7 @@ public void resolve(SpellAbility sa) { final List tgts = getTargetPlayersWithDuplicates(true, "Defined", sa); - for (final Player p : Sets.newHashSet(tgts)) { + for (final Player p : Sets.newLinkedHashSet(tgts)) { if (!p.isInGame()) { continue; } diff --git a/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java index bd34b2310c0..53bff88b5e0 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java @@ -98,7 +98,7 @@ private Card getNeighboringCard(Card c, int direction) { // as well as the current card attachments that are visually located next to the requested card or are assumed to be near it. Player controller = c.getController(); ArrayList attachments = Lists.newArrayList(); - ArrayList cardsOTB = Lists.newArrayList(CardLists.filter( + CardCollection cardsOTB = CardLists.filter( controller.getCardsIn(ZoneType.Battlefield), card -> { if (card.isAttachedToEntity(c)) { attachments.add(card); @@ -114,7 +114,7 @@ private Card getNeighboringCard(Card c, int direction) { } return card.sharesCardTypeWith(c); } - )); + ); // Chance to hit an attachment float hitAttachment = 0.50f; diff --git a/forge-game/src/main/java/forge/game/ability/effects/LifeGainEffect.java b/forge-game/src/main/java/forge/game/ability/effects/LifeGainEffect.java index 2e960e40f6f..330fc305093 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/LifeGainEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/LifeGainEffect.java @@ -26,7 +26,7 @@ protected String getStackDescription(SpellAbility sa) { sb.append(Lang.joinHomogenous(getDefinedPlayersOrTargeted(sa))); if (sb.length() == 0 && spellDesc != null) { - return (spellDesc); + return spellDesc; } else { sb.append(getDefinedPlayersOrTargeted(sa).size() > 1 ? " gain " : " gains "); if (!StringUtils.isNumeric(amountStr) && spellDesc != null && spellDesc.contains("life equal to")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java index 67bfc46627a..f521b0b8aef 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java @@ -29,7 +29,7 @@ public void resolve(SpellAbility sa) { List restartZones = new ArrayList<>(Arrays.asList(ZoneType.Battlefield, ZoneType.Library, ZoneType.Graveyard, ZoneType.Hand, ZoneType.Exile)); - ZoneType leaveZone = ZoneType.smartValueOf(sa.hasParam("RestrictFromZone") ? sa.getParam("RestrictFromZone") : null); + ZoneType leaveZone = ZoneType.smartValueOf(sa.getParam("RestrictFromZone")); restartZones.remove(leaveZone); String leaveRestriction = sa.getParamOrDefault("RestrictFromValid", "Card"); diff --git a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java index 6997822971e..313261f7459 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java @@ -21,7 +21,7 @@ import forge.game.cost.Cost; import forge.game.keyword.Keyword; import forge.game.player.Player; -import forge.game.player.PlayerController.ManaPaymentPurpose; +import forge.game.player.PlayerCollection; import forge.game.spellability.SpellAbility; import forge.game.trigger.TriggerType; import forge.game.zone.ZoneType; @@ -42,8 +42,7 @@ public void resolve(SpellAbility sa) { && activator.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantPayEcho") + " {0}?", null)) { isPaid = true; } else { - isPaid = activator.getController().payManaOptional(host, new Cost(sa.getParam("Echo"), true), - sa, Localizer.getInstance().getMessage("lblPayEcho"), ManaPaymentPurpose.Echo); + isPaid = activator.getController().payCostToPreventEffect(new Cost(sa.getParam("Echo"), true), sa, false, new PlayerCollection(activator)); } final Map runParams = AbilityKey.mapFromCard(host); runParams.put(AbilityKey.EchoPaid, isPaid); @@ -66,10 +65,7 @@ public void resolve(SpellAbility sa) { game.updateLastStateForCard(host); - StringBuilder sb = new StringBuilder(); - sb.append("Cumulative upkeep for ").append(host); - - boolean isPaid = activator.getController().payManaOptional(host, payCost, sa, sb.toString(), ManaPaymentPurpose.CumulativeUpkeep); + boolean isPaid = activator.getController().payCostToPreventEffect(payCost, sa, false, new PlayerCollection(activator)); final Map runParams = AbilityKey.mapFromCard(host); runParams.put(AbilityKey.CumulativeUpkeepPaid, isPaid); runParams.put(AbilityKey.PayingMana, StringUtils.join(sa.getPayingMana(), "")); diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index fa73243ad80..eee430d863f 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -2459,8 +2459,7 @@ public final String keywordsToText(final Collection keywords) sbLong.append(inst.getReminderText()).append("\r\n"); } else if (keyword.startsWith("Strive") || keyword.startsWith("Escalate") || keyword.startsWith("ETBReplacement") - || keyword.startsWith("Affinity") - || keyword.startsWith("UpkeepCost")) { + || keyword.startsWith("Affinity")) { } else if (keyword.equals("Provoke") || keyword.equals("Ingest") || keyword.equals("Unleash") || keyword.equals("Soulbond") || keyword.equals("Partner") || keyword.equals("Retrace") || keyword.equals("Living Weapon") || keyword.equals("Myriad") || keyword.equals("Exploit") diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index dd27c45bd26..043b0183e9b 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -1151,15 +1151,12 @@ public static void addTriggerAbility(final KeywordInterface inst, final Card car final String[] k = keyword.split(":"); final String n = k[1]; - final String name = StringUtils.join(k); - final String trigStr = "Mode$ ChangesZone | Destination$ Battlefield " + " | ValidCard$ Card.Self | Secondary$ True" + " | TriggerDescription$ Fabricate " + n + " (" + inst.getReminderText() + ")"; final String token = "DB$ Token | TokenAmount$ " + n + " | TokenScript$ c_1_1_a_servo" - + " | UnlessCost$ AddCounter<" + n + "/P1P1> | UnlessPayer$ You | UnlessAI$ " + name - + " | SpellDescription$ Fabricate - Create " + + " | UnlessCost$ AddCounter<" + n + "/P1P1> | UnlessPayer$ You | SpellDescription$ Fabricate - Create " + Lang.nounWithNumeral(n, "1/1 colorless Servo artifact creature token") + "."; final Trigger trigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic); @@ -1941,29 +1938,6 @@ public static void addTriggerAbility(final KeywordInterface inst, final Card car final Trigger parsedTrigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic); parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(effect, card)); - inst.addTrigger(parsedTrigger); - } else if (keyword.startsWith("UpkeepCost")) { - final String[] k = keyword.split(":"); - final Cost cost = new Cost(k[1], true); - - final StringBuilder sb = new StringBuilder(); - sb.append("At the beginning of your upkeep, sacrifice CARDNAME unless you "); - if (cost.isOnlyManaCost()) { - sb.append("pay "); - } - final String costStr = k.length == 3 ? k[2] : cost.toSimpleString(); - - sb.append(costStr.substring(0, 1).toLowerCase()).append(costStr.substring(1)); - sb.append("."); - - String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | " + - "TriggerDescription$ " + sb.toString(); - - String effect = "DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ " + k[1]; - - final Trigger parsedTrigger = TriggerHandler.parseTrigger(upkeepTrig, card, intrinsic); - parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(effect, card)); - inst.addTrigger(parsedTrigger); } else if (keyword.startsWith("Vanishing")) { // Remove Time counter trigger @@ -2511,7 +2485,7 @@ public static void addReplacementEffect(final KeywordInterface inst, final CardS inst.addReplacement(cardre); } else if (keyword.equals("Riot")) { - final String hasteStr = "DB$ Animate | Defined$ Self | Keywords$ Haste | Duration$ Permanent | UnlessCost$ AddCounter<1/P1P1> | UnlessPayer$ You | UnlessAI$ Riot | SpellDescription$ Riot"; + final String hasteStr = "DB$ Animate | Defined$ Self | Keywords$ Haste | Duration$ Permanent | UnlessCost$ AddCounter<1/P1P1> | UnlessPayer$ You | SpellDescription$ Riot"; final SpellAbility hasteSa = AbilityFactory.getAbility(hasteStr, card); ReplacementEffect cardre = createETBReplacement(card, ReplacementLayer.Other, hasteSa, false, true, intrinsic, "Card.Self", ""); diff --git a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java index 5a2f9fcefb7..6ee1753b7be 100644 --- a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java +++ b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java @@ -33,8 +33,8 @@ public class CostAdjustment { - public static Cost adjust(final Cost cost, final SpellAbility sa) { - if (sa.isTrigger() || cost == null) { + public static Cost adjust(final Cost cost, final SpellAbility sa, boolean effect) { + if (sa.isTrigger() || cost == null || effect) { return cost; } diff --git a/forge-game/src/main/java/forge/game/cost/CostPayment.java b/forge-game/src/main/java/forge/game/cost/CostPayment.java index d6a47c75ca9..67f14630c18 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPayment.java +++ b/forge-game/src/main/java/forge/game/cost/CostPayment.java @@ -97,7 +97,7 @@ public static boolean canPayAdditionalCosts(Cost cost, final SpellAbility abilit return true; } - cost = CostAdjustment.adjust(cost, ability); + cost = CostAdjustment.adjust(cost, ability, effect); return cost.canPay(ability, effect); } @@ -129,7 +129,7 @@ public final void refundPayment() { } public boolean payCost(final CostDecisionMakerBase decisionMaker) { - adjustedCost = CostAdjustment.adjust(cost, ability); + adjustedCost = CostAdjustment.adjust(cost, ability, decisionMaker.isEffect()); List costParts = adjustedCost.getCostPartsWithZeroMana(); if (adjustedCost.getCostParts().size() > 1) { @@ -177,7 +177,7 @@ public final boolean payComputerCosts(final CostDecisionMakerBase decisionMaker) Map decisions = Maps.newHashMap(); // for Trinisphere make sure to include Zero - List parts = CostAdjustment.adjust(cost, ability).getCostPartsWithZeroMana(); + List parts = CostAdjustment.adjust(cost, ability, decisionMaker.isEffect()).getCostPartsWithZeroMana(); // Set all of the decisions before attempting to pay anything diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index a9906359a34..e03ae69a7c0 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -733,6 +733,7 @@ public final int addDamageAfterPrevention(final int amount, final Card source, f // not change the game state) // 2012/01/02: No longer used in calculating the finalized damage, but // retained for damageprediction. -Hellfish + // TODO make this more generic in looking at the ReplacementEffects @Override public final int staticReplaceDamage(final int damage, final Card source, final boolean isCombat) { int restDamage = damage; diff --git a/forge-game/src/main/java/forge/game/player/PlayerController.java b/forge-game/src/main/java/forge/game/player/PlayerController.java index 4f77bb7197a..75bfd74954f 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerController.java +++ b/forge-game/src/main/java/forge/game/player/PlayerController.java @@ -50,10 +50,7 @@ public abstract class PlayerController { public enum ManaPaymentPurpose { DeclareAttacker, - DeclareBlocker, - Echo, - Multikicker, - CumulativeUpkeep + DeclareBlocker } public enum BinaryChoiceType { diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java index 742a5dc318d..5e58e9b6a22 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -252,7 +252,7 @@ public void addNoCounterEffect(SpellAbility saBeingPaid) { eff.setColor(MagicColor.COLORLESS); eff.setGamePieceType(GamePieceType.EFFECT); - String cantcounterstr = "Event$ Counter | ValidCard$ Card.IsRemembered | Description$ That spell can't be countered."; + String cantcounterstr = "Event$ Counter | ValidSA$ Spell.IsRemembered | Description$ That spell can't be countered."; ReplacementEffect re = ReplacementHandler.parseReplacement(cantcounterstr, eff, true); re.setLayer(ReplacementLayer.CantHappen); eff.addReplacementEffect(re); diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index d45ef97b333..480c59dcb6f 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -453,11 +453,9 @@ public Player getActivatingPlayer() { } public void setActivatingPlayer(final Player player) { // trickle down activating player - boolean updated = false; // don't use equals because player might be from simulation if (player == null || player != activatingPlayer) { activatingPlayer = player; - updated = true; } if (subAbility != null) { subAbility.setActivatingPlayer(player); diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerSearchedLibrary.java b/forge-game/src/main/java/forge/game/trigger/TriggerSearchedLibrary.java index 026aa9bce8e..508e6fa2ff9 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerSearchedLibrary.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerSearchedLibrary.java @@ -17,7 +17,6 @@ */ package forge.game.trigger; -import java.util.List; import java.util.Map; import forge.game.ability.AbilityKey; @@ -60,9 +59,8 @@ public final boolean performTest(final Map runParams) { return false; } if (hasParam("SearchOwnLibrary")) { - @SuppressWarnings("unchecked") - List targets = (List) runParams.get(AbilityKey.Target); - if (!targets.contains(runParams.get(AbilityKey.Player))) { + Player target = (Player) runParams.get(AbilityKey.Target); + if (!target.equals(runParams.get(AbilityKey.Player))) { return false; } } diff --git a/forge-gui/MANUAL.txt b/forge-gui/MANUAL.txt index 58652319817..7462da47ea1 100644 --- a/forge-gui/MANUAL.txt +++ b/forge-gui/MANUAL.txt @@ -458,7 +458,7 @@ Schemes have been added to the Archenemy mode. This is a work in progress and th Quest Worlds: ------------- -This version allows you to travel between the regular quest world and the other worlds (Shandalar, Ravnica, Jamuraa, more may be added in the future) to get different duel opponents and challenges. You will have to complete your current challenges before traveling or you will lose them. +This version allows you to travel between the regular quest world and the other worlds (Shandalar, Ravnica, Jamuraa, more may be added in the future) to get different duel opponents and challenges. You will have to complete your current challenges before traveling or you will lose them. World-specific format enforcing and starting world selection are available. Something has to be done about locked (non-repeatable) challenges so they do not end up locking other challenges in different worlds. @@ -524,7 +524,7 @@ From a technical perspective, the following sets are available to the player in Jamuraa contains: - 81 opponent decks, broken down as follows: 13 'easy' decks, 17 'medium' decks, 31 'hard' decks, and 20 'very hard' decks. - 9 challenges, including the 4 demon planeswalkers (the 3 demon rulers and Lilith) and 5 other special scenarios set in Jamuraa. All challenges are repeatable. All are fairly hard, and the 4 demon challenges are especially fiendish. -For the most part, the opponent duel and challenge decks are built with the same format restrictions as your own cardpool, and some of the easiest opponent decks were in fact based on a limited cardpool. But there will be exceptions, especially in the hard/very hard decks and challenges, which can be more like Vintage/T1 decks than pure Mirage + 5th Edition decks. There will be older cards here and there, and maybe even a random Tempest card or two (although these are extremely scarce!). +For the most part, the opponent duel and challenge decks are built with the same format restrictions as your own cardpool, and some of the easiest opponent decks were in fact based on a limited cardpool. But there will be exceptions, especially in the hard/very hard decks and challenges, which can be more like Vintage/T1 decks than pure Mirage + 5th Edition decks. There will be older cards here and there, and maybe even a random Tempest card or two (although these are extremely scarce!). Hint: if you find the later 'Vintage' opponent decks unfair or near-impossible to beat with your 5th Edition/Mirage block cards, you can Travel to Shandalar and collect some old power cards there, and then return to Jamuraa. Just remember to complete your challenges before traveling. Information on the quest worlds format can be found in this topic: @@ -588,7 +588,7 @@ Work was also done on making the UI more keyboard-friendly. For example, the OK Gatecrash Guild Sealed game mode: --------------------------------- -Gatecrash Guild Sealed game mode has been added. To use it, start a new Sealed Mode Game, select "Block / Set" and "Gatecrash Guild Sealed". Select the first (default) configuration in the "Choose Set Combination" dialog, and when asked to pick your boosters, choose the guild you want twice (once for the guild-specific booster, and then for the extra promo cards). +Gatecrash Guild Sealed game mode has been added. To use it, start a new Sealed Mode Game, select "Block / Set" and "Gatecrash Guild Sealed". Select the first (default) configuration in the "Choose Set Combination" dialog, and when asked to pick your boosters, choose the guild you want twice (once for the guild-specific booster, and then for the extra promo cards). The following cards are not included in the guild boosters of this game mode because they are not currently implemented in Forge: Bane Alley Broker, Bioshift, Killing Glare, Simic Manipulator. @@ -827,9 +827,9 @@ The feature is half-complete now. Forge can read subfolders in constructed deck, === Forge now has a Menubar === -- Toggle visibility using the F1 key. -- The Forge menu contains Exit and Restart options. -- The Layout menu contains the selector for the various themes and a Set Window Size option. +- Toggle visibility using the F1 key. +- The Forge menu contains Exit and Restart options. +- The Layout menu contains the selector for the various themes and a Set Window Size option. - The Help menu contains links to online articles and the local TXT files. === Duel/Match Screen Changes === @@ -1187,7 +1187,7 @@ You can then construct large and very specific expressions to find exactly what ("human" || "cat") && ("warrior" || "cleric" || "soldier") will find all humans or cats that are also warriors, clerics, or soldiers. -More improvements are on their way in the next version of Forge, including removing the need for quotation marks and adding NOT operators. +More improvements are on their way in the next version of Forge, including removing the need for quotation marks and adding NOT operators. - Improved auto-yield support - @@ -1254,11 +1254,11 @@ When triggered abilities have only one valid target, that target will now be aut - AI improvements - Some artificial intelligence improvements were made in this version of Forge. -The AI will now always attack with creatures that it has temporarily gained control of until end of turn in order not to miss the opportunity and thus waste the gain control spell. +The AI will now always attack with creatures that it has temporarily gained control of until end of turn in order not to miss the opportunity and thus waste the gain control spell. The AI will no longer bounce guild lands back to hand right after playing them, which sometimes caused the AI to lock itself on land drops completely. -The AI will now try to predict if it wants to cast a spell in Main 2 and reserve some mana for it instead of aggressively pumping creatures with all available mana. -The AI will now also consider pumping a creature if it can predict that this creature will deal more damage to the opponent with its increased power, which should eliminate the senseless attacks with 0/1 creatures that do not get pumped even when they do not get blocked. -The AI will no longer waste equipment on cards that are useless (e.g. are tapped and do not normally untap, or can't attack or block anymore, etc.). +The AI will now try to predict if it wants to cast a spell in Main 2 and reserve some mana for it instead of aggressively pumping creatures with all available mana. +The AI will now also consider pumping a creature if it can predict that this creature will deal more damage to the opponent with its increased power, which should eliminate the senseless attacks with 0/1 creatures that do not get pumped even when they do not get blocked. +The AI will no longer waste equipment on cards that are useless (e.g. are tapped and do not normally untap, or can't attack or block anymore, etc.). The AI can now optionally move equipment from one creature to another. For this purpose, a new AI profile variable was added: MOVE_EQUIPMENT_TO_BETTER_CREATURES. It defines whether the AI will always move equipment to better creatures if it has mana ('always'), will only move if the currently equipped creature becomes useless as defined above or because the AI loses control of the creature ('from_useless_only'), or will never move equipment around ('never'). The "Default" profile is set to only move the equipment to other creatures if the currently equipped creatures become useless for the AI, while the "Reckless" AI profile always moves equipment to better creatures when given a chance to (if it has mana and if it doesn't need to reserve mana for a future spell in Main 2). @@ -1310,7 +1310,7 @@ These examples should get you started with this powerful-now-that-it's-finished - Trigger Fixes - -The long awaited fixes to Blood Artist/Deathgreeter triggering with multiple other creatures is finally here. There may be some residual oddness with other triggers, so please report those if you see them so we can get that worked out. +The long awaited fixes to Blood Artist/Deathgreeter triggering with multiple other creatures is finally here. There may be some residual oddness with other triggers, so please report those if you see them so we can get that worked out. - New Floating Zone Windows - @@ -1536,7 +1536,7 @@ It is now possible to simulate random AI vs. AI match outcome instead of playing - Incremental Updates and Fixes - -This release features many bug fixes and general improvements. Multiple corner cases and card interactions were fixed or changed to conform to Magic: the Gathering rules more closely. For example, the damage subsystem has been rewritten to allow proper interaction with replacement effects and lifelink (e.g. Gisela, Blade of Goldnight + Kalitas, Traitor or Ghet or Ajani's Pridemate getting a counter from Kalitas, Traitor of Ghet dealing damage to multiple creatures via Chandra's Ignition). Splice onto Arcane has been reworked to make it more intuitive for the player and more rule compliant. +This release features many bug fixes and general improvements. Multiple corner cases and card interactions were fixed or changed to conform to Magic: the Gathering rules more closely. For example, the damage subsystem has been rewritten to allow proper interaction with replacement effects and lifelink (e.g. Gisela, Blade of Goldnight + Kalitas, Traitor or Ghet or Ajani's Pridemate getting a counter from Kalitas, Traitor of Ghet dealing damage to multiple creatures via Chandra's Ignition). Splice onto Arcane has been reworked to make it more intuitive for the player and more rule compliant. - AI Updates and New AI-Playable Cards - diff --git a/forge-gui/res/adventure/common/custom_cards/sorins_amulet.txt b/forge-gui/res/adventure/common/custom_cards/sorins_amulet.txt index 7dc95be3160..12d5317d8b4 100644 --- a/forge-gui/res/adventure/common/custom_cards/sorins_amulet.txt +++ b/forge-gui/res/adventure/common/custom_cards/sorins_amulet.txt @@ -1,9 +1,9 @@ Name:Sorin's Amulet ManaCost:no cost Types:Enchantment -A:AB$ MakeCard | Cost$ B B PayShards<4> PayLife<4> | IsPresent$ Vampire.YouCtrl | PresentZone$ Library | PresentCompare$ GE20 | ActivationZone$ Command | GameActivationLimit$ 1 | Conjure$ True | AtRandom$ True | Spellbook$ Sorin; Grim Nemesis,Sorin; Imperious Bloodlord,Sorin; Lord of Innistrad,Sorin Markov,Sorin; Solemn Visitor,Sorin the Mirthless,Sorin; Vampire Lord,Sorin; Vengeful Bloodlord,| WithCounter$ TIME | WithCounterNum$ 3 | Zone$ Exile | RememberMade$ True | SubAbility$ GiveSuspend | SpellDescription$ Conjure a Sorin planeswalker into exile with three time counters, it gains suspend. Activate this ability only if your library contains 20 or more vampires and only once each game. +A:AB$ MakeCard | Cost$ B B PayShards<4> PayLife<4> | IsPresent$ Vampire.YouCtrl | PresentZone$ Library | PresentCompare$ GE20 | ActivationZone$ Command | GameActivationLimit$ 1 | Conjure$ True | AtRandom$ True | Spellbook$ Sorin; Grim Nemesis,Sorin; Imperious Bloodlord,Sorin; Lord of Innistrad,Sorin Markov,Sorin; Solemn Visitor,Sorin the Mirthless,Sorin; Vampire Lord,Sorin; Vengeful Bloodlord,| WithCounter$ TIME | WithCounterNum$ 3 | Zone$ Exile | RememberMade$ True | SubAbility$ GiveSuspend | SpellDescription$ Conjure a Sorin planeswalker into exile with three time counters, it gains suspend. Activate this ability only if your library contains 20 or more vampires and only once each game. SVar:GiveSuspend:DB$ Pump | Defined$ Remembered | KW$ Suspend | PumpZone$ Exile | Duration$ Permanent | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -S:Mode$ Continuous | EffectZone$ Command | Affected$ Vampire.YouCtrl | AddPower$ 1 | AddToughness$ 1 | CheckSVar$ YourLife | SVarCompare$ LT10 | AddKeyword$ Lifelink | Description$ As long as your life total is lower than 10, Vampires you control have +1/+1 and have lifelink. +S:Mode$ Continuous | EffectZone$ Command | Affected$ Vampire.YouCtrl | AddPower$ 1 | AddToughness$ 1 | CheckSVar$ YourLife | SVarCompare$ LT10 | AddKeyword$ Lifelink | Description$ As long as your life total is lower than 10, Vampires you control have +1/+1 and have lifelink. SVar:YourLife:Count$YourLifeTotal -Oracle:{B}{B},{M}{M}{M}{M},Pay 4 life: Conjure a Sorin planeswalker into exile with three time counters, it gains suspend. Activate this ability only if your library contains 20 or more vampires and only once each game. \nAs long as your life total is lower than 10, Vampires you control have +1/+1 and have lifelink. +Oracle:{B}{B},{M}{M}{M}{M},Pay 4 life: Conjure a Sorin planeswalker into exile with three time counters, it gains suspend. Activate this ability only if your library contains 20 or more vampires and only once each game. \nAs long as your life total is lower than 10, Vampires you control have +1/+1 and have lifelink. diff --git a/forge-gui/res/adventure/common/custom_cards/sorins_boss_effect.txt b/forge-gui/res/adventure/common/custom_cards/sorins_boss_effect.txt index faed4930aaf..489f7f2e72f 100644 --- a/forge-gui/res/adventure/common/custom_cards/sorins_boss_effect.txt +++ b/forge-gui/res/adventure/common/custom_cards/sorins_boss_effect.txt @@ -1,18 +1,18 @@ Name:Sorin's Boss Effect ManaCost:no cost Types:Enchantment -T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | CheckSVar$ YourLife | SVarCompare$ GE40 | Execute$ TrigSeek | TriggerDescription$ As long as Sorin's life total is 40 or more, Sorin seeks two nonland cards every upkeep and can't lose the game and his opponents can't win the game. +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | CheckSVar$ YourLife | SVarCompare$ GE40 | Execute$ TrigSeek | TriggerDescription$ As long as Sorin's life total is 40 or more, Sorin seeks two nonland cards every upkeep and can't lose the game and his opponents can't win the game. SVar:TrigSeek:DB$ Seek | Num$ 2 | Type$ Card.nonLand R:Event$ GameLoss | ActiveZones$ Command | ValidPlayer$ You | Layer$ CantHappen | CheckSVar$ YourLife | SVarCompare$ GE40 | Secondary$ True | Description$ You can't lose the game and your opponents can't win the game. R:Event$ GameWin | ActiveZones$ Battlefield | ValidPlayer$ Opponent | Layer$ CantHappen | Secondary$ True | EffectZone$ Command | CheckSVar$ YourLife | Secondary$ True | SVarCompare$ GE40 | Description$ You can't lose the game and your opponents can't win the game. -T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | CheckSVar$ YourLifeCompare | SVarCompare$ EQ2 | Execute$ TrigConjure | TriggerDescription$ As long as Sorin's life total is between 20 and 40, at Sorin's upkeep, conjure a card from Sorin's Spellbook into exile with 2 time counters on it, it gains suspend. +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | CheckSVar$ YourLifeCompare | SVarCompare$ EQ2 | Execute$ TrigConjure | TriggerDescription$ As long as Sorin's life total is between 20 and 40, at Sorin's upkeep, conjure a card from Sorin's Spellbook into exile with 2 time counters on it, it gains suspend. SVar:TrigConjure:DB$ MakeCard | Conjure$ True | AtRandom$ True | Spellbook$ Sorin; Grim Nemesis,Sorin; Imperious Bloodlord,Sorin; Lord of Innistrad,Sorin Markov,Sorin; Solemn Visitor,Sorin the Mirthless,Sorin; Vampire Lord,Sorin; Vengeful Bloodlord,Timothar; Baron of Bats,Olivia Voldaren,Patriarch's Bidding,Licia; Sanguine Tribune,Astarion; the Decadent,Strefan; Maurer Progenitor,Evelyn; the Covetous,Anje; Maid of Dishonor,Edgar Markov,Swords to Plowshares,Cruel Celebrant,Olivia's Wrath,Markov Baron,Champion of Dusk,Vein Ripper,Anguished Unmaking,Mortify,Void Rend,Terminate,Despark,Bedevil,Utter End,Ruinous Ultimatum,Sign in Blood,Reanimate,Victimize | Zone$ Exile | RememberMade$ True | SubAbility$ DBPutCounter SVar:DBPutCounter:DB$ PutCounter | Defined$ Remembered | CounterNum$ 3 | CounterType$ TIME | SubAbility$ GiveSuspend SVar:GiveSuspend:DB$ Pump | Defined$ Remembered | KW$ Suspend | PumpZone$ Exile | Duration$ Permanent | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -S:Mode$ Continuous | EffectZone$ Command | Affected$ Vampire.YouCtrl | AddPower$ 2 | AddToughness$ 2 | CheckSVar$ YourLife | SVarCompare$ LT20 | AddKeyword$ Lifelink | Description$ As long as Sorin's life total is lower than 20, Sorin's Vampires get +2/+2 and have lifelink. +S:Mode$ Continuous | EffectZone$ Command | Affected$ Vampire.YouCtrl | AddPower$ 2 | AddToughness$ 2 | CheckSVar$ YourLife | SVarCompare$ LT20 | AddKeyword$ Lifelink | Description$ As long as Sorin's life total is lower than 20, Sorin's Vampires get +2/+2 and have lifelink. SVar:Y:Count$Compare YourLife GE20.1.0 SVar:X:Count$Compare YourLife LE40.1.0 SVar:YourLifeCompare:SVar$X/Plus.Y SVar:YourLife:Count$YourLifeTotal -Oracle:As long as Sorin's life total is 40 or more, Sorin seeks two nonland cards every upkeep and can't lose the game and his opponents can't win the game. \nAs long as Sorin's life total is between 20 and 40, at Sorin's upkeep, conjure a card from Sorin's Spellbook into exile with 2 time counters on it, it gains suspend.\nAs long as Sorin's life total is lower than 20, Sorin's Vampires get +2/+2 and have lifelink. +Oracle:As long as Sorin's life total is 40 or more, Sorin seeks two nonland cards every upkeep and can't lose the game and his opponents can't win the game. \nAs long as Sorin's life total is between 20 and 40, at Sorin's upkeep, conjure a card from Sorin's Spellbook into exile with 2 time counters on it, it gains suspend.\nAs long as Sorin's life total is lower than 20, Sorin's Vampires get +2/+2 and have lifelink. diff --git a/forge-gui/res/cardsfolder/a/adarkar_unicorn.txt b/forge-gui/res/cardsfolder/a/adarkar_unicorn.txt index ed102f3d46c..886be1725ec 100644 --- a/forge-gui/res/cardsfolder/a/adarkar_unicorn.txt +++ b/forge-gui/res/cardsfolder/a/adarkar_unicorn.txt @@ -4,6 +4,5 @@ Types:Creature Unicorn PT:2/2 A:AB$ Mana | Cost$ T | Produced$ C U | RestrictValid$ CumulativeUpkeep | SpellDescription$ Add {C}{U}. Spend this mana only to pay cumulative upkeep costs. A:AB$ Mana | Cost$ T | Produced$ U | RestrictValid$ CumulativeUpkeep | SpellDescription$ Add {U}. Spend this mana only to pay cumulative upkeep costs. -AI:RemoveDeck:All AI:RemoveDeck:Random Oracle:{T}: Add {U} or {C}{U}. Spend this mana only to pay cumulative upkeep costs. diff --git a/forge-gui/res/cardsfolder/a/alliance_of_arms.txt b/forge-gui/res/cardsfolder/a/alliance_of_arms.txt index 62eb062936d..c128af6828f 100644 --- a/forge-gui/res/cardsfolder/a/alliance_of_arms.txt +++ b/forge-gui/res/cardsfolder/a/alliance_of_arms.txt @@ -3,7 +3,7 @@ ManaCost:W Types:Sorcery A:SP$ RepeatEach | RepeatPlayers$ Player | StartingWith$ You | RepeatSubAbility$ DBPay | SubAbility$ DBToken | StackDescription$ SpellDescription | SpellDescription$ Join forces — Starting with you, each player may pay any amount of mana. Each player creates X 1/1 white Soldier creature tokens, where X is the total amount of mana paid this way. SVar:DBPay:DB$ ChooseNumber | Defined$ Player.IsRemembered | ChooseAnyNumber$ True | ListTitle$ amount of mana to pay | SubAbility$ DBStore -SVar:DBStore:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ CountSVar | Expression$ JoinForcesAmount/Plus.Y | UnlessCost$ Y | UnlessPayer$ Player.IsRemembered | UnlessSwitched$ True | UnlessAI$ OnlyOwn +SVar:DBStore:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ CountSVar | Expression$ JoinForcesAmount/Plus.Y | UnlessCost$ Y | UnlessPayer$ Player.IsRemembered | UnlessSwitched$ True SVar:DBToken:DB$ Token | TokenAmount$ JoinForcesAmount | TokenScript$ w_1_1_soldier | TokenOwner$ Player | StackDescription$ None SVar:Y:Count$ChosenNumber SVar:JoinForcesAmount:Number$0 diff --git a/forge-gui/res/cardsfolder/a/anurid_scavenger.txt b/forge-gui/res/cardsfolder/a/anurid_scavenger.txt index e4d11c6ab2a..7fef32907df 100644 --- a/forge-gui/res/cardsfolder/a/anurid_scavenger.txt +++ b/forge-gui/res/cardsfolder/a/anurid_scavenger.txt @@ -3,7 +3,8 @@ ManaCost:2 G Types:Creature Frog Beast PT:3/3 K:Protection from black -K:UpkeepCost:PutCardToLibFromGrave<1/-1/Card> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you put a card from your graveyard on the bottom of your library. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ PutCardToLibFromGrave<1/-1/Card> SVar:NeedsToPlayVar:Y GE3 SVar:Y:Count$InYourYard Oracle:Protection from black\nAt the beginning of your upkeep, sacrifice Anurid Scavenger unless you put a card from your graveyard on the bottom of your library. diff --git a/forge-gui/res/cardsfolder/a/arcades_sabboth.txt b/forge-gui/res/cardsfolder/a/arcades_sabboth.txt index 83b924c7b99..593cecef1fc 100644 --- a/forge-gui/res/cardsfolder/a/arcades_sabboth.txt +++ b/forge-gui/res/cardsfolder/a/arcades_sabboth.txt @@ -3,7 +3,8 @@ ManaCost:2 G G W W U U Types:Legendary Creature Elder Dragon PT:7/7 K:Flying -K:UpkeepCost:G W U +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {G}{W}{U}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ G W U S:Mode$ Continuous | Affected$ Creature.notattacking+untapped+YouCtrl | AddToughness$ 2 | Description$ Each untapped creature you control gets +0/+2 as long as it's not attacking. A:AB$ Pump | Cost$ W | Defined$ Self | NumDef$ +1 | SpellDescription$ CARDNAME gets +0/+1 until end of turn. DeckHints:Type$Wall & Keyword$Defender diff --git a/forge-gui/res/cardsfolder/a/athreos_god_of_passage.txt b/forge-gui/res/cardsfolder/a/athreos_god_of_passage.txt index e462d64df1d..a748a28461e 100644 --- a/forge-gui/res/cardsfolder/a/athreos_god_of_passage.txt +++ b/forge-gui/res/cardsfolder/a/athreos_god_of_passage.txt @@ -6,7 +6,6 @@ K:Indestructible S:Mode$ Continuous | Affected$ Card.Self | RemoveType$ Creature | CheckSVar$ X | SVarCompare$ LT7 | Description$ As long as your devotion to white and black is less than seven, NICKNAME isn't a creature. SVar:X:Count$DevotionDual.White.Black T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.YouOwn+Other | TriggerZones$ Battlefield | Execute$ TrigReturn | TriggerDescription$ Whenever another creature you own dies, return it to your hand unless target opponent pays 3 life. -SVar:TrigReturn:DB$ Pump | ValidTgts$ Opponent | IsCurse$ True | SubAbility$ DBReturn -SVar:DBReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Hand | UnlessCost$ PayLife<3> | UnlessPayer$ Targeted | UnlessAI$ nonToken +SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Hand | UnlessCost$ PayLife<3> | UnlessPayer$ Targeted | ValidTgts$ Opponent | IsCurse$ True SVar:BuffedBy:Permanent.White,Permanent.Black Oracle:Indestructible\nAs long as your devotion to white and black is less than seven, Athreos isn't a creature.\nWhenever another creature you own dies, return it to your hand unless target opponent pays 3 life. diff --git a/forge-gui/res/cardsfolder/a/aura_flux.txt b/forge-gui/res/cardsfolder/a/aura_flux.txt index ce658b94a3f..601c2381f00 100644 --- a/forge-gui/res/cardsfolder/a/aura_flux.txt +++ b/forge-gui/res/cardsfolder/a/aura_flux.txt @@ -1,6 +1,8 @@ Name:Aura Flux ManaCost:2 U Types:Enchantment -S:Mode$ Continuous | Affected$ Enchantment.Other | AddKeyword$ UpkeepCost:2 | Description$ Other enchantments have "At the beginning of your upkeep, sacrifice this enchantment unless you pay {2}." +S:Mode$ Continuous | Affected$ Enchantment.Other | AddTrigger$ UpkeepCostTrigger | Description$ Other enchantments have "At the beginning of your upkeep, sacrifice this enchantment unless you pay {2}." +SVar:UpkeepCostTrigger:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {2}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 2 AI:RemoveDeck:Random Oracle:Other enchantments have "At the beginning of your upkeep, sacrifice this enchantment unless you pay {2}." diff --git a/forge-gui/res/cardsfolder/b/barrow_ghoul.txt b/forge-gui/res/cardsfolder/b/barrow_ghoul.txt index 79df3178490..b832990f499 100644 --- a/forge-gui/res/cardsfolder/b/barrow_ghoul.txt +++ b/forge-gui/res/cardsfolder/b/barrow_ghoul.txt @@ -2,7 +2,8 @@ Name:Barrow Ghoul ManaCost:1 B Types:Creature Zombie PT:4/4 -K:UpkeepCost:ExileFromGrave<1/Card.TopGraveyardCreature> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you exile the top creature card of your graveyard. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ ExileFromGrave<1/Card.TopGraveyardCreature> SVar:NeedsToPlayVar:Y GE2 SVar:Y:Count$TypeInYourYard.Creature SVar:NeedsOrderedGraveyard:TRUE diff --git a/forge-gui/res/cardsfolder/b/binding_grasp.txt b/forge-gui/res/cardsfolder/b/binding_grasp.txt index 9d64b2ea039..f832dc80f42 100644 --- a/forge-gui/res/cardsfolder/b/binding_grasp.txt +++ b/forge-gui/res/cardsfolder/b/binding_grasp.txt @@ -2,7 +2,8 @@ Name:Binding Grasp ManaCost:3 U Types:Enchantment Aura K:Enchant creature -K:UpkeepCost:1 U +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {1}{U}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 1 U A:SP$ Attach | Cost$ 3 U | ValidTgts$ Creature | AILogic$ GainControl S:Mode$ Continuous | Affected$ Card.EnchantedBy | GainControl$ You | Description$ You control enchanted creature. S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddToughness$ 1 | Description$ Enchanted creature gets +0/+1. diff --git a/forge-gui/res/cardsfolder/b/bog_elemental.txt b/forge-gui/res/cardsfolder/b/bog_elemental.txt index 9a77a98ff2f..5d3a941e687 100644 --- a/forge-gui/res/cardsfolder/b/bog_elemental.txt +++ b/forge-gui/res/cardsfolder/b/bog_elemental.txt @@ -3,6 +3,7 @@ ManaCost:3 B B Types:Creature Elemental PT:5/4 K:Protection from white -K:UpkeepCost:Sac<1/Land> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you sacrifice a land. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Sac<1/Land> AI:RemoveDeck:All Oracle:Protection from white\nAt the beginning of your upkeep, sacrifice Bog Elemental unless you sacrifice a land. diff --git a/forge-gui/res/cardsfolder/b/breeding_pit.txt b/forge-gui/res/cardsfolder/b/breeding_pit.txt index e5333eb1ad7..4c65c7ab32c 100644 --- a/forge-gui/res/cardsfolder/b/breeding_pit.txt +++ b/forge-gui/res/cardsfolder/b/breeding_pit.txt @@ -1,7 +1,8 @@ Name:Breeding Pit ManaCost:3 B Types:Enchantment -K:UpkeepCost:B B +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {B}{B}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ B B T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of your end step, create a 0/1 black Thrull creature token. SVar:TrigToken:DB$ Token | TokenScript$ b_0_1_thrull | TokenOwner$ You SVar:PitSac:SVar:SacMe:1 diff --git a/forge-gui/res/cardsfolder/c/chain_lightning.txt b/forge-gui/res/cardsfolder/c/chain_lightning.txt index 2bf7e42b35d..7446cbaecfb 100644 --- a/forge-gui/res/cardsfolder/c/chain_lightning.txt +++ b/forge-gui/res/cardsfolder/c/chain_lightning.txt @@ -2,5 +2,5 @@ Name:Chain Lightning ManaCost:R Types:Sorcery A:SP$ DealDamage | ValidTgts$ Any | TgtPrompt$ Select target | NumDmg$ 3 | SubAbility$ DBCopy1 | SpellDescription$ CARDNAME deals 3 damage to any target. Then that player or that permanent's controller may pay {R}{R}. If the player does, they may copy this spell and may choose a new target for that copy. -SVar:DBCopy1:DB$ CopySpellAbility | Defined$ Parent | Controller$ TargetedOrController | UnlessPayer$ TargetedOrController | UnlessCost$ R R | UnlessSwitched$ True | MayChooseTarget$ True | StackDescription$ None +SVar:DBCopy1:DB$ CopySpellAbility | Defined$ Parent | Controller$ TargetedOrController | UnlessPayer$ TargetedOrController | UnlessCost$ R R | UnlessSwitched$ True | MayChooseTarget$ True | AILogic$ Always | StackDescription$ None Oracle:Chain Lightning deals 3 damage to any target. Then that player or that permanent's controller may pay {R}{R}. If the player does, they may copy this spell and may choose a new target for that copy. diff --git a/forge-gui/res/cardsfolder/c/chain_of_vapor.txt b/forge-gui/res/cardsfolder/c/chain_of_vapor.txt index 410ef36f5ee..ce13888270b 100644 --- a/forge-gui/res/cardsfolder/c/chain_of_vapor.txt +++ b/forge-gui/res/cardsfolder/c/chain_of_vapor.txt @@ -2,5 +2,5 @@ Name:Chain of Vapor ManaCost:U Types:Instant A:SP$ ChangeZone | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | Origin$ Battlefield | Destination$ Hand | SubAbility$ DBCopy | StackDescription$ SpellDescription | SpellDescription$ Return target nonland permanent to its owner's hand. Then that permanent's controller may sacrifice a land. If the player does, they may copy this spell and may choose a new target for that copy. -SVar:DBCopy:DB$ CopySpellAbility | Defined$ Parent | Controller$ TargetedController | UnlessPayer$ TargetedController | UnlessCost$ Sac<1/Land> | UnlessSwitched$ True | StackDescription$ None | MayChooseTarget$ True +SVar:DBCopy:DB$ CopySpellAbility | Defined$ Parent | Controller$ TargetedController | UnlessPayer$ TargetedController | UnlessCost$ Sac<1/Land> | UnlessSwitched$ True | UnlessAI$ ChainOfVapor | StackDescription$ None | MayChooseTarget$ True Oracle:Return target nonland permanent to its owner's hand. Then that permanent's controller may sacrifice a land. If the player does, they may copy this spell and may choose a new target for that copy. diff --git a/forge-gui/res/cardsfolder/c/chandra_flameshaper.txt b/forge-gui/res/cardsfolder/c/chandra_flameshaper.txt index 65abcdf2d1a..07cbc85bf3a 100644 --- a/forge-gui/res/cardsfolder/c/chandra_flameshaper.txt +++ b/forge-gui/res/cardsfolder/c/chandra_flameshaper.txt @@ -2,7 +2,7 @@ Name:Chandra, Flameshaper ManaCost:5 R R Types:Legendary Planeswalker Chandra Loyalty:6 -A:AB$ Mana | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | Produced$ R | Amount$ 3 | AILogic$ Always | SubAbility$ DBExile | SpellDescription$ Add {R}{R}{R}. +A:AB$ Mana | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | Produced$ R | Amount$ 3 | AILogic$ Always | SubAbility$ DBExile | SpellDescription$ Add {R}{R}{R}. SVar:DBExile:DB$ Dig | Defined$ You | DigNum$ 3 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBChoose | SpellDescription$ Exile the top three cards of your library. Choose one. You may play that card this turn. SVar:DBChoose:DB$ ChooseCard | Choices$ Card.IsRemembered | ChoiceZone$ Exile | Mandatory$ True | ForgetOtherRemembered$ True | SubAbility$ DBEffect SVar:DBEffect:DB$ Effect | RememberObjects$ ChosenCard | StaticAbilities$ Play | ForgetOnMoved$ Exile | SubAbility$ DBCleanup diff --git a/forge-gui/res/cardsfolder/c/child_of_gaea.txt b/forge-gui/res/cardsfolder/c/child_of_gaea.txt index a450a31bac7..26cfeac0303 100644 --- a/forge-gui/res/cardsfolder/c/child_of_gaea.txt +++ b/forge-gui/res/cardsfolder/c/child_of_gaea.txt @@ -3,6 +3,7 @@ ManaCost:3 G G G Types:Creature Elemental PT:7/7 K:Trample -K:UpkeepCost:G G +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {G}{G}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ G G A:AB$ Regenerate | Cost$ 1 G | SpellDescription$ Regenerate CARDNAME. Oracle:Trample\nAt the beginning of your upkeep, sacrifice Child of Gaea unless you pay {G}{G}.\n{1}{G}: Regenerate Child of Gaea. diff --git a/forge-gui/res/cardsfolder/c/chromium.txt b/forge-gui/res/cardsfolder/c/chromium.txt index 01361d6096e..aedaa24fb70 100644 --- a/forge-gui/res/cardsfolder/c/chromium.txt +++ b/forge-gui/res/cardsfolder/c/chromium.txt @@ -4,5 +4,6 @@ Types:Legendary Creature Elder Dragon PT:7/7 K:Flying K:Rampage:2 -K:UpkeepCost:W U B +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {W}{U}{B}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ W U B Oracle:Flying\nRampage 2 (Whenever this creature becomes blocked, it gets +2/+2 until end of turn for each creature blocking it beyond the first.)\nAt the beginning of your upkeep, sacrifice Chromium unless you pay {W}{U}{B}. diff --git a/forge-gui/res/cardsfolder/c/circling_vultures.txt b/forge-gui/res/cardsfolder/c/circling_vultures.txt index 5f824659b43..b74d32e150f 100644 --- a/forge-gui/res/cardsfolder/c/circling_vultures.txt +++ b/forge-gui/res/cardsfolder/c/circling_vultures.txt @@ -3,7 +3,8 @@ ManaCost:B Types:Creature Bird PT:3/2 K:Flying -K:UpkeepCost:ExileFromGrave<1/Card.TopGraveyardCreature> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you exile the top creature card of your graveyard. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ ExileFromGrave<1/Card.TopGraveyardCreature> A:ST$ Discard | Cost$ 0 | Mode$ Defined | DefinedCards$ Self | Optional$ True | DiscardMessage$ Do you want discard this card? | ActivationZone$ Hand | InstantSpeed$ True | SpellDescription$ You may discard CARDNAME any time you could cast an instant. AI:RemoveDeck:All SVar:NeedsOrderedGraveyard:TRUE diff --git a/forge-gui/res/cardsfolder/c/clackbridge_troll.txt b/forge-gui/res/cardsfolder/c/clackbridge_troll.txt index 2c6318e71b0..38da27600f7 100644 --- a/forge-gui/res/cardsfolder/c/clackbridge_troll.txt +++ b/forge-gui/res/cardsfolder/c/clackbridge_troll.txt @@ -8,7 +8,7 @@ K:Trample T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TripleGoat | TriggerDescription$ When CARDNAME enters, target opponent creates three 0/1 white Goat creature tokens. SVar:TripleGoat:DB$ Token | TokenAmount$ 3 | TokenScript$ w_0_1_goat | ValidTgts$ Opponent | TokenOwner$ Targeted T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | Execute$ TrigTap | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of combat on your turn, any opponent may sacrifice a creature. If a player does, tap CARDNAME, you gain 3 life, and you draw a card. -SVar:TrigTap:DB$ Tap | Defined$ Self | UnlessCost$ Sac<1/Creature> | UnlessPayer$ Player.Opponent | UnlessSwitched$ True | UnlessAI$ LifeLE10 | UnlessResolveSubs$ WhenPaid | SubAbility$ DBGainLife +SVar:TrigTap:DB$ Tap | Defined$ Self | UnlessCost$ Sac<1/Creature> | UnlessPayer$ Player.Opponent | UnlessSwitched$ True | UnlessResolveSubs$ WhenPaid | SubAbility$ DBGainLife SVar:DBGainLife:DB$ GainLife | LifeAmount$ 3 | SubAbility$ DBDraw SVar:DBDraw:DB$ Draw | NumCards$ 1 | Defined$ You Oracle:Trample, haste\nWhen Clackbridge Troll enters, target opponent creates three 0/1 white Goat creature tokens.\nAt the beginning of combat on your turn, any opponent may sacrifice a creature. If a player does, tap Clackbridge Troll, you gain 3 life, and you draw a card. diff --git a/forge-gui/res/cardsfolder/c/cleansing.txt b/forge-gui/res/cardsfolder/c/cleansing.txt index ae1fa7b699c..e355997680f 100644 --- a/forge-gui/res/cardsfolder/c/cleansing.txt +++ b/forge-gui/res/cardsfolder/c/cleansing.txt @@ -2,6 +2,6 @@ Name:Cleansing ManaCost:W W W Types:Sorcery A:SP$ RepeatEach | RepeatSubAbility$ DBSac | RepeatCards$ Land | SpellDescription$ For each land, destroy that land unless any player pays 1 life. -SVar:DBSac:DB$ Destroy | UnlessCost$ PayLife<1> | UnlessPayer$ Player | UnlessAI$ DefinedRememberedController | Defined$ Remembered | StackDescription$ Destroy {c:Remembered} +SVar:DBSac:DB$ Destroy | Defined$ Remembered | UnlessCost$ PayLife<1> | UnlessPayer$ Player | StackDescription$ Destroy {c:Remembered} AI:RemoveDeck:All Oracle:For each land, destroy that land unless any player pays 1 life. diff --git a/forge-gui/res/cardsfolder/c/collective_voyage.txt b/forge-gui/res/cardsfolder/c/collective_voyage.txt index 29a42406517..76c99008032 100644 --- a/forge-gui/res/cardsfolder/c/collective_voyage.txt +++ b/forge-gui/res/cardsfolder/c/collective_voyage.txt @@ -3,7 +3,7 @@ ManaCost:G Types:Sorcery A:SP$ RepeatEach | RepeatPlayers$ Player | StartingWith$ You | RepeatSubAbility$ DBPay | SubAbility$ DBSearch | StackDescription$ SpellDescription | SpellDescription$ Join forces — Starting with you, each player may pay any amount of mana. Each player searches their library for up to X basic land cards, where X is the total amount of mana paid this way, puts them onto the battlefield tapped, then shuffles. SVar:DBPay:DB$ ChooseNumber | Defined$ Player.IsRemembered | ChooseAnyNumber$ True | ListTitle$ amount of mana to pay | SubAbility$ DBStore -SVar:DBStore:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ CountSVar | Expression$ JoinForcesAmount/Plus.Y | UnlessCost$ Y | UnlessPayer$ Player.IsRemembered | UnlessSwitched$ True | UnlessAI$ OnlyOwn +SVar:DBStore:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ CountSVar | Expression$ JoinForcesAmount/Plus.Y | UnlessCost$ Y | UnlessPayer$ Player.IsRemembered | UnlessSwitched$ True SVar:DBSearch:DB$ ChangeZone | DefinedPlayer$ Player | ChangeType$ Land.Basic | ChangeNum$ JoinForcesAmount | Origin$ Library | Destination$ Battlefield | Tapped$ True | SubAbility$ DBReset | StackDescription$ None SVar:DBReset:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ Number | Expression$ 0 SVar:Y:Count$ChosenNumber diff --git a/forge-gui/res/cardsfolder/c/contamination.txt b/forge-gui/res/cardsfolder/c/contamination.txt index 2a3b39ee4c9..657467d570c 100644 --- a/forge-gui/res/cardsfolder/c/contamination.txt +++ b/forge-gui/res/cardsfolder/c/contamination.txt @@ -1,7 +1,8 @@ Name:Contamination ManaCost:2 B Types:Enchantment -K:UpkeepCost:Sac<1/Creature> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you sacrifice a creature. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Sac<1/Creature> R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ReplaceWith$ ProduceB | Description$ If a land is tapped for mana, it produces {B} instead of any other type and amount. SVar:ProduceB:DB$ ReplaceMana | ReplaceMana$ B AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/c/conversion.txt b/forge-gui/res/cardsfolder/c/conversion.txt index 88015de04f2..082ebd09352 100644 --- a/forge-gui/res/cardsfolder/c/conversion.txt +++ b/forge-gui/res/cardsfolder/c/conversion.txt @@ -1,7 +1,8 @@ Name:Conversion ManaCost:2 W W Types:Enchantment -K:UpkeepCost:W W +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless youpay {W}{W}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ W W S:Mode$ Continuous | Affected$ Mountain | AddType$ Plains | RemoveLandTypes$ True | Description$ All Mountains are Plains. AI:RemoveDeck:Random DeckHints:Keyword$Plainswalk diff --git a/forge-gui/res/cardsfolder/c/cosmic_larva.txt b/forge-gui/res/cardsfolder/c/cosmic_larva.txt index cd5f5ebcb07..43297a0f870 100644 --- a/forge-gui/res/cardsfolder/c/cosmic_larva.txt +++ b/forge-gui/res/cardsfolder/c/cosmic_larva.txt @@ -3,6 +3,7 @@ ManaCost:1 R R Types:Creature Beast PT:7/6 K:Trample -K:UpkeepCost:Sac<2/Land> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you sacrifice two lands. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Sac<2/Land> AI:RemoveDeck:All Oracle:Trample\nAt the beginning of your upkeep, sacrifice Cosmic Larva unless you sacrifice two lands. diff --git a/forge-gui/res/cardsfolder/d/dance_of_many.txt b/forge-gui/res/cardsfolder/d/dance_of_many.txt index 479c7e7ce42..aa038c81731 100644 --- a/forge-gui/res/cardsfolder/d/dance_of_many.txt +++ b/forge-gui/res/cardsfolder/d/dance_of_many.txt @@ -8,6 +8,7 @@ SVar:TrigExile:DB$ ChangeZone | Defined$ Remembered | Origin$ Battlefield | Dest SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.IsRemembered | Execute$ TrigSac | TriggerDescription$ When the token leaves the battlefield, sacrifice CARDNAME. SVar:TrigSac:DB$ Sacrifice | SubAbility$ DBCleanup -K:UpkeepCost:U U +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {U}{U}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ U U DeckHas:Ability$Token|Sacrifice Oracle:When Dance of Many enters, create a token that's a copy of target nontoken creature.\nWhen Dance of Many leaves the battlefield, exile the token.\nWhen the token leaves the battlefield, sacrifice Dance of Many.\nAt the beginning of your upkeep, sacrifice Dance of Many unless you pay {U}{U}. diff --git a/forge-gui/res/cardsfolder/d/darba.txt b/forge-gui/res/cardsfolder/d/darba.txt index 7985a36c9a6..0abcb44302e 100644 --- a/forge-gui/res/cardsfolder/d/darba.txt +++ b/forge-gui/res/cardsfolder/d/darba.txt @@ -2,5 +2,6 @@ Name:Darba ManaCost:3 G Types:Creature Bird Beast PT:5/4 -K:UpkeepCost:G G +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {G}{G}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ G G Oracle:At the beginning of your upkeep, sacrifice Darba unless you pay {G}{G}. diff --git a/forge-gui/res/cardsfolder/d/deep_spawn.txt b/forge-gui/res/cardsfolder/d/deep_spawn.txt index 5c0ec910f20..a9dee3ad5d9 100644 --- a/forge-gui/res/cardsfolder/d/deep_spawn.txt +++ b/forge-gui/res/cardsfolder/d/deep_spawn.txt @@ -3,7 +3,8 @@ ManaCost:5 U U U Types:Creature Homarid PT:6/6 K:Trample -K:UpkeepCost:Mill<2> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you mill two cards. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Mill<2> A:AB$ Pump | Cost$ U | Defined$ Self | KW$ Shroud | SubAbility$ DBPump | SpellDescription$ CARDNAME gains shroud until end of turn and doesn't untap during your next untap step. Tap CARDNAME. (A permanent with shroud can't be the target of spells or abilities.) SVar:DBPump:DB$ Pump | Defined$ Self | KW$ HIDDEN This card doesn't untap during your next untap step. | Duration$ Permanent | SubAbility$ DBTap SVar:DBTap:DB$ Tap | Defined$ Self diff --git a/forge-gui/res/cardsfolder/d/demanding_dragon.txt b/forge-gui/res/cardsfolder/d/demanding_dragon.txt index 0a1029650f6..5d143da5d24 100644 --- a/forge-gui/res/cardsfolder/d/demanding_dragon.txt +++ b/forge-gui/res/cardsfolder/d/demanding_dragon.txt @@ -4,5 +4,5 @@ Types:Creature Dragon PT:5/5 K:Flying T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigDealDamage | TriggerDescription$ When CARDNAME enters, it deals 5 damage to target opponent unless that player sacrifices a creature. -SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | NumDmg$ 5 | UnlessCost$ Sac<1/Creature> | UnlessPayer$ Targeted | UnlessAI$ LifeLE5 +SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | NumDmg$ 5 | UnlessCost$ Sac<1/Creature> | UnlessPayer$ Targeted Oracle:Flying\nWhen Demanding Dragon enters, it deals 5 damage to target opponent unless that player sacrifices a creature. diff --git a/forge-gui/res/cardsfolder/d/dragon_tyrant.txt b/forge-gui/res/cardsfolder/d/dragon_tyrant.txt index 5c483d918d5..600bf6a6d42 100644 --- a/forge-gui/res/cardsfolder/d/dragon_tyrant.txt +++ b/forge-gui/res/cardsfolder/d/dragon_tyrant.txt @@ -5,6 +5,7 @@ PT:6/6 K:Flying K:Trample K:Double Strike -K:UpkeepCost:R R R R +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {R}{R}{R}{R}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ R R R R A:AB$ Pump | Cost$ R | Defined$ Self | NumAtt$ +1 | SpellDescription$ CARDNAME gets +1/+0 until end of turn. Oracle:Flying, trample\nDouble strike (This creature deals both first-strike and regular combat damage.)\nAt the beginning of your upkeep, sacrifice Dragon Tyrant unless you pay {R}{R}{R}{R}.\n{R}: Dragon Tyrant gets +1/+0 until end of turn. diff --git a/forge-gui/res/cardsfolder/d/drifter_il_dal.txt b/forge-gui/res/cardsfolder/d/drifter_il_dal.txt index d51ffab9906..144e4a00f28 100644 --- a/forge-gui/res/cardsfolder/d/drifter_il_dal.txt +++ b/forge-gui/res/cardsfolder/d/drifter_il_dal.txt @@ -2,6 +2,7 @@ Name:Drifter il-Dal ManaCost:U Types:Creature Human Wizard PT:2/1 -K:UpkeepCost:U +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {U}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ U K:Shadow Oracle:Shadow (This creature can block or be blocked by only creatures with shadow.)\nAt the beginning of your upkeep, sacrifice Drifter il-Dal unless you pay {U}. diff --git a/forge-gui/res/cardsfolder/d/drifting_djinn.txt b/forge-gui/res/cardsfolder/d/drifting_djinn.txt index 9648ab8eb7b..1c62b987cbc 100644 --- a/forge-gui/res/cardsfolder/d/drifting_djinn.txt +++ b/forge-gui/res/cardsfolder/d/drifting_djinn.txt @@ -4,5 +4,6 @@ Types:Creature Djinn PT:5/5 K:Flying K:Cycling:2 -K:UpkeepCost:1 U +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {1}{U}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 1 U Oracle:Flying\nAt the beginning of your upkeep, sacrifice Drifting Djinn unless you pay {1}{U}.\nCycling {2} ({2}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/d/drought.txt b/forge-gui/res/cardsfolder/d/drought.txt index 2c9c4d88220..f7af1a00849 100644 --- a/forge-gui/res/cardsfolder/d/drought.txt +++ b/forge-gui/res/cardsfolder/d/drought.txt @@ -1,7 +1,8 @@ Name:Drought ManaCost:2 W W Types:Enchantment -K:UpkeepCost:W W +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {W}{W}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ W W S:Mode$ RaiseCost | Type$ Spell | ForEachShard$ Black | Cost$ Sac<1/Swamp> | EffectZone$ All | Description$ Spells cost an additional "Sacrifice a Swamp" to cast for each black mana symbol in their mana costs. S:Mode$ RaiseCost | Type$ Ability | ForEachShard$ Black | Cost$ Sac<1/Swamp> | EffectZone$ All | Description$ Activated abilities cost an additional "Sacrifice a Swamp" to activate for each black mana symbol in their activation costs. Oracle:At the beginning of your upkeep, sacrifice Drought unless you pay {W}{W}.\nSpells cost an additional "Sacrifice a Swamp" to cast for each black mana symbol in their mana costs.\nActivated abilities cost an additional "Sacrifice a Swamp" to activate for each black mana symbol in their activation costs. diff --git a/forge-gui/res/cardsfolder/e/earthlink.txt b/forge-gui/res/cardsfolder/e/earthlink.txt index 21db3bee3e6..90ddb205603 100644 --- a/forge-gui/res/cardsfolder/e/earthlink.txt +++ b/forge-gui/res/cardsfolder/e/earthlink.txt @@ -1,7 +1,8 @@ Name:Earthlink ManaCost:3 B R G Types:Enchantment -K:UpkeepCost:2 +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {2}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 2 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature | TriggerZones$ Battlefield | Execute$ TrigSac | TriggerDescription$ Whenever a creature dies, that creature's controller sacrifices a land. SVar:TrigSac:DB$ Sacrifice | SacValid$ Land | Defined$ TriggeredCardController AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/e/eldest_dragon_highlander.txt b/forge-gui/res/cardsfolder/e/eldest_dragon_highlander.txt index 499d218bd5a..b96c8bf05f4 100644 --- a/forge-gui/res/cardsfolder/e/eldest_dragon_highlander.txt +++ b/forge-gui/res/cardsfolder/e/eldest_dragon_highlander.txt @@ -6,5 +6,6 @@ K:Flying K:Trample K:Rampage:2 A:AB$ PumpAll | Cost$ W U B R G | ValidCards$ Dragon.Elder+YouCtrl | NumAtt$ 7 | NumDef$ 7 | SpellDescription$ Elder Dragons you control get +7/+7 until end of turn. -K:UpkeepCost:W U B R G +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {W}{U}{B}{R}{G}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ W U B R G Oracle:Flying, trample, rampage 2\n{W}{U}{B}{R}{G}: Elder Dragons you control get +7/+7 until end of turn.\nAt the beginning of your upkeep, sacrifice Eldest Dragon Highlander unless you pay {W}{U}{B}{R}{G}. diff --git a/forge-gui/res/cardsfolder/e/ellivere_of_the_wild_court.txt b/forge-gui/res/cardsfolder/e/ellivere_of_the_wild_court.txt index 999a55d9b92..bc57089577f 100644 --- a/forge-gui/res/cardsfolder/e/ellivere_of_the_wild_court.txt +++ b/forge-gui/res/cardsfolder/e/ellivere_of_the_wild_court.txt @@ -4,7 +4,7 @@ Types:Legendary Creature Human Knight PT:4/4 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ Whenever CARDNAME enters or attacks, create a Virtuous Role token attached to another target creature you control. (If you control another Role on it, put that one into the graveyard. Enchanted creature gets +1/+1 for each enchantment you control.) T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigToken | Secondary$ True | TriggerDescription$ Whenever CARDNAME enters or attacks, create a Virtuous Role token attached to another target creature you control. (If you control another Role on it, put that one into the graveyard. Enchanted creature gets +1/+1 for each enchantment you control.) -SVar:TrigToken:DB$ Token | TokenScript$ role_virtuous | AttachedTo$ Targeted | ValidTgts$ Creature.YouCtrl+Other +SVar:TrigToken:DB$ Token | TokenScript$ role_virtuous | AttachedTo$ Targeted | ValidTgts$ Creature.YouCtrl+Other | TokenOwner$ You T:Mode$ DamageDone | ValidSource$ Creature.YouCtrl+enchanted | ValidTarget$ Player | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever an enchanted creature you control deals combat damage to a player, draw a card. SVar:TrigDraw:DB$ Draw SVar:HasAttackEffect:TRUE diff --git a/forge-gui/res/cardsfolder/e/emberwilde_djinn.txt b/forge-gui/res/cardsfolder/e/emberwilde_djinn.txt index 34d24086442..fb08b15960b 100644 --- a/forge-gui/res/cardsfolder/e/emberwilde_djinn.txt +++ b/forge-gui/res/cardsfolder/e/emberwilde_djinn.txt @@ -5,6 +5,6 @@ PT:5/4 K:Flying T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigChoose | TriggerDescription$ At the beginning of each player's upkeep, that player may pay {R}{R} or 2 life. If the player does, they gain control of CARDNAME. SVar:TrigChoose:DB$ GenericChoice | Defined$ TriggeredPlayer | AILogic$ PayUnlessCost | Choices$ PayRR,Pay2Life -SVar:PayRR:DB$ GainControl | Defined$ Self | NewController$ TriggeredPlayer | UnlessCost$ R R | UnlessPayer$ TriggeredPlayer | UnlessSwitched$ True | UnlessAI$ OnlyDontControl | SpellDescription$ Pay R R to gain control of CARDNAME. -SVar:Pay2Life:DB$ GainControl | Defined$ Self | NewController$ TriggeredPlayer | UnlessCost$ PayLife<2> | UnlessPayer$ TriggeredPlayer | UnlessSwitched$ True | UnlessAI$ OnlyDontControl | SpellDescription$ Pay 2 life to gain control of CARDNAME. +SVar:PayRR:DB$ GainControl | Defined$ Self | NewController$ TriggeredPlayer | UnlessCost$ R R | UnlessPayer$ TriggeredPlayer | UnlessSwitched$ True | SpellDescription$ Pay R R to gain control of CARDNAME. +SVar:Pay2Life:DB$ GainControl | Defined$ Self | NewController$ TriggeredPlayer | UnlessCost$ PayLife<2> | UnlessPayer$ TriggeredPlayer | UnlessSwitched$ True | SpellDescription$ Pay 2 life to gain control of CARDNAME. Oracle:Flying\nAt the beginning of each player's upkeep, that player may pay {R}{R} or 2 life. If the player does, they gain control of Emberwilde Djinn. diff --git a/forge-gui/res/cardsfolder/e/endless_wurm.txt b/forge-gui/res/cardsfolder/e/endless_wurm.txt index 56e03b4c6d3..bd0402dfd67 100644 --- a/forge-gui/res/cardsfolder/e/endless_wurm.txt +++ b/forge-gui/res/cardsfolder/e/endless_wurm.txt @@ -3,6 +3,7 @@ ManaCost:3 G G Types:Creature Wurm PT:9/9 K:Trample -K:UpkeepCost:Sac<1/Enchantment> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you sacrifice an enchantment. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Sac<1/Enchantment> SVar:NeedsToPlay:Enchantment.YouCtrl Oracle:Trample\nAt the beginning of your upkeep, sacrifice Endless Wurm unless you sacrifice an enchantment. diff --git a/forge-gui/res/cardsfolder/e/energy_flux.txt b/forge-gui/res/cardsfolder/e/energy_flux.txt index 70ed272859b..0fd14930bbb 100644 --- a/forge-gui/res/cardsfolder/e/energy_flux.txt +++ b/forge-gui/res/cardsfolder/e/energy_flux.txt @@ -1,7 +1,9 @@ Name:Energy Flux ManaCost:2 U Types:Enchantment -S:Mode$ Continuous | Affected$ Artifact | AddKeyword$ UpkeepCost:2 | Description$ All artifacts have "At the beginning of your upkeep, sacrifice this artifact unless you pay {2}." +S:Mode$ Continuous | Affected$ Artifact | AddTrigger$ UpkeepCostTrigger | Description$ All artifacts have "At the beginning of your upkeep, sacrifice this artifact unless you pay {2}." +SVar:UpkeepCostTrigger:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {2}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 2 SVar:NeedsToPlayVar:CountOpps GTCountMe SVar:CountOpps:Count$Valid Artifact.OppCtrl/LimitMax.5 SVar:CountMe:Count$Valid Artifact.YouCtrl diff --git a/forge-gui/res/cardsfolder/e/extravagant_spirit.txt b/forge-gui/res/cardsfolder/e/extravagant_spirit.txt index 1382476bcd0..a4369044ffe 100644 --- a/forge-gui/res/cardsfolder/e/extravagant_spirit.txt +++ b/forge-gui/res/cardsfolder/e/extravagant_spirit.txt @@ -3,7 +3,8 @@ ManaCost:3 U Types:Creature Spirit PT:4/4 K:Flying -K:UpkeepCost:Y:{1} for each card in your hand +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {1} for each card in your hand. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Y SVar:Y:Count$InYourHand AI:RemoveDeck:All Oracle:Flying\nAt the beginning of your upkeep, sacrifice Extravagant Spirit unless you pay {1} for each card in your hand. diff --git a/forge-gui/res/cardsfolder/f/fettergeist.txt b/forge-gui/res/cardsfolder/f/fettergeist.txt index aff6230668f..e4d80c3b8a9 100644 --- a/forge-gui/res/cardsfolder/f/fettergeist.txt +++ b/forge-gui/res/cardsfolder/f/fettergeist.txt @@ -3,7 +3,8 @@ ManaCost:2 U Types:Creature Spirit PT:3/4 K:Flying -K:UpkeepCost:Y:{1} for each other creature you control +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {1} for each other creature you control. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Y SVar:Y:Count$Valid Creature.Other+YouCtrl AI:RemoveDeck:All Oracle:Flying\nAt the beginning of your upkeep, sacrifice Fettergeist unless you pay {1} for each other creature you control. diff --git a/forge-gui/res/cardsfolder/f/forbidden_ritual.txt b/forge-gui/res/cardsfolder/f/forbidden_ritual.txt index a39f629c185..27aa4b9bbf3 100644 --- a/forge-gui/res/cardsfolder/f/forbidden_ritual.txt +++ b/forge-gui/res/cardsfolder/f/forbidden_ritual.txt @@ -4,8 +4,8 @@ Types:Sorcery A:SP$ Repeat | ValidTgts$ Opponent | RepeatSubAbility$ DBSac | RepeatOptional$ True | StackDescription$ SpellDescription | SpellDescription$ Sacrifice a nontoken permanent. If you do, target opponent loses 2 life unless that player sacrifices a permanent or discards a card. You may repeat this process any number of times. SVar:DBSac:DB$ Sacrifice | SacValid$ Permanent.nonToken | SacMessage$ nontoken permanent | RememberSacrificed$ True | SubAbility$ DBGenericChoice SVar:DBGenericChoice:DB$ GenericChoice | Choices$ PaySac,PayDiscard | Defined$ Targeted | AILogic$ PayUnlessCost | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup -SVar:PaySac:DB$ LoseLife | LifeAmount$ 2 | Defined$ Targeted | UnlessCost$ Sac<1/Permanent> | UnlessPayer$ Targeted | UnlessAI$ LifeLE2 | SpellDescription$ You lose 2 life unless you sacrifice a permanent. -SVar:PayDiscard:DB$ LoseLife | LifeAmount$ 2 | Defined$ Targeted | UnlessCost$ Discard<1/Card> | UnlessPayer$ Targeted | UnlessAI$ LifeLE2 | SpellDescription$ You lose 2 life unless you discard a card. +SVar:PaySac:DB$ LoseLife | LifeAmount$ 2 | Defined$ Targeted | UnlessCost$ Sac<1/Permanent> | UnlessPayer$ Targeted | SpellDescription$ You lose 2 life unless you sacrifice a permanent. +SVar:PayDiscard:DB$ LoseLife | LifeAmount$ 2 | Defined$ Targeted | UnlessCost$ Discard<1/Card> | UnlessPayer$ Targeted | SpellDescription$ You lose 2 life unless you discard a card. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:All AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/f/forethought_amulet.txt b/forge-gui/res/cardsfolder/f/forethought_amulet.txt index e58a9c25867..19d9ad1755c 100644 --- a/forge-gui/res/cardsfolder/f/forethought_amulet.txt +++ b/forge-gui/res/cardsfolder/f/forethought_amulet.txt @@ -1,7 +1,8 @@ Name:Forethought Amulet ManaCost:5 Types:Artifact -K:UpkeepCost:3 +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {3}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 3 R:Event$ DamageDone | ActiveZones$ Battlefield | ValidSource$ Instant,Sorcery | ValidTarget$ You | DamageAmount$ GE3 | ReplaceWith$ Dmg2 | Description$ If an instant or sorcery source would deal 3 or more damage to you, it deals 2 damage to you instead. SVar:Dmg2:DB$ ReplaceEffect | VarName$ DamageAmount | VarValue$ 2 AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/g/glaciers.txt b/forge-gui/res/cardsfolder/g/glaciers.txt index c487e11b3b9..af0d4c26211 100644 --- a/forge-gui/res/cardsfolder/g/glaciers.txt +++ b/forge-gui/res/cardsfolder/g/glaciers.txt @@ -1,7 +1,8 @@ Name:Glaciers ManaCost:2 W U Types:Enchantment -K:UpkeepCost:W U +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {W}{U}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ W U S:Mode$ Continuous | Affected$ Mountain | AddType$ Plains | RemoveLandTypes$ True | Description$ All Mountains are Plains. AI:RemoveDeck:Random Oracle:At the beginning of your upkeep, sacrifice Glaciers unless you pay {W}{U}.\nAll Mountains are Plains. diff --git a/forge-gui/res/cardsfolder/g/grenzo_dungeon_warden.txt b/forge-gui/res/cardsfolder/g/grenzo_dungeon_warden.txt index 2acfd98896e..e92bf3a2352 100644 --- a/forge-gui/res/cardsfolder/g/grenzo_dungeon_warden.txt +++ b/forge-gui/res/cardsfolder/g/grenzo_dungeon_warden.txt @@ -4,7 +4,7 @@ Types:Legendary Creature Goblin Rogue PT:2/2 K:etbCounter:P1P1:X SVar:X:Count$xPaid -A:AB$ ChangeZone | Cost$ 2 | Defined$ BottomOfLibrary | Origin$ Library | Destination$ Graveyard | RememberChanged$ True | SubAbility$ DBChangeZone | StackDescription$ SpellDescription | SpellDescription$ Put the bottom card of your library into your graveyard. If it's a creature card with power less than or equal to CARDNAME's power, put it onto the battlefield. +A:AB$ ChangeZone | Cost$ 2 | Defined$ BottomOfLibrary | Origin$ Library | Destination$ Graveyard | RememberChanged$ True | SubAbility$ DBChangeZone | StackDescription$ SpellDescription | SpellDescription$ Put the bottom card of your library into your graveyard. If it's a creature card with power less than or equal to NICKNAME's power, put it onto the battlefield. SVar:DBChangeZone:DB$ ChangeZone | Defined$ Remembered | Origin$ All | Destination$ Battlefield | Hidden$ True | ConditionDefined$ Remembered | ConditionPresent$ Card.Creature+powerLEY | ConditionCompare$ GE1 | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:Y:Count$CardPower diff --git a/forge-gui/res/cardsfolder/g/gurzigost.txt b/forge-gui/res/cardsfolder/g/gurzigost.txt index b44bf0ae08f..cf718957510 100644 --- a/forge-gui/res/cardsfolder/g/gurzigost.txt +++ b/forge-gui/res/cardsfolder/g/gurzigost.txt @@ -2,7 +2,8 @@ Name:Gurzigost ManaCost:3 G G Types:Creature Beast PT:6/8 -K:UpkeepCost:PutCardToLibFromGrave<2/-1/Card> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you put two cards from your graveyard on the bottom of your library. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ PutCardToLibFromGrave<2/-1/Card> A:AB$ Effect | Cost$ G G Discard<1/Card> | StaticAbilities$ Static | Duration$ UntilHostLeavesPlayOrEOT | SpellDescription$ You may have CARDNAME assign its combat damage this turn as though it weren't blocked. SVar:Static:Mode$ AssignCombatDamageAsUnblocked | EffectZone$ Command | ValidCard$ Card.EffectSource | Optional$ True | Description$ You may have EFFECTSOURCE assign its combat damage this turn as though it weren't blocked. AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/h/haazda_shield_mate.txt b/forge-gui/res/cardsfolder/h/haazda_shield_mate.txt index e0f6611edae..34a292da8f9 100644 --- a/forge-gui/res/cardsfolder/h/haazda_shield_mate.txt +++ b/forge-gui/res/cardsfolder/h/haazda_shield_mate.txt @@ -2,7 +2,8 @@ Name:Haazda Shield Mate ManaCost:2 W Types:Creature Human Soldier PT:1/1 -K:UpkeepCost:W W +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {W}{W}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ W W A:AB$ ChooseSource | Cost$ W | Choices$ Card,Emblem | AILogic$ NeedsPrevention | SubAbility$ DBEffect | SpellDescription$ The next time a source of your choice would deal damage to you this turn, prevent that damage. SVar:DBEffect:DB$ Effect | ReplacementEffects$ RPreventNextFromSource | SubAbility$ DBCleanup | ConditionDefined$ ChosenCard | ConditionPresent$ Card,Emblem SVar:RPreventNextFromSource:Event$ DamageDone | ValidSource$ Card.ChosenCardStrict,Emblem.ChosenCard | ValidTarget$ You | ReplaceWith$ ExileEffect | PreventionEffect$ True | Description$ The next time the chosen source deals damage to you, prevent that damage. diff --git a/forge-gui/res/cardsfolder/h/hungry_mist.txt b/forge-gui/res/cardsfolder/h/hungry_mist.txt index b431bfc9905..94581e2b24f 100644 --- a/forge-gui/res/cardsfolder/h/hungry_mist.txt +++ b/forge-gui/res/cardsfolder/h/hungry_mist.txt @@ -2,5 +2,6 @@ Name:Hungry Mist ManaCost:2 G G Types:Creature Elemental PT:6/2 -K:UpkeepCost:G G +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {G}{G}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ G G Oracle:At the beginning of your upkeep, sacrifice Hungry Mist unless you pay {G}{G}. diff --git a/forge-gui/res/cardsfolder/i/icy_prison.txt b/forge-gui/res/cardsfolder/i/icy_prison.txt index a5a77895dfe..9d13fac2e19 100644 --- a/forge-gui/res/cardsfolder/i/icy_prison.txt +++ b/forge-gui/res/cardsfolder/i/icy_prison.txt @@ -2,11 +2,11 @@ Name:Icy Prison ManaCost:U U Types:Enchantment T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters, exile target creature. -SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | ForgetOtherRemembered$ True | ValidTgts$ Creature | TgtPrompt$ Select target creature +SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Creature | TgtPrompt$ Select target creature | IsCurse$ True T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME leaves the battlefield, return the exiled card to the battlefield under its owner's control. -SVar:TrigReturn:DB$ ChangeZone | Origin$ Exile | Destination$ Battlefield | Defined$ Remembered +SVar:TrigReturn:DB$ ChangeZone | Origin$ Exile | Destination$ Battlefield | Defined$ ExiledWith T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigSac | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless any player pays {3}. -SVar:TrigSac:DB$ Sacrifice | UnlessCost$ 3 | UnlessPayer$ Player | UnlessAI$ OnlyOwn +SVar:TrigSac:DB$ Sacrifice | UnlessCost$ 3 | UnlessPayer$ Player SVar:PlayMain1:TRUE SVar:NeedsToPlayVar:Y GE3 SVar:Y:Count$Valid Land.YouCtrl diff --git a/forge-gui/res/cardsfolder/i/indulgent_tormentor.txt b/forge-gui/res/cardsfolder/i/indulgent_tormentor.txt index 37302221955..0cb593359d3 100644 --- a/forge-gui/res/cardsfolder/i/indulgent_tormentor.txt +++ b/forge-gui/res/cardsfolder/i/indulgent_tormentor.txt @@ -4,7 +4,8 @@ Types:Creature Demon PT:5/3 K:Flying T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigChoice | TriggerDescription$ At the beginning of your upkeep, draw a card unless target opponent sacrifices a creature or pays 3 life. -SVar:TrigChoice:DB$ GenericChoice | ValidTgts$ Opponent | Choices$ DBPayLife,DBSacCreature | AILogic$ PayUnlessCost -SVar:DBSacCreature:DB$ Draw | Defined$ You | UnlessCost$ Sac<1/Creature> | UnlessPayer$ Targeted | UnlessAI$ Never | SpellDescription$ CARDNAME's controller draws a card unless you sacrifice a creature. -SVar:DBPayLife:DB$ Draw | Defined$ You | UnlessCost$ PayLife<3> | UnlessPayer$ Targeted | UnlessAI$ LowPriority | SpellDescription$ CARDNAME's controller draws a card unless you pay 3 life. +SVar:TrigChoice:DB$ GenericChoice | ValidTgts$ Opponent | Choices$ DBPayLife,DBSacCreature | AILogic$ PayUnlessCost | FallbackAbility$ DrawFallback +SVar:DBSacCreature:DB$ Draw | Defined$ You | UnlessCost$ Sac<1/Creature> | UnlessPayer$ Targeted | UnlessAI$ LowPriority | SpellDescription$ CARDNAME's controller draws a card unless you sacrifice a creature. +SVar:DBPayLife:DB$ Draw | Defined$ You | UnlessCost$ PayLife<3> | UnlessPayer$ Targeted | SpellDescription$ CARDNAME's controller draws a card unless you pay 3 life. +SVar:DrawFallback:DB$ Draw Oracle:Flying\nAt the beginning of your upkeep, draw a card unless target opponent sacrifices a creature or pays 3 life. diff --git a/forge-gui/res/cardsfolder/j/junk_golem.txt b/forge-gui/res/cardsfolder/j/junk_golem.txt index 3ef110438d5..eafdef1419a 100644 --- a/forge-gui/res/cardsfolder/j/junk_golem.txt +++ b/forge-gui/res/cardsfolder/j/junk_golem.txt @@ -3,7 +3,8 @@ ManaCost:4 Types:Artifact Creature Golem PT:0/0 K:etbCounter:P1P1:3 -K:UpkeepCost:SubCounter<1/P1P1> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you remove a +1/+1 counter from it. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ SubCounter<1/P1P1> SVar:TrigSac:DB$ Sacrifice | UnlessCost$ SubCounter<1/P1P1> | UnlessPayer$ You A:AB$ PutCounter | Cost$ 1 Discard<1/Card> | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on CARDNAME. AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/j/junun_efreet.txt b/forge-gui/res/cardsfolder/j/junun_efreet.txt index 65d3a483fa2..0ed03992e60 100644 --- a/forge-gui/res/cardsfolder/j/junun_efreet.txt +++ b/forge-gui/res/cardsfolder/j/junun_efreet.txt @@ -3,5 +3,6 @@ ManaCost:1 B B Types:Creature Efreet PT:3/3 K:Flying -K:UpkeepCost:B B +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {B}{B}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ B B Oracle:Flying\nAt the beginning of your upkeep, sacrifice Junún Efreet unless you pay {B}{B}. diff --git a/forge-gui/res/cardsfolder/j/justice.txt b/forge-gui/res/cardsfolder/j/justice.txt index 88e94f12a23..067420d3045 100644 --- a/forge-gui/res/cardsfolder/j/justice.txt +++ b/forge-gui/res/cardsfolder/j/justice.txt @@ -1,7 +1,8 @@ Name:Justice ManaCost:2 W W Types:Enchantment -K:UpkeepCost:W W +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {W}{W}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ W W T:Mode$ DamageDealtOnce | ValidSource$ Instant.Red,Sorcery.Red,Creature.Red+inZoneBattlefield | Execute$ TrigDamage | TriggerZones$ Battlefield | TriggerDescription$ Whenever a red creature or spell deals damage, CARDNAME deals that much damage to that creature's or spell's controller. SVar:TrigDamage:DB$ DealDamage | Defined$ TriggeredSourceController | NumDmg$ X SVar:X:TriggerCount$DamageAmount diff --git a/forge-gui/res/cardsfolder/k/kami_of_the_tended_garden.txt b/forge-gui/res/cardsfolder/k/kami_of_the_tended_garden.txt index 94dc384f986..6a5b90b010a 100644 --- a/forge-gui/res/cardsfolder/k/kami_of_the_tended_garden.txt +++ b/forge-gui/res/cardsfolder/k/kami_of_the_tended_garden.txt @@ -2,6 +2,7 @@ Name:Kami of the Tended Garden ManaCost:3 G Types:Creature Spirit PT:4/4 -K:UpkeepCost:G +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {G}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ G K:Soulshift:3 Oracle:At the beginning of your upkeep, sacrifice Kami of the Tended Garden unless you pay {G}.\nSoulshift 3 (When this creature dies, you may return target Spirit card with mana value 3 or less from your graveyard to your hand.) diff --git a/forge-gui/res/cardsfolder/k/kataki_wars_wage.txt b/forge-gui/res/cardsfolder/k/kataki_wars_wage.txt index d6140051651..87222335d8c 100644 --- a/forge-gui/res/cardsfolder/k/kataki_wars_wage.txt +++ b/forge-gui/res/cardsfolder/k/kataki_wars_wage.txt @@ -2,6 +2,8 @@ Name:Kataki, War's Wage ManaCost:1 W Types:Legendary Creature Spirit PT:2/1 -S:Mode$ Continuous | Affected$ Artifact | AddKeyword$ UpkeepCost:1 | Description$ All artifacts have "At the beginning of your upkeep, sacrifice this artifact unless you pay {1}." +S:Mode$ Continuous | Affected$ Artifact | AddTrigger$ UpkeepCostTrigger | Description$ All artifacts have "At the beginning of your upkeep, sacrifice this artifact unless you pay {1}." +SVar:UpkeepCostTrigger:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {2}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 2 AI:RemoveDeck:Random Oracle:All artifacts have "At the beginning of your upkeep, sacrifice this artifact unless you pay {1}." diff --git a/forge-gui/res/cardsfolder/k/knight_of_the_mists.txt b/forge-gui/res/cardsfolder/k/knight_of_the_mists.txt index a81ac2c5989..8847ccfaec7 100644 --- a/forge-gui/res/cardsfolder/k/knight_of_the_mists.txt +++ b/forge-gui/res/cardsfolder/k/knight_of_the_mists.txt @@ -4,6 +4,6 @@ Types:Creature Human Knight PT:2/2 K:Flanking T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDestroy | TriggerDescription$ When CARDNAME enters, you may pay {U}. If you don't, destroy target Knight and it can't be regenerated. -SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Creature.Knight | TgtPrompt$ Select target Knight | NoRegen$ True | UnlessCost$ U | UnlessPayer$ You | UnlessAI$ DefinedTargetedController +SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Creature.Knight | TgtPrompt$ Select target Knight | NoRegen$ True | UnlessCost$ U | UnlessPayer$ You AI:RemoveDeck:All Oracle:Flanking (Whenever a creature without flanking blocks this creature, the blocking creature gets -1/-1 until end of turn.)\nWhen Knight of the Mists enters, you may pay {U}. If you don't, destroy target Knight and it can't be regenerated. diff --git a/forge-gui/res/cardsfolder/k/koskun_falls.txt b/forge-gui/res/cardsfolder/k/koskun_falls.txt index 8b6a84f56f3..8ecd885896a 100644 --- a/forge-gui/res/cardsfolder/k/koskun_falls.txt +++ b/forge-gui/res/cardsfolder/k/koskun_falls.txt @@ -1,7 +1,8 @@ Name:Koskun Falls ManaCost:2 B B Types:World Enchantment -K:UpkeepCost:tapXType<1/Creature> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you tap an untapped creature you control. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ tapXType<1/Creature> S:Mode$ CantAttackUnless | ValidCard$ Creature | Target$ You | Cost$ 2 | Description$ Creatures can't attack you unless their controller pays {2} for each creature they control that's attacking you. SVar:NeedsToPlayVar:Y GE1 SVar:Y:Count$Valid Creature.YouCtrl diff --git a/forge-gui/res/cardsfolder/k/krosan_cloudscraper.txt b/forge-gui/res/cardsfolder/k/krosan_cloudscraper.txt index d6443ffa102..244bf29ca05 100644 --- a/forge-gui/res/cardsfolder/k/krosan_cloudscraper.txt +++ b/forge-gui/res/cardsfolder/k/krosan_cloudscraper.txt @@ -2,6 +2,7 @@ Name:Krosan Cloudscraper ManaCost:7 G G G Types:Creature Beast Mutant PT:13/13 -K:UpkeepCost:G G +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {G}{G}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ G G K:Morph:7 G G Oracle:At the beginning of your upkeep, sacrifice Krosan Cloudscraper unless you pay {G}{G}.\nMorph {7}{G}{G} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost.) diff --git a/forge-gui/res/cardsfolder/k/kuro_pitlord.txt b/forge-gui/res/cardsfolder/k/kuro_pitlord.txt index 8b4d2a0c49d..ce8c678f3e1 100644 --- a/forge-gui/res/cardsfolder/k/kuro_pitlord.txt +++ b/forge-gui/res/cardsfolder/k/kuro_pitlord.txt @@ -2,7 +2,8 @@ Name:Kuro, Pitlord ManaCost:6 B B B Types:Legendary Creature Demon Spirit PT:9/9 -K:UpkeepCost:B B B B +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {B}{B}{B}{B}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ B B B B # TODO: the AI can be improved for this, it'll currently only use it to finish off creatures with toughness 1 A:AB$ Pump | Cost$ PayLife<1> | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -1 | NumDef$ -1 | IsCurse$ True | SpellDescription$ Target creature gets -1/-1 until end of turn. SVar:NeedsToPlayVar:Z GE4 diff --git a/forge-gui/res/cardsfolder/l/lithophage.txt b/forge-gui/res/cardsfolder/l/lithophage.txt index 293256bc31c..d5099b4e42f 100644 --- a/forge-gui/res/cardsfolder/l/lithophage.txt +++ b/forge-gui/res/cardsfolder/l/lithophage.txt @@ -2,6 +2,7 @@ Name:Lithophage ManaCost:3 R R Types:Creature Insect PT:7/7 -K:UpkeepCost:Sac<1/Mountain> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you sacrifice a Mountain. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Sac<1/Mountain> SVar:NeedsToPlay:Mountain.YouCtrl Oracle:At the beginning of your upkeep, sacrifice Lithophage unless you sacrifice a Mountain. diff --git a/forge-gui/res/cardsfolder/l/living_tsunami.txt b/forge-gui/res/cardsfolder/l/living_tsunami.txt index e33c30866cd..1b52e754275 100644 --- a/forge-gui/res/cardsfolder/l/living_tsunami.txt +++ b/forge-gui/res/cardsfolder/l/living_tsunami.txt @@ -3,5 +3,6 @@ ManaCost:2 U U Types:Creature Elemental PT:4/4 K:Flying -K:UpkeepCost:Return<1/Land> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you return a land you control to its owner's hand. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Return<1/Land> Oracle:Flying\nAt the beginning of your upkeep, sacrifice Living Tsunami unless you return a land you control to its owner's hand. diff --git a/forge-gui/res/cardsfolder/m/magus_of_the_tabernacle.txt b/forge-gui/res/cardsfolder/m/magus_of_the_tabernacle.txt index c594073b51c..48f3c17ebe9 100644 --- a/forge-gui/res/cardsfolder/m/magus_of_the_tabernacle.txt +++ b/forge-gui/res/cardsfolder/m/magus_of_the_tabernacle.txt @@ -2,6 +2,8 @@ Name:Magus of the Tabernacle ManaCost:3 W Types:Creature Human Wizard PT:2/6 -S:Mode$ Continuous | Affected$ Creature | AddKeyword$ UpkeepCost:1 | Description$ All creatures have "At the beginning of your upkeep, sacrifice this creature unless you pay {1}." +S:Mode$ Continuous | Affected$ Creature | AddTrigger$ UpkeepCostTrigger | Description$ All creatures have "At the beginning of your upkeep, sacrifice this creature unless you pay {1}." +SVar:UpkeepCostTrigger:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {21. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 1 AI:RemoveDeck:Random Oracle:All creatures have "At the beginning of your upkeep, sacrifice this creature unless you pay {1}." diff --git a/forge-gui/res/cardsfolder/m/mana_charged_dragon.txt b/forge-gui/res/cardsfolder/m/mana_charged_dragon.txt index 047144bf6b5..03b83aded7f 100644 --- a/forge-gui/res/cardsfolder/m/mana_charged_dragon.txt +++ b/forge-gui/res/cardsfolder/m/mana_charged_dragon.txt @@ -8,7 +8,7 @@ T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigJoinForces | TriggerDescri T:Mode$ Blocks | ValidCard$ Card.Self | Execute$ TrigJoinForces | Secondary$ True | TriggerDescription$ Join forces — Whenever CARDNAME attacks or blocks, each player starting with you may pay any amount of mana. CARDNAME gets +X/+0 until end of turn, where X is the total amount of mana paid this way. SVar:TrigJoinForces:DB$ RepeatEach | RepeatPlayers$ Player | StartingWith$ You | RepeatSubAbility$ DBPay | SubAbility$ DBPump SVar:DBPay:DB$ ChooseNumber | Defined$ Player.IsRemembered | ChooseAnyNumber$ True | ListTitle$ amount of mana to pay | SubAbility$ DBStore -SVar:DBStore:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ CountSVar | Expression$ JoinForcesAmount/Plus.Y | UnlessCost$ Y | UnlessPayer$ Player.IsRemembered | UnlessSwitched$ True | UnlessAI$ OnlyOwn +SVar:DBStore:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ CountSVar | Expression$ JoinForcesAmount/Plus.Y | UnlessCost$ Y | UnlessPayer$ Player.IsRemembered | UnlessSwitched$ True SVar:DBPump:DB$ Pump | Defined$ Self | NumAtt$ JoinForcesAmount | SubAbility$ DBReset SVar:DBReset:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ Number | Expression$ 0 SVar:Y:Count$ChosenNumber diff --git a/forge-gui/res/cardsfolder/m/mangaras_equity.txt b/forge-gui/res/cardsfolder/m/mangaras_equity.txt index 7b89ab024f4..f53b1acc6dc 100644 --- a/forge-gui/res/cardsfolder/m/mangaras_equity.txt +++ b/forge-gui/res/cardsfolder/m/mangaras_equity.txt @@ -1,7 +1,8 @@ Name:Mangara's Equity ManaCost:1 W W Types:Enchantment -K:UpkeepCost:1 W +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {1}{W}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 1 W K:ETBReplacement:Other:ChooseBlackOrRed SVar:ChooseBlackOrRed:DB$ ChooseColor | Defined$ You | Choices$ black,red | AILogic$ MostProminentHumanCreatures | SpellDescription$ As CARDNAME enters, choose black or red. T:Mode$ DamageDone | ValidSource$ Creature.ChosenColor+inZoneBattlefield | ValidTarget$ Creature.White+YouCtrl,You | TriggerZones$ Battlefield | Execute$ MangarasRetribution | TriggerDescription$ Whenever a creature of the chosen color deals damage to you or a white creature you control, CARDNAME deals that much damage to that creature. diff --git a/forge-gui/res/cardsfolder/m/masticore.txt b/forge-gui/res/cardsfolder/m/masticore.txt index 62eb9a87be6..ea356e27612 100644 --- a/forge-gui/res/cardsfolder/m/masticore.txt +++ b/forge-gui/res/cardsfolder/m/masticore.txt @@ -2,7 +2,8 @@ Name:Masticore ManaCost:4 Types:Artifact Creature Masticore PT:4/4 -K:UpkeepCost:Discard<1/Card> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you discard a card. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Discard<1/Card> A:AB$ Regenerate | Cost$ 2 | SpellDescription$ Regenerate CARDNAME. A:AB$ DealDamage | Cost$ 2 | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to target creature. AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/m/melancholy.txt b/forge-gui/res/cardsfolder/m/melancholy.txt index b53a26dc554..93b41385ffe 100644 --- a/forge-gui/res/cardsfolder/m/melancholy.txt +++ b/forge-gui/res/cardsfolder/m/melancholy.txt @@ -1,7 +1,8 @@ Name:Melancholy ManaCost:2 B Types:Enchantment Aura -K:UpkeepCost:B +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {B}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ B K:Enchant creature A:SP$ Attach | Cost$ 2 B | ValidTgts$ Creature | AILogic$ KeepTapped S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step. | Description$ Enchanted creature doesn't untap during its controller's untap step. diff --git a/forge-gui/res/cardsfolder/m/minds_aglow.txt b/forge-gui/res/cardsfolder/m/minds_aglow.txt index 0c734eb5066..cc6f5b92ddc 100644 --- a/forge-gui/res/cardsfolder/m/minds_aglow.txt +++ b/forge-gui/res/cardsfolder/m/minds_aglow.txt @@ -3,7 +3,7 @@ ManaCost:U Types:Sorcery A:SP$ RepeatEach | RepeatPlayers$ Player | StartingWith$ You | RepeatSubAbility$ DBPay | SubAbility$ DBDraw | StackDescription$ SpellDescription | SpellDescription$ Join forces — Starting with you, each player may pay any amount of mana. Each player draws X cards, where X is the total amount of mana paid this way. SVar:DBPay:DB$ ChooseNumber | Defined$ Player.IsRemembered | ChooseAnyNumber$ True | ListTitle$ amount of mana to pay | SubAbility$ DBStore -SVar:DBStore:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ CountSVar | Expression$ JoinForcesAmount/Plus.Y | UnlessCost$ Y | UnlessPayer$ Player.IsRemembered | UnlessSwitched$ True | UnlessAI$ OnlyOwn +SVar:DBStore:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ CountSVar | Expression$ JoinForcesAmount/Plus.Y | UnlessCost$ Y | UnlessPayer$ Player.IsRemembered | UnlessSwitched$ True SVar:DBDraw:DB$ Draw | Defined$ Player | NumCards$ JoinForcesAmount | SubAbility$ DBReset | StackDescription$ None SVar:DBReset:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ Number | Expression$ 0 SVar:Y:Count$ChosenNumber diff --git a/forge-gui/res/cardsfolder/m/mogis_god_of_slaughter.txt b/forge-gui/res/cardsfolder/m/mogis_god_of_slaughter.txt index e2971f752e3..cd5f5db4b8f 100644 --- a/forge-gui/res/cardsfolder/m/mogis_god_of_slaughter.txt +++ b/forge-gui/res/cardsfolder/m/mogis_god_of_slaughter.txt @@ -6,6 +6,6 @@ K:Indestructible S:Mode$ Continuous | Affected$ Card.Self | RemoveType$ Creature | CheckSVar$ X | SVarCompare$ LT7 | Description$ As long as your devotion to black and red is less than seven, NICKNAME isn't a creature. SVar:X:Count$DevotionDual.Black.Red T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player.Opponent | Execute$ TrigDmg | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of each opponent's upkeep, NICKNAME deals 2 damage to that player unless they sacrifice a creature. -SVar:TrigDmg:DB$ DealDamage | NumDmg$ 2 | Defined$ TriggeredPlayer | UnlessCost$ Sac<1/Creature> | UnlessAI$ LifeLE2 | UnlessPayer$ TriggeredPlayer +SVar:TrigDmg:DB$ DealDamage | NumDmg$ 2 | Defined$ TriggeredPlayer | UnlessCost$ Sac<1/Creature> | UnlessPayer$ TriggeredPlayer SVar:BuffedBy:Permanent.Black,Permanent.Red Oracle:Indestructible\nAs long as your devotion to black and red is less than seven, Mogis isn't a creature.\nAt the beginning of each opponent's upkeep, Mogis deals 2 damage to that player unless they sacrifice a creature. diff --git a/forge-gui/res/cardsfolder/m/molten_tail_masticore.txt b/forge-gui/res/cardsfolder/m/molten_tail_masticore.txt index 73c122ad7aa..ef4e71d111c 100644 --- a/forge-gui/res/cardsfolder/m/molten_tail_masticore.txt +++ b/forge-gui/res/cardsfolder/m/molten_tail_masticore.txt @@ -2,7 +2,8 @@ Name:Molten-Tail Masticore ManaCost:4 Types:Artifact Creature Masticore PT:4/4 -K:UpkeepCost:Discard<1/Card> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you discard a card. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Discard<1/Card> A:AB$ DealDamage | Cost$ 4 ExileFromGrave<1/Creature> | NumDmg$ 4 | ValidTgts$ Any | SpellDescription$ CARDNAME deals 4 damage to any target. A:AB$ Regenerate | Cost$ 2 | SpellDescription$ Regenerate CARDNAME. SVar:NeedsToPlayVar:Z GE3 diff --git a/forge-gui/res/cardsfolder/m/molting_harpy.txt b/forge-gui/res/cardsfolder/m/molting_harpy.txt index f15d01c94dc..06238097fab 100644 --- a/forge-gui/res/cardsfolder/m/molting_harpy.txt +++ b/forge-gui/res/cardsfolder/m/molting_harpy.txt @@ -2,6 +2,7 @@ Name:Molting Harpy ManaCost:B Types:Creature Harpy Mercenary PT:2/1 -K:UpkeepCost:2 +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {2}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 2 K:Flying Oracle:Flying\nAt the beginning of your upkeep, sacrifice Molting Harpy unless you pay {2}. diff --git a/forge-gui/res/cardsfolder/n/natures_wrath.txt b/forge-gui/res/cardsfolder/n/natures_wrath.txt index 0d61edf2302..32876f5cea9 100644 --- a/forge-gui/res/cardsfolder/n/natures_wrath.txt +++ b/forge-gui/res/cardsfolder/n/natures_wrath.txt @@ -1,7 +1,8 @@ Name:Nature's Wrath ManaCost:4 G G Types:Enchantment -K:UpkeepCost:G +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {G}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ G T:Mode$ ChangesZone | ValidCard$ Island,Card.Blue | Origin$ Any | Destination$ Battlefield | TriggerZones$ Battlefield | Execute$ TrigSacrificeBlue | TriggerDescription$ Whenever a player puts an Island or blue permanent onto the battlefield, that player sacrifices an Island or blue permanent. SVar:TrigSacrificeBlue:DB$ Sacrifice | Defined$ TriggeredCardController | SacValid$ Island,Card.Blue | SacMessage$ Island or a blue permanent T:Mode$ ChangesZone | ValidCard$ Swamp,Card.Black | Origin$ Any | Destination$ Battlefield | TriggerZones$ Battlefield | Execute$ TrigSacrificeBlack | TriggerDescription$ Whenever a player puts an Swamp or black permanent onto the battlefield, that player sacrifices a Swamp or black permanent. diff --git a/forge-gui/res/cardsfolder/n/nicol_bolas.txt b/forge-gui/res/cardsfolder/n/nicol_bolas.txt index 6849fd7cf6d..3ef7ca12171 100644 --- a/forge-gui/res/cardsfolder/n/nicol_bolas.txt +++ b/forge-gui/res/cardsfolder/n/nicol_bolas.txt @@ -3,7 +3,8 @@ ManaCost:2 U U B B R R Types:Legendary Creature Elder Dragon PT:7/7 K:Flying -K:UpkeepCost:U B R +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {U}{B}{R}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ U B R T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Opponent | TriggerZones$ Battlefield | Execute$ TrigDiscard | TriggerDescription$ Whenever CARDNAME deals damage to an opponent, that player discards their hand. SVar:TrigDiscard:DB$ Discard | Defined$ TriggeredTarget | Mode$ Hand DeckHas:Ability$Discard diff --git a/forge-gui/res/cardsfolder/o/osseous_sticktwister.txt b/forge-gui/res/cardsfolder/o/osseous_sticktwister.txt index eae65250c46..d7e6f74d135 100644 --- a/forge-gui/res/cardsfolder/o/osseous_sticktwister.txt +++ b/forge-gui/res/cardsfolder/o/osseous_sticktwister.txt @@ -5,8 +5,8 @@ PT:2/2 K:Lifelink T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | Delirium$ True | TriggerZones$ Battlefield | Execute$ TrigMaySacorDiscard | TriggerDescription$ Delirium — At the beginning of your end step, if there are four or more card types among cards in your graveyard, each opponent may sacrifice a nonland permanent or discard a card. Then CARDNAME deals damage equal to its power to each opponent who didn't sacrifice a permanent or discard a card this way. SVar:TrigMaySacorDiscard:DB$ GenericChoice | Choices$ PaySac,PayDiscard | TempRemember$ Chooser | Defined$ Opponent | AILogic$ PayUnlessCost | DamageMap$ True -SVar:PaySac:DB$ DealDamage | NumDmg$ X | Defined$ Remembered | UnlessCost$ Sac<1/Permanent.nonLand> | UnlessPayer$ Remembered | UnlessAI$ LifeLEX | SpellDescription$ CARDNAME deals damage equal to its power to you unless you sacrifice a nonland permanent. -SVar:PayDiscard:DB$ DealDamage | NumDmg$ X | Defined$ Remembered | UnlessCost$ Discard<1/Card> | UnlessPayer$ Remembered | UnlessAI$ LifeLEX | SpellDescription$ CARDNAME deals damage equal to its power to you unless you discard a card. +SVar:PaySac:DB$ DealDamage | NumDmg$ X | Defined$ Remembered | UnlessCost$ Sac<1/Permanent.nonLand> | UnlessPayer$ Remembered | SpellDescription$ CARDNAME deals damage equal to its power to you unless you sacrifice a nonland permanent. +SVar:PayDiscard:DB$ DealDamage | NumDmg$ X | Defined$ Remembered | UnlessCost$ Discard<1/Card> | UnlessPayer$ Remembered | SpellDescription$ CARDNAME deals damage equal to its power to you unless you discard a card. SVar:X:Count$CardPower DeckHints:Ability$Graveyard|Discard DeckHas:Ability$Delirium diff --git a/forge-gui/res/cardsfolder/p/palladia_mors.txt b/forge-gui/res/cardsfolder/p/palladia_mors.txt index 1ff379e02c0..25d80175615 100644 --- a/forge-gui/res/cardsfolder/p/palladia_mors.txt +++ b/forge-gui/res/cardsfolder/p/palladia_mors.txt @@ -4,5 +4,6 @@ Types:Legendary Creature Elder Dragon PT:7/7 K:Flying K:Trample -K:UpkeepCost:R G W +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {R}{G}{W}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ R G W Oracle:Flying, trample\nAt the beginning of your upkeep, sacrifice Palladia-Mors unless you pay {R}{G}{W}. diff --git a/forge-gui/res/cardsfolder/p/paralyze.txt b/forge-gui/res/cardsfolder/p/paralyze.txt index 53defcf8f5c..7c6f66b36d0 100644 --- a/forge-gui/res/cardsfolder/p/paralyze.txt +++ b/forge-gui/res/cardsfolder/p/paralyze.txt @@ -7,5 +7,5 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S SVar:TrigTap:DB$ Tap | Defined$ Enchanted S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step. | Description$ Enchanted creature doesn't untap during its controller's untap step. T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player.EnchantedController | TriggerZones$ Battlefield | Execute$ TrigUntap | TriggerDescription$ At the beginning of the upkeep of enchanted creature's controller, that player may pay {4}. If the player does, untap the creature. -SVar:TrigUntap:DB$ Untap | Defined$ Enchanted | UnlessCost$ 4 | UnlessPayer$ EnchantedController | UnlessSwitched$ True | UnlessAI$ Paralyze +SVar:TrigUntap:DB$ Untap | Defined$ Enchanted | UnlessCost$ 4 | UnlessPayer$ EnchantedController | UnlessSwitched$ True Oracle:Enchant creature\nWhen Paralyze enters, tap enchanted creature.\nEnchanted creature doesn't untap during its controller's untap step.\nAt the beginning of the upkeep of enchanted creature's controller, that player may pay {4}. If the player does, untap the creature. diff --git a/forge-gui/res/cardsfolder/p/peacekeeper.txt b/forge-gui/res/cardsfolder/p/peacekeeper.txt index 3db2764e4db..e7d44e306c3 100644 --- a/forge-gui/res/cardsfolder/p/peacekeeper.txt +++ b/forge-gui/res/cardsfolder/p/peacekeeper.txt @@ -2,7 +2,8 @@ Name:Peacekeeper ManaCost:2 W Types:Creature Human PT:1/1 -K:UpkeepCost:1 W +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {1}{W}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 1 W S:Mode$ CantAttack | ValidCard$ Creature | Description$ Creatures can't attack. AI:RemoveDeck:All Oracle:At the beginning of your upkeep, sacrifice Peacekeeper unless you pay {1}{W}.\nCreatures can't attack. diff --git a/forge-gui/res/cardsfolder/p/pendrell_mists.txt b/forge-gui/res/cardsfolder/p/pendrell_mists.txt index 644c396410b..dfef4b61dba 100644 --- a/forge-gui/res/cardsfolder/p/pendrell_mists.txt +++ b/forge-gui/res/cardsfolder/p/pendrell_mists.txt @@ -1,6 +1,8 @@ Name:Pendrell Mists ManaCost:3 U Types:Enchantment -S:Mode$ Continuous | Affected$ Creature | AddKeyword$ UpkeepCost:1 | Description$ All creatures have "At the beginning of your upkeep, sacrifice this creature unless you pay {1}." +S:Mode$ Continuous | Affected$ Creature | AddTrigger$ UpkeepCostTrigger | Description$ All creatures have "At the beginning of your upkeep, sacrifice this creature unless you pay {1}." +SVar:UpkeepCostTrigger:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {21. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 1 AI:RemoveDeck:Random Oracle:All creatures have "At the beginning of your upkeep, sacrifice this creature unless you pay {1}." diff --git a/forge-gui/res/cardsfolder/p/perplex.txt b/forge-gui/res/cardsfolder/p/perplex.txt index c736476fe1e..0b86903c83d 100644 --- a/forge-gui/res/cardsfolder/p/perplex.txt +++ b/forge-gui/res/cardsfolder/p/perplex.txt @@ -2,5 +2,5 @@ Name:Perplex ManaCost:1 U B Types:Instant K:Transmute:1 U B -A:SP$ Counter | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | UnlessCost$ Discard<1/Hand> | AILogic$ OppDiscardsHand | SpellDescription$ Counter target spell unless its controller discards their hand. +A:SP$ Counter | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | UnlessCost$ Discard<1/Hand> | SpellDescription$ Counter target spell unless its controller discards their hand. Oracle:Counter target spell unless its controller discards their hand.\nTransmute {1}{U}{B} ({1}{U}{B}, Discard this card: Search your library for a card with the same mana value as this card, reveal it, put it into your hand, then shuffle. Transmute only as a sorcery.) diff --git a/forge-gui/res/cardsfolder/p/phantasmal_forces.txt b/forge-gui/res/cardsfolder/p/phantasmal_forces.txt index 68c008856e9..b52326881f2 100644 --- a/forge-gui/res/cardsfolder/p/phantasmal_forces.txt +++ b/forge-gui/res/cardsfolder/p/phantasmal_forces.txt @@ -3,5 +3,6 @@ ManaCost:3 U Types:Creature Illusion PT:4/1 K:Flying -K:UpkeepCost:U +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {U}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ U Oracle:Flying\nAt the beginning of your upkeep, sacrifice Phantasmal Forces unless you pay {U}. diff --git a/forge-gui/res/cardsfolder/p/pias_revolution.txt b/forge-gui/res/cardsfolder/p/pias_revolution.txt index 88047851da6..0d44d999a32 100644 --- a/forge-gui/res/cardsfolder/p/pias_revolution.txt +++ b/forge-gui/res/cardsfolder/p/pias_revolution.txt @@ -2,7 +2,6 @@ Name:Pia's Revolution ManaCost:2 R Types:Enchantment T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Artifact.YouOwn+nonToken | TriggerZones$ Battlefield | Execute$ TrigReturn | TriggerDescription$ Whenever a nontoken artifact is put into your graveyard from the battlefield, return that card to your hand unless target opponent has CARDNAME deal 3 damage to them. -SVar:TrigReturn:DB$ Pump | ValidTgts$ Opponent | IsCurse$ True | SubAbility$ DBReturn -SVar:DBReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Hand | UnlessCost$ DamageYou<3> | UnlessPayer$ Targeted | UnlessAI$ nonToken +SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Hand | UnlessCost$ DamageYou<3> | UnlessPayer$ Targeted | ValidTgts$ Opponent | IsCurse$ True SVar:BuffedBy:Permanent.White,Permanent.Black Oracle:Whenever a nontoken artifact is put into your graveyard from the battlefield, return that card to your hand unless target opponent has Pia's Revolution deal 3 damage to them. diff --git a/forge-gui/res/cardsfolder/p/piru_the_volatile.txt b/forge-gui/res/cardsfolder/p/piru_the_volatile.txt index 39f64377964..7a9f8b61d48 100644 --- a/forge-gui/res/cardsfolder/p/piru_the_volatile.txt +++ b/forge-gui/res/cardsfolder/p/piru_the_volatile.txt @@ -4,7 +4,8 @@ Types:Legendary Creature Elder Dragon PT:7/7 K:Flying K:Lifelink -K:UpkeepCost:R W B +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {R}{W}{B}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ R W B T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigDamage | TriggerDescription$ When CARDNAME dies, it deals 7 damage to each nonlegendary creature. SVar:TrigDamage:DB$ DamageAll | ValidCards$ Creature.nonLegendary | NumDmg$ 7 | ValidDescription$ each nonlegendary creature. Oracle:Flying, lifelink\nAt the beginning of your upkeep, sacrifice Piru, the Volatile unless you pay {R}{W}{B}.\nWhen Piru dies, it deals 7 damage to each nonlegendary creature. diff --git a/forge-gui/res/cardsfolder/p/pit_raptor.txt b/forge-gui/res/cardsfolder/p/pit_raptor.txt index 878d35bce90..700ec157f34 100644 --- a/forge-gui/res/cardsfolder/p/pit_raptor.txt +++ b/forge-gui/res/cardsfolder/p/pit_raptor.txt @@ -4,5 +4,6 @@ Types:Creature Bird Mercenary PT:4/3 K:Flying K:First Strike -K:UpkeepCost:2 B B +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {2}{B}{B}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 2 B B Oracle:Flying, first strike\nAt the beginning of your upkeep, sacrifice Pit Raptor unless you pay {2}{B}{B}. diff --git a/forge-gui/res/cardsfolder/p/pit_spawn.txt b/forge-gui/res/cardsfolder/p/pit_spawn.txt index a3a4c6454b7..3469c83a65e 100644 --- a/forge-gui/res/cardsfolder/p/pit_spawn.txt +++ b/forge-gui/res/cardsfolder/p/pit_spawn.txt @@ -3,7 +3,8 @@ ManaCost:4 B B B Types:Creature Demon PT:6/4 K:First Strike -K:UpkeepCost:B B +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {B}{B}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ B B T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Creature | Execute$ TrigExile | TriggerDescription$ Whenever CARDNAME deals damage to a creature, exile that creature. SVar:TrigExile:DB$ ChangeZone | Defined$ TriggeredTargetLKICopy | Origin$ Battlefield | Destination$ Exile Oracle:First strike\nAt the beginning of your upkeep, sacrifice Pit Spawn unless you pay {B}{B}.\nWhenever Pit Spawn deals damage to a creature, exile that creature. diff --git a/forge-gui/res/cardsfolder/r/razormane_masticore.txt b/forge-gui/res/cardsfolder/r/razormane_masticore.txt index 1bd38522be3..02081836d9f 100644 --- a/forge-gui/res/cardsfolder/r/razormane_masticore.txt +++ b/forge-gui/res/cardsfolder/r/razormane_masticore.txt @@ -3,7 +3,8 @@ ManaCost:5 Types:Artifact Creature Masticore PT:5/5 K:First Strike -K:UpkeepCost:Discard<1/Card> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you discard a card. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Discard<1/Card> T:Mode$ Phase | Phase$ Draw | ValidPlayer$ You | OptionalDecider$ You | Execute$ TrigDealDamage | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your draw step, you may have CARDNAME deal 3 damage to target creature. SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumDmg$ 3 AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/r/remorseless_punishment.txt b/forge-gui/res/cardsfolder/r/remorseless_punishment.txt index 8ec364dd734..89eaccd9a81 100644 --- a/forge-gui/res/cardsfolder/r/remorseless_punishment.txt +++ b/forge-gui/res/cardsfolder/r/remorseless_punishment.txt @@ -3,7 +3,7 @@ ManaCost:3 B B Types:Sorcery A:SP$ Repeat | ValidTgts$ Opponent | RepeatSubAbility$ DBChoose | MaxRepeat$ 2 | StackDescription$ SpellDescription | SpellDescription$ Target opponent loses 5 life unless that player discards two cards or sacrifices a creature or planeswalker. Repeat this process once. SVar:DBChoose:DB$ GenericChoice | Defined$ ParentTarget | Choices$ Discard,Sacrifice | AILogic$ PayUnlessCost -SVar:Discard:DB$ LoseLife | LifeAmount$ 5 | Defined$ ParentTarget | UnlessCost$ Discard<2/Card> | UnlessPayer$ ParentTarget | UnlessAI$ LifeLE5 | SpellDescription$ you lose 5 life unless you discard two cards. -SVar:Sacrifice:DB$ LoseLife | LifeAmount$ 5 | Defined$ ParentTarget | UnlessCost$ Sac<1/Creature;Planeswalker/creature or planeswalker> | UnlessPayer$ ParentTarget | UnlessAI$ LifeLE5 | SpellDescription$ you lose 5 life unless you sacrifice a creature or planeswalker. +SVar:Discard:DB$ LoseLife | LifeAmount$ 5 | Defined$ ParentTarget | UnlessCost$ Discard<2/Card> | UnlessPayer$ ParentTarget | SpellDescription$ you lose 5 life unless you discard two cards. +SVar:Sacrifice:DB$ LoseLife | LifeAmount$ 5 | Defined$ ParentTarget | UnlessCost$ Sac<1/Creature;Planeswalker/creature or planeswalker> | UnlessPayer$ ParentTarget | SpellDescription$ you lose 5 life unless you sacrifice a creature or planeswalker. SVar:AIPreference:SacCost$Creature.token Oracle:Target opponent loses 5 life unless that player discards two cards or sacrifices a creature or planeswalker. Repeat this process once. diff --git a/forge-gui/res/cardsfolder/r/research_development.txt b/forge-gui/res/cardsfolder/r/research_development.txt index bd8b3526f55..4858267dfc4 100644 --- a/forge-gui/res/cardsfolder/r/research_development.txt +++ b/forge-gui/res/cardsfolder/r/research_development.txt @@ -12,5 +12,5 @@ Name:Development ManaCost:3 U R Types:Instant A:SP$ Repeat | RepeatSubAbility$ DBToken | MaxRepeat$ 3 | StackDescription$ SpellDescription | SpellDescription$ Create a 3/1 red Elemental creature token unless any opponent has you draw a card. Repeat this process two more times. -SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ r_3_1_elemental | TokenOwner$ You | UnlessPayer$ Player.Opponent | UnlessCost$ Draw<1/Player.Activator> | UnlessAI$ MorePowerful +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ r_3_1_elemental | TokenOwner$ You | UnlessPayer$ Player.Opponent | UnlessCost$ Draw<1/Player.Activator> Oracle:Create a 3/1 red Elemental creature token unless any opponent has you draw a card. Repeat this process two more times. diff --git a/forge-gui/res/cardsfolder/r/reservoir_kraken.txt b/forge-gui/res/cardsfolder/r/reservoir_kraken.txt index ca70d4871d8..d2dec031c9a 100644 --- a/forge-gui/res/cardsfolder/r/reservoir_kraken.txt +++ b/forge-gui/res/cardsfolder/r/reservoir_kraken.txt @@ -5,7 +5,7 @@ PT:6/6 K:Trample K:Ward:2 T:Mode$ Phase | Phase$ BeginCombat | TriggerZones$ Battlefield | Execute$ TrigTap | PresentDefined$ Self | IsPresent$ Card.untapped | TriggerDescription$ At the beginning of each combat, if CARDNAME is untapped, any opponent may tap an untapped creature they control. If they do, tap CARDNAME and create a 1/1 blue Fish creature token with "This creature can't be blocked." -SVar:TrigTap:DB$ Tap | Defined$ Self | UnlessCost$ tapXType<1/Creature> | UnlessPayer$ Player.Opponent | UnlessSwitched$ True | UnlessAI$ LifeLE8 | UnlessResolveSubs$ WhenPaid | SubAbility$ DBToken +SVar:TrigTap:DB$ Tap | Defined$ Self | UnlessCost$ tapXType<1/Creature> | UnlessPayer$ Player.Opponent | UnlessSwitched$ True | UnlessResolveSubs$ WhenPaid | SubAbility$ DBToken SVar:DBToken:DB$ Token | TokenScript$ u_1_1_fish_unblockable DeckHas:Ability$Token & Type$Fish Oracle:Trample, ward {2}\nAt the beginning of each combat, if Reservoir Kraken is untapped, any opponent may tap an untapped creature they control. If they do, tap Reservoir Kraken and create a 1/1 blue Fish creature token with "This creature can't be blocked." diff --git a/forge-gui/res/cardsfolder/r/risk_factor.txt b/forge-gui/res/cardsfolder/r/risk_factor.txt index a6519028dec..d8d8471c8de 100644 --- a/forge-gui/res/cardsfolder/r/risk_factor.txt +++ b/forge-gui/res/cardsfolder/r/risk_factor.txt @@ -2,5 +2,5 @@ Name:Risk Factor ManaCost:2 R Types:Instant K:Jump-start -A:SP$ Draw | Defined$ You | NumCards$ 3 | StackDescription$ {p:Targeted} may have CARDNAME deal 4 damage to them. If {p:Targeted} doesn't, {p:You} draws three cards. | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | UnlessCost$ DamageYou<4> | UnlessPayer$ Targeted | UnlessAI$ RiskFactor | IsCurse$ True | SpellDescription$ Target opponent may have CARDNAME deal 4 damage to them. If that player doesn't, you draw three cards. +A:SP$ Draw | Defined$ You | NumCards$ 3 | StackDescription$ {p:Targeted} may have CARDNAME deal 4 damage to them. If {p:Targeted} doesn't, {p:You} draws three cards. | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | UnlessCost$ DamageYou<4> | UnlessPayer$ Targeted | IsCurse$ True | SpellDescription$ Target opponent may have CARDNAME deal 4 damage to them. If that player doesn't, you draw three cards. Oracle:Target opponent may have Risk Factor deal 4 damage to them. If that player doesn't, you draw three cards.\nJump-start (You may cast this card from your graveyard by discarding a card in addition to paying its other costs. Then exile this card.) diff --git a/forge-gui/res/cardsfolder/s/sacred_mesa.txt b/forge-gui/res/cardsfolder/s/sacred_mesa.txt index 8995d30a732..9faf01b2961 100644 --- a/forge-gui/res/cardsfolder/s/sacred_mesa.txt +++ b/forge-gui/res/cardsfolder/s/sacred_mesa.txt @@ -1,7 +1,8 @@ Name:Sacred Mesa ManaCost:2 W Types:Enchantment -K:UpkeepCost:Sac<1/Pegasus> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you sacrifice a Pegasus. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Sac<1/Pegasus> A:AB$ Token | Cost$ 1 W | TokenAmount$ 1 | TokenScript$ w_1_1_pegasus_flying | TokenOwner$ You | SpellDescription$ Create a 1/1 white Pegasus creature token with flying. AI:RemoveDeck:All Oracle:At the beginning of your upkeep, sacrifice Sacred Mesa unless you sacrifice a Pegasus.\n{1}{W}: Create a 1/1 white Pegasus creature token with flying. diff --git a/forge-gui/res/cardsfolder/s/school_of_piranha.txt b/forge-gui/res/cardsfolder/s/school_of_piranha.txt index dcb0fb7d01c..8d39416ec8c 100644 --- a/forge-gui/res/cardsfolder/s/school_of_piranha.txt +++ b/forge-gui/res/cardsfolder/s/school_of_piranha.txt @@ -2,5 +2,6 @@ Name:School of Piranha ManaCost:1 U Types:Creature Fish PT:3/3 -K:UpkeepCost:1 U +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {1}{U}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 1 U Oracle:At the beginning of your upkeep, sacrifice School of Piranha unless you pay {1}{U}. diff --git a/forge-gui/res/cardsfolder/s/season_of_the_witch.txt b/forge-gui/res/cardsfolder/s/season_of_the_witch.txt index 038d56f9904..3d8e1420c31 100644 --- a/forge-gui/res/cardsfolder/s/season_of_the_witch.txt +++ b/forge-gui/res/cardsfolder/s/season_of_the_witch.txt @@ -1,7 +1,8 @@ Name:Season of the Witch ManaCost:B B B Types:Enchantment -K:UpkeepCost:PayLife<2> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay 2 life. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ PayLife<2> T:Mode$ Phase | Phase$ Declare Attackers | Execute$ TrigMarkCouldAttack | Static$ True SVar:TrigMarkCouldAttack:DB$ PumpAll | ValidCards$ Creature.couldAttackButNotAttacking | RememberPumped$ True T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | Execute$ TrigDestroyAll | TriggerDescription$ At the beginning of the end step, destroy all untapped creatures that didn't attack this turn, except for creatures that couldn't attack. diff --git a/forge-gui/res/cardsfolder/s/serra_bestiary.txt b/forge-gui/res/cardsfolder/s/serra_bestiary.txt index 0cd9c011472..3d34ed41461 100644 --- a/forge-gui/res/cardsfolder/s/serra_bestiary.txt +++ b/forge-gui/res/cardsfolder/s/serra_bestiary.txt @@ -2,7 +2,8 @@ Name:Serra Bestiary ManaCost:W W Types:Enchantment Aura K:Enchant creature -K:UpkeepCost:W W +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {W}{W}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ W W A:SP$ Attach | Cost$ W W | ValidTgts$ Creature | AILogic$ Curse S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddHiddenKeyword$ CARDNAME can't attack or block. | Description$ Enchanted creature can't attack or block, and its activated abilities with {T} in their costs can't be activated. S:Mode$ CantBeActivated | ValidCard$ Card.EnchantedBy | ValidSA$ Activated.hasTapCost diff --git a/forge-gui/res/cardsfolder/s/shared_trauma.txt b/forge-gui/res/cardsfolder/s/shared_trauma.txt index f4b92d126e5..c684620eefd 100644 --- a/forge-gui/res/cardsfolder/s/shared_trauma.txt +++ b/forge-gui/res/cardsfolder/s/shared_trauma.txt @@ -3,7 +3,7 @@ ManaCost:B Types:Sorcery A:SP$ RepeatEach | RepeatPlayers$ Player | StartingWith$ You | RepeatSubAbility$ DBPay | SubAbility$ DBMill | StackDescription$ SpellDescription | SpellDescription$ Join forces — Starting with you, each player may pay any amount of mana. Each player mills X cards, where X is the total amount of mana paid this way. SVar:DBPay:DB$ ChooseNumber | Defined$ Player.IsRemembered | ChooseAnyNumber$ True | ListTitle$ amount of mana to pay | SubAbility$ DBStore -SVar:DBStore:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ CountSVar | Expression$ JoinForcesAmount/Plus.Y | UnlessCost$ Y | UnlessPayer$ Player.IsRemembered | UnlessSwitched$ True | UnlessAI$ OnlyOwn +SVar:DBStore:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ CountSVar | Expression$ JoinForcesAmount/Plus.Y | UnlessCost$ Y | UnlessPayer$ Player.IsRemembered | UnlessSwitched$ True SVar:DBMill:DB$ Mill | Defined$ Player | NumCards$ JoinForcesAmount | SubAbility$ DBReset | StackDescription$ None SVar:DBReset:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ Number | Expression$ 0 SVar:Y:Count$ChosenNumber diff --git a/forge-gui/res/cardsfolder/s/shrine_keeper.txt b/forge-gui/res/cardsfolder/s/shrine_keeper.txt index 50af2c691c9..78682375bba 100644 --- a/forge-gui/res/cardsfolder/s/shrine_keeper.txt +++ b/forge-gui/res/cardsfolder/s/shrine_keeper.txt @@ -1,5 +1,5 @@ Name:Shrine Keeper -ManaCost:1 W +ManaCost:W W Types:Creature Human Cleric PT:2/2 Oracle: diff --git a/forge-gui/res/cardsfolder/s/slow_motion.txt b/forge-gui/res/cardsfolder/s/slow_motion.txt index 3ee78bb1711..2af3b75125a 100644 --- a/forge-gui/res/cardsfolder/s/slow_motion.txt +++ b/forge-gui/res/cardsfolder/s/slow_motion.txt @@ -3,7 +3,8 @@ ManaCost:2 U Types:Enchantment Aura K:Enchant creature A:SP$ Attach | Cost$ 2 U | ValidTgts$ Creature | AILogic$ Curse -S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ UpkeepCost:2 | Description$ At the beginning of the upkeep of enchanted creature's controller, that player sacrifices that creature unless they pay {2}. +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player.EnchantedController | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of the upkeep of enchanted creature's controller, that player sacrifices that creature unless they pay {2}. +SVar:TrigUpkeep:DB$ SacrificeAll | Defined$ Enchanted | UnlessPayer$ EnchantedController | UnlessCost$ 2 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME is put into a graveyard from the battlefield, return CARDNAME to its owner's hand. SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | Defined$ TriggeredNewCardLKICopy SVar:SacMe:2 diff --git a/forge-gui/res/cardsfolder/s/solitary_confinement.txt b/forge-gui/res/cardsfolder/s/solitary_confinement.txt index a45e43f4307..8cb7c34a94c 100644 --- a/forge-gui/res/cardsfolder/s/solitary_confinement.txt +++ b/forge-gui/res/cardsfolder/s/solitary_confinement.txt @@ -1,7 +1,8 @@ Name:Solitary Confinement ManaCost:2 W Types:Enchantment -K:UpkeepCost:Discard<1/Card> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you discard a card. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Discard<1/Card> R:Event$ BeginPhase | ActiveZones$ Battlefield | ValidPlayer$ You | Phase$ Draw | Skip$ True | Description$ Skip your draw step. S:Mode$ Continuous | Affected$ You | AddKeyword$ Shroud | Description$ You have shroud. (You can't be the target of spells or abilities.) R:Event$ DamageDone | ActiveZones$ Battlefield | Prevent$ True | ValidTarget$ You | Description$ Prevent all damage that would be dealt to you. diff --git a/forge-gui/res/cardsfolder/s/spindrift_drake.txt b/forge-gui/res/cardsfolder/s/spindrift_drake.txt index 56c0741175d..5e736ea656f 100644 --- a/forge-gui/res/cardsfolder/s/spindrift_drake.txt +++ b/forge-gui/res/cardsfolder/s/spindrift_drake.txt @@ -3,5 +3,6 @@ ManaCost:U Types:Creature Drake PT:2/1 K:Flying -K:UpkeepCost:U +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {U}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ U Oracle:Flying\nAt the beginning of your upkeep, sacrifice Spindrift Drake unless you pay {U}. diff --git a/forge-gui/res/cardsfolder/s/starseer_mentor.txt b/forge-gui/res/cardsfolder/s/starseer_mentor.txt index db80d37872e..225e40354f9 100644 --- a/forge-gui/res/cardsfolder/s/starseer_mentor.txt +++ b/forge-gui/res/cardsfolder/s/starseer_mentor.txt @@ -6,8 +6,8 @@ K:Flying K:Vigilance T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE1 | Execute$ TrigGenericChoice | TriggerDescription$ At the beginning of your end step, if you gained or lost life this turn, target opponent loses 3 life unless they sacrifice a nonland permanent or discard a card. SVar:TrigGenericChoice:DB$ GenericChoice | ValidTgts$ Opponent | Choices$ PaySac,PayDiscard | FallbackAbility$ LoseLifeFallback | AILogic$ PayUnlessCost -SVar:PaySac:DB$ LoseLife | LifeAmount$ 3 | Defined$ Targeted | UnlessCost$ Sac<1/Permanent.nonland/nonland permanent> | UnlessPayer$ Targeted | UnlessAI$ LifeLE3 | SpellDescription$ You lose 3 life unless you sacrifice a nonland permanent. -SVar:PayDiscard:DB$ LoseLife | LifeAmount$ 3 | Defined$ Targeted | UnlessCost$ Discard<1/Card> | UnlessPayer$ Targeted | UnlessAI$ LifeLE3 | SpellDescription$ You lose 3 life unless you discard a card. +SVar:PaySac:DB$ LoseLife | LifeAmount$ 3 | Defined$ Targeted | UnlessCost$ Sac<1/Permanent.nonland/nonland permanent> | UnlessPayer$ Targeted | SpellDescription$ You lose 3 life unless you sacrifice a nonland permanent. +SVar:PayDiscard:DB$ LoseLife | LifeAmount$ 3 | Defined$ Targeted | UnlessCost$ Discard<1/Card> | UnlessPayer$ Targeted | SpellDescription$ You lose 3 life unless you discard a card. # TODO: Most likely the ChooseGenericEffect code can be simplified somehow to avoid the necessity of having a dedicated fallback ability SVar:LoseLifeFallback:DB$ LoseLife | Defined$ Targeted | LifeAmount$ 3 SVar:X:Count$LifeYouGainedThisTurn/Plus.Y diff --git a/forge-gui/res/cardsfolder/s/stasis.txt b/forge-gui/res/cardsfolder/s/stasis.txt index e378f29d928..4bccb1cf0fc 100644 --- a/forge-gui/res/cardsfolder/s/stasis.txt +++ b/forge-gui/res/cardsfolder/s/stasis.txt @@ -2,7 +2,8 @@ Name:Stasis ManaCost:1 U Types:Enchantment R:Event$ BeginPhase | ActiveZones$ Battlefield | Phase$ Untap | Skip$ True | Description$ Players skip their untap steps. -K:UpkeepCost:U +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {U}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ U AI:RemoveDeck:Random SVar:NonStackingEffect:True Oracle:Players skip their untap steps.\nAt the beginning of your upkeep, sacrifice Stasis unless you pay {U}. diff --git a/forge-gui/res/cardsfolder/s/sunken_city.txt b/forge-gui/res/cardsfolder/s/sunken_city.txt index 10863c0072a..904289d5e53 100644 --- a/forge-gui/res/cardsfolder/s/sunken_city.txt +++ b/forge-gui/res/cardsfolder/s/sunken_city.txt @@ -1,7 +1,8 @@ Name:Sunken City ManaCost:U U Types:Enchantment -K:UpkeepCost:U U +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {U}{U}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ U U S:Mode$ Continuous | Affected$ Creature.Blue | AddPower$ 1 | AddToughness$ 1 | Description$ Blue creatures get +1/+1. SVar:PlayMain1:TRUE SVar:NeedsToPlay:Creature.Blue+YouCtrl diff --git a/forge-gui/res/cardsfolder/t/tergrid_god_of_fright_tergrids_lantern.txt b/forge-gui/res/cardsfolder/t/tergrid_god_of_fright_tergrids_lantern.txt index 0891ec62eab..28cc89a80da 100644 --- a/forge-gui/res/cardsfolder/t/tergrid_god_of_fright_tergrids_lantern.txt +++ b/forge-gui/res/cardsfolder/t/tergrid_god_of_fright_tergrids_lantern.txt @@ -18,8 +18,8 @@ Types:Legendary Artifact A:AB$ Pump | Cost$ T | ValidTgts$ Player | TgtPrompt$ Select target player | SubAbility$ TrigGenericChoice | IsCurse$ True | SpellDescription$ Target player loses 3 life unless they sacrifice a nonland permanent or discard a card. | StackDescription$ {p:Targeted} loses 3 life unless they sacrifice a nonland permanent or discard a card. A:AB$ Untap | Cost$ 3 B | SpellDescription$ Untap CARDNAME. SVar:TrigGenericChoice:DB$ GenericChoice | Defined$ Targeted | Choices$ PaySac,PayDiscard | FallbackAbility$ LoseLifeFallback | AILogic$ PayUnlessCost -SVar:PaySac:DB$ LoseLife | LifeAmount$ 3 | Defined$ Targeted | UnlessCost$ Sac<1/Permanent.nonland/nonland permanent> | UnlessPayer$ Targeted | UnlessAI$ LifeLE3 | SpellDescription$ You lose 3 life unless you sacrifice a nonland permanent. -SVar:PayDiscard:DB$ LoseLife | LifeAmount$ 3 | Defined$ Targeted | UnlessCost$ Discard<1/Card> | UnlessPayer$ Targeted | UnlessAI$ LifeLE3 | SpellDescription$ You lose 3 life unless you discard a card. +SVar:PaySac:DB$ LoseLife | LifeAmount$ 3 | Defined$ Targeted | UnlessCost$ Sac<1/Permanent.nonland/nonland permanent> | UnlessPayer$ Targeted | SpellDescription$ You lose 3 life unless you sacrifice a nonland permanent. +SVar:PayDiscard:DB$ LoseLife | LifeAmount$ 3 | Defined$ Targeted | UnlessCost$ Discard<1/Card> | UnlessPayer$ Targeted | SpellDescription$ You lose 3 life unless you discard a card. # TODO: Most likely the ChooseGenericEffect code can be simplified somehow to avoid the necessity of having a dedicated fallback ability SVar:LoseLifeFallback:DB$ LoseLife | Defined$ Targeted | LifeAmount$ 3 Oracle:{T}: Target player loses 3 life unless they sacrifice a nonland permanent or discard a card.\n{3}{B}: Untap Tergrid's Lantern. diff --git a/forge-gui/res/cardsfolder/t/territorial_dispute.txt b/forge-gui/res/cardsfolder/t/territorial_dispute.txt index e87d7f0e1c6..425e2f4f970 100644 --- a/forge-gui/res/cardsfolder/t/territorial_dispute.txt +++ b/forge-gui/res/cardsfolder/t/territorial_dispute.txt @@ -1,7 +1,8 @@ Name:Territorial Dispute ManaCost:4 R R Types:Enchantment -K:UpkeepCost:Sac<1/Land> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you sacrifice a land. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Sac<1/Land> S:Mode$ CantPlayLand | Description$ Players can't play lands. AI:RemoveDeck:Random SVar:NonStackingEffect:True diff --git a/forge-gui/res/cardsfolder/t/the_gitrog_monster.txt b/forge-gui/res/cardsfolder/t/the_gitrog_monster.txt index 57da20f92ef..6620180d6a8 100644 --- a/forge-gui/res/cardsfolder/t/the_gitrog_monster.txt +++ b/forge-gui/res/cardsfolder/t/the_gitrog_monster.txt @@ -3,7 +3,8 @@ ManaCost:3 B G Types:Legendary Creature Frog Horror PT:6/6 K:Deathtouch -K:UpkeepCost:Sac<1/Land> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you sacrifice a land. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Sac<1/Land> S:Mode$ Continuous | Affected$ You | AdjustLandPlays$ 1 | Description$ You may play an additional land on each of your turns. T:Mode$ ChangesZoneAll | ValidCards$ Land.YouOwn+nonToken | Origin$ Any | Destination$ Graveyard | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever one or more land cards are put into your graveyard from anywhere, draw a card. SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 diff --git a/forge-gui/res/cardsfolder/t/the_great_juggernaut.txt b/forge-gui/res/cardsfolder/t/the_great_juggernaut.txt index 189e35bf94c..5ed267f3a8b 100644 --- a/forge-gui/res/cardsfolder/t/the_great_juggernaut.txt +++ b/forge-gui/res/cardsfolder/t/the_great_juggernaut.txt @@ -2,7 +2,8 @@ Name:The Great Juggernaut ManaCost:3 R Types:Legendary Artifact Creature Juggernaut PT:5/3 -K:UpkeepCost:Discard<1/Card> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you discard a card. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Discard<1/Card> T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigShuffle | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME attacks, shuffle your library then exile the top card of your library. You may play that card without paying its mana cost this turn. SVar:TrigShuffle:DB$ Shuffle | Defined$ You | SubAbility$ DBExile SVar:DBExile:DB$ Dig | Defined$ You | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect diff --git a/forge-gui/res/cardsfolder/t/the_roaring_toeclaws.txt b/forge-gui/res/cardsfolder/t/the_roaring_toeclaws.txt index 334afd68cad..db30889ad27 100644 --- a/forge-gui/res/cardsfolder/t/the_roaring_toeclaws.txt +++ b/forge-gui/res/cardsfolder/t/the_roaring_toeclaws.txt @@ -2,7 +2,7 @@ Name:The Roaring Toeclaws ManaCost:3 G G Types:Legendary Creature Dinosaur PT:5/5 -T:Mode$ Untaps | ValidCard$ Card.Self,Creature.Other+YouCtrl+powerGE5 | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever CARDNAME or another creature you control with mana value five or greater becomes untapped, put +1/+1 counters on it equal to its power. +T:Mode$ Untaps | ValidCard$ Card.Self,Creature.Other+YouCtrl+cmcGE5 | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever CARDNAME or another creature you control with mana value five or greater becomes untapped, put +1/+1 counters on it equal to its power. SVar:TrigPutCounter:DB$ PutCounter | Defined$ TriggeredCard | CounterType$ P1P1 | CounterNum$ X SVar:X:TriggeredCard$CardPower DeckHas:Ability$Counters diff --git a/forge-gui/res/cardsfolder/t/thelons_chant.txt b/forge-gui/res/cardsfolder/t/thelons_chant.txt index d7f7a2ccc95..2a7aa266c38 100644 --- a/forge-gui/res/cardsfolder/t/thelons_chant.txt +++ b/forge-gui/res/cardsfolder/t/thelons_chant.txt @@ -1,7 +1,8 @@ Name:Thelon's Chant ManaCost:1 G G Types:Enchantment -K:UpkeepCost:G +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {G}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ G T:Mode$ ChangesZone | ValidCard$ Swamp | Origin$ Any | Destination$ Battlefield | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Whenever a player puts a Swamp onto the battlefield, CARDNAME deals 3 damage to that player unless the player puts a -1/-1 counter on a creature they control. SVar:TrigDmg:DB$ DealDamage | Defined$ TriggeredCardController | NumDmg$ 3 | UnlessCost$ AddCounter<1/M1M1/Creature.YouCtrl/a creature you control> | UnlessPayer$ TriggeredCardController AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/t/thirst.txt b/forge-gui/res/cardsfolder/t/thirst.txt index 480a60d446d..26360d1858f 100644 --- a/forge-gui/res/cardsfolder/t/thirst.txt +++ b/forge-gui/res/cardsfolder/t/thirst.txt @@ -2,7 +2,8 @@ Name:Thirst ManaCost:2 U Types:Enchantment Aura K:Enchant creature -K:UpkeepCost:U +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {U}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ U A:SP$ Attach | Cost$ 2 U | ValidTgts$ Creature | AILogic$ KeepTapped S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step. | Description$ Enchanted creature doesn't untap during its controller's untap step. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigTap | TriggerDescription$ When CARDNAME enters, tap enchanted creature. diff --git a/forge-gui/res/cardsfolder/t/thornplate_intimidator.txt b/forge-gui/res/cardsfolder/t/thornplate_intimidator.txt index 1e8291913e0..90d6652c360 100644 --- a/forge-gui/res/cardsfolder/t/thornplate_intimidator.txt +++ b/forge-gui/res/cardsfolder/t/thornplate_intimidator.txt @@ -5,8 +5,8 @@ PT:4/3 K:Offspring:3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigGenericChoice | TriggerDescription$ When this creature enters, target opponent loses 3 life unless they sacrifice a nonland permanent or discard a card. SVar:TrigGenericChoice:DB$ GenericChoice | ValidTgts$ Opponent | Choices$ PaySac,PayDiscard | FallbackAbility$ LoseLifeFallback | AILogic$ PayUnlessCost -SVar:PaySac:DB$ LoseLife | LifeAmount$ 3 | Defined$ Targeted | UnlessCost$ Sac<1/Permanent.nonland/nonland permanent> | UnlessPayer$ Targeted | UnlessAI$ LifeLE3 | SpellDescription$ You lose 3 life unless you sacrifice a nonland permanent. -SVar:PayDiscard:DB$ LoseLife | LifeAmount$ 3 | Defined$ Targeted | UnlessCost$ Discard<1/Card> | UnlessPayer$ Targeted | UnlessAI$ LifeLE3 | SpellDescription$ You lose 3 life unless you discard a card. +SVar:PaySac:DB$ LoseLife | LifeAmount$ 3 | Defined$ Targeted | UnlessCost$ Sac<1/Permanent.nonland/nonland permanent> | UnlessPayer$ Targeted | SpellDescription$ You lose 3 life unless you sacrifice a nonland permanent. +SVar:PayDiscard:DB$ LoseLife | LifeAmount$ 3 | Defined$ Targeted | UnlessCost$ Discard<1/Card> | UnlessPayer$ Targeted | SpellDescription$ You lose 3 life unless you discard a card. # TODO: Most likely the ChooseGenericEffect code can be simplified somehow to avoid the necessity of having a dedicated fallback ability SVar:LoseLifeFallback:DB$ LoseLife | Defined$ Targeted | LifeAmount$ 3 Oracle:Offspring {3} (You may pay an additional {3} as you cast this spell. If you do, when this creature enters, create a 1/1 token copy of it.)\nWhen this creature enters, target opponent loses 3 life unless they sacrifice a nonland permanent or discard a card. diff --git a/forge-gui/res/cardsfolder/t/torment_of_scarabs.txt b/forge-gui/res/cardsfolder/t/torment_of_scarabs.txt index 3063a30aa1b..5885fd4c728 100644 --- a/forge-gui/res/cardsfolder/t/torment_of_scarabs.txt +++ b/forge-gui/res/cardsfolder/t/torment_of_scarabs.txt @@ -5,8 +5,8 @@ K:Enchant player A:SP$ Attach | Cost$ 3 B | ValidTgts$ Player | AILogic$ Curse T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player.EnchantedBy | TriggerZones$ Battlefield | Execute$ TrigGenericChoice | TriggerDescription$ At the beginning of enchanted player's upkeep, that player loses 3 life unless they sacrifice a nonland permanent or discard a card. SVar:TrigGenericChoice:DB$ GenericChoice | Choices$ PaySac,PayDiscard | Defined$ TriggeredPlayer | FallbackAbility$ LoseLifeFallback | AILogic$ PayUnlessCost -SVar:PaySac:DB$ LoseLife | LifeAmount$ 3 | Defined$ TriggeredPlayer | UnlessCost$ Sac<1/Permanent.nonland/nonland permanent> | UnlessPayer$ TriggeredPlayer | UnlessAI$ LifeLE3 | SpellDescription$ You lose 3 life unless you sacrifice a nonland permanent. -SVar:PayDiscard:DB$ LoseLife | LifeAmount$ 3 | Defined$ TriggeredPlayer | UnlessCost$ Discard<1/Card> | UnlessPayer$ TriggeredPlayer | UnlessAI$ LifeLE3 | SpellDescription$ You lose 3 life unless you discard a card. +SVar:PaySac:DB$ LoseLife | LifeAmount$ 3 | Defined$ TriggeredPlayer | UnlessCost$ Sac<1/Permanent.nonland/nonland permanent> | UnlessPayer$ TriggeredPlayer | SpellDescription$ You lose 3 life unless you sacrifice a nonland permanent. +SVar:PayDiscard:DB$ LoseLife | LifeAmount$ 3 | Defined$ TriggeredPlayer | UnlessCost$ Discard<1/Card> | UnlessPayer$ TriggeredPlayer | SpellDescription$ You lose 3 life unless you discard a card. # TODO: Most likely the ChooseGenericEffect code can be simplified somehow to avoid the necessity of having a dedicated fallback ability SVar:LoseLifeFallback:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ 3 Oracle:Enchant player\nAt the beginning of enchanted player's upkeep, that player loses 3 life unless they sacrifice a nonland permanent or discard a card. diff --git a/forge-gui/res/cardsfolder/t/torment_of_venom.txt b/forge-gui/res/cardsfolder/t/torment_of_venom.txt index 7cfe0cbeeeb..a31a0e12e36 100644 --- a/forge-gui/res/cardsfolder/t/torment_of_venom.txt +++ b/forge-gui/res/cardsfolder/t/torment_of_venom.txt @@ -3,8 +3,8 @@ ManaCost:2 B B Types:Instant A:SP$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ M1M1 | CounterNum$ 3 | IsCurse$ True | RememberTargets$ True | SubAbility$ DBGenericChoice | SpellDescription$ Put three -1/-1 counters on target creature. Its controller loses 3 life unless they sacrifice another nonland permanent or discards a card. SVar:DBGenericChoice:DB$ GenericChoice | Choices$ PaySac,PayDiscard | Defined$ TargetedController | FallbackAbility$ LoseLifeFallback | AILogic$ PayUnlessCost | SubAbility$ DBCleanup -SVar:PaySac:DB$ LoseLife | LifeAmount$ 3 | Defined$ TargetedController | UnlessCost$ Sac<1/Permanent.IsNotRemembered+nonland/another nonland permanent> | UnlessPayer$ TargetedController | UnlessAI$ LifeLE3 | SpellDescription$ You lose 3 life unless you sacrifice another nonland permanent. -SVar:PayDiscard:DB$ LoseLife | LifeAmount$ 3 | Defined$ TargetedController | UnlessCost$ Discard<1/Card> | UnlessPayer$ TargetedController | UnlessAI$ LifeLE3 | SpellDescription$ You lose 3 life unless you discard a card. +SVar:PaySac:DB$ LoseLife | LifeAmount$ 3 | Defined$ TargetedController | UnlessCost$ Sac<1/Permanent.IsNotRemembered+nonland/another nonland permanent> | UnlessPayer$ TargetedController | SpellDescription$ You lose 3 life unless you sacrifice another nonland permanent. +SVar:PayDiscard:DB$ LoseLife | LifeAmount$ 3 | Defined$ TargetedController | UnlessCost$ Discard<1/Card> | UnlessPayer$ TargetedController | SpellDescription$ You lose 3 life unless you discard a card. # TODO: Most likely the ChooseGenericEffect code can be simplified somehow to avoid the necessity of having a dedicated fallback ability SVar:LoseLifeFallback:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ 3 SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/t/tourachs_chant.txt b/forge-gui/res/cardsfolder/t/tourachs_chant.txt index 978407686ba..f62134ac4b4 100644 --- a/forge-gui/res/cardsfolder/t/tourachs_chant.txt +++ b/forge-gui/res/cardsfolder/t/tourachs_chant.txt @@ -1,7 +1,8 @@ Name:Tourach's Chant ManaCost:1 B B Types:Enchantment -K:UpkeepCost:B +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {B}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ B T:Mode$ ChangesZone | ValidCard$ Forest | Origin$ Any | Destination$ Battlefield | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Whenever a player puts a Forest onto the battlefield, CARDNAME deals 3 damage to that player unless they put a -1/-1 counter on a creature they control. SVar:TrigDmg:DB$ DealDamage | Defined$ TriggeredCardController | NumDmg$ 3 | UnlessCost$ AddCounter<1/M1M1/Creature.YouCtrl/a creature you control> | UnlessPayer$ TriggeredCardController AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/t/trailtracker_scout.txt b/forge-gui/res/cardsfolder/t/trailtracker_scout.txt index 718d79e3eed..8be87474ef3 100644 --- a/forge-gui/res/cardsfolder/t/trailtracker_scout.txt +++ b/forge-gui/res/cardsfolder/t/trailtracker_scout.txt @@ -1,6 +1,6 @@ Name:Trailtracker Scout ManaCost:1 G -Types:Creature Raccooon Scout +Types:Creature Raccoon Scout PT:1/3 A:AB$ Mana | Cost$ T | Produced$ Any | SpellDescription$ Add one mana of any color. T:Mode$ ManaExpend | Amount$ 8 | Player$ You | TriggerZones$ Battlefield | Execute$ TrigChangeZone | TriggerDescription$ Whenever you expend 8, return up to one target permanent card from your graveyard to your hand. (You expend 8 as you spend your eighth total mana to cast spells during a turn.) diff --git a/forge-gui/res/cardsfolder/v/vaevictis_asmadi.txt b/forge-gui/res/cardsfolder/v/vaevictis_asmadi.txt index 65c8af6ba8f..6ed66180ed4 100644 --- a/forge-gui/res/cardsfolder/v/vaevictis_asmadi.txt +++ b/forge-gui/res/cardsfolder/v/vaevictis_asmadi.txt @@ -3,7 +3,8 @@ ManaCost:2 B B R R G G Types:Legendary Creature Elder Dragon PT:7/7 K:Flying -K:UpkeepCost:B R G +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {B}{R}{G}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ B R G A:AB$ Pump | Cost$ B | NumAtt$ +1 | SpellDescription$ CARDNAME gets +1/+0 until end of turn. A:AB$ Pump | Cost$ R | NumAtt$ +1 | SpellDescription$ CARDNAME gets +1/+0 until end of turn. A:AB$ Pump | Cost$ G | NumAtt$ +1 | SpellDescription$ CARDNAME gets +1/+0 until end of turn. diff --git a/forge-gui/res/cardsfolder/v/vapor_snare.txt b/forge-gui/res/cardsfolder/v/vapor_snare.txt index cacd45ee89d..8d22957c3ac 100644 --- a/forge-gui/res/cardsfolder/v/vapor_snare.txt +++ b/forge-gui/res/cardsfolder/v/vapor_snare.txt @@ -3,6 +3,7 @@ ManaCost:4 U Types:Enchantment Aura K:Enchant creature A:SP$ Attach | Cost$ 4 U | ValidTgts$ Creature | AILogic$ GainControl -K:UpkeepCost:Return<1/Land> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you return a land you control to its owner's hand. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Return<1/Land> S:Mode$ Continuous | Affected$ Card.EnchantedBy | GainControl$ You | Description$ You control enchanted creature. Oracle:Enchant creature\nYou control enchanted creature.\nAt the beginning of your upkeep, sacrifice Vapor Snare unless you return a land you control to its owner's hand. diff --git a/forge-gui/res/cardsfolder/v/veiled_apparition.txt b/forge-gui/res/cardsfolder/v/veiled_apparition.txt index 8fbfb628a6a..ea023c9c5af 100644 --- a/forge-gui/res/cardsfolder/v/veiled_apparition.txt +++ b/forge-gui/res/cardsfolder/v/veiled_apparition.txt @@ -2,5 +2,7 @@ Name:Veiled Apparition ManaCost:1 U Types:Enchantment T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ Opponent | TriggerZones$ Battlefield | IsPresent$ Card.Self+Enchantment | Execute$ TrigAnimate | TriggerDescription$ When an opponent casts a spell, if CARDNAME is an enchantment, CARDNAME becomes a 3/3 Illusion creature with flying and "At the beginning of your upkeep, sacrifice CARDNAME unless you pay {1}{U}." -SVar:TrigAnimate:DB$ Animate | Defined$ Self | Power$ 3 | Toughness$ 3 | Keywords$ Flying & UpkeepCost:1 U | Types$ Creature,Illusion | RemoveCardTypes$ True | RemoveCreatureTypes$ True | Duration$ Permanent +SVar:TrigAnimate:DB$ Animate | Defined$ Self | Power$ 3 | Toughness$ 3 | Keywords$ Flying | Triggers$ UpkeepCostTrigger | Types$ Creature,Illusion | RemoveCardTypes$ True | RemoveCreatureTypes$ True | Duration$ Permanent +SVar:UpkeepCostTrigger:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {1}{U}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ 1 U Oracle:When an opponent casts a spell, if Veiled Apparition is an enchantment, Veiled Apparition becomes a 3/3 Illusion creature with flying and "At the beginning of your upkeep, sacrifice Veiled Apparition unless you pay {1}{U}." diff --git a/forge-gui/res/cardsfolder/v/vile_consumption.txt b/forge-gui/res/cardsfolder/v/vile_consumption.txt index a09db45f73f..aabb9ad09c5 100644 --- a/forge-gui/res/cardsfolder/v/vile_consumption.txt +++ b/forge-gui/res/cardsfolder/v/vile_consumption.txt @@ -1,6 +1,8 @@ Name:Vile Consumption ManaCost:1 U B Types:Enchantment -S:Mode$ Continuous | Affected$ Creature | AddKeyword$ UpkeepCost:PayLife<1> | Description$ All creatures have "At the beginning of your upkeep, sacrifice this creature unless you pay 1 life." +S:Mode$ Continuous | Affected$ Creature | AddTrigger$ UpkeepCostTrigger | Description$ All creatures have "At the beginning of your upkeep, sacrifice this creature unless you pay 1 life." +SVar:UpkeepCostTrigger:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay 1 life. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ PayLife<1> AI:RemoveDeck:Random Oracle:All creatures have "At the beginning of your upkeep, sacrifice this creature unless you pay 1 life." diff --git a/forge-gui/res/cardsfolder/w/waterspout_djinn.txt b/forge-gui/res/cardsfolder/w/waterspout_djinn.txt index ab0e433878c..726fb073bef 100644 --- a/forge-gui/res/cardsfolder/w/waterspout_djinn.txt +++ b/forge-gui/res/cardsfolder/w/waterspout_djinn.txt @@ -3,6 +3,7 @@ ManaCost:2 U U Types:Creature Djinn PT:4/4 K:Flying -K:UpkeepCost:Return<1/Island.untapped+YouCtrl/untapped Island you control> +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you return an untapped Island you control to its owner's hand. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ Return<1/Island.untapped+YouCtrl/untapped Island you control> SVar:NeedsToPlay:Island.YouCtrl Oracle:Flying\nAt the beginning of your upkeep, sacrifice Waterspout Djinn unless you return an untapped Island you control to its owner's hand. diff --git a/forge-gui/res/cardsfolder/w/whipstitched_zombie.txt b/forge-gui/res/cardsfolder/w/whipstitched_zombie.txt index 9128cc54fc0..6f9ef5fcf94 100644 --- a/forge-gui/res/cardsfolder/w/whipstitched_zombie.txt +++ b/forge-gui/res/cardsfolder/w/whipstitched_zombie.txt @@ -2,5 +2,6 @@ Name:Whipstitched Zombie ManaCost:1 B Types:Creature Zombie PT:2/2 -K:UpkeepCost:B +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {B}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ B Oracle:At the beginning of your upkeep, sacrifice Whipstitched Zombie unless you pay {B}. diff --git a/forge-gui/res/cardsfolder/w/wild_leotau.txt b/forge-gui/res/cardsfolder/w/wild_leotau.txt index 9c66c9c3a99..f333419ef10 100644 --- a/forge-gui/res/cardsfolder/w/wild_leotau.txt +++ b/forge-gui/res/cardsfolder/w/wild_leotau.txt @@ -2,5 +2,6 @@ Name:Wild Leotau ManaCost:2 G G Types:Creature Cat PT:5/4 -K:UpkeepCost:G +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {G}. +SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ G Oracle:At the beginning of your upkeep, sacrifice Wild Leotau unless you pay {G}. diff --git a/forge-gui/res/cardsfolder/w/withercrown.txt b/forge-gui/res/cardsfolder/w/withercrown.txt index f5cf548cfec..184e3a5ece8 100644 --- a/forge-gui/res/cardsfolder/w/withercrown.txt +++ b/forge-gui/res/cardsfolder/w/withercrown.txt @@ -5,5 +5,5 @@ K:Enchant creature A:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature | AILogic$ Curse S:Mode$ Continuous | Affected$ Card.EnchantedBy | SetPower$ 0 | AddTrigger$ WitherTrig | AddSVar$ TrigLoseLife | Description$ Enchanted creature has base power 0 and has "At the beginning of your upkeep, you lose 1 life unless you sacrifice this creature." SVar:WitherTrig:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigLoseLife | TriggerDescription$ At the beginning of your upkeep, you lose 1 life unless you sacrifice this creature. -SVar:TrigLoseLife:DB$ LoseLife | LifeAmount$ 1 | UnlessCost$ Sac<1/CARDNAME> | UnlessPayer$ You | UnlessAI$ LifeLE10 +SVar:TrigLoseLife:DB$ LoseLife | LifeAmount$ 1 | UnlessCost$ Sac<1/CARDNAME> | UnlessPayer$ You Oracle:Enchant creature\nEnchanted creature has base power 0 and has "At the beginning of your upkeep, you lose 1 life unless you sacrifice this creature." diff --git a/forge-gui/res/cardsfolder/z/zoyowa_lava_tongue.txt b/forge-gui/res/cardsfolder/z/zoyowa_lava_tongue.txt index e4b656e5e2e..1eb7c3c0077 100644 --- a/forge-gui/res/cardsfolder/z/zoyowa_lava_tongue.txt +++ b/forge-gui/res/cardsfolder/z/zoyowa_lava_tongue.txt @@ -5,7 +5,7 @@ PT:2/2 K:Deathtouch T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You.descended | TriggerZones$ Battlefield | Execute$ TrigMaySacorDiscard | TriggerDescription$ At the beginning of your end step, if you descended this turn, each opponent may discard a card or sacrifice a permanent. CARDNAME deals 3 damage to each opponent who didn't. (You descended if a permanent card was put into your graveyard from anywhere.) SVar:TrigMaySacorDiscard:DB$ GenericChoice | Choices$ PaySac,PayDiscard | TempRemember$ Chooser | Defined$ Opponent | AILogic$ PayUnlessCost | DamageMap$ True -SVar:PaySac:DB$ DealDamage | NumDmg$ 3 | Defined$ Remembered | UnlessCost$ Sac<1/Permanent> | UnlessPayer$ Remembered | UnlessAI$ LifeLE2 | SpellDescription$ CARDNAME deals 3 damage to you unless you sacrifice a permanent. -SVar:PayDiscard:DB$ DealDamage | NumDmg$ 3 | Defined$ Remembered | UnlessCost$ Discard<1/Card> | UnlessPayer$ Remembered | UnlessAI$ LifeLE2 | SpellDescription$ CARDNAME deals 3 damage to you unless you discard a card. +SVar:PaySac:DB$ DealDamage | NumDmg$ 3 | Defined$ Remembered | UnlessCost$ Sac<1/Permanent> | UnlessPayer$ Remembered | SpellDescription$ CARDNAME deals 3 damage to you unless you sacrifice a permanent. +SVar:PayDiscard:DB$ DealDamage | NumDmg$ 3 | Defined$ Remembered | UnlessCost$ Discard<1/Card> | UnlessPayer$ Remembered | SpellDescription$ CARDNAME deals 3 damage to you unless you discard a card. DeckHas:Ability$Discard|Sacrifice Oracle:Deathtouch\nAt the beginning of your end step, if you descended this turn, each opponent may discard a card or sacrifice a permanent. Zoyowa Lava-Tongue deals 3 damage to each opponent who didn't. (You descended if a permanent card was put into your graveyard from anywhere.) diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index 93829130b7e..b152b1b063e 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -109,7 +109,9 @@ public final boolean playAbility(final boolean mayChooseTargets, final boolean i ability.setPaidLife(0); } - ability = GameActionUtil.addExtraKeywordCost(ability); + if (ability.isSpell() && !c.isCopiedSpell()) { + ability = GameActionUtil.addExtraKeywordCost(ability); + } Cost abCost = ability.getPayCosts(); CostPayment payment = new CostPayment(abCost, ability); diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index a78b84d323f..3f3de0e8719 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -1916,10 +1916,15 @@ public String chooseProtectionType(final String string, final SpellAbility sa, f } @Override - public boolean payCostToPreventEffect(final Cost cost, final SpellAbility sa, final boolean alreadyPaid, - final FCollectionView allPayers) { + public boolean payCostToPreventEffect(final Cost cost, final SpellAbility sa, final boolean alreadyPaid, final FCollectionView allPayers) { // if it's paid by the AI already the human can pay, but it won't change anything - return HumanPlay.payCostDuringAbilityResolve(this, player, sa.getHostCard(), cost, sa, null); + String prompt = null; + if (sa.isKeyword(Keyword.ECHO)) { + prompt = Localizer.getInstance().getMessage("lblPayEcho"); + } else if (sa.isKeyword(Keyword.CUMULATIVE_UPKEEP)) { + prompt = "Cumulative upkeep for " + sa.getHostCard(); + } + return HumanPlay.payCostDuringAbilityResolve(this, player, sa.getHostCard(), cost, sa, prompt); } // stores saved order for different sets of SpellAbilities