From d6788b2803e7fa5d818a5216e20f4790a9d63309 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Fri, 12 Feb 2021 08:50:24 +0000 Subject: [PATCH] Refactor: Taps for mana to differ between the Player who gets the Mana and the Player to taps the card --- .../src/main/java/forge/ai/AiController.java | 6 +- .../src/main/java/forge/ai/ComputerUtil.java | 3 +- .../main/java/forge/ai/ComputerUtilMana.java | 552 ++++++++++++------ .../java/forge/ai/PlayerControllerAi.java | 3 + .../main/java/forge/ai/SpellAbilityAi.java | 2 +- .../main/java/forge/ai/ability/AnimateAi.java | 2 +- .../java/forge/ai/ability/ChangeZoneAi.java | 4 +- .../java/forge/ai/ability/CountersPutAi.java | 2 +- .../java/forge/ai/ability/LifeGainAi.java | 2 +- .../java/forge/ai/ability/PermanentAi.java | 2 +- .../java/forge/game/CardTraitPredicates.java | 11 + .../src/main/java/forge/game/ForgeScript.java | 4 + .../main/java/forge/game/GameActionUtil.java | 57 +- .../forge/game/TriggerReplacementBase.java | 1 + .../forge/game/ability/AbilityApiBased.java | 11 +- .../java/forge/game/ability/AbilityUtils.java | 9 +- .../main/java/forge/game/ability/ApiType.java | 1 + .../forge/game/ability/SpellApiBased.java | 8 +- .../game/ability/effects/ManaEffect.java | 100 ++-- .../ability/effects/ManaReflectedEffect.java | 4 +- .../ability/effects/ReplaceDamageEffect.java | 8 + .../game/ability/effects/ReplaceEffect.java | 11 + .../ability/effects/ReplaceManaEffect.java | 109 ++++ .../effects/ReplaceSplitDamageEffect.java | 2 +- .../src/main/java/forge/game/card/Card.java | 4 +- .../java/forge/game/card/CardFactory.java | 4 +- .../java/forge/game/card/CardFactoryUtil.java | 2 +- .../java/forge/game/card/CardProperty.java | 2 +- .../main/java/forge/game/card/CardUtil.java | 50 +- .../forge/game/mana/ManaCostBeingPaid.java | 27 +- .../main/java/forge/game/mana/ManaPool.java | 24 +- .../main/java/forge/game/player/Player.java | 7 + .../game/replacement/ReplaceProduceMana.java | 19 +- .../game/replacement/ReplacementEffect.java | 10 + .../game/replacement/ReplacementHandler.java | 46 +- .../game/spellability/AbilityManaPart.java | 225 ++++--- .../forge/game/spellability/AbilitySub.java | 8 +- .../forge/game/spellability/SpellAbility.java | 116 +++- .../forge/game/trigger/TriggerHandler.java | 18 +- .../game/trigger/TriggerTapsForMana.java | 15 +- .../a/a_display_of_my_dark_power.txt | 3 +- forge-gui/res/cardsfolder/c/caged_sun.txt | 7 +- forge-gui/res/cardsfolder/c/chaos_moon.txt | 4 +- forge-gui/res/cardsfolder/c/contamination.txt | 5 +- forge-gui/res/cardsfolder/c/crypt_ghast.txt | 5 +- .../res/cardsfolder/d/damping_sphere.txt | 5 +- forge-gui/res/cardsfolder/d/deep_water.txt | 5 +- .../cardsfolder/d/dictate_of_karametra.txt | 3 +- forge-gui/res/cardsfolder/d/doubling_cube.txt | 3 +- forge-gui/res/cardsfolder/e/eloren_wilds.txt | 2 +- .../res/cardsfolder/e/extraplanar_lens.txt | 3 +- forge-gui/res/cardsfolder/f/false_dawn.txt | 5 +- .../res/cardsfolder/f/forsaken_monument.txt | 2 +- .../res/cardsfolder/h/hall_of_gemstone.txt | 5 +- forge-gui/res/cardsfolder/h/harvest_mage.txt | 4 +- .../res/cardsfolder/h/heartbeat_of_spring.txt | 4 +- .../res/cardsfolder/i/infernal_darkness.txt | 5 +- .../cardsfolder/k/keeper_of_progenitus.txt | 3 +- .../cardsfolder/k/kinnan_bonder_prodigy.txt | 4 +- forge-gui/res/cardsfolder/m/mana_flare.txt | 3 +- .../res/cardsfolder/m/mana_reflection.txt | 5 +- forge-gui/res/cardsfolder/m/miraris_wake.txt | 5 +- forge-gui/res/cardsfolder/m/mirri.txt | 4 +- .../res/cardsfolder/n/naked_singularity.txt | 21 +- .../cardsfolder/n/nikya_of_the_old_ways.txt | 4 +- .../res/cardsfolder/n/nirkana_revenant.txt | 5 +- .../n/nissa_who_shakes_the_world.txt | 4 +- .../res/cardsfolder/n/nyxbloom_ancient.txt | 4 +- forge-gui/res/cardsfolder/o/overabundance.txt | 3 +- forge-gui/res/cardsfolder/p/pale_moon.txt | 5 +- .../res/cardsfolder/p/pulse_of_llanowar.txt | 4 +- .../cardsfolder/q/quarum_trench_gnomes.txt | 6 +- forge-gui/res/cardsfolder/r/reality_twist.txt | 17 +- .../res/cardsfolder/r/regal_behemoth.txt | 5 +- .../res/cardsfolder/r/ritual_of_subdual.txt | 5 +- .../res/cardsfolder/r/river_of_tears.txt | 5 +- ...asaya_orochi_ascendant_sasayas_essence.txt | 4 +- .../res/cardsfolder/s/savage_firecat.txt | 5 +- .../cardsfolder/s/selesnya_loft_gardens.txt | 5 +- forge-gui/res/cardsfolder/s/sisay.txt | 5 +- forge-gui/res/cardsfolder/u/urzas_mine.txt | 4 +- .../res/cardsfolder/u/urzas_power_plant.txt | 4 +- forge-gui/res/cardsfolder/u/urzas_tower.txt | 4 +- forge-gui/res/cardsfolder/upcoming/piracy.txt | 6 + .../v/vorinclex_voice_of_hunger.txt | 4 +- forge-gui/res/cardsfolder/w/winters_night.txt | 2 +- .../res/cardsfolder/z/zendikar_resurgent.txt | 7 +- .../res/cardsfolder/z/zhur_taa_ancient.txt | 3 +- .../java/forge/match/input/InputPayMana.java | 301 ++++------ .../java/forge/sound/EventVisualizer.java | 14 +- 90 files changed, 1163 insertions(+), 878 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java create mode 100644 forge-gui/res/cardsfolder/upcoming/piracy.txt diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index fe64fcdebf6..28453966716 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -429,8 +429,7 @@ public class AiController { byte color = MagicColor.fromName(c); for (Card land : landList) { for (final SpellAbility m : ComputerUtilMana.getAIPlayableMana(land)) { - AbilityManaPart mp = m.getManaPart(); - if (mp.canProduce(MagicColor.toShortString(color), m)) { + if (m.canProduce(MagicColor.toShortString(color))) { return land; } } @@ -483,8 +482,7 @@ public class AiController { return land; } for (final SpellAbility m : ComputerUtilMana.getAIPlayableMana(land)) { - AbilityManaPart mp = m.getManaPart(); - if (mp.canProduce(MagicColor.toShortString(color), m)) { + if (m.canProduce(MagicColor.toShortString(color))) { return land; } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 4424b60a1bb..aa061068677 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -2075,9 +2075,8 @@ public class ComputerUtil { for (Card c : lands) { for (SpellAbility sa : c.getManaAbilities()) { - AbilityManaPart abmana = sa.getManaPart(); for (byte col : MagicColor.WUBRG) { - if (abmana.canProduce(MagicColor.toLongString(col))) { + if (sa.canProduce(MagicColor.toLongString(col))) { numProducers.get(col).add(c); } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index 1a0985f9acc..ba2cb1bea5d 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -10,11 +10,10 @@ import forge.card.mana.ManaAtom; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostParser; import forge.card.mana.ManaCostShard; +import forge.game.CardTraitPredicates; import forge.game.Game; import forge.game.GameActionUtil; -import forge.game.ability.AbilityKey; -import forge.game.ability.AbilityUtils; -import forge.game.ability.ApiType; +import forge.game.ability.*; import forge.game.card.*; import forge.game.combat.CombatUtil; import forge.game.cost.*; @@ -25,10 +24,13 @@ import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.player.PlayerPredicates; import forge.game.replacement.ReplacementEffect; +import forge.game.replacement.ReplacementLayer; import forge.game.replacement.ReplacementType; import forge.game.spellability.AbilityManaPart; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerType; import forge.game.zone.ZoneType; import forge.util.MyRandom; import forge.util.TextUtil; @@ -52,17 +54,17 @@ public class ComputerUtilMana { public static boolean canPayManaCost(final SpellAbility sa, final Player ai, final int extraMana) { return payManaCost(sa, ai, true, extraMana, true); } - + /** * Return the number of colors used for payment for Converge */ public static int getConvergeCount(final SpellAbility sa, final Player ai) { - ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0); - if (payManaCost(cost, sa, ai, true, true)) { - return cost.getSunburst(); - } else { - return 0; - } + ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0); + if (payManaCost(cost, sa, ai, true, true)) { + return cost.getSunburst(); + } else { + return 0; + } } // Does not check if mana sources can be used right now, just checks for potential chance. @@ -124,7 +126,7 @@ public class ComputerUtilMana { for (final ManaCostShard shard : manaAbilityMap.keySet()) { for (SpellAbility ability : manaAbilityMap.get(shard)) { - final Card hostCard = ability.getHostCard(); + final Card hostCard = ability.getHostCard(); if (!manaCardMap.containsKey(hostCard)) { manaCardMap.put(hostCard, scoreManaProducingCard(hostCard)); orderedCards.add(hostCard); @@ -238,7 +240,7 @@ public class ComputerUtilMana { } } } - + public static SpellAbility chooseManaAbility(ManaCostBeingPaid cost, SpellAbility sa, Player ai, ManaCostShard toPay, Collection saList, boolean checkCosts) { for (final SpellAbility ma : saList) { @@ -316,7 +318,191 @@ public class ComputerUtilMana { } return null; } - + + public static String predictManaReplacement(SpellAbility saPayment, Player ai, ManaCostShard toPay) { + Card hostCard = saPayment.getHostCard(); + Game game = hostCard.getGame(); + String manaProduced = toPay.isSnow() && hostCard.isSnow() ? "S" : GameActionUtil.generatedTotalMana(saPayment); + //String originalProduced = manaProduced; + + final Map repParams = AbilityKey.newMap(); + repParams.put(AbilityKey.Mana, manaProduced); + repParams.put(AbilityKey.Affected, hostCard); + repParams.put(AbilityKey.Player, ai); + repParams.put(AbilityKey.AbilityMana, saPayment); // RootAbility + + // TODO Damping Sphere might replace later? + + // add flags to replacementEffects to filter better? + List reList = game.getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other); + + List replaceMana = Lists.newArrayList(); + List replaceType = Lists.newArrayList(); + List replaceAmount = Lists.newArrayList(); // currently only multi + + // try to guess the color the mana gets replaced to + for (ReplacementEffect re : reList) { + SpellAbility o = re.getOverridingAbility(); + + if (o == null || o.getApi() != ApiType.ReplaceMana) { + continue; + } + + // this one does replace the amount too + if (o.hasParam("ReplaceMana")) { + replaceMana.add(o); + } else if (o.hasParam("ReplaceType") || o.hasParam("ReplaceColor")) { + // this one replaces the color/type + // check if this one can be replaced into wanted mana shard + replaceType.add(o); + } else if (o.hasParam("ReplaceAmount")) { + replaceAmount.add(o); + } + } + + // it is better to apply these ones first + if (!replaceMana.isEmpty()) { + for (SpellAbility saMana : replaceMana) { + // one of then has to Any + // one of then has to C + // one of then has to B + String m = saMana.getParam("ReplaceMana"); + if ("Any".equals(m)) { + byte rs = MagicColor.GREEN; + for (byte c : MagicColor.WUBRGC) { + if (toPay.canBePaidWithManaOfColor(c)) { + rs = c; + break; + } + } + manaProduced = MagicColor.toShortString(rs); + } else { + manaProduced = m; + } + } + } + + // then apply this one + if (!replaceType.isEmpty()) { + for (SpellAbility saMana : replaceAmount) { + Card card = saMana.getHostCard(); + if (saMana.hasParam("ReplaceType")) { + // replace color and colorless + String color = saMana.getParam("ReplaceType"); + if ("Any".equals(color)) { + byte rs = MagicColor.GREEN; + for (byte c : MagicColor.WUBRGC) { + if (toPay.canBePaidWithManaOfColor(c)) { + rs = c; + break; + } + } + color = MagicColor.toShortString(rs); + } + for (byte c : MagicColor.WUBRGC) { + String s = MagicColor.toShortString(c); + manaProduced = manaProduced.replace(s, color); + } + } else if (saMana.hasParam("ReplaceColor")) { + // replace color + String color = saMana.getParam("ReplaceColor"); + if ("Chosen".equals(color)) { + if (card.hasChosenColor()) { + color = MagicColor.toShortString(card.getChosenColor()); + } + } + if (saMana.hasParam("ReplaceOnly")) { + manaProduced = manaProduced.replace(saMana.getParam("ReplaceOnly"), color); + } else { + for (byte c : MagicColor.WUBRG) { + String s = MagicColor.toShortString(c); + manaProduced = manaProduced.replace(s, color); + } + } + } + } + } + + // then multiply if able + if (!replaceAmount.isEmpty()) { + int totalAmount = 1; + for (SpellAbility saMana : replaceAmount) { + totalAmount *= Integer.valueOf(saMana.getParam("ReplaceAmount")); + } + manaProduced = StringUtils.repeat(manaProduced, " ", totalAmount); + } + + return manaProduced; + } + + public static String predictManafromSpellAbility(SpellAbility saPayment, Player ai, ManaCostShard toPay) { + Card hostCard = saPayment.getHostCard(); + + String manaProduced = predictManaReplacement(saPayment, ai, toPay); + String originalProduced = manaProduced; + + if (originalProduced.isEmpty()) { + return manaProduced; + } + + // Run triggers like Nissa + final Map runParams = AbilityKey.mapFromCard(hostCard); + runParams.put(AbilityKey.Player, ai); // assuming AI would only ever gives itself mana + runParams.put(AbilityKey.AbilityMana, saPayment); + runParams.put(AbilityKey.Produced, manaProduced); + runParams.put(AbilityKey.Activator, ai); + for (Trigger tr : ai.getGame().getTriggerHandler().getActiveTrigger(TriggerType.TapsForMana, runParams)) { + SpellAbility trSA = tr.ensureAbility(); + if (trSA == null) { + continue; + } + if (ApiType.Mana.equals(trSA.getApi())) { + int pAmount = trSA.hasParam("Amount") ? Integer.valueOf(trSA.getParam("Amount")) : 1; + String produced = trSA.getParam("Produced"); + if (produced.equals("Chosen")) { + produced = MagicColor.toShortString(trSA.getHostCard().getChosenColor()); + } + manaProduced += " " + StringUtils.repeat(produced, pAmount); + } else if (ApiType.ManaReflected.equals(trSA.getApi())) { + final String colorOrType = trSA.getParamOrDefault("ColorOrType", "Color"); + // currently Color or Type, Type is colors + colorless + final String reflectProperty = trSA.getParam("ReflectProperty"); + + if (reflectProperty.equals("Produced") && !originalProduced.isEmpty()) { + // check if a colorless shard can be paid from the trigger + if (toPay.equals(ManaCostShard.COLORLESS) && colorOrType.equals("Type") && originalProduced.contains("C")) { + manaProduced += " " + "C"; + } else if (originalProduced.length() == 1) { + // if length is only one, and it either is equal C == Type + if (colorOrType.equals("Type") || !originalProduced.equals("C")) { + manaProduced += " " + originalProduced; + } + } else { + // should it look for other shards too? + boolean found = false; + for (String s : originalProduced.split(" ")) { + if (colorOrType.equals("Type") || !s.equals("C") && toPay.canBePaidWithManaOfColor(MagicColor.fromName(s))) { + found = true; + manaProduced += " " + s; + break; + } + } + // no good mana found? just add the first generated color + if (!found) { + for (String s : originalProduced.split(" ")) { + if (colorOrType.equals("Type") || !s.equals("C")) { + manaProduced += " " + s; + break; + } + } + } + } + } + } + } + return manaProduced; + } + public static CardCollection getManaSourcesToPayCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) { CardCollection manaSources = new CardCollection(); @@ -392,29 +578,19 @@ public class ComputerUtilMana { manaSources.add(saPayment.getHostCard()); setExpressColorChoice(sa, ai, cost, toPay, saPayment); - String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment); - manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced); + String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay); + //System.out.println(manaProduced); payMultipleMana(cost, manaProduced, ai); // remove from available lists - /* - * Refactoring this code to sourcesForShards.values().removeIf((SpellAbility srcSa) -> srcSa.getHostCard().equals(saPayment.getHostCard())); - * causes Android build not to compile - * */ - Iterator itSa = sourcesForShards.values().iterator(); - while (itSa.hasNext()) { - SpellAbility srcSa = itSa.next(); - if (srcSa.getHostCard().equals(saPayment.getHostCard())) { - itSa.remove(); - } - } + Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard())); } handleOfferingsAI(sa, true, cost.isPaid()); refundMana(manaSpentToPay, ai, sa); - + return manaSources; } // getManaSourcesToPayCost() @@ -427,14 +603,14 @@ public class ComputerUtilMana { List paymentList = Lists.newArrayList(); if (payManaCostFromPool(cost, sa, ai, test, manaSpentToPay)) { - return true; // paid all from floating mana + return true; // paid all from floating mana } - + boolean hasConverge = sa.getHostCard().hasConverge(); ListMultimap sourcesForShards = getSourcesForShards(cost, sa, ai, test, - checkPlayable, manaSpentToPay, hasConverge); + checkPlayable, manaSpentToPay, hasConverge); if (sourcesForShards == null && !purePhyrexian) { - return false; // no mana abilities to use for paying + return false; // no mana abilities to use for paying } final ManaPool manapool = ai.getManaPool(); @@ -443,26 +619,43 @@ public class ComputerUtilMana { // Loop over mana needed while (!cost.isPaid()) { + while (!cost.isPaid() && !manapool.isEmpty()) { + boolean found = false; + for (byte color : MagicColor.WUBRGC) { + if (manapool.tryPayCostWithColor(color, sa, cost)) { + found = true; + break; + } + } + if (!found) { + break; + } + } + if (cost.isPaid()) { + break; + } toPay = getNextShardToPay(cost); boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B"); Collection saList = null; - if (hasConverge && - (toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) { - final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION; - for (final byte b : ColorSet.fromMask(unpaidColors)) { // try and pay other colors for converge - final ManaCostShard shard = ManaCostShard.valueOf(b); - saList = sourcesForShards.get(shard); - if (saList != null && !saList.isEmpty()) { - toPay = shard; - break; - } - } - if (saList == null || saList.isEmpty()) { // failed to converge, revert to paying generic - saList = sourcesForShards.get(toPay); - hasConverge = false; - } + if (hasConverge && + (toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) { + final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION; + for (final byte b : ColorSet.fromMask(unpaidColors)) { + // try and pay other colors for converge + final ManaCostShard shard = ManaCostShard.valueOf(b); + saList = sourcesForShards.get(shard); + if (saList != null && !saList.isEmpty()) { + toPay = shard; + break; + } + } + if (saList == null || saList.isEmpty()) { + // failed to converge, revert to paying generic + saList = sourcesForShards.get(toPay); + hasConverge = false; + } } else { if (!(sourcesForShards == null && purePhyrexian)) { saList = sourcesForShards.get(toPay); @@ -529,33 +722,23 @@ public class ComputerUtilMana { setExpressColorChoice(sa, ai, cost, toPay, saPayment); if (test) { - // Check energy when testing - CostPayEnergy energyCost = saPayment.getPayCosts().getCostEnergy(); - if (energyCost != null) { - testEnergyPool -= Integer.parseInt(energyCost.getAmount()); - if (testEnergyPool < 0) { - // Can't pay energy cost - break; - } - } + // Check energy when testing + CostPayEnergy energyCost = saPayment.getPayCosts().getCostEnergy(); + if (energyCost != null) { + testEnergyPool -= Integer.parseInt(energyCost.getAmount()); + if (testEnergyPool < 0) { + // Can't pay energy cost + break; + } + } - String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment); - manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced); - //System.out.println(manaProduced); + String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay); + + // System.out.println(manaProduced); payMultipleMana(cost, manaProduced, ai); // remove from available lists - /* - * Refactoring this code to sourcesForShards.values().removeIf((SpellAbility srcSa) -> srcSa.getHostCard().equals(saPayment.getHostCard())); - * causes Android build not to compile - * */ - Iterator itSa = sourcesForShards.values().iterator(); - while (itSa.hasNext()) { - SpellAbility srcSa = itSa.next(); - if (srcSa.getHostCard().equals(saPayment.getHostCard())) { - itSa.remove(); - } - } + Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard())); } else { final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment); @@ -570,20 +753,10 @@ public class ComputerUtilMana { // no need to remove abilities from resource map, // once their costs are paid and consume resources, they can not be used again - - if (hasConverge) { // hack to prevent converge re-using sources - // remove from available lists - /* - * Refactoring this code to sourcesForShards.values().removeIf((SpellAbility srcSa) -> srcSa.getHostCard().equals(saPayment.getHostCard())); - * causes Android build not to compile - * */ - Iterator itSa = sourcesForShards.values().iterator(); - while (itSa.hasNext()) { - SpellAbility srcSa = itSa.next(); - if (srcSa.getHostCard().equals(saPayment.getHostCard())) { - itSa.remove(); - } - } + + if (hasConverge) { + // hack to prevent converge re-using sources + Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard())); } } } @@ -596,15 +769,6 @@ public class ComputerUtilMana { // extraMana, sa.getHostCard(), sa.toUnsuppressedString(), StringUtils.join(paymentPlan, "\n\t")); // } - // See if it's possible to pay with something that was left in the mana pool in corner cases, - // e.g. Gemstone Caverns with a Luck counter on it generating colored mana (which fails to be - // processed correctly on a per-ability basis, leaving floating mana in the pool) - if (!cost.isPaid() && !manapool.isEmpty()) { - for (byte color : MagicColor.WUBRGC) { - manapool.tryPayCostWithColor(color, sa, cost); - } - } - // The cost is still unpaid, so refund the mana and report if (!cost.isPaid()) { refundMana(manaSpentToPay, ai, sa); @@ -633,15 +797,16 @@ public class ComputerUtilMana { } - /** - * Creates a mapping between the required mana shards and the available spell abilities to pay for them - */ - private static ListMultimap getSourcesForShards(final ManaCostBeingPaid cost, - final SpellAbility sa, final Player ai, final boolean test, final boolean checkPlayable, - List manaSpentToPay, final boolean hasConverge) { - // arrange all mana abilities by color produced. + /** + * Creates a mapping between the required mana shards and the available spell abilities to pay for them + */ + private static ListMultimap getSourcesForShards(final ManaCostBeingPaid cost, + final SpellAbility sa, final Player ai, final boolean test, final boolean checkPlayable, + List manaSpentToPay, final boolean hasConverge) { + // arrange all mana abilities by color produced. final ListMultimap manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, checkPlayable); - if (manaAbilityMap.isEmpty()) { // no mana abilities, bailing out + if (manaAbilityMap.isEmpty()) { + // no mana abilities, bailing out refundMana(manaSpentToPay, ai, sa); handleOfferingsAI(sa, test, cost.isPaid()); return null; @@ -652,25 +817,26 @@ public class ComputerUtilMana { // select which abilities may be used for each shard ListMultimap sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost); - if (hasConverge) { // add extra colors for paying converge - final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION; - for (final byte b : ColorSet.fromMask(unpaidColors)) { - final ManaCostShard shard = ManaCostShard.valueOf(b); - if (!sourcesForShards.containsKey(shard)) { - if (ai.getManaPool().canPayForShardWithColor(shard, b)) { + if (hasConverge) { + // add extra colors for paying converge + final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION; + for (final byte b : ColorSet.fromMask(unpaidColors)) { + final ManaCostShard shard = ManaCostShard.valueOf(b); + if (!sourcesForShards.containsKey(shard)) { + if (ai.getManaPool().canPayForShardWithColor(shard, b)) { for (SpellAbility saMana : manaAbilityMap.get((int)b)) { - sourcesForShards.get(shard).add(sourcesForShards.get(shard).size(), saMana); + sourcesForShards.get(shard).add(saMana); } } - } - } + } + } } sortManaAbilities(sourcesForShards, sa); if (DEBUG_MANA_PAYMENT) { System.out.println("DEBUG_MANA_PAYMENT: sourcesForShards = " + sourcesForShards); } - return sourcesForShards; - } + return sourcesForShards; + } /** * Checks if the given mana cost can be paid from floating mana. @@ -681,9 +847,9 @@ public class ComputerUtilMana { * @param manaSpentToPay list of mana spent * @return whether the floating mana is sufficient to pay the cost fully */ - private static boolean payManaCostFromPool(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, + private static boolean payManaCostFromPool(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, List manaSpentToPay) { - final boolean hasConverge = sa.getHostCard().hasConverge(); + final boolean hasConverge = sa.getHostCard().hasConverge(); List unpaidShards = cost.getUnpaidShards(); Collections.sort(unpaidShards); // most difficult shards must come first for (ManaCostShard part : unpaidShards) { @@ -764,15 +930,15 @@ public class ComputerUtilMana { // if we are simulating mana payment for the human controller, use the first mana available (and avoid prompting the human player) if (!(ai.getController() instanceof PlayerControllerAi)) { - return manaChoices.get(0); + return manaChoices.get(0); } // Let them choose then return ai.getController().chooseManaFromPool(manaChoices); } - private static List> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard, - final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid) { + private static List> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard, + final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid) { final List> weightedOptions = new ArrayList<>(); for (final Mana thisMana : manapool) { if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) { @@ -794,11 +960,11 @@ public class ComputerUtilMana { int weight = 0; if (colorsPaid == -1) { - // prefer colorless mana to spend - weight += thisMana.isColorless() ? 5 : 0; + // prefer colorless mana to spend + weight += thisMana.isColorless() ? 5 : 0; } else { - // get more colors for converge - weight += (thisMana.getColor() | colorsPaid) != colorsPaid ? 5 : 0; + // get more colors for converge + weight += (thisMana.getColor() | colorsPaid) != colorsPaid ? 5 : 0; } // prefer restricted mana to spend @@ -815,7 +981,7 @@ public class ComputerUtilMana { } return weightedOptions; } - + private static void setExpressColorChoice(final SpellAbility sa, final Player ai, ManaCostBeingPaid cost, ManaCostShard toPay, SpellAbility saPayment) { @@ -856,7 +1022,7 @@ public class ComputerUtilMana { if (isManaSourceReserved(ai, sourceCard, sa)) { return false; } - + if (toPay.isSnow() && !sourceCard.isSnow()) { return false; } @@ -1002,7 +1168,7 @@ public class ComputerUtilMana { *

* getComboManaChoice. *

- * + * * @param manaAb * a {@link forge.game.spellability.SpellAbility} object. * @param saRoot @@ -1026,7 +1192,7 @@ public class ComputerUtilMana { choice = abMana.getExpressChoice(); abMana.clearExpressChoice(); byte colorMask = ManaAtom.fromName(choice); - if (abMana.canProduce(choice, manaAb) && testCost.isAnyPartPayableWith(colorMask, ai.getManaPool())) { + if (manaAb.canProduce(choice) && testCost.isAnyPartPayableWith(colorMask, ai.getManaPool())) { choiceString.append(choice); payMultipleMana(testCost, choice, ai); continue; @@ -1103,7 +1269,7 @@ public class ComputerUtilMana { } return unused.isEmpty() ? null : StringUtils.join(unused, ' '); } - + /** * Find all mana sources. * @param manaAbilityMap The map of SpellAbilities that produce mana. @@ -1135,7 +1301,7 @@ public class ComputerUtilMana { res.putAll(shard, manaAbilityMap.get(ManaAtom.GENERIC)); continue; } - + if (shard == ManaCostShard.GENERIC) { continue; } @@ -1163,7 +1329,7 @@ public class ComputerUtilMana { * @return ManaCost */ public static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final boolean test, final int extraMana) { - Card card = sa.getHostCard(); + Card card = sa.getHostCard(); ZoneType castFromBackup = null; if (test && sa.isSpell()) { castFromBackup = card.getCastFrom(); @@ -1196,14 +1362,14 @@ public class ComputerUtilMana { sa.setXManaCostPaid(manaToAdd / cost.getXcounter()); } } - + CostAdjustment.adjust(cost, sa, null, test); int timesMultikicked = card.getKickerMagnitude(); if (timesMultikicked > 0 && sa.hasParam("Announce") && sa.getParam("Announce").startsWith("Multikicker")) { ManaCost mkCost = sa.getMultiKickerManaCost(); for (int i = 0; i < timesMultikicked; i++) { - cost.addManaCost(mkCost); + cost.addManaCost(mkCost); } sa.setSVar("Multikicker", String.valueOf(timesMultikicked)); } @@ -1387,20 +1553,6 @@ public class ComputerUtilMana { final ListMultimap manaMap = ArrayListMultimap.create(); final Game game = ai.getGame(); - List replacementEffects = new ArrayList<>(); - for (final Player p : game.getPlayers()) { - for (final Card crd : p.getAllCards()) { - for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) { - if (replacementEffect.requirementsCheck(game) - && replacementEffect.getMode() == ReplacementType.ProduceMana - && replacementEffect.hasParam("ManaReplacement") - && replacementEffect.zonesCheck(game.getZoneOf(crd))) { - replacementEffects.add(replacementEffect); - } - } - } - } - // Loop over all current available mana sources for (final Card sourceCard : getAvailableManaSources(ai, checkPlayable)) { if (DEBUG_MANA_PAYMENT) { @@ -1430,48 +1582,80 @@ public class ComputerUtilMana { } manaMap.get(ManaAtom.GENERIC).add(m); // add to generic source list - AbilityManaPart mp = m.getManaPart(); - // setup produce mana replacement effects - final Map repParams = AbilityKey.newMap(); - repParams.put(AbilityKey.Mana, mp.getOrigProduced()); - repParams.put(AbilityKey.Affected, sourceCard); - repParams.put(AbilityKey.Player, ai); - repParams.put(AbilityKey.AbilityMana, m); + SpellAbility tail = m; + while (tail != null) { + AbilityManaPart mp = m.getManaPart(); + if (mp != null && tail.metConditions()) { + // TODO Replacement Check currently doesn't work for reflected colors - for (final ReplacementEffect replacementEffect : replacementEffects) { - if (replacementEffect.canReplace(repParams)) { - Card crd = replacementEffect.getHostCard(); - String repType = crd.getSVar(replacementEffect.getParam("ManaReplacement")); - if (repType.contains("Chosen")) { - repType = TextUtil.fastReplace(repType, "Chosen", MagicColor.toShortString(crd.getChosenColor())); + // setup produce mana replacement effects + String origin = mp.getOrigProduced(); + final Map repParams = AbilityKey.newMap(); + repParams.put(AbilityKey.Mana, origin); + repParams.put(AbilityKey.Affected, sourceCard); + repParams.put(AbilityKey.Player, ai); + repParams.put(AbilityKey.AbilityMana, m); // RootAbility + + List reList = game.getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other); + + if (reList.isEmpty()) { + Set reflectedColors = CardUtil.getReflectableManaColors(m); + // find possible colors + for (byte color : MagicColor.WUBRG) { + if (tail.canThisProduce(MagicColor.toShortString(color)) || reflectedColors.contains(MagicColor.toLongString(color))) { + manaMap.put((int)color, m); + } + } + if (m.canThisProduce("C") || reflectedColors.contains(MagicColor.Constant.COLORLESS)) { + manaMap.put(ManaAtom.COLORLESS, m); + } + } else { + // try to guess the color the mana gets replaced to + for (ReplacementEffect re : reList) { + SpellAbility o = re.getOverridingAbility(); + String replaced = origin; + if (o == null || o.getApi() != ApiType.ReplaceMana) { + continue; + } + if (o.hasParam("ReplaceMana")) { + replaced = o.getParam("ReplaceMana"); + } else if (o.hasParam("ReplaceType")) { + String color = o.getParam("ReplaceType"); + for (byte c : MagicColor.WUBRGC) { + String s = MagicColor.toShortString(c); + replaced = replaced.replace(s, color); + } + } else if (o.hasParam("ReplaceColor")) { + String color = o.getParam("ReplaceColor"); + if (o.hasParam("ReplaceOnly")) { + replaced = replaced.replace(o.getParam("ReplaceOnly"), color); + } else { + for (byte c : MagicColor.WUBRG) { + String s = MagicColor.toShortString(c); + replaced = replaced.replace(s, color); + } + } + } + + for (byte color : MagicColor.WUBRG) { + if ("Any".equals(replaced) || replaced.contains(MagicColor.toShortString(color))) { + manaMap.put((int)color, m); + } + } + + if (replaced.contains("C")) { + manaMap.put(ManaAtom.COLORLESS, m); + } + + } } - mp.setManaReplaceType(repType); } + tail = tail.getSubAbility(); } - Set reflectedColors = CardUtil.getReflectableManaColors(m); - // find possible colors - if (mp.canProduce("W", m) || reflectedColors.contains(MagicColor.Constant.WHITE)) { - manaMap.get(ManaAtom.WHITE).add(m); - } - if (mp.canProduce("U", m) || reflectedColors.contains(MagicColor.Constant.BLUE)) { - manaMap.get(ManaAtom.BLUE).add(m); - } - if (mp.canProduce("B", m) || reflectedColors.contains(MagicColor.Constant.BLACK)) { - manaMap.get(ManaAtom.BLACK).add(m); - } - if (mp.canProduce("R", m) || reflectedColors.contains(MagicColor.Constant.RED)) { - manaMap.get(ManaAtom.RED).add(m); - } - if (mp.canProduce("G", m) || reflectedColors.contains(MagicColor.Constant.GREEN)) { - manaMap.get(ManaAtom.GREEN).add(m); - } - if (mp.canProduce("C", m) || reflectedColors.contains(MagicColor.Constant.COLORLESS)) { - manaMap.get(ManaAtom.COLORLESS).add(m); - } - if (mp.isSnow()) { - manaMap.get(ManaAtom.IS_SNOW).add(m); + if (m.getHostCard().isSnow()) { + manaMap.put(ManaAtom.IS_SNOW, m); } if (DEBUG_MANA_PAYMENT) { System.out.println("DEBUG_MANA_PAYMENT: groupSourcesByManaColor manaMap = " + manaMap); @@ -1486,7 +1670,7 @@ public class ComputerUtilMana { *

* determineLeftoverMana. *

- * + * * @param sa * a {@link forge.game.spellability.SpellAbility} object. * @param player @@ -1507,7 +1691,7 @@ public class ComputerUtilMana { *

* determineLeftoverMana. *

- * + * * @param sa * a {@link forge.game.spellability.SpellAbility} object. * @param player @@ -1536,7 +1720,7 @@ public class ComputerUtilMana { *

* getAIPlayableMana. *

- * + * * @return a {@link java.util.List} object. */ public static List getAIPlayableMana(Card c) { @@ -1583,8 +1767,8 @@ public class ComputerUtilMana { sa.resetSacrificedAsEmerge(); } } - - + + /** * Matches list of creatures to shards in mana cost for convoking. * @param cost cost of convoked ability diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index b88caf8c09a..ab50c1a806f 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -833,6 +833,9 @@ public class PlayerControllerAi extends PlayerController { @Override public byte chooseColor(String message, SpellAbility sa, ColorSet colors) { + if (colors.countColors() < 2) { + return Iterables.getFirst(colors, MagicColor.WHITE); + } // You may switch on sa.getApi() here and use sa.getParam("AILogic") CardCollectionView hand = player.getCardsIn(ZoneType.Hand); if (sa.getApi() == ApiType.Mana) { diff --git a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java index fa33c11087a..d8c185684fd 100644 --- a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java @@ -111,7 +111,7 @@ public abstract class SpellAbilityAi { if (aiLogic.equals("CheckCondition")) { SpellAbility saCopy = sa.copy(); saCopy.setActivatingPlayer(ai); - return saCopy.getConditions().areMet(saCopy); + return saCopy.metConditions(); } return !("Never".equals(aiLogic)); 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 7fcc06b0507..d7b12a02bcd 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java @@ -122,7 +122,7 @@ public class AnimateAi extends SpellAbilityAi { final Card source = sa.getHostCard(); final Game game = aiPlayer.getGame(); final PhaseHandler ph = game.getPhaseHandler(); - if (sa.getConditions() != null && !sa.getConditions().areMet(sa) && sa.getSubAbility() == null) { + if (!sa.metConditions() && sa.getSubAbility() == null) { return false; // what is this for? } if (!game.getStack().isEmpty() && game.getStack().peekAbility().getApi() == ApiType.Sacrifice) { 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 74c5c0aa1ab..1923fb877ac 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -304,10 +304,10 @@ public class ChangeZoneAi extends SpellAbilityAi { } // don't play if the conditions aren't met, unless it would trigger a beneficial sub-condition - if (!activateForCost && !sa.getConditions().areMet(sa)) { + if (!activateForCost && !sa.metConditions()) { final AbilitySub abSub = sa.getSubAbility(); if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) { - if (!abSub.getConditions().areMet(abSub)) { + if (!abSub.metConditions()) { return false; } } else { diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java index 36e8a3839d2..162b90f82f8 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -290,7 +290,7 @@ public class CountersPutAi extends SpellAbilityAi { return doMoveCounterLogic(ai, sa, ph); } - if (sa.getConditions() != null && !sa.getConditions().areMet(sa) && sa.getSubAbility() == null) { + if (!sa.metConditions() && sa.getSubAbility() == null) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java index 98e351bfdf5..78044dedaec 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java @@ -146,7 +146,7 @@ public class LifeGainAi extends SpellAbilityAi { } // don't play if the conditions aren't met, unless it would trigger a // beneficial sub-condition - if (!activateForCost && !sa.getConditions().areMet(sa)) { + if (!activateForCost && !sa.metConditions()) { final AbilitySub abSub = sa.getSubAbility(); if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) { if (!abSub.getConditions().areMet(abSub)) { 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 bd4ea2d6091..f3ec36f837d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java @@ -279,7 +279,7 @@ public class PermanentAi extends SpellAbilityAi { final Card source = sa.getHostCard(); final Cost cost = sa.getPayCosts(); - if (sa.getConditions() != null && !sa.getConditions().areMet(sa)) { + if (!sa.metConditions()) { return false; } diff --git a/forge-game/src/main/java/forge/game/CardTraitPredicates.java b/forge-game/src/main/java/forge/game/CardTraitPredicates.java index 3e9115aee38..c89e0556648 100644 --- a/forge-game/src/main/java/forge/game/CardTraitPredicates.java +++ b/forge-game/src/main/java/forge/game/CardTraitPredicates.java @@ -2,8 +2,19 @@ package forge.game; import com.google.common.base.Predicate; +import forge.game.card.Card; + public class CardTraitPredicates { + public static final Predicate isHostCard(final Card host) { + return new Predicate() { + @Override + public boolean apply(final CardTraitBase sa) { + return host.equals(sa.getHostCard()); + } + }; + } + public static final Predicate hasParam(final String name) { return new Predicate() { @Override diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java index 0565c8981e5..26ecb7d6980 100644 --- a/forge-game/src/main/java/forge/game/ForgeScript.java +++ b/forge-game/src/main/java/forge/game/ForgeScript.java @@ -5,6 +5,7 @@ import forge.card.MagicColor; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardState; +import forge.game.cost.Cost; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.staticability.StaticAbility; @@ -131,6 +132,9 @@ public class ForgeScript { return !sa.isManaAbility(); } else if (property.equals("withoutXCost")) { return !sa.costHasManaX(); + } else if (property.equals("hasTapCost")) { + Cost cost = sa.getPayCosts(); + return cost != null && cost.hasTapCost(); } else if (property.equals("Buyback")) { return sa.isBuyBackAbility(); } else if (property.equals("Cycling")) { diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index 3d3d5f03d65..14856324307 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -17,7 +17,6 @@ */ package forge.game; -import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -252,6 +251,18 @@ public final class GameActionUtil { } } + if (sa.isManaAbility() && sa.isActivatedAbility() && activator.hasKeyword("Piracy") && source.isLand() && source.isInPlay() && !activator.equals(source.getController()) && sa.getPayCosts().hasTapCost()) { + SpellAbility newSA = sa.copy(activator); + // to bypass Activator restriction, set Activator to Player + sa.getRestrictions().setActivator("Player"); + + // extra Mana restriction to only Spells + for (AbilityManaPart mp : newSA.getAllManaParts()) { + mp.setExtraManaRestriction("Spell"); + } + alternatives.add(newSA); + } + // below are for some special cases of activated abilities if (sa.isCycling() && activator.hasKeyword("CyclingForZero")) { for (final KeywordInterface inst : source.getKeywords()) { @@ -277,7 +288,6 @@ public final class GameActionUtil { newSA.setDescription(sb.toString()); alternatives.add(newSA); - break; } } @@ -556,44 +566,21 @@ public final class GameActionUtil { return eff; } - private static boolean hasUrzaLands(final Player p) { - final CardCollectionView landsControlled = p.getCardsIn(ZoneType.Battlefield); - return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine"))) - && Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Power-Plant"))) - && Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Tower"))); - } - - public static int amountOfManaGenerated(final SpellAbility sa, boolean multiply) { - // Calculate generated mana here for stack description and resolving - - int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa) : 1; - AbilityManaPart abMana = sa.getManaPartRecursive(); - - if (sa.hasParam("Bonus")) { - // For mana abilities that get a bonus - // Bonus currently MULTIPLIES the base amount. Base Amounts should - // ALWAYS be Base - int bonus = 0; - if (sa.getParam("Bonus").equals("UrzaLands")) { - if (hasUrzaLands(sa.getActivatingPlayer())) { - bonus = Integer.parseInt(sa.getParam("BonusProduced")); - } + public static String generatedTotalMana(final SpellAbility sa) { + StringBuilder sb = new StringBuilder(); + SpellAbility tail = sa; + while (tail != null) { + String value = generatedMana(tail); + if (!value.isEmpty() && !"0".equals(value)) { + sb.append(value).append(" "); } - - amount += bonus; - } - - if (!multiply || abMana.isAnyMana() || abMana.isComboMana() || abMana.isSpecialMana()) { - return amount; - } else { - // For cards that produce like {C}{R} vs cards that produce {R}{R}. - return abMana.mana().split(" ").length * amount; + tail = tail.getSubAbility(); } + return sb.toString().trim(); } - public static String generatedMana(final SpellAbility sa) { - int amount = amountOfManaGenerated(sa, false); + int amount = sa.amountOfManaGenerated(false); AbilityManaPart abMana = sa.getManaPart(); String baseMana; diff --git a/forge-game/src/main/java/forge/game/TriggerReplacementBase.java b/forge-game/src/main/java/forge/game/TriggerReplacementBase.java index 1ff5b94e3ae..0764bd7b2fc 100644 --- a/forge-game/src/main/java/forge/game/TriggerReplacementBase.java +++ b/forge-game/src/main/java/forge/game/TriggerReplacementBase.java @@ -77,4 +77,5 @@ public abstract class TriggerReplacementBase extends CardTraitBase implements II this.overridingAbility = overridingAbility0; } + abstract public SpellAbility ensureAbility(); } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityApiBased.java b/forge-game/src/main/java/forge/game/ability/AbilityApiBased.java index a471e78703a..4603cee8d98 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityApiBased.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityApiBased.java @@ -1,9 +1,5 @@ package forge.game.ability; -import forge.game.ability.effects.ChangeZoneAllEffect; -import forge.game.ability.effects.ChangeZoneEffect; -import forge.game.ability.effects.ManaEffect; -import forge.game.ability.effects.ManaReflectedEffect; import forge.game.card.Card; import forge.game.cost.Cost; import forge.game.spellability.AbilityActivated; @@ -15,8 +11,6 @@ import java.util.Map; public class AbilityApiBased extends AbilityActivated { private final SpellAbilityEffect effect; - private static final long serialVersionUID = -4183793555528531978L; - public AbilityApiBased(ApiType api0, Card sourceCard, Cost abCost, TargetRestrictions tgt, Map params0) { super(sourceCard, abCost, tgt); originalMapParams.putAll(params0); @@ -24,13 +18,12 @@ public class AbilityApiBased extends AbilityActivated { api = api0; effect = api.getSpellEffect(); - if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) { - + if (api.equals(ApiType.Mana) || api.equals(ApiType.ManaReflected)) { this.setManaPart(new AbilityManaPart(sourceCard, mapParams)); this.setUndoable(true); // will try at least } - if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) { + if (api.equals(ApiType.ChangeZone) || api.equals(ApiType.ChangeZoneAll)) { AbilityFactory.adjustChangeZoneTarget(mapParams, this); } } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index 68d68535008..8e85e2bb2ea 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1388,7 +1388,7 @@ public class AbilityUtils { ); // check conditions - if (sa.getConditions().areMet(sa)) { + if (sa.metConditions()) { if (sa.isWrapper() || StringUtils.isBlank(sa.getParam("UnlessCost"))) { sa.resolve(); } @@ -1656,10 +1656,15 @@ public class AbilityUtils { // Count$Kicked.. if (sq[0].startsWith("Kicked")) { - boolean kicked = ((SpellAbility)ctb).isKicked() || c.getKickerMagnitude() > 0; + boolean kicked = sa.isKicked() || c.getKickerMagnitude() > 0; return CardFactoryUtil.doXMath(Integer.parseInt(kicked ? sq[1] : sq[2]), expr, c); } + // Count$UrzaLands.. + if (sq[0].startsWith("UrzaLands")) { + return CardFactoryUtil.doXMath(Integer.parseInt(sa.getActivatingPlayer().hasUrzaLands() ? sq[1] : sq[2]), expr, c); + } + //Count$SearchedLibrary. if (sq[0].contains("SearchedLibrary")) { int sum = 0; diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index f21cb010fc6..400398c93ad 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -135,6 +135,7 @@ public enum ApiType { Repeat (RepeatEffect.class), RepeatEach (RepeatEachEffect.class), ReplaceEffect (ReplaceEffect.class), + ReplaceMana (ReplaceManaEffect.class), ReplaceDamage (ReplaceDamageEffect.class), ReplaceSplitDamage (ReplaceSplitDamageEffect.class), RestartGame (RestartGameEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/SpellApiBased.java b/forge-game/src/main/java/forge/game/ability/SpellApiBased.java index d96401336a4..d7903a2c502 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellApiBased.java +++ b/forge-game/src/main/java/forge/game/ability/SpellApiBased.java @@ -2,10 +2,6 @@ package forge.game.ability; import java.util.Map; -import forge.game.ability.effects.ChangeZoneAllEffect; -import forge.game.ability.effects.ChangeZoneEffect; -import forge.game.ability.effects.ManaEffect; -import forge.game.ability.effects.ManaReflectedEffect; import forge.game.card.Card; import forge.game.cost.Cost; import forge.game.spellability.AbilityManaPart; @@ -28,11 +24,11 @@ public class SpellApiBased extends Spell { // A spell is always intrinsic this.setIntrinsic(true); - if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) { + if (api.equals(ApiType.Mana) || api.equals(ApiType.ManaReflected)) { this.setManaPart(new AbilityManaPart(sourceCard, mapParams)); } - if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) { + if (api.equals(ApiType.ChangeZone) || api.equals(ApiType.ChangeZoneAll)) { AbilityFactory.adjustChangeZoneTarget(mapParams, this); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java index 0a7ab66ac8d..74f6a3bdc3a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java @@ -15,7 +15,6 @@ import forge.game.mana.Mana; import forge.game.player.Player; import forge.game.spellability.AbilityManaPart; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType; import forge.util.Localizer; @@ -37,7 +36,6 @@ public class ManaEffect extends SpellAbilityEffect { sa.setUndoable(sa.isAbility() && sa.isUndoable()); final List tgtPlayers = getTargetPlayers(sa); - final TargetRestrictions tgt = sa.getTargetRestrictions(); final boolean optional = sa.hasParam("Optional"); final Game game = sa.getActivatingPlayer().getGame(); @@ -45,46 +43,26 @@ public class ManaEffect extends SpellAbilityEffect { return; } - if (sa.hasParam("DoubleManaInPool")) { - for (final Player player : tgtPlayers) { - for (byte color : ManaAtom.MANATYPES) { - int amountColor = player.getManaPool().getAmountOfColor(color); - for (int i = 0; i < amountColor; i++) { - abMana.produceMana(MagicColor.toShortString(color), player, sa); - } - } + for (Player p : tgtPlayers) { + if (sa.usesTargeting() && !p.canBeTargetedBy(sa)) { + // Illegal target. Skip. + continue; } - } - if (sa.hasParam("ProduceNoOtherMana")) { - return; - } - - if (abMana.isComboMana()) { - for (Player p : tgtPlayers) { + if (abMana.isComboMana()) { int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa) : 1; - if (tgt != null && !p.canBeTargetedBy(sa)) { - // Illegal target. Skip. - continue; - } - Player activator = sa.getActivatingPlayer(); String express = abMana.getExpressChoice(); String[] colorsProduced = abMana.getComboColors().split(" "); final StringBuilder choiceString = new StringBuilder(); - ColorSet colorOptions = null; + ColorSet colorOptions = ColorSet.fromNames(colorsProduced); String[] colorsNeeded = express.isEmpty() ? null : express.split(" "); - if (!abMana.isAnyMana()) { - colorOptions = ColorSet.fromNames(colorsProduced); - } else { - colorOptions = ColorSet.fromNames(MagicColor.Constant.ONLY_COLORS); - } boolean differentChoice = abMana.getOrigProduced().contains("Different"); ColorSet fullOptions = colorOptions; for (int nMana = 0; nMana < amount; nMana++) { String choice = ""; - if (colorsNeeded != null && colorsNeeded.length > nMana) { // select from express choices if possible + if (colorsNeeded != null && colorsNeeded.length > nMana) { // select from express choices if possible colorOptions = ColorSet .fromMask(fullOptions.getColor() & ManaAtom.fromName(colorsNeeded[nMana])); } @@ -93,10 +71,10 @@ public class ManaEffect extends SpellAbilityEffect { // just use the first possible color. choice = colorsProduced[differentChoice ? nMana : 0]; } else { - byte chosenColor = activator.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, + byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, differentChoice ? fullOptions : colorOptions); if (chosenColor == 0) - throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + activator + " color mana choice is empty for " + card.getName()); + throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + p + " color mana choice is empty for " + card.getName()); fullOptions = ColorSet.fromMask(fullOptions.getMyColor() - chosenColor); choice = MagicColor.toShortString(chosenColor); @@ -116,18 +94,10 @@ public class ManaEffect extends SpellAbilityEffect { return; } - game.action.nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", activator.getName(), choiceString), activator); + game.getAction().nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), choiceString), p); abMana.setExpressChoice(choiceString.toString()); } - } - else if (abMana.isAnyMana()) { - for (Player p : tgtPlayers) { - if (tgt != null && !p.canBeTargetedBy(sa)) { - // Illegal target. Skip. - continue; - } - - Player act = sa.getActivatingPlayer(); + else if (abMana.isAnyMana()) { // AI color choice is set in ComputerUtils so only human players need to make a choice String colorsNeeded = abMana.getExpressChoice(); @@ -142,20 +112,14 @@ public class ManaEffect extends SpellAbilityEffect { colorMenu = mask == 0 ? ColorSet.ALL_COLORS : ColorSet.fromMask(mask); byte val = p.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu); if (0 == val) { - throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + act + " color mana choice is empty for " + card.getName()); + throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + p + " color mana choice is empty for " + card.getName()); } choice = MagicColor.toShortString(val); - game.action.nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", act.getName(), choice), act); + game.getAction().nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), choice), p); abMana.setExpressChoice(choice); } - } - else if (abMana.isSpecialMana()) { - for (Player p : tgtPlayers) { - if (tgt != null && !p.canBeTargetedBy(sa)) { - // Illegal target. Skip. - continue; - } + else if (abMana.isSpecialMana()) { String type = abMana.getOrigProduced().split("Special ")[1]; @@ -177,7 +141,7 @@ public class ManaEffect extends SpellAbilityEffect { if (cs.isMonoColor()) sb.append(MagicColor.toShortString(s.getColorMask())); else /* (cs.isMulticolor()) */ { - byte chosenColor = sa.getActivatingPlayer().getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs); + byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs); sb.append(MagicColor.toShortString(chosenColor)); } } @@ -210,34 +174,36 @@ public class ManaEffect extends SpellAbilityEffect { abMana.setExpressChoice(ColorSet.fromMask(colors)); } else if (type.startsWith("EachColoredManaSymbol")) { final String res = type.split("_")[1]; - final CardCollection list = AbilityUtils.getDefinedCards(card, res, sa); StringBuilder sb = new StringBuilder(); - for (Card c : list) { - String mana = c.getManaCost().toString(); - for (int i = 0; i < mana.length(); i++) { - char symbol = mana.charAt(i); - switch (symbol) { - case 'W': - case 'U': - case 'B': - case 'R': - case 'G': - sb.append(symbol).append(' '); - break; + for (Card c : AbilityUtils.getDefinedCards(card, res, sa)) { + for (ManaCostShard s : c.getManaCost()) { + ColorSet cs = ColorSet.fromMask(s.getColorMask()); + if(cs.isColorless()) + continue; + sb.append(' '); + if (cs.isMonoColor()) + sb.append(MagicColor.toShortString(s.getColorMask())); + else /* (cs.isMulticolor()) */ { + byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs); + sb.append(MagicColor.toShortString(chosenColor)); } } } abMana.setExpressChoice(sb.toString().trim()); + } else if (type.startsWith("DoubleManaInPool")) { + StringBuilder sb = new StringBuilder(); + for (byte color : ManaAtom.MANATYPES) { + sb.append(StringUtils.repeat(MagicColor.toShortString(color), " ", p.getManaPool().getAmountOfColor(color))).append(" "); + } + abMana.setExpressChoice(sb.toString().trim()); } if (abMana.getExpressChoice().isEmpty()) { System.out.println("AbilityFactoryMana::manaResolve() - special mana effect is empty for " + sa.getHostCard().getName()); } } - } - for (final Player player : tgtPlayers) { - abMana.produceMana(GameActionUtil.generatedMana(sa), player, sa); + abMana.produceMana(GameActionUtil.generatedMana(sa), p, sa); } // Only clear express choice after mana has been produced diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java index c5867722a2c..bdf69fbd81f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java @@ -11,7 +11,6 @@ import forge.game.spellability.SpellAbility; import forge.util.Localizer; import java.util.Collection; -import java.util.List; import org.apache.commons.lang3.StringUtils; @@ -29,8 +28,7 @@ public class ManaReflectedEffect extends SpellAbilityEffect { final Collection colors = CardUtil.getReflectableManaColors(sa); - final List tgtPlayers = getTargetPlayers(sa); - for (final Player player : tgtPlayers) { + for (final Player player : getTargetPlayers(sa)) { final String generated = generatedReflectedMana(sa, colors, player); ma.produceMana(generated, player, sa); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java index 028980c02bf..eee8cdfbd0a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java @@ -6,12 +6,14 @@ import forge.game.ability.AbilityKey; import org.apache.commons.lang3.StringUtils; import forge.game.Game; +import forge.game.GameLogEntryType; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; +import forge.util.TextUtil; public class ReplaceDamageEffect extends SpellAbilityEffect { @@ -58,6 +60,12 @@ public class ReplaceDamageEffect extends SpellAbilityEffect { } params.put(AbilityKey.DamageAmount, dmg); + // need to log Updated events there, or the log is wrong order + String message = sa.getReplacementEffect().toString(); + if ( !StringUtils.isEmpty(message)) { + message = TextUtil.fastReplace(message, "CARDNAME", card.getName()); + game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); + } //try to call replacementHandler with new Params ReplacementResult result = game.getReplacementHandler().run(event, params); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java index af761de13fb..e15dd8731f1 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java @@ -3,9 +3,12 @@ package forge.game.ability.effects; import java.util.List; import java.util.Map; +import org.apache.commons.lang3.StringUtils; + import com.google.common.collect.Maps; import forge.game.Game; +import forge.game.GameLogEntryType; import forge.game.GameObject; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; @@ -16,6 +19,7 @@ import forge.game.player.Player; import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; +import forge.util.TextUtil; public class ReplaceEffect extends SpellAbilityEffect { @@ -61,6 +65,13 @@ public class ReplaceEffect extends SpellAbilityEffect { params.put(AbilityKey.EffectOnly, true); } + // need to log Updated events there, or the log is wrong order + String message = sa.getReplacementEffect().toString(); + if ( !StringUtils.isEmpty(message)) { + message = TextUtil.fastReplace(message, "CARDNAME", card.getName()); + game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); + } + //try to call replacementHandler with new Params ReplacementResult result = game.getReplacementHandler().run(retype, params); switch (result) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java new file mode 100644 index 00000000000..246a1857255 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java @@ -0,0 +1,109 @@ +package forge.game.ability.effects; + +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.collect.Maps; + +import forge.card.ColorSet; +import forge.card.MagicColor; +import forge.game.Game; +import forge.game.GameLogEntryType; +import forge.game.ability.AbilityKey; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.replacement.ReplacementResult; +import forge.game.replacement.ReplacementType; +import forge.game.spellability.SpellAbility; +import forge.util.TextUtil; + +public class ReplaceManaEffect extends SpellAbilityEffect { + + @Override + public void resolve(SpellAbility sa) { + final Card card = sa.getHostCard(); + final Player player = sa.getActivatingPlayer(); + final Game game = card.getGame(); + + // outside of Replacement Effect, unwanted result + if (!sa.isReplacementAbility()) { + return; + } + + final ReplacementType event = sa.getReplacementEffect().getMode(); + @SuppressWarnings("unchecked") + Map originalParams = (Map) sa.getReplacingObject(AbilityKey.OriginalParams); + Map params = Maps.newHashMap(originalParams); + + String replaced = (String)sa.getReplacingObject(AbilityKey.Mana); + if (sa.hasParam("ReplaceMana")) { + // replace type and amount + replaced = sa.getParam("ReplaceMana"); + if ("Any".equals(replaced)) { + byte rs = MagicColor.GREEN; + rs = player.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS); + replaced = MagicColor.toShortString(rs); + } + } else if (sa.hasParam("ReplaceType")) { + // replace color and colorless + String color = sa.getParam("ReplaceType"); + if ("Any".equals(color)) { + byte rs = MagicColor.GREEN; + rs = player.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS); + color = MagicColor.toShortString(rs); + } + for (byte c : MagicColor.WUBRGC) { + String s = MagicColor.toShortString(c); + replaced = replaced.replace(s, color); + } + } else if (sa.hasParam("ReplaceColor")) { + // replace color + String color = sa.getParam("ReplaceColor"); + if ("Chosen".equals(color)) { + if (card.hasChosenColor()) { + color = MagicColor.toShortString(card.getChosenColor()); + } + } + if (sa.hasParam("ReplaceOnly")) { + replaced = replaced.replace(sa.getParam("ReplaceOnly"), color); + } else { + for (byte c : MagicColor.WUBRG) { + String s = MagicColor.toShortString(c); + replaced = replaced.replace(s, color); + } + } + } else if (sa.hasParam("ReplaceAmount")) { + // replace amount = multiples + replaced = StringUtils.repeat(replaced, " ", Integer.valueOf(sa.getParam("ReplaceAmount"))); + } + params.put(AbilityKey.Mana, replaced); + + // need to log Updated events there, or the log is wrong order + String message = sa.getReplacementEffect().toString(); + if ( !StringUtils.isEmpty(message)) { + message = TextUtil.fastReplace(message, "CARDNAME", card.getName()); + game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); + } + + //try to call replacementHandler with new Params + ReplacementResult result = game.getReplacementHandler().run(event, params); + switch (result) { + case NotReplaced: + case Updated: { + for (Map.Entry e : params.entrySet()) { + originalParams.put(e.getKey(), e.getValue()); + } + // effect was updated + originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated); + break; + } + default: + // effect was replaced with something else + originalParams.put(AbilityKey.ReplacementResult, result); + break; + } + } + +} diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java index f06565f1486..ccf1fa909e5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java @@ -53,7 +53,7 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect { if (card.getType().hasStringType("Effect") && prevent <= 0) { game.getAction().exile(card, null); } else if (!StringUtils.isNumeric(varValue)) { - card.setSVar(varValue, "Number$" + prevent); + sa.setSVar(varValue, "Number$" + prevent); } Card sourceLKI = (Card) sa.getReplacingObject(AbilityKey.Source); 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 c1c2c0c32a5..5417bfbf63c 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -2353,7 +2353,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { if (ab.getApi() == ApiType.ManaReflected) { colors.addAll(CardUtil.getReflectableManaColors(ab)); } else { - colors = CardUtil.canProduce(6, ab.getManaPart(), colors); + colors = CardUtil.canProduce(6, ab, colors); } } @@ -2364,7 +2364,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return true; } } else { - if (mana.getManaPart().canProduce(MagicColor.toShortString(s))) { + if (mana.canProduce(MagicColor.toShortString(s))) { return true; } } diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index bbcc57a8d4b..422876c4428 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -358,12 +358,12 @@ public class CardFactory { // Name first so Senty has the Card name c.setName(face.getName()); + for (Entry v : face.getVariables()) c.setSVar(v.getKey(), v.getValue()); + for (String r : face.getReplacements()) c.addReplacementEffect(ReplacementHandler.parseReplacement(r, c, true)); for (String s : face.getStaticAbilities()) c.addStaticAbility(s); for (String t : face.getTriggers()) c.addTrigger(TriggerHandler.parseTrigger(t, c, true)); - for (Entry v : face.getVariables()) c.setSVar(v.getKey(), v.getValue()); - // keywords not before variables c.addIntrinsicKeywords(face.getKeywords(), false); 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 ad5c44f57e9..bcf466f0886 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -1475,7 +1475,7 @@ public class CardFactoryUtil { for (Card card : otb) { if (!card.isTapped() || !untappedOnly) { for (SpellAbility ma : card.getManaAbilities()) { - if (ma.getManaPart().canProduce(MagicColor.toShortString(color))) { + if (ma.canProduce(MagicColor.toShortString(color))) { uniqueColors++; continue outer; } diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 369eaab2527..b7ddb4e435d 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -911,7 +911,7 @@ public class CardProperty { } else if (property.startsWith("canProduceManaColor")) { final String color = property.split("canProduceManaColor ")[1]; for (SpellAbility ma : card.getManaAbilities()) { - if (ma.getManaPart().canProduce(MagicColor.toShortString(color))) { + if (ma.canProduce(MagicColor.toShortString(color))) { return true; } } diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index e1726689621..975c43a62cc 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -391,7 +391,6 @@ public final class CardUtil { final String colorOrType = sa.getParam("ColorOrType"); // currently Color or Type, Type is colors + colorless - final String validCard = sa.getParam("Valid"); final String reflectProperty = sa.getParam("ReflectProperty"); // Produce (Reflecting Pool) or Is (Meteor Crater) @@ -400,28 +399,30 @@ public final class CardUtil { maxChoices++; } - CardCollection cards = null; + CardCollection cards; - // Reuse AF_Defined in a slightly different way - if (validCard.startsWith("Defined.")) { - cards = AbilityUtils.getDefinedCards(card, TextUtil.fastReplace(validCard, "Defined.", ""), abMana); - } else { - if (sa.getActivatingPlayer() == null) { - sa.setActivatingPlayer(sa.getHostCard().getController()); + if (sa.hasParam("Valid")) { + final String validCard = sa.getParam("Valid"); + // Reuse AF_Defined in a slightly different way + if (validCard.startsWith("Defined.")) { + cards = AbilityUtils.getDefinedCards(card, TextUtil.fastReplace(validCard, "Defined.", ""), abMana); + } else { + if (sa.getActivatingPlayer() == null) { + sa.setActivatingPlayer(sa.getHostCard().getController()); + } + final Game game = sa.getActivatingPlayer().getGame(); + cards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), validCard, abMana.getActivatingPlayer(), card); } - final Game game = sa.getActivatingPlayer().getGame(); - cards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), validCard, abMana.getActivatingPlayer(), card); - } - // remove anything cards that is already in parents - for (final Card p : parents) { - cards.remove(p); - } + // remove anything cards that is already in parents + cards.removeAll(parents); - if ((cards.size() == 0) && !reflectProperty.equals("Produced")) { - return colors; + if (cards.isEmpty()) { + return colors; + } + } else { + cards = new CardCollection(); } - if (reflectProperty.equals("Is")) { // Meteor Crater for (final Card card1 : cards) { // For each card, go through all the colors and if the card is that color, add @@ -436,7 +437,7 @@ public final class CardUtil { } } else if (reflectProperty.equals("Produced")) { // Why is this name so similar to the one below? - final String producedColors = abMana instanceof AbilitySub ? (String) abMana.getRootAbility().getTriggeringObject(AbilityKey.Produced) : (String) abMana.getTriggeringObject(AbilityKey.Produced); + final String producedColors = (String) abMana.getRootAbility().getTriggeringObject(AbilityKey.Produced); for (final String col : MagicColor.Constant.ONLY_COLORS) { final String s = MagicColor.toShortString(col); if (producedColors.contains(s)) { @@ -469,7 +470,7 @@ public final class CardUtil { } continue; } - colors = canProduce(maxChoices, ab.getManaPart(), colors); + colors = canProduce(maxChoices, ab, colors); if (!parents.contains(ab.getHostCard())) { parents.add(ab.getHostCard()); } @@ -486,19 +487,18 @@ public final class CardUtil { return colors; } - public static Set canProduce(final int maxChoices, final AbilityManaPart ab, + public static Set canProduce(final int maxChoices, final SpellAbility sa, final Set colors) { - if (ab == null) { + if (sa == null) { return colors; } for (final String col : MagicColor.Constant.ONLY_COLORS) { - final String s = MagicColor.toShortString(col); - if (ab.canProduce(s)) { + if (sa.canProduce(MagicColor.toShortString(col))) { colors.add(col); } } - if (maxChoices == 6 && ab.canProduce("C")) { + if (maxChoices == 6 && sa.canProduce("C")) { colors.add(MagicColor.Constant.COLORLESS); } diff --git a/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java b/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java index 6ab3e39c5dd..c46309f38bd 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java +++ b/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java @@ -20,6 +20,8 @@ package forge.game.mana; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.IParserManaCost; @@ -105,11 +107,16 @@ public class ManaCostBeingPaid { xCount = copy.xCount; totalCount = copy.totalCount; } + + @Override + public String toString() { + return "{x=" + xCount + " total=" + totalCount + "}"; + } } // holds Mana_Part objects // ManaPartColor is stored before ManaPartGeneric - private final Map unpaidShards = new HashMap<>(); + private final Map unpaidShards = Maps.newHashMap(); private Map xManaCostPaidByColor; private final String sourceRestriction; private byte sunburstMap = 0; @@ -124,7 +131,7 @@ public class ManaCostBeingPaid { unpaidShards.put(m.getKey(), new ShardCount(m.getValue())); } if (manaCostBeingPaid.xManaCostPaidByColor != null) { - xManaCostPaidByColor = new HashMap<>(manaCostBeingPaid.xManaCostPaidByColor); + xManaCostPaidByColor = Maps.newHashMap(manaCostBeingPaid.xManaCostPaidByColor); } sourceRestriction = manaCostBeingPaid.sourceRestriction; sunburstMap = manaCostBeingPaid.sunburstMap; @@ -503,7 +510,7 @@ public class ManaCostBeingPaid { sc.xCount--; String color = MagicColor.toShortString(colorMask); if (xManaCostPaidByColor == null) { - xManaCostPaidByColor = new HashMap<>(); + xManaCostPaidByColor = Maps.newHashMap(); } Integer xColor = xManaCostPaidByColor.get(color); if (xColor == null) { @@ -602,19 +609,7 @@ public class ManaCostBeingPaid { } int nGeneric = getGenericManaAmount(); - List shards = new ArrayList<>(unpaidShards.keySet()); - - // TODO Fix this. Should we really be changing Shards here? - if (false && pool != null) { //replace shards with generic mana if they can be paid with any color mana - for (int i = 0; i < shards.size(); i++) { - ManaCostShard shard = shards.get(i); - if (shard != ManaCostShard.GENERIC && pool.getPossibleColorUses(shard.getColorMask()) == ManaAtom.ALL_MANA_TYPES) { - nGeneric += unpaidShards.get(shard).totalCount; - shards.remove(i); - i--; - } - } - } + List shards = Lists.newArrayList(unpaidShards.keySet()); if (nGeneric > 0) { if (nGeneric <= 20) { diff --git a/forge-game/src/main/java/forge/game/mana/ManaPool.java b/forge-game/src/main/java/forge/game/mana/ManaPool.java index 48cf1e038b4..013ab399c3a 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaPool.java +++ b/forge-game/src/main/java/forge/game/mana/ManaPool.java @@ -166,23 +166,27 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { owner.updateManaForView(); } - private void removeMana(final Mana mana) { - Collection cm = floatingMana.get(mana.getColor()); - if (cm.remove(mana)) { + private boolean removeMana(final Mana mana) { + if (floatingMana.remove(mana.getColor(), mana)) { owner.updateManaForView(); owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Removed, mana)); + return true; } + return false; } public final void payManaFromAbility(final SpellAbility saPaidFor, ManaCostBeingPaid manaCost, final SpellAbility saPayment) { // Mana restriction must be checked before this method is called final List paidAbs = saPaidFor.getPayingManaAbilities(); - AbilityManaPart abManaPart = saPayment.getManaPartRecursive(); paidAbs.add(saPayment); // assumes some part on the mana produced by the ability will get used - for (final Mana mana : abManaPart.getLastManaProduced()) { - if (tryPayCostWithMana(saPaidFor, manaCost, mana, false)) { - saPaidFor.getPayingMana().add(0, mana); + + // need to get all mana from all ManaAbilities of the SpellAbility + for (AbilityManaPart mp : saPayment.getAllManaParts()) { + for (final Mana mana : mp.getLastManaProduced()) { + if (tryPayCostWithMana(saPaidFor, manaCost, mana, false)) { + saPaidFor.getPayingMana().add(0, mana); + } } } } @@ -216,8 +220,12 @@ public class ManaPool extends ManaConversionMatrix implements Iterable { if (!manaCost.isNeeded(mana, this)) { return false; } + // only pay mana into manaCost when the Mana could be removed from the Mana pool + // if the mana wasn't in the mana pool then something is wrong + if (!removeMana(mana)) { + return false; + } manaCost.payMana(mana, this); - removeMana(mana); return true; } 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 c5a0287007f..71805528b9a 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -3528,4 +3528,11 @@ public class Player extends GameEntity implements Comparable { public void resetCycledThisTurn() { cycledThisTurn = 0; } + + public boolean hasUrzaLands() { + final CardCollectionView landsControlled = getCardsIn(ZoneType.Battlefield); + return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine"))) + && Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Power-Plant"))) + && Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Tower"))); + } } diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java b/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java index 5c95623af22..2c31c859b83 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java @@ -31,10 +31,10 @@ public class ReplaceProduceMana extends ReplacementEffect { */ @Override public boolean canReplace(Map runParams) { - //Check for tapping - if (!hasParam("NoTapCheck")) { + + if (hasParam("ValidAbility")) { final SpellAbility manaAbility = (SpellAbility) runParams.get(AbilityKey.AbilityMana); - if (manaAbility == null || !manaAbility.getRootAbility().getPayCosts().hasTapCost()) { + if (!matchesValid(manaAbility, getParam("ValidAbility").split(","), getHostCard())) { return false; } } @@ -43,15 +43,23 @@ public class ReplaceProduceMana extends ReplacementEffect { String full = getParam("ManaAmount"); String operator = full.substring(0, 2); String operand = full.substring(2); + int intoperand = AbilityUtils.calculateAmount(getHostCard(), operand, this); + int manaAmount = StringUtils.countMatches((String) runParams.get(AbilityKey.Mana), " ") + 1; if (!Expressions.compare(manaAmount, operator, intoperand)) { return false; } } + if (hasParam("ValidPlayer")) { + if (!matchesValid(runParams.get(AbilityKey.Player), getParam("ValidPlayer").split(","), getHostCard())) { + return false; + } + } + if (hasParam("ValidCard")) { - if (!matchesValid(runParams.get(AbilityKey.Affected), getParam("ValidCard").split(","), this.getHostCard())) { + if (!matchesValid(runParams.get(AbilityKey.Affected), getParam("ValidCard").split(","), getHostCard())) { return false; } } @@ -60,4 +68,7 @@ public class ReplaceProduceMana extends ReplacementEffect { } + public void setReplacingObjects(Map runParams, SpellAbility sa) { + sa.setReplacingObject(AbilityKey.Mana, runParams.get(AbilityKey.Mana)); + } } diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java index 06e6bdc95d4..d1aa184e48f 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java @@ -19,6 +19,7 @@ package forge.game.replacement; import forge.game.Game; import forge.game.TriggerReplacementBase; +import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.card.Card; @@ -271,4 +272,13 @@ public abstract class ReplacementEffect extends TriggerReplacementBase { void setMode(ReplacementType mode) { this.mode = mode; } + + public SpellAbility ensureAbility() { + SpellAbility sa = getOverridingAbility(); + if (sa == null && hasParam("ReplaceWith")) { + sa = AbilityFactory.getAbility(getHostCard(), getParam("ReplaceWith")); + setOverridingAbility(sa); + } + return sa; + } } diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java index 9842d8ca356..8215153e57e 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java @@ -17,7 +17,6 @@ */ package forge.game.replacement; -import forge.card.MagicColor; import forge.game.Game; import forge.game.GameLogEntryType; import forge.game.ability.AbilityFactory; @@ -259,12 +258,17 @@ public class ReplacementHandler { chosenRE.setHasRun(false); hasRun.remove(chosenRE); chosenRE.setOtherChoices(null); - String message = chosenRE.getDescription(); - if ( !StringUtils.isEmpty(message)) - if (chosenRE.getHostCard() != null) { - message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName()); - } - game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); + + // Updated Replacements need to be logged elsewhere because its otherwise in the wrong order + if (res != ReplacementResult.Updated) { + String message = chosenRE.getDescription(); + if ( !StringUtils.isEmpty(message)) + if (chosenRE.getHostCard() != null) { + message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName()); + } + game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message); + } + return res; } @@ -344,25 +348,11 @@ public class ReplacementHandler { Player player = host.getController(); - if (mapParams.containsKey("ManaReplacement")) { - final SpellAbility manaAb = (SpellAbility) runParams.get(AbilityKey.AbilityMana); - final Player player1 = (Player) runParams.get(AbilityKey.Player); - final String rep = (String) runParams.get(AbilityKey.Mana); - // Replaced mana type - final Card repHost = host; - String repType = repHost.getSVar(mapParams.get("ManaReplacement")); - if (repType.contains("Chosen") && repHost.hasChosenColor()) { - repType = TextUtil.fastReplace(repType, "Chosen", MagicColor.toShortString(repHost.getChosenColor())); - } - manaAb.getManaPart().setManaReplaceType(repType); - manaAb.getManaPart().produceMana(rep, player1, manaAb); - } else { - player.getController().playSpellAbilityNoStack(effectSA, true); - // if the spellability is a replace effect then its some new logic - // if ReplacementResult is set in run params use that instead - if (runParams.containsKey(AbilityKey.ReplacementResult)) { - return (ReplacementResult) runParams.get(AbilityKey.ReplacementResult); - } + player.getController().playSpellAbilityNoStack(effectSA, true); + // if the spellability is a replace effect then its some new logic + // if ReplacementResult is set in run params use that instead + if (runParams.containsKey(AbilityKey.ReplacementResult)) { + return (ReplacementResult) runParams.get(AbilityKey.ReplacementResult); } return ReplacementResult.Replaced; @@ -407,6 +397,10 @@ public class ReplacementHandler { ret.setActiveZone(EnumSet.copyOf(ZoneType.listValueOf(activeZones))); } + if (mapParams.containsKey("ReplaceWith")) { + ret.setOverridingAbility(AbilityFactory.getAbility(host, mapParams.get("ReplaceWith"), ret)); + } + return ret; } } 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 9f9b4901fd2..9bb1ff3dd0e 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -18,15 +18,18 @@ package forge.game.spellability; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; + import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.ManaAtom; import forge.card.mana.ManaCostShard; +import forge.game.Game; import forge.game.GameActionUtil; import forge.game.ability.AbilityKey; +import forge.game.ability.ApiType; import forge.game.card.Card; +import forge.game.card.CardUtil; import forge.game.mana.Mana; import forge.game.mana.ManaPool; import forge.game.player.Player; @@ -39,8 +42,6 @@ import org.apache.commons.lang3.StringUtils; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** *

@@ -57,6 +58,7 @@ public class AbilityManaPart implements java.io.Serializable { private final String origProduced; private String lastExpressChoice = ""; private final String manaRestrictions; + private String extraManaRestrictions = ""; private final String cannotCounterSpell; private final String addsKeywords; private final String addsKeywordsType; @@ -64,7 +66,6 @@ public class AbilityManaPart implements java.io.Serializable { private final String addsCounters; private final String triggersWhenSpent; private final boolean persistentMana; - private String manaReplaceType; private transient List lastManaProduced = Lists.newArrayList(); @@ -94,7 +95,6 @@ public class AbilityManaPart implements java.io.Serializable { this.addsCounters = params.get("AddsCounters"); this.triggersWhenSpent = params.get("TriggersWhenSpent"); this.persistentMana = (null != params.get("PersistentMana")) && "True".equalsIgnoreCase(params.get("PersistentMana")); - this.manaReplaceType = params.containsKey("ManaReplaceType") ? params.get("ManaReplaceType") : ""; } /** @@ -121,14 +121,26 @@ public class AbilityManaPart implements java.io.Serializable { public final void produceMana(final String produced, final Player player, SpellAbility sa) { final Card source = this.getSourceCard(); final ManaPool manaPool = player.getManaPool(); - String afterReplace = applyManaReplacement(sa, produced); + String afterReplace = produced; + + SpellAbility root = sa == null ? null : sa.getRootAbility(); + final Map repParams = AbilityKey.mapFromAffected(source); - repParams.put(AbilityKey.Mana, produced); + repParams.put(AbilityKey.Mana, afterReplace); repParams.put(AbilityKey.Player, player); - repParams.put(AbilityKey.AbilityMana, sa); - if (player.getGame().getReplacementHandler().run(ReplacementType.ProduceMana, repParams) != ReplacementResult.NotReplaced) { + repParams.put(AbilityKey.AbilityMana, root); + repParams.put(AbilityKey.Activator, root == null ? null : root.getActivatingPlayer()); + + switch (player.getGame().getReplacementHandler().run(ReplacementType.ProduceMana, repParams)) { + case NotReplaced: + break; + case Updated: + afterReplace = (String) repParams.get(AbilityKey.Mana); + break; + default: return; } + //clear lastProduced this.lastManaProduced.clear(); @@ -154,14 +166,14 @@ public class AbilityManaPart implements java.io.Serializable { // Run triggers final Map runParams = AbilityKey.mapFromCard(source); runParams.put(AbilityKey.Player, player); - runParams.put(AbilityKey.AbilityMana, sa); runParams.put(AbilityKey.Produced, afterReplace); + runParams.put(AbilityKey.AbilityMana, root); + runParams.put(AbilityKey.Activator, root == null ? null : root.getActivatingPlayer()); + player.getGame().getTriggerHandler().runTrigger(TriggerType.TapsForMana, runParams, false); - if (source.isLand()) { - player.setTappedLandForManaThisTurn(true); + if (source.isLand() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost() ) { + player.setTappedLandForManaThisTurn(true); } - // Clear Mana replacement - this.manaReplaceType = ""; } // end produceMana(String) /** @@ -256,7 +268,15 @@ public class AbilityManaPart implements java.io.Serializable { * @return a {@link java.lang.String} object. */ public String getManaRestrictions() { - return this.manaRestrictions; + return manaRestrictions; + } + + public void setExtraManaRestriction(String str) { + this.extraManaRestrictions = str; + } + + public boolean meetsManaRestrictions(final SpellAbility sa) { + return meetsManaRestrictions(sa, this.manaRestrictions) && meetsManaRestrictions(sa, this.extraManaRestrictions); } /** @@ -268,14 +288,14 @@ public class AbilityManaPart implements java.io.Serializable { * a {@link forge.game.spellability.SpellAbility} object. * @return a boolean. */ - public boolean meetsManaRestrictions(final SpellAbility sa) { + public boolean meetsManaRestrictions(final SpellAbility sa, String restrictions) { // No restrictions - if (this.manaRestrictions.isEmpty()) { + if (restrictions.isEmpty()) { return true; } // Loop over restrictions - for (String restriction : this.manaRestrictions.split(",")) { + for (String restriction : restrictions.split(",")) { if (restriction.equals("nonSpell")) { return !sa.isSpell(); } @@ -306,9 +326,9 @@ public class AbilityManaPart implements java.io.Serializable { if (sa.isValid(restriction, this.getSourceCard().getController(), this.getSourceCard(), null)) { return true; } - + if (restriction.equals("CantPayGenericCosts")) { - return true; + return true; } if (sa.isAbility()) { @@ -331,7 +351,7 @@ public class AbilityManaPart implements java.io.Serializable { return false; } - + /** *

* meetsManaShardRestrictions. @@ -340,44 +360,44 @@ public class AbilityManaPart implements java.io.Serializable { * @param shard * a {@link forge.card.mana.ManaCostShard} object. * @param color - * the color of mana being paid + * the color of mana being paid * @return a boolean. */ public boolean meetsManaShardRestrictions(final ManaCostShard shard, final byte color) { - if (this.manaRestrictions.isEmpty()) { + if (this.manaRestrictions.isEmpty()) { return true; } for (String restriction : this.manaRestrictions.split(",")) { - if (restriction.equals("CantPayGenericCosts")) { - if (shard.isGeneric()) { - if (shard.isOr2Generic() && shard.isColor(color)) { - continue; - } else { - return false; - } - } else { - continue; - } - } + if (restriction.equals("CantPayGenericCosts")) { + if (shard.isGeneric()) { + if (shard.isOr2Generic() && shard.isColor(color)) { + continue; + } else { + return false; + } + } else { + continue; + } + } } return true; } - + /** *

* meetsSpellAndShardRestrictions. *

- * + * * @param sa * a {@link forge.game.spellability.SpellAbility} object. * @param shard * a {@link forge.card.mana.ManaCostShard} object. * @param color - * the color of mana being paid + * the color of mana being paid * @return a boolean. */ public boolean meetsSpellAndShardRestrictions(final SpellAbility sa, final ManaCostShard shard, final byte color) { - return this.meetsManaRestrictions(sa) && this.meetsManaShardRestrictions(shard, color); + return this.meetsManaRestrictions(sa) && this.meetsManaShardRestrictions(shard, color); } /** @@ -465,10 +485,6 @@ public class AbilityManaPart implements java.io.Serializable { return this.getOrigProduced().contains("Special"); } - public final boolean canProduce(final String s) { - return canProduce(s, null); - } - /** *

* canProduce. @@ -493,24 +509,9 @@ public class AbilityManaPart implements java.io.Serializable { if (isComboMana()) { return getComboColors().contains(s); } - if (sa != null) { - return applyManaReplacement(sa, origProduced).contains(s); - } return origProduced.contains(s); } - /** - *

- * isBasic. - *

- * - * @return a boolean. - */ - public final boolean isBasic() { - return this.getOrigProduced().length() == 1 || this.getOrigProduced().contains("Any") - || this.getOrigProduced().contains("Chosen"); - } - /** {@inheritDoc} */ @Override public final boolean equals(final Object o) { @@ -586,81 +587,59 @@ public class AbilityManaPart implements java.io.Serializable { return this.persistentMana; } - /** - * @return the manaReplaceType - */ - public String getManaReplaceType() { - return manaReplaceType; - } + boolean abilityProducesManaColor(final SpellAbility am, final byte neededColor) { + if (0 != (neededColor & ManaAtom.GENERIC)) { + return true; + } - /** - * setManaReplaceType. - */ - public void setManaReplaceType(final String type) { - this.manaReplaceType = type; - } - /** - *

- * applyManaReplacement. - *

- * @return a String - */ - public static String applyManaReplacement(final SpellAbility sa, final String original) { - final Map repMap = Maps.newHashMap(); - final Player act = sa != null ? sa.getActivatingPlayer() : null; - final String manaReplace = sa != null ? sa.getManaPart().getManaReplaceType(): ""; - if (manaReplace.isEmpty()) { - if (act != null && act.getLandsPlayedThisTurn() > 0 && sa.hasParam("ReplaceIfLandPlayed")) { - return sa.getParam("ReplaceIfLandPlayed"); - } - return original; + if (isAnyMana()) { + return true; } - if (manaReplace.startsWith("Any")) { - // Replace any type and amount - String replaced = manaReplace.split("->")[1]; - if (replaced.equals("Any")) { - byte rs = MagicColor.GREEN; - if (act != null) { - rs = act.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS); + + // check for produce mana replacement effects - they mess this up, so just use the mana ability + final Card source = am.getHostCard(); + final Player activator = am.getActivatingPlayer(); + final Game g = source.getGame(); + final Map repParams = AbilityKey.newMap(); + repParams.put(AbilityKey.Mana, getOrigProduced()); + repParams.put(AbilityKey.Affected, source); + repParams.put(AbilityKey.Player, activator); + repParams.put(AbilityKey.AbilityMana, am.getRootAbility()); + + for (final Player p : g.getPlayers()) { + for (final Card crd : p.getAllCards()) { + for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) { + if (replacementEffect.requirementsCheck(g) + && replacementEffect.getMode() == ReplacementType.ProduceMana + && replacementEffect.canReplace(repParams) + && replacementEffect.zonesCheck(g.getZoneOf(crd))) { + return true; + } } - replaced = MagicColor.toShortString(rs); - } - return replaced; - } - final Pattern splitter = Pattern.compile("->"); - // Replace any type - for (String part : manaReplace.split(" & ")) { - final String[] v = splitter.split(part, 2); - // TODO Colorless mana replacement is probably different now? - if (v[0].equals("Colorless")) { - repMap.put("[0-9][0-9]?", v.length > 1 ? v[1].trim() : ""); - } else { - repMap.put(v[0], v.length > 1 ? v[1].trim() : ""); } } - // Handle different replacement simultaneously - Pattern pattern = Pattern.compile(StringUtils.join(repMap.keySet().iterator(), "|")); - Matcher m = pattern.matcher(original); - StringBuffer sb = new StringBuffer(); - while(m.find()) { - if (m.group().matches("[0-9][0-9]?")) { - final String rep = StringUtils.repeat(repMap.get("[0-9][0-9]?") + " ", - Integer.parseInt(m.group())).trim(); - m.appendReplacement(sb, rep); - } else { - m.appendReplacement(sb, repMap.get(m.group())); + + if (am.getApi() == ApiType.ManaReflected) { + final Iterable reflectableColors = CardUtil.getReflectableManaColors(am); + for (final String color : reflectableColors) { + if (0 != (neededColor & ManaAtom.fromName(color))) { + return true; + } } } - m.appendTail(sb); - String replaced = sb.toString(); - while (replaced.contains("Any")) { - byte rs = MagicColor.GREEN; - if (act != null) { - rs = act.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS); + else { + // treat special mana if it always can be paid + if (isSpecialMana()) { + return true; + } + String colorsProduced = isComboMana() ? getComboColors() : mana(); + for (final String color : colorsProduced.split(" ")) { + if (0 != (neededColor & ManaAtom.fromName(color))) { + return true; + } } - replaced = replaced.replaceFirst("Any", MagicColor.toShortString(rs)); } - return replaced; + return false; } } // end class AbilityMana diff --git a/forge-game/src/main/java/forge/game/spellability/AbilitySub.java b/forge-game/src/main/java/forge/game/spellability/AbilitySub.java index a426e23bf9f..c83e23e714d 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilitySub.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilitySub.java @@ -20,10 +20,6 @@ package forge.game.spellability; import forge.game.ability.AbilityFactory; import forge.game.ability.ApiType; import forge.game.ability.SpellAbilityEffect; -import forge.game.ability.effects.ChangeZoneAllEffect; -import forge.game.ability.effects.ChangeZoneEffect; -import forge.game.ability.effects.ManaEffect; -import forge.game.ability.effects.ManaReflectedEffect; import forge.game.card.Card; import forge.game.cost.Cost; @@ -92,11 +88,11 @@ public final class AbilitySub extends SpellAbility implements java.io.Serializab effect = api.getSpellEffect(); - if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) { + if (api.equals(ApiType.Mana) || api.equals(ApiType.ManaReflected)) { this.setManaPart(new AbilityManaPart(ca, mapParams)); } - if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) { + if (api.equals(ApiType.ChangeZone) || api.equals(ApiType.ChangeZoneAll)) { AbilityFactory.adjustChangeZoneTarget(mapParams, this); } } 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 8c522d60d0c..c48267ac682 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -237,19 +237,101 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit view.updateDescription(this); //description can change if host card does } + public boolean canThisProduce(final String s) { + AbilityManaPart mp = getManaPart(); + if (mp != null && metConditions() && mp.canProduce(s, this)) { + return true; + } + return false; + } + + public boolean canProduce(final String s) { + if (canThisProduce(s)) { + return true; + } + + return this.subAbility != null ? this.subAbility.canProduce(s) : false; + } + + public boolean isManaAbilityFor(SpellAbility saPaidFor, byte colorNeeded) { + // is root ability + if (this.getParent() == null) { + if (!canPlay()) { + return false; + } + if (isAbility() && getRestrictions().isInstantSpeed()) { + return false; + } + } + + AbilityManaPart mp = getManaPart(); + if (mp != null && metConditions() && mp.meetsManaRestrictions(saPaidFor) && mp.abilityProducesManaColor(this, colorNeeded)) { + return true; + } + return this.subAbility != null ? this.subAbility.isManaAbilityFor(saPaidFor, colorNeeded) : false; + } + + public boolean isManaCannotCounter(SpellAbility saPaidFor) { + AbilityManaPart mp = getManaPart(); + if (mp != null && metConditions() && mp.meetsManaRestrictions(saPaidFor) && mp.cannotCounterPaidWith(saPaidFor)) { + return true; + } + return this.subAbility != null ? this.subAbility.isManaCannotCounter(saPaidFor) : false; + } + + public int amountOfManaGenerated(boolean multiply) { + int result = 0; + AbilityManaPart mp = getManaPart(); + if (mp != null && metConditions()) { + int amount = hasParam("Amount") ? AbilityUtils.calculateAmount(getHostCard(), getParam("Amount"), this) : 1; + + if (!multiply || mp.isAnyMana() || mp.isComboMana() || mp.isSpecialMana()) { + result += amount; + } else { + // For cards that produce like {C}{R} vs cards that produce {R}{R}. + result += mp.mana().split(" ").length * amount; + } + } + return result; + } + + public int totalAmountOfManaGenerated(SpellAbility saPaidFor, boolean multiply) { + int result = 0; + AbilityManaPart mp = getManaPart(); + if (mp != null && metConditions() && mp.meetsManaRestrictions(saPaidFor)) { + result += amountOfManaGenerated(multiply); + } + result += subAbility != null ? subAbility.totalAmountOfManaGenerated(saPaidFor, multiply) : 0; + return result; + } + + public void setManaExpressChoice(ColorSet cs) { + AbilityManaPart mp = getManaPart(); + if (mp != null) { + mp.setExpressChoice(cs); + } + if (subAbility != null) { + subAbility.setManaExpressChoice(cs); + } + } + public final AbilityManaPart getManaPart() { return manaPart; } - public final AbilityManaPart getManaPartRecursive() { - SpellAbility tail = this; - while (tail != null) { - if (tail.manaPart != null) { - return tail.manaPart; - } - tail = tail.getSubAbility(); + public final List getAllManaParts() { + AbilityManaPart mp = getManaPart(); + if (mp == null && subAbility == null) { + return ImmutableList.of(); } - return null; + List result = Lists.newArrayList(); + if (mp != null) { + result.add(mp); + } + if (subAbility != null) { + result.addAll(subAbility.getAllManaParts()); + } + return result; } public final boolean isManaAbility() { @@ -266,7 +348,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return false; } - return getManaPartRecursive() != null; + SpellAbility tail = this; + while (tail != null) { + if (tail.manaPart != null) { + return true; + } + tail = tail.getSubAbility(); + } + return false; } protected final void setManaPart(AbilityManaPart manaPart0) { @@ -464,6 +553,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit conditions = condition; } + public boolean metConditions() { + return getConditions() != null && getConditions().areMet(this); + } + public List getPayingMana() { return payingMana; } @@ -1880,9 +1973,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public boolean tracksManaSpent() { if (hostCard == null || hostCard.getRules() == null) { return false; } - if (hostCard.hasKeyword(Keyword.SUNBURST)) { + if (isSpell() && hostCard.hasConverge()) { return true; } + String text = hostCard.getRules().getOracleText(); if (isSpell() && text.contains("was spent to cast")) { return true; @@ -2019,7 +2113,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit String mana = manaPart.mana(); if (!mana.equals("Any")) { score += mana.length(); - if (!manaPart.canProduce("C")) { + if (!canProduce("C")) { // Producing colorless should produce a slightly lower score score += 1; } diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java index 16d9ab638ea..ab6f39712d2 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java @@ -169,13 +169,7 @@ public class TriggerHandler { if (wt.getTriggers() != null) continue; - List trigger = Lists.newArrayList(); - for (final Trigger t : activeTriggers) { - if (canRunTrigger(t,wt.getMode(),wt.getParams())) { - trigger.add(t); - } - } - wt.setTriggers(trigger); + wt.setTriggers(getActiveTrigger(wt.getMode(), wt.getParams())); } } @@ -678,4 +672,14 @@ public class TriggerHandler { return n; } + + public List getActiveTrigger(final TriggerType mode, final Map runParams) { + List trigger = Lists.newArrayList(); + for (final Trigger t : activeTriggers) { + if (canRunTrigger(t, mode, runParams)) { + trigger.add(t); + } + } + return trigger; + } } diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java b/forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java index 148191feb75..f7895ba385e 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerTapsForMana.java @@ -20,7 +20,6 @@ package forge.game.trigger; import forge.card.MagicColor; import forge.game.ability.AbilityKey; import forge.game.card.Card; -import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.util.Localizer; @@ -68,25 +67,19 @@ public class TriggerTapsForMana extends Trigger { } if (hasParam("ValidCard")) { - final Card tapper = (Card) runParams.get(AbilityKey.Card); - if (!tapper.isValid(getParam("ValidCard").split(","), this.getHostCard().getController(), - this.getHostCard(), null)) { + if (!matchesValid(runParams.get(AbilityKey.Card), getParam("ValidCard").split(","), getHostCard())) { return false; } } if (hasParam("Player")) { - final Player player = (Player) runParams.get(AbilityKey.Player); - if (!player.isValid(getParam("Player").split(","), this.getHostCard().getController(), this.getHostCard(), null)) { + if (!matchesValid(runParams.get(AbilityKey.Player), getParam("Player").split(","), getHostCard())) { return false; } } if (hasParam("Activator")) { - final SpellAbility sa = (SpellAbility) runParams.get(AbilityKey.AbilityMana); - if (sa == null) return false; - final Player activator = sa.getActivatingPlayer(); - if (!activator.isValid(getParam("Activator").split(","), this.getHostCard().getController(), this.getHostCard(), null)) { + if (!matchesValid(runParams.get(AbilityKey.Activator), getParam("Activator").split(","), getHostCard())) { return false; } } @@ -113,7 +106,7 @@ public class TriggerTapsForMana extends Trigger { /** {@inheritDoc} */ @Override public final void setTriggeringObjects(final SpellAbility sa, Map runParams) { - sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card, AbilityKey.Player, AbilityKey.Produced); + sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card, AbilityKey.Player, AbilityKey.Produced, AbilityKey.Activator); } @Override diff --git a/forge-gui/res/cardsfolder/a/a_display_of_my_dark_power.txt b/forge-gui/res/cardsfolder/a/a_display_of_my_dark_power.txt index 07e13d171dc..37bb8929def 100644 --- a/forge-gui/res/cardsfolder/a/a_display_of_my_dark_power.txt +++ b/forge-gui/res/cardsfolder/a/a_display_of_my_dark_power.txt @@ -4,6 +4,5 @@ Types:Scheme T:Mode$ SetInMotion | ValidCard$ Card.Self | Execute$ DarkEffect | TriggerZones$ Command | TriggerDescription$ When you set this scheme in motion, until your next turn, whenever a player taps a land for mana, that player adds one mana of any type that land produced. SVar:DarkEffect:DB$ Effect | Name$ Dark Power Scheme | Duration$ UntilYourNextTurn | Triggers$ DarkPower | SVars$ DarkMana SVar:DarkPower:Mode$ TapsForMana | ValidCard$ Land | Execute$ DarkMana | TriggerZones$ Command | Static$ True | TriggerDescription$ Whenever a player taps a land for mana, that player adds one mana of any type that land produced. -SVar:DarkMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer -SVar:Picture:https://downloads.cardforge.org/images/cards/ARC/A Display of My Dark Power.full.jpg +SVar:DarkMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator Oracle:When you set this scheme in motion, until your next turn, whenever a player taps a land for mana, that player adds one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/c/caged_sun.txt b/forge-gui/res/cardsfolder/c/caged_sun.txt index 75e0d4f3152..c4021dbd298 100644 --- a/forge-gui/res/cardsfolder/c/caged_sun.txt +++ b/forge-gui/res/cardsfolder/c/caged_sun.txt @@ -4,7 +4,6 @@ Types:Artifact K:ETBReplacement:Other:ChooseColor SVar:ChooseColor:DB$ ChooseColor | Defined$ You | AILogic$ MostProminentInComputerDeck | SpellDescription$ As CARDNAME enters the battlefield, choose a color. S:Mode$ Continuous | Affected$ Creature.ChosenColor+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Creatures you control of the chosen color get +1/+1. -T:Mode$ TapsForMana | ValidCard$ Land | Produced$ ChosenColor | NoTapCheck$ True | Player$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a land's ability adds one or more mana of the chosen color, add one additional mana of that color. -SVar:TrigMana:DB$ Mana | Produced$ Chosen | Amount$ 1 | Defined$ TriggeredPlayer -SVar:Picture:http://www.wizards.com/global/images/magic/general/caged_sun.jpg -Oracle:As Caged Sun enters the battlefield, choose a color.\nCreatures you control of the chosen color get +1/+1.\nWhenever a land's ability adds one or more mana of the chosen color, add one additional mana of that color. +T:Mode$ TapsForMana | ValidCard$ Land | Produced$ ChosenColor | NoTapCheck$ True | Player$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a land’s ability causes you to add one or more mana of the chosen color, add one additional mana of that color. +SVar:TrigMana:DB$ Mana | Produced$ Chosen | Amount$ 1 | Defined$ You +Oracle:As Caged Sun enters the battlefield, choose a color.\nCreatures you control of the chosen color get +1/+1.\nWhenever a land’s ability causes you to add one or more mana of the chosen color, add one additional mana of that color. diff --git a/forge-gui/res/cardsfolder/c/chaos_moon.txt b/forge-gui/res/cardsfolder/c/chaos_moon.txt index 40d9cca6867..3b116e19969 100644 --- a/forge-gui/res/cardsfolder/c/chaos_moon.txt +++ b/forge-gui/res/cardsfolder/c/chaos_moon.txt @@ -7,8 +7,8 @@ SVar:DBEffect:DB$ Effect | ReplacementEffects$ RepCurse | SVars$ ProduceColorles SVar:TrigRamp:Mode$ TapsForMana | ValidCard$ Mountain | Execute$ TrigMana | Static$ True | TriggerZones$ Command | TriggerDescription$ Whenever a player taps a Mountain for mana, that player adds {R}. SVar:TrigMana:DB$ Mana | Produced$ R | Amount$ 1 | Defined$ TriggeredCardController SVar:STPump:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature.Red | AddPower$ 1 | AddToughness$ 1 -SVar:RepCurse:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Mountain | ManaReplacement$ ProduceColorless | Description$ If a player taps a Mountain for mana, that Mountain produces colorless mana instead of any other type. -SVar:ProduceColorless:R->1 & B->1 & U->1 & G->1 & W->1 +SVar:RepCurse:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Mountain | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceColorless | Description$ If a player taps a Mountain for mana, that Mountain produces colorless mana instead of any other type. +SVar:ProduceColorless:DB$ ReplaceMana | ReplaceType$ C SVar:STCurse:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature.Red | AddPower$ -1 | AddToughness$ -1 SVar:X:Count$Valid Permanent AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/c/contamination.txt b/forge-gui/res/cardsfolder/c/contamination.txt index 06d4eadc4b9..96f611b217e 100644 --- a/forge-gui/res/cardsfolder/c/contamination.txt +++ b/forge-gui/res/cardsfolder/c/contamination.txt @@ -2,9 +2,8 @@ Name:Contamination ManaCost:2 B Types:Enchantment K:UpkeepCost:Sac<1/Creature> -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ManaReplacement$ ProduceB | Description$ If a land is tapped for mana, it produces {B} instead of any other type and amount. -SVar:ProduceB:Any->B +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ValidAbility$ Activated.hasTapCost | 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 SVar:NonStackingEffect:True -SVar:Picture:http://www.wizards.com/global/images/magic/general/contamination.jpg Oracle:At the beginning of your upkeep, sacrifice Contamination unless you sacrifice a creature.\nIf a land is tapped for mana, it produces {B} instead of any other type and amount. diff --git a/forge-gui/res/cardsfolder/c/crypt_ghast.txt b/forge-gui/res/cardsfolder/c/crypt_ghast.txt index 2ec4975bfc3..6ea99a1860b 100644 --- a/forge-gui/res/cardsfolder/c/crypt_ghast.txt +++ b/forge-gui/res/cardsfolder/c/crypt_ghast.txt @@ -3,7 +3,6 @@ ManaCost:3 B Types:Creature Spirit PT:2/2 K:Extort -T:Mode$ TapsForMana | ValidCard$ Swamp.YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a Swamp for mana, add an additional {B}. -SVar:TrigMana:DB$Mana | Produced$ B | Amount$ 1 -SVar:Picture:http://www.wizards.com/global/images/magic/general/crypt_ghast.jpg +T:Mode$ TapsForMana | ValidCard$ Swamp | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a Swamp for mana, add an additional {B}. +SVar:TrigMana:DB$ Mana | Produced$ B | Amount$ 1 Oracle:Extort (Whenever you cast a spell, you may pay {W/B}. If you do, each opponent loses 1 life and you gain that much life.)\nWhenever you tap a Swamp for mana, add an additional {B}. diff --git a/forge-gui/res/cardsfolder/d/damping_sphere.txt b/forge-gui/res/cardsfolder/d/damping_sphere.txt index 1674dfb13e2..92bdb8cb095 100644 --- a/forge-gui/res/cardsfolder/d/damping_sphere.txt +++ b/forge-gui/res/cardsfolder/d/damping_sphere.txt @@ -1,10 +1,9 @@ Name:Damping Sphere ManaCost:2 Types:Artifact -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ManaAmount$ GE2 | ManaReplacement$ ProduceC | Description$ If a land is tapped for two or more mana, it produces {C} instead of any other type and amount. -SVar:ProduceC:Any->C +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ValidAbility$ Activated.hasTapCost | ManaAmount$ GE2 | ReplaceWith$ ProduceC | Description$ If a land is tapped for two or more mana, it produces {C} instead of any other type and amount. +SVar:ProduceC:DB$ ReplaceMana | ReplaceMana$ C S:Mode$ RaiseCost | Activator$ Player | Type$ Spell | Amount$ X | AffectedAmount$ True | Description$ Each spell a player casts costs {1} more to cast for each other spell that player has cast this turn. SVar:X:Count$ThisTurnCast_Card.YouCtrl SVar:NonStackingEffect:True -SVar:Picture:http://www.wizards.com/global/images/magic/general/damping_sphere.jpg Oracle:If a land is tapped for two or more mana, it produces {C} instead of any other type and amount.\nEach spell a player casts costs {1} more to cast for each other spell that player has cast this turn. diff --git a/forge-gui/res/cardsfolder/d/deep_water.txt b/forge-gui/res/cardsfolder/d/deep_water.txt index 7aeeaa6a777..244889a5aef 100644 --- a/forge-gui/res/cardsfolder/d/deep_water.txt +++ b/forge-gui/res/cardsfolder/d/deep_water.txt @@ -2,9 +2,8 @@ Name:Deep Water ManaCost:U U Types:Enchantment A:AB$ Effect | Cost$ U | ReplacementEffects$ ReplaceU | SVars$ ProduceU | SpellDescription$ Until end of turn, if you tap a land you control for mana, it produces {U} instead of any other type. -SVar:ReplaceU:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land.YouCtrl | ManaReplacement$ ProduceU | Description$ If you tap a land you control for mana, it produces U instead of any other type. -SVar:ProduceU:C->U & B->U & R->U & G->U & W->U +SVar:ReplaceU:Event$ ProduceMana | ActiveZones$ Command | ValidPlayer$ You | ValidCard$ Land.YouCtrl | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceU | Description$ If you tap a land you control for mana, it produces U instead of any other type. +SVar:ProduceU:DB$ ReplaceMana | ReplaceType$ U AI:RemoveDeck:All AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/deep_water.jpg Oracle:{U}: Until end of turn, if you tap a land you control for mana, it produces {U} instead of any other type. diff --git a/forge-gui/res/cardsfolder/d/dictate_of_karametra.txt b/forge-gui/res/cardsfolder/d/dictate_of_karametra.txt index 59cbfac0bff..de07863d915 100644 --- a/forge-gui/res/cardsfolder/d/dictate_of_karametra.txt +++ b/forge-gui/res/cardsfolder/d/dictate_of_karametra.txt @@ -3,6 +3,5 @@ ManaCost:3 G G Types:Enchantment K:Flash T:Mode$ TapsForMana | ValidCard$ Land | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a player taps a land for mana, that player adds one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer -SVar:Picture:http://www.wizards.com/global/images/magic/general/dictate_of_karametra.jpg +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator Oracle:Flash\nWhenever a player taps a land for mana, that player adds one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/d/doubling_cube.txt b/forge-gui/res/cardsfolder/d/doubling_cube.txt index 1f505f864dc..f25110460ba 100644 --- a/forge-gui/res/cardsfolder/d/doubling_cube.txt +++ b/forge-gui/res/cardsfolder/d/doubling_cube.txt @@ -1,7 +1,6 @@ Name:Doubling Cube ManaCost:2 Types:Artifact -A:AB$ Mana | Cost$ 3 T | DoubleManaInPool$ True | ProduceNoOtherMana$ True | SpellDescription$ Double the amount of each type of unspent mana you have. +A:AB$ Mana | Cost$ 3 T | Produced$ Special DoubleManaInPool | SpellDescription$ Double the amount of each type of unspent mana you have. AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/doubling_cube.jpg Oracle:{3}, {T}: Double the amount of each type of unspent mana you have. diff --git a/forge-gui/res/cardsfolder/e/eloren_wilds.txt b/forge-gui/res/cardsfolder/e/eloren_wilds.txt index 5493db87546..e0d31fe71b5 100644 --- a/forge-gui/res/cardsfolder/e/eloren_wilds.txt +++ b/forge-gui/res/cardsfolder/e/eloren_wilds.txt @@ -2,7 +2,7 @@ Name:Eloren Wilds ManaCost:no cost Types:Plane Shandalar T:Mode$ TapsForMana | ValidCard$ Permanent | Execute$ TrigMana | TriggerZones$ Command | Static$ True | TriggerDescription$ Whenever a player taps a permanent for mana, that player adds one mana of any type that permanent produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, target player can't cast spells until a player planeswalks. SVar:RolledChaos:DB$ Effect | ValidTgts$ Player | IsCurse$ True | Name$ Eloren Wilds Effect | StaticAbilities$ STCantCast | Triggers$ TrigPlaneswalk | SVars$ ExileSelf | RememberObjects$ Targeted | Duration$ Permanent SVar:STCantCast:Mode$ CantBeCast | EffectZone$ Command | ValidCard$ Card | Caster$ Player.IsRemembered | Description$ Target player can't cast spells until a player planeswalks. diff --git a/forge-gui/res/cardsfolder/e/extraplanar_lens.txt b/forge-gui/res/cardsfolder/e/extraplanar_lens.txt index cd725a4329f..c50b4ec94de 100644 --- a/forge-gui/res/cardsfolder/e/extraplanar_lens.txt +++ b/forge-gui/res/cardsfolder/e/extraplanar_lens.txt @@ -4,12 +4,11 @@ Types:Artifact T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigExile | OptionalDecider$ You | TriggerDescription$ Imprint — When CARDNAME enters the battlefield, you may exile target land you control. SVar:TrigExile:DB$ ChangeZone | Imprint$ True | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Land.YouCtrl | TgtPrompt$ Select a target land you control | AILogic$ ExtraplanarLens T:Mode$ TapsForMana | ValidCard$ Land.sharesNameWith Imprinted | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a land with the same name as the exiled card is tapped for mana, its controller adds one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | Valid$ Defined.Triggered | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredPlayer +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredCardController T:Mode$ ChangesZone | Origin$ Battlefield | ValidCard$ Card.Self | Destination$ Any | Execute$ DBCleanup | Static$ True SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True T:Mode$ ChangesZone | ValidCard$ Card.IsImprinted+ExiledWithSource | Origin$ Exile | Execute$ DBForget | Static$ True SVar:DBForget:DB$ Pump | ForgetImprinted$ TriggeredCard SVar:NeedsToPlay:Land.Basic+YouCtrl AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/extraplanar_lens.jpg Oracle:Imprint — When Extraplanar Lens enters the battlefield, you may exile target land you control.\nWhenever a land with the same name as the exiled card is tapped for mana, its controller adds one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/f/false_dawn.txt b/forge-gui/res/cardsfolder/f/false_dawn.txt index f9edade399e..0b182ef7274 100644 --- a/forge-gui/res/cardsfolder/f/false_dawn.txt +++ b/forge-gui/res/cardsfolder/f/false_dawn.txt @@ -3,9 +3,8 @@ ManaCost:1 W Types:Sorcery A:SP$ Effect | Cost$ 1 W | ReplacementEffects$ FDRep | StaticAbilities$ FDManaConvertion | SVars$ ProduceW | SubAbility$ DBDraw | SpellDescription$ Until end of turn, spells and abilities you control that would add colored mana add that much white mana instead. Until end of turn, you may spend white mana as though it were mana of any color. Draw a card. SVar:DBDraw:DB$ Draw | NumCards$ 1 -SVar:FDRep:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Card.YouCtrl | NoTapCheck$ True | ManaReplacement$ ProduceW | Description$ Spells and abilities you control that would add colored mana add that much white mana instead. -SVar:ProduceW:R->W & B->W & U->W & G->W +SVar:FDRep:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Card.YouCtrl | ReplaceWith$ ProduceW | Description$ Spells and abilities you control that would add colored mana add that much white mana instead. +SVar:ProduceW:DB$ ReplaceMana | ReplaceColor$ W SVar:FDManaConvertion:Mode$ Continuous | EffectZone$ Command | Affected$ You | ManaColorConversion$ Additive | WhiteConversion$ Color | Description$ You may spend white mana as though it were mana of any color. AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/false_dawn.jpg Oracle:Until end of turn, spells and abilities you control that would add colored mana add that much white mana instead. Until end of turn, you may spend white mana as though it were mana of any color.\nDraw a card. diff --git a/forge-gui/res/cardsfolder/f/forsaken_monument.txt b/forge-gui/res/cardsfolder/f/forsaken_monument.txt index 5282f5a0ce1..3f32b9111dd 100644 --- a/forge-gui/res/cardsfolder/f/forsaken_monument.txt +++ b/forge-gui/res/cardsfolder/f/forsaken_monument.txt @@ -2,7 +2,7 @@ Name:Forsaken Monument ManaCost:5 Types:Legendary Artifact S:Mode$ Continuous | Affected$ Creature.Colorless+YouCtrl | AddPower$ 2 | AddToughness$ 2 | Description$ Colorless creatures you control get +2/+2. -T:Mode$ TapsForMana | ValidCard$ Permanent.YouCtrl | Produced$ C | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a permanent for {C}, add an additional {C}. +T:Mode$ TapsForMana | ValidCard$ Permanent | Activator$ You | Produced$ C | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a permanent for {C}, add an additional {C}. SVar:TrigMana:DB$ Mana | Produced$ C T:Mode$ SpellCast | ValidCard$ Card.Colorless | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigGainLife | TriggerDescription$ Whenever you cast a colorless spell, you gain 2 life. SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 2 diff --git a/forge-gui/res/cardsfolder/h/hall_of_gemstone.txt b/forge-gui/res/cardsfolder/h/hall_of_gemstone.txt index 3b1ecfe9733..f759a16f6ce 100644 --- a/forge-gui/res/cardsfolder/h/hall_of_gemstone.txt +++ b/forge-gui/res/cardsfolder/h/hall_of_gemstone.txt @@ -4,8 +4,7 @@ Types:World Enchantment T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | Execute$ TrigChoose | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of each player's upkeep, that player chooses a color. Until end of turn, lands tapped for mana produce mana of the chosen color instead of any other color. SVar:TrigChoose:DB$ ChooseColor | Defined$ TriggeredPlayer | AILogic$ MostProminentInActivePlayerHand | SubAbility$ DBEffect SVar:DBEffect:DB$ Effect | ReplacementEffects$ ReplaceChosen | SVars$ ProduceChosen -SVar:ReplaceChosen:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land | ManaReplacement$ ProduceChosen | Description$ Lands tapped for mana produce mana of the chosen color instead of any other color. -SVar:ProduceChosen:C->Chosen & U->Chosen & B->Chosen & R->Chosen & G->Chosen & W->Chosen +SVar:ReplaceChosen:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceChosen | Description$ Lands tapped for mana produce mana of the chosen color instead of any other color. +SVar:ProduceChosen:DB$ ReplaceMana | ReplaceColor$ Chosen AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/hall_of_gemstone.jpg Oracle:At the beginning of each player's upkeep, that player chooses a color. Until end of turn, lands tapped for mana produce mana of the chosen color instead of any other color. diff --git a/forge-gui/res/cardsfolder/h/harvest_mage.txt b/forge-gui/res/cardsfolder/h/harvest_mage.txt index 4210c4d3a46..883dd8ebf4f 100644 --- a/forge-gui/res/cardsfolder/h/harvest_mage.txt +++ b/forge-gui/res/cardsfolder/h/harvest_mage.txt @@ -3,8 +3,8 @@ ManaCost:G Types:Creature Human Spellshaper PT:1/1 A:AB$ Effect | Cost$ G T Discard<1/Card> | ReplacementEffects$ HarvestReplacement | SVars$ HarvestProduce | References$ HarvestReplacement,HarvestProduce | AILogic$ Never | Stackable$ False | SpellDescription$ Until end of turn, if you tap a land for mana, it produces one mana of a color of your choice instead of any other type and amount. -SVar:HarvestReplacement:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land.YouCtrl | ManaReplacement$ HarvestProduce | Description$ If you tap a land for mana, it produces one mana of a color of your choice instead of any other type and amount. -SVar:HarvestProduce:Any->Any +SVar:HarvestReplacement:Event$ ProduceMana | ActiveZones$ Command | ValidPlayer$ You | ValidCard$ Land | ValidAbility$ Activated.hasTapCost | ReplaceWith$ HarvestProduce | Description$ If you tap a land for mana, it produces one mana of a color of your choice instead of any other type and amount. +SVar:HarvestProduce:DB$ ReplaceMana | ReplaceMana$ Any AI:RemoveDeck:All SVar:NonStackingEffect:True SVar:Picture:http://www.wizards.com/global/images/magic/general/harvest_mage.jpg diff --git a/forge-gui/res/cardsfolder/h/heartbeat_of_spring.txt b/forge-gui/res/cardsfolder/h/heartbeat_of_spring.txt index c30b56eb47f..2c9033feaea 100644 --- a/forge-gui/res/cardsfolder/h/heartbeat_of_spring.txt +++ b/forge-gui/res/cardsfolder/h/heartbeat_of_spring.txt @@ -2,7 +2,5 @@ Name:Heartbeat of Spring ManaCost:2 G Types:Enchantment T:Mode$ TapsForMana | ValidCard$ Land | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a player taps a land for mana, that player adds one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer -AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/heartbeat_of_spring.jpg +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator Oracle:Whenever a player taps a land for mana, that player adds one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/i/infernal_darkness.txt b/forge-gui/res/cardsfolder/i/infernal_darkness.txt index 59c9333f082..547ea7192c2 100644 --- a/forge-gui/res/cardsfolder/i/infernal_darkness.txt +++ b/forge-gui/res/cardsfolder/i/infernal_darkness.txt @@ -2,9 +2,8 @@ Name:Infernal Darkness ManaCost:2 B B Types:Enchantment K:Cumulative upkeep:B PayLife<1>:Pay {B} and 1 life. -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ManaReplacement$ ProduceB | Description$ If a land is tapped for mana, it produces {B} instead of any other type. -SVar:ProduceB:C->B & U->B & R->B & G->B & W->B +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceB | Description$ If a land is tapped for mana, it produces {B} instead of any other type. +SVar:ProduceB:DB$ ReplaceMana | ReplaceType$ B AI:RemoveDeck:All AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/infernal_darkness.jpg Oracle:Cumulative upkeep—Pay {B} and 1 life. (At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it.)\nIf a land is tapped for mana, it produces {B} instead of any other type. diff --git a/forge-gui/res/cardsfolder/k/keeper_of_progenitus.txt b/forge-gui/res/cardsfolder/k/keeper_of_progenitus.txt index d9d3a9bc7aa..4525f98f52d 100644 --- a/forge-gui/res/cardsfolder/k/keeper_of_progenitus.txt +++ b/forge-gui/res/cardsfolder/k/keeper_of_progenitus.txt @@ -3,7 +3,6 @@ ManaCost:3 G Types:Creature Elf Druid PT:1/3 T:Mode$ TapsForMana | ValidCard$ Mountain,Forest,Plains | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a player taps a Mountain, Forest, or Plains for mana, that player adds one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/keeper_of_progenitus.jpg Oracle:Whenever a player taps a Mountain, Forest, or Plains for mana, that player adds one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/k/kinnan_bonder_prodigy.txt b/forge-gui/res/cardsfolder/k/kinnan_bonder_prodigy.txt index e73a35fff33..5b1ba46361e 100755 --- a/forge-gui/res/cardsfolder/k/kinnan_bonder_prodigy.txt +++ b/forge-gui/res/cardsfolder/k/kinnan_bonder_prodigy.txt @@ -2,7 +2,7 @@ Name:Kinnan, Bonder Prodigy ManaCost:G U Types:Legendary Creature Human Druid PT:2/2 -T:Mode$ TapsForMana | ValidCard$ Permanent.nonLand+YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a nonland permanent for mana, add one mana of any type that permanent produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer +T:Mode$ TapsForMana | ValidCard$ Permanent.nonLand | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a nonland permanent for mana, add one mana of any type that permanent produced. +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You A:AB$ Dig | Cost$ 5 G U | ForceRevealToController$ True | DigNum$ 5 | ChangeNum$ 1 | Optional$ True | ChangeValid$ Creature.nonHuman | DestinationZone$ Battlefield | RestRandomOrder$ True | SpellDescription$ Look at the top five cards of your library. You may put a non-Human creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. Oracle:Whenever you tap a nonland permanent for mana, add one mana of any type that permanent produced.\n{5}{G}{U}: Look at the top five cards of your library. You may put a non-Human creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. diff --git a/forge-gui/res/cardsfolder/m/mana_flare.txt b/forge-gui/res/cardsfolder/m/mana_flare.txt index 5410caf9d2a..d9c858848b1 100644 --- a/forge-gui/res/cardsfolder/m/mana_flare.txt +++ b/forge-gui/res/cardsfolder/m/mana_flare.txt @@ -2,6 +2,5 @@ Name:Mana Flare ManaCost:2 R Types:Enchantment T:Mode$ TapsForMana | ValidCard$ Land | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a player taps a land for mana, that player adds one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer -SVar:Picture:http://www.wizards.com/global/images/magic/general/mana_flare.jpg +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator Oracle:Whenever a player taps a land for mana, that player adds one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/m/mana_reflection.txt b/forge-gui/res/cardsfolder/m/mana_reflection.txt index c233484ccb7..04ebe0d17df 100644 --- a/forge-gui/res/cardsfolder/m/mana_reflection.txt +++ b/forge-gui/res/cardsfolder/m/mana_reflection.txt @@ -1,7 +1,6 @@ Name:Mana Reflection ManaCost:4 G G Types:Enchantment -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Permanent.YouCtrl | ManaReplacement$ ProduceTwice | Description$ If you tap a permanent for mana, it produces twice as much of that mana instead. -SVar:ProduceTwice:C->C C & R->R R & B->B B & U->U U & G->G G & W->W W -SVar:Picture:http://www.wizards.com/global/images/magic/general/mana_reflection.jpg +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidPlayer$ You | ValidCard$ Permanent | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceTwice | Description$ If you tap a permanent for mana, it produces twice as much of that mana instead. +SVar:ProduceTwice:DB$ ReplaceMana | ReplaceAmount$ 2 Oracle:If you tap a permanent for mana, it produces twice as much of that mana instead. diff --git a/forge-gui/res/cardsfolder/m/miraris_wake.txt b/forge-gui/res/cardsfolder/m/miraris_wake.txt index b0824cc90a1..3fcdbd0f81c 100644 --- a/forge-gui/res/cardsfolder/m/miraris_wake.txt +++ b/forge-gui/res/cardsfolder/m/miraris_wake.txt @@ -2,7 +2,6 @@ Name:Mirari's Wake ManaCost:3 G W Types:Enchantment S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Creatures you control get +1/+1. -T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. -SVar:TrigMana:DB$ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer -SVar:Picture:http://www.wizards.com/global/images/magic/general/miraris_wake.jpg +T:Mode$ TapsForMana | ValidCard$ Land | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You Oracle:Creatures you control get +1/+1.\nWhenever you tap a land for mana, add one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/m/mirri.txt b/forge-gui/res/cardsfolder/m/mirri.txt index ddbf7096e00..163d728ebe3 100644 --- a/forge-gui/res/cardsfolder/m/mirri.txt +++ b/forge-gui/res/cardsfolder/m/mirri.txt @@ -2,8 +2,8 @@ Name:Mirri ManaCost:no cost Types:Vanguard HandLifeModifier:+0/+5 -R:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land.Basic+YouCtrl | ManaReplacement$ ProduceAny | Description$ If a basic land you control is tapped for mana, it produces mana of a color of your choice instead of any other type. -SVar:ProduceAny:C->Any & B->Any & R->Any & G->Any & W->Any & U->Any +R:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land.Basic+YouCtrl | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceAny | Description$ If a basic land you control is tapped for mana, it produces mana of a color of your choice instead of any other type. +SVar:ProduceAny:DB$ ReplaceMana | ReplaceType$ Any AI:RemoveDeck:All SVar:Picture:https://downloads.cardforge.org/images/cards/VAN/Mirri.full.jpg Oracle:Hand +0, life +5\nIf a basic land you control is tapped for mana, it produces mana of a color of your choice instead of any other type. diff --git a/forge-gui/res/cardsfolder/n/naked_singularity.txt b/forge-gui/res/cardsfolder/n/naked_singularity.txt index 9da248d9f55..ce0bfaac124 100644 --- a/forge-gui/res/cardsfolder/n/naked_singularity.txt +++ b/forge-gui/res/cardsfolder/n/naked_singularity.txt @@ -3,17 +3,16 @@ ManaCost:5 Types:Artifact Text:If tapped for mana, Plains produce {R}, Islands produce {G}, Swamps produce {W}, Mountains produce {U}, and Forests produce {B} instead of any other type. K:Cumulative upkeep:3 -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Plains | ManaReplacement$ ProduceR | Secondary$ True | Description$ If tapped for mana, Plains produce R. -SVar:ProduceR:C->R & B->R & U->R & G->R & W->R -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Island | ManaReplacement$ ProduceG | Secondary$ True | Description$ If tapped for mana, Islands produce G. -SVar:ProduceG:C->G & B->G & U->G & R->G & W->G -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Swamp | ManaReplacement$ ProduceW | Secondary$ True | Description$ If tapped for mana, Swamps produce W. -SVar:ProduceW:C->W & B->W & U->W & R->W & G->W -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Mountain | ManaReplacement$ ProduceU | Secondary$ True | Description$ If tapped for mana, Mountains produce U. -SVar:ProduceU:C->U & B->U & G->U & R->U & W->U -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Forest | ManaReplacement$ ProduceB | Secondary$ True | Description$ If tapped for mana, Forests produce B. -SVar:ProduceB:C->B & G->B & U->B & R->B & W->B +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Plains | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceR | Secondary$ True | Description$ If tapped for mana, Plains produce R. +SVar:ProduceR:DB$ ReplaceMana | ReplaceType$ R +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Island | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceG | Secondary$ True | Description$ If tapped for mana, Islands produce G. +SVar:ProduceG:DB$ ReplaceMana | ReplaceType$ G +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Swamp | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceW | Secondary$ True | Description$ If tapped for mana, Swamps produce W. +SVar:ProduceW:DB$ ReplaceMana | ReplaceType$ W +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Mountain | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceU | Secondary$ True | Description$ If tapped for mana, Mountains produce U. +SVar:ProduceU:DB$ ReplaceMana | ReplaceType$ U +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Forest | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceB | Secondary$ True | Description$ If tapped for mana, Forests produce B. +SVar:ProduceB:DB$ ReplaceMana | ReplaceType$ B AI:RemoveDeck:All AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/naked_singularity.jpg Oracle:Cumulative upkeep {3} (At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it.)\nIf tapped for mana, Plains produce {R}, Islands produce {G}, Swamps produce {W}, Mountains produce {U}, and Forests produce {B} instead of any other type. diff --git a/forge-gui/res/cardsfolder/n/nikya_of_the_old_ways.txt b/forge-gui/res/cardsfolder/n/nikya_of_the_old_ways.txt index 99e2fe3d7bd..f2ef730ff3c 100644 --- a/forge-gui/res/cardsfolder/n/nikya_of_the_old_ways.txt +++ b/forge-gui/res/cardsfolder/n/nikya_of_the_old_ways.txt @@ -3,6 +3,6 @@ ManaCost:3 R G Types:Legendary Creature Centaur Druid PT:5/5 S:Mode$ CantBeCast | ValidCard$ Card.nonCreature | Caster$ You | Description$ You can't cast noncreature spells. -T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. -SVar:TrigMana:DB$ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer +T:Mode$ TapsForMana | ValidCard$ Land | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You Oracle:You can't cast noncreature spells.\nWhenever you tap a land for mana, add one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/n/nirkana_revenant.txt b/forge-gui/res/cardsfolder/n/nirkana_revenant.txt index 25d69b82369..6385d51421f 100644 --- a/forge-gui/res/cardsfolder/n/nirkana_revenant.txt +++ b/forge-gui/res/cardsfolder/n/nirkana_revenant.txt @@ -3,7 +3,6 @@ ManaCost:4 B B Types:Creature Vampire Shade PT:4/4 A:AB$ Pump | Cost$ B | NumAtt$ +1 | NumDef$ +1 | SpellDescription$ CARDNAME gets +1/+1 until end of turn. -T:Mode$ TapsForMana | ValidCard$ Swamp.YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a Swamp for mana, add an additional {B}. -SVar:TrigMana:DB$Mana | Produced$ B | Amount$ 1 -SVar:Picture:http://www.wizards.com/global/images/magic/general/nirkana_revenant.jpg +T:Mode$ TapsForMana | ValidCard$ Swamp | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a Swamp for mana, add an additional {B}. +SVar:TrigMana:DB$ Mana | Produced$ B | Amount$ 1 Oracle:Whenever you tap a Swamp for mana, add an additional {B}.\n{B}: Nirkana Revenant gets +1/+1 until end of turn. diff --git a/forge-gui/res/cardsfolder/n/nissa_who_shakes_the_world.txt b/forge-gui/res/cardsfolder/n/nissa_who_shakes_the_world.txt index 6db26539168..d0d28f708dc 100644 --- a/forge-gui/res/cardsfolder/n/nissa_who_shakes_the_world.txt +++ b/forge-gui/res/cardsfolder/n/nissa_who_shakes_the_world.txt @@ -2,8 +2,8 @@ Name:Nissa, Who Shakes the World ManaCost:3 G G Types:Legendary Planeswalker Nissa Loyalty:5 -T:Mode$ TapsForMana | ValidCard$ Forest.YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a Forest for mana, add an additional {G}. -SVar:TrigMana:DB$Mana | Produced$ G | Amount$ 1 +T:Mode$ TapsForMana | ValidCard$ Forest | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a Forest for mana, add an additional {G}. +SVar:TrigMana:DB$ Mana | Produced$ G | Amount$ 1 A:AB$ PutCounter | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | CounterType$ P1P1 | CounterNum$ 3 | ValidTgts$ Land.nonCreature+YouCtrl | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select target noncreature land you control | SubAbility$ DBUntap | SpellDescription$ Put three +1/+1 counters on up to one target noncreature land you control. Untap it. It becomes a 0/0 Elemental creature with vigilance and haste that's still a land. SVar:DBUntap:DB$ Untap | Defined$ Targeted | SubAbility$ DBAnimate SVar:DBAnimate:DB$ Animate | Defined$ Targeted | Power$ 0 | Toughness$ 0 | Types$ Creature,Elemental | Keywords$ Vigilance & Haste | Permanent$ True diff --git a/forge-gui/res/cardsfolder/n/nyxbloom_ancient.txt b/forge-gui/res/cardsfolder/n/nyxbloom_ancient.txt index 97cab14f3f4..6186b559ecf 100755 --- a/forge-gui/res/cardsfolder/n/nyxbloom_ancient.txt +++ b/forge-gui/res/cardsfolder/n/nyxbloom_ancient.txt @@ -3,6 +3,6 @@ ManaCost:4 G G G Types:Enchantment Creature Elemental PT:5/5 K:Trample -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Permanent.YouCtrl | ManaReplacement$ ProduceThrice | Description$ If you tap a permanent for mana, it produces three times as much of that mana instead. -SVar:ProduceThrice:C->C C C & R->R R R & B->B B B & U->U U U & G->G G G & W->W W W +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Permanent.YouCtrl | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceThrice | Description$ If you tap a permanent for mana, it produces three times as much of that mana instead. +SVar:ProduceThrice:DB$ ReplaceMana | ReplaceAmount$ 3 Oracle:Trample\nIf you tap a permanent for mana, it produces three times as much of that mana instead. diff --git a/forge-gui/res/cardsfolder/o/overabundance.txt b/forge-gui/res/cardsfolder/o/overabundance.txt index fb5958be812..cf0c413dfa5 100644 --- a/forge-gui/res/cardsfolder/o/overabundance.txt +++ b/forge-gui/res/cardsfolder/o/overabundance.txt @@ -3,7 +3,6 @@ ManaCost:1 R G Types:Enchantment T:Mode$ TapsForMana | ValidCard$ Land | Execute$ TrigDmg | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a player taps a land for mana, that player adds one mana of any type that land produced, and CARDNAME deals 1 damage to them. SVar:TrigDmg:DB$ DealDamage | Defined$ TriggeredCardController | NumDmg$ 1 | SubAbility$ DBMana -SVar:DBMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredCardController +SVar:DBMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/overabundance.jpg Oracle:Whenever a player taps a land for mana, that player adds one mana of any type that land produced, and Overabundance deals 1 damage to them. diff --git a/forge-gui/res/cardsfolder/p/pale_moon.txt b/forge-gui/res/cardsfolder/p/pale_moon.txt index c88cf4adfcf..5a618b3116c 100644 --- a/forge-gui/res/cardsfolder/p/pale_moon.txt +++ b/forge-gui/res/cardsfolder/p/pale_moon.txt @@ -2,9 +2,8 @@ Name:Pale Moon ManaCost:1 U Types:Instant A:SP$ Effect | Cost$ 1 U | ReplacementEffects$ ReplaceColorless | SVars$ ProduceColorless | SpellDescription$ Until end of turn, if a player taps a nonbasic land for mana, it produces colorless mana instead of any other type. -SVar:ReplaceColorless:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land.nonBasic | ManaReplacement$ ProduceColorless | Description$ If a player taps a nonbasic land for mana, it produces colorless mana instead of any other type. -SVar:ProduceColorless:U->1 & B->1 & R->1 & G->1 & W->1 +SVar:ReplaceColorless:Event$ ProduceMana | ActiveZones$ Command | ValidCard$ Land.nonBasic | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceColorless | Description$ If a player taps a nonbasic land for mana, it produces colorless mana instead of any other type. +SVar:ProduceColorless:DB$ ReplaceMana | ReplaceType$ C AI:RemoveDeck:All AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/pale_moon.jpg Oracle:Until end of turn, if a player taps a nonbasic land for mana, it produces colorless mana instead of any other type. diff --git a/forge-gui/res/cardsfolder/p/pulse_of_llanowar.txt b/forge-gui/res/cardsfolder/p/pulse_of_llanowar.txt index 473470e27af..c669015798a 100644 --- a/forge-gui/res/cardsfolder/p/pulse_of_llanowar.txt +++ b/forge-gui/res/cardsfolder/p/pulse_of_llanowar.txt @@ -1,8 +1,8 @@ Name:Pulse of Llanowar ManaCost:3 G Types:Enchantment -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land.Basic+YouCtrl | ManaReplacement$ ProduceAny | Description$ If a basic land you control is tapped for mana, it produces mana of a color of your choice instead of any other type. -SVar:ProduceAny:C->Any & R->Any & B->Any & U->Any & G->Any & W->Any +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land.Basic+YouCtrl | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceAny | Description$ If a basic land you control is tapped for mana, it produces mana of a color of your choice instead of any other type. +SVar:ProduceAny:DB$ ReplaceMana | ReplaceType$ Any AI:RemoveDeck:All SVar:NonStackingEffect:True SVar:Picture:http://www.wizards.com/global/images/magic/general/pulse_of_llanowar.jpg diff --git a/forge-gui/res/cardsfolder/q/quarum_trench_gnomes.txt b/forge-gui/res/cardsfolder/q/quarum_trench_gnomes.txt index f7d1e9915fb..d2b951ce90d 100644 --- a/forge-gui/res/cardsfolder/q/quarum_trench_gnomes.txt +++ b/forge-gui/res/cardsfolder/q/quarum_trench_gnomes.txt @@ -2,9 +2,9 @@ Name:Quarum Trench Gnomes ManaCost:3 R Types:Creature Gnome PT:1/1 -A:AB$ Animate | Cost$ T | ValidTgts$ Plains | IsCurse$ True | TgtPrompt$ Choose target plains | Replacements$ QuarumReplacement | sVars$ QuarumProduce | Permanent$ True | StackDescription$ If target {c:Targeted} is tapped for mana, it produces colorless mana instead of white mana. (This effect lasts indefinitely.) | SpellDescription$ If target Plains is tapped for mana, it produces colorless mana instead of white mana. (This effect lasts indefinitely.) -SVar:QuarumReplacement:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Card.Plains+Self | ManaReplacement$ QuarumProduce | Description$ If CARDNAME is tapped for mana, it produces colorless mana instead of white mana. -SVar:QuarumProduce:W->1 +A:AB$ Effect | Cost$ T | ValidTgts$ Plains | IsCurse$ True | TgtPrompt$ Choose target plains | RememberObjects$ Targeted | ForgetOnMoved$ Battlefield | Duration$ Permanent | ReplacementEffects$ QuarumReplacement | SVars$ QuarumProduce | StackDescription$ If target {c:Targeted} is tapped for mana, it produces colorless mana instead of white mana. (This effect lasts indefinitely.) | SpellDescription$ If target Plains is tapped for mana, it produces colorless mana instead of white mana. (This effect lasts indefinitely.) +SVar:QuarumReplacement:Event$ ProduceMana | ValidCard$ Card.IsRemembered | ReplaceWith$ QuarumProduce | Description$ If this Land is tapped for mana, it produces colorless mana instead of white mana. +SVar:QuarumProduce:DB$ ReplaceMana | ReplaceColor$ C | ReplaceOnly$ W AI:RemoveDeck:All AI:RemoveDeck:Random SVar:Picture:http://www.wizards.com/global/images/magic/general/quarum_trench_gnomes.jpg diff --git a/forge-gui/res/cardsfolder/r/reality_twist.txt b/forge-gui/res/cardsfolder/r/reality_twist.txt index 66c104972ca..ff1cb58d421 100644 --- a/forge-gui/res/cardsfolder/r/reality_twist.txt +++ b/forge-gui/res/cardsfolder/r/reality_twist.txt @@ -3,16 +3,15 @@ ManaCost:U U U Types:Enchantment Text:If tapped for mana, Plains produce {R}, Swamps produce {G}, Mountains produce {W}, and Forests produce {B} instead of any other type. K:Cumulative upkeep:1 U U -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Plains | ManaReplacement$ ProduceR | Secondary$ True | Description$ If tapped for mana, Plains produce R. -SVar:ProduceR:C->R & B->R & U->R & G->R & W->R -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Swamp | ManaReplacement$ ProduceG | Secondary$ True | Description$ If tapped for mana, Swamps produce G. -SVar:ProduceG:C->G & B->G & U->G & R->G & W->G -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Mountain | ManaReplacement$ ProduceW | Secondary$ True | Description$ If tapped for mana, Mountains produce U. -SVar:ProduceW:C->W & B->W & G->W & R->W & U->W -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Forest | ManaReplacement$ ProduceB | Secondary$ True | Description$ If tapped for mana, Forests produce B. -SVar:ProduceB:C->B & G->B & U->B & R->B & W->B +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Plains | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceR | Secondary$ True | Description$ If tapped for mana, Plains produce R. +SVar:ProduceR:DB$ ReplaceMana | ReplaceType$ R +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Swamp | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceG | Secondary$ True | Description$ If tapped for mana, Swamps produce G. +SVar:ProduceG:DB$ ReplaceMana | ReplaceType$ G +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Mountain | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceW | Secondary$ True | Description$ If tapped for mana, Mountains produce U. +SVar:ProduceW:DB$ ReplaceMana | ReplaceType$ W +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Forest | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceB | Secondary$ True | Description$ If tapped for mana, Forests produce B. +SVar:ProduceB:DB$ ReplaceMana | ReplaceType$ B AI:RemoveDeck:All AI:RemoveDeck:Random SVar:NonStackingEffect:True -SVar:Picture:http://www.wizards.com/global/images/magic/general/reality_twist.jpg Oracle:Cumulative upkeep {1}{U}{U} (At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it.)\nIf tapped for mana, Plains produce {R}, Swamps produce {G}, Mountains produce {W}, and Forests produce {B} instead of any other type. diff --git a/forge-gui/res/cardsfolder/r/regal_behemoth.txt b/forge-gui/res/cardsfolder/r/regal_behemoth.txt index 17622a1a20b..0364bf56dda 100644 --- a/forge-gui/res/cardsfolder/r/regal_behemoth.txt +++ b/forge-gui/res/cardsfolder/r/regal_behemoth.txt @@ -5,7 +5,6 @@ PT:5/5 K:Trample T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMonarch | TriggerDescription$ When CARDNAME enters the battlefield, you become the monarch. SVar:TrigMonarch:DB$ BecomeMonarch | Defined$ You -T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | CheckDefinedPlayer$ You.isMonarch | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana while you're the monarch, add an additional one mana of any color. +T:Mode$ TapsForMana | ValidCard$ Land | Activator$ You | CheckDefinedPlayer$ You.isMonarch | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana while you're the monarch, add an additional one mana of any color. SVar:TrigMana:DB$ Mana | Produced$ Combo Any | Amount$ 1 | AILogic$ MostProminentInComputerHand -SVar:Picture:http://www.wizards.com/global/images/magic/general/regal_behemoth.jpg -Oracle:Trample\nWhen Regal Behemoth enters the battlefield, you become the monarch.\nWhenever you tap a land for mana while you're the monarch, add an additional one mana of any color. \ No newline at end of file +Oracle:Trample\nWhen Regal Behemoth enters the battlefield, you become the monarch.\nWhenever you tap a land for mana while you're the monarch, add an additional one mana of any color. diff --git a/forge-gui/res/cardsfolder/r/ritual_of_subdual.txt b/forge-gui/res/cardsfolder/r/ritual_of_subdual.txt index 87972a95860..6b2e6d3bb38 100644 --- a/forge-gui/res/cardsfolder/r/ritual_of_subdual.txt +++ b/forge-gui/res/cardsfolder/r/ritual_of_subdual.txt @@ -2,10 +2,9 @@ Name:Ritual of Subdual ManaCost:4 G G Types:Enchantment K:Cumulative upkeep:2 -R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ManaReplacement$ ProduceColorless | Description$ If a land is tapped for mana, it produces colorless mana instead of any other type. -SVar:ProduceColorless:B->1 & U->1 & R->1 & G->1 & W->1 +R:Event$ ProduceMana | ActiveZones$ Battlefield | ValidCard$ Land | ValidAbility$ Activated.hasTapCost | ReplaceWith$ ProduceColorless | Description$ If a land is tapped for mana, it produces colorless mana instead of any other type. +SVar:ProduceColorless:DB$ ReplaceMana | ReplaceType$ C AI:RemoveDeck:All AI:RemoveDeck:Random SVar:NonStackingEffect:True -SVar:Picture:http://www.wizards.com/global/images/magic/general/ritual_of_subdual.jpg Oracle:Cumulative upkeep {2} (At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you pay its upkeep cost for each age counter on it.)\nIf a land is tapped for mana, it produces colorless mana instead of any other type. diff --git a/forge-gui/res/cardsfolder/r/river_of_tears.txt b/forge-gui/res/cardsfolder/r/river_of_tears.txt index 867be205b00..afa80fc920b 100644 --- a/forge-gui/res/cardsfolder/r/river_of_tears.txt +++ b/forge-gui/res/cardsfolder/r/river_of_tears.txt @@ -1,8 +1,9 @@ Name:River of Tears ManaCost:no cost Types:Land -A:AB$ Mana | Cost$ T | Produced$ U | ReplaceIfLandPlayed$ B | SpellDescription$ Add {U}. If you played a land this turn, add {B} instead. +A:AB$ Mana | Cost$ T | Produced$ U | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ0 | References$ X | SubAbility$ ManaB | SpellDescription$ Add {U}. If you played a land this turn, add {B} instead. +SVar:ManaB:DB$ Mana | Produced$ B | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | References$ X AI:RemoveDeck:Random DeckHints:Color$Blue|Black -SVar:Picture:http://www.wizards.com/global/images/magic/general/river_of_tears.jpg +SVar:X:Count$YourLandsPlayed Oracle:{T}: Add {U}. If you played a land this turn, add {B} instead. diff --git a/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt b/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt index 7a5bec76bc6..e40e8095955 100644 --- a/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt +++ b/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt @@ -5,7 +5,6 @@ PT:2/3 A:AB$ SetState | Cost$ Reveal<1/Hand> | Defined$ Self | Mode$ Flip | ConditionCheckSVar$ CheckHandLand | ConditionSVarCompare$ GE7 | AILogic$ CheckCondition | References$ CheckHandLand | SpellDescription$ If you have seven or more land cards in your hand, flip CARDNAME. SVar:CheckHandLand:Count$ValidHand Land.YouCtrl AlternateMode:Flip -SVar:Picture:http://www.wizards.com/global/images/magic/general/sasaya_orochi_ascendant.jpg Oracle:Reveal your hand: If you have seven or more land cards in your hand, flip Sasaya, Orochi Ascendant. ALTERNATE @@ -17,7 +16,6 @@ Types:Legendary Enchantment T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigMana | Static$ True | TriggerDescription$ Whenever a land you control is tapped for mana, for each other land you control with the same name, add one mana of any type that land produced. SVar:TrigMana:DB$ Pump | RememberObjects$ TriggeredCard | SubAbility$ DBRepeat SVar:DBRepeat:DB$ RepeatEach | UseImprinted$ True | RepeatCards$ Land.YouCtrl+IsNotRemembered+sharesNameWith Remembered | RepeatSubAbility$ DBManaReflect | SubAbility$ DBCleanup -SVar:DBManaReflect:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Imprinted | ReflectProperty$ Produced | Defined$ You +SVar:DBManaReflect:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:Picture:http://www.wizards.com/global/images/magic/general/sasayas_essence.jpg Oracle:Whenever a land you control is tapped for mana, for each other land you control with the same name, add one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/s/savage_firecat.txt b/forge-gui/res/cardsfolder/s/savage_firecat.txt index 627ae9a3575..55bf0687eba 100644 --- a/forge-gui/res/cardsfolder/s/savage_firecat.txt +++ b/forge-gui/res/cardsfolder/s/savage_firecat.txt @@ -4,8 +4,7 @@ Types:Creature Elemental Cat PT:0/0 K:etbCounter:P1P1:7 K:Trample -T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | Execute$ TrigRemoveCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever you tap a land for mana, remove a +1/+1 counter from CARDNAME. -SVar:TrigRemoveCounter:DB$RemoveCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +T:Mode$ TapsForMana | ValidCard$ Land | Activator$ You | Execute$ TrigRemoveCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever you tap a land for mana, remove a +1/+1 counter from CARDNAME. +SVar:TrigRemoveCounter:DB$ RemoveCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 AI:RemoveDeck:All -SVar:Picture:http://www.wizards.com/global/images/magic/general/savage_firecat.jpg Oracle:Trample\nSavage Firecat enters the battlefield with seven +1/+1 counters on it.\nWhenever you tap a land for mana, remove a +1/+1 counter from Savage Firecat. diff --git a/forge-gui/res/cardsfolder/s/selesnya_loft_gardens.txt b/forge-gui/res/cardsfolder/s/selesnya_loft_gardens.txt index e3d6c9c4808..48829905e25 100644 --- a/forge-gui/res/cardsfolder/s/selesnya_loft_gardens.txt +++ b/forge-gui/res/cardsfolder/s/selesnya_loft_gardens.txt @@ -9,8 +9,7 @@ SVar:Y:ReplaceCount$TokenNum/Twice SVar:Z:ReplaceCount$CounterNum/Twice T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, until end of turn, whenever you tap a land for mana, add one mana of any type that land produced. SVar:RolledChaos:DB$ Effect | AILogic$ Always | Triggers$ TrigTapForMana | SVars$ TrigMana -SVar:TrigTapForMana:Mode$ TapsForMana | TriggerZones$ Command | ValidCard$ Land.YouCtrl | Execute$ TrigMana | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer -SVar:Picture:http://www.wizards.com/global/images/magic/general/selesnya_loft_gardens.jpg +SVar:TrigTapForMana:Mode$ TapsForMana | TriggerZones$ Command | ValidCard$ Land | Activator$ You | Execute$ TrigMana | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You SVar:AIRollPlanarDieParams:Mode$ Always | MinTurn$ 1 | RollInMain1$ True Oracle:If an effect would create one or more tokens, it creates twice that many of those tokens instead.\nIf an effect would put one or more counters on a permanent, it puts twice that many of those counters on that permanent instead.\nWhenever you roll {CHAOS}, until end of turn, whenever you tap a land for mana, add one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/s/sisay.txt b/forge-gui/res/cardsfolder/s/sisay.txt index cb53c690a27..0c33d5fc7e5 100644 --- a/forge-gui/res/cardsfolder/s/sisay.txt +++ b/forge-gui/res/cardsfolder/s/sisay.txt @@ -2,8 +2,7 @@ Name:Sisay ManaCost:no cost Types:Vanguard HandLifeModifier:-2/-3 -T:Mode$ TapsForMana | TriggerZones$ Command | ValidCard$ Land.YouCtrl | Execute$ TrigMana | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer +T:Mode$ TapsForMana | TriggerZones$ Command | ValidCard$ Land | Activator$ You | Execute$ TrigMana | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You AI:RemoveDeck:All -SVar:Picture:https://downloads.cardforge.org/images/cards/VAN/Sisay.full.jpg Oracle:Hand -2, life -3\nWhenever you tap a land for mana, add one mana of any type that land produced. diff --git a/forge-gui/res/cardsfolder/u/urzas_mine.txt b/forge-gui/res/cardsfolder/u/urzas_mine.txt index dac61afc558..4450f4c8173 100644 --- a/forge-gui/res/cardsfolder/u/urzas_mine.txt +++ b/forge-gui/res/cardsfolder/u/urzas_mine.txt @@ -1,7 +1,7 @@ Name:Urza's Mine ManaCost:no cost Types:Land Urza's Mine -A:AB$ Mana | Cost$ T | Produced$ C | Bonus$ UrzaLands | BonusProduced$ 1 | SpellDescription$ Add {C}. If you control an Urza's Power-Plant and an Urza's Tower, add {C}{C} instead. +A:AB$ Mana | Cost$ T | Produced$ C | Amount$ UrzaAmount | References$ UrzaAmount | SpellDescription$ Add {C}. If you control an Urza's Power-Plant and an Urza's Tower, add {C}{C} instead. +SVar:UrzaAmount:Count$UrzaLands.2.1 AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/urzas_mine.jpg Oracle:{T}: Add {C}. If you control an Urza's Power-Plant and an Urza's Tower, add {C}{C} instead. diff --git a/forge-gui/res/cardsfolder/u/urzas_power_plant.txt b/forge-gui/res/cardsfolder/u/urzas_power_plant.txt index 0cf4980b9a1..78bd2068f0c 100644 --- a/forge-gui/res/cardsfolder/u/urzas_power_plant.txt +++ b/forge-gui/res/cardsfolder/u/urzas_power_plant.txt @@ -1,7 +1,7 @@ Name:Urza's Power Plant ManaCost:no cost Types:Land Urza's Power-Plant -A:AB$ Mana | Cost$ T | Produced$ C | Bonus$ UrzaLands | BonusProduced$ 1 | SpellDescription$ Add {C}. If you control an Urza's Mine and an Urza's Tower, add {C}{C} instead. +A:AB$ Mana | Cost$ T | Produced$ C | Amount$ UrzaAmount | References$ UrzaAmount | SpellDescription$ Add {C}. If you control an Urza's Mine and an Urza's Tower, add {C}{C} instead. +SVar:UrzaAmount:Count$UrzaLands.2.1 AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/urzas_power_plant.jpg Oracle:{T}: Add {C}. If you control an Urza's Mine and an Urza's Tower, add {C}{C} instead. diff --git a/forge-gui/res/cardsfolder/u/urzas_tower.txt b/forge-gui/res/cardsfolder/u/urzas_tower.txt index c6beb6130e6..52257b4be12 100644 --- a/forge-gui/res/cardsfolder/u/urzas_tower.txt +++ b/forge-gui/res/cardsfolder/u/urzas_tower.txt @@ -1,7 +1,7 @@ Name:Urza's Tower ManaCost:no cost Types:Land Urza's Tower -A:AB$ Mana | Cost$ T | Produced$ C | Bonus$ UrzaLands | BonusProduced$ 2 | SpellDescription$ Add {C}. If you control an Urza's Mine and an Urza's Power-Plant, add {C}{C}{C} instead. +A:AB$ Mana | Cost$ T | Produced$ C | Amount$ UrzaAmount | References$ UrzaAmount | SpellDescription$ Add {C}. If you control an Urza's Mine and an Urza's Power-Plant, add {C}{C}{C} instead. +SVar:UrzaAmount:Count$UrzaLands.3.1 AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/urzas_tower.jpg Oracle:{T}: Add {C}. If you control an Urza's Mine and an Urza's Power-Plant, add {C}{C}{C} instead. diff --git a/forge-gui/res/cardsfolder/upcoming/piracy.txt b/forge-gui/res/cardsfolder/upcoming/piracy.txt new file mode 100644 index 00000000000..96aeb5ea258 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/piracy.txt @@ -0,0 +1,6 @@ +Name:Piracy +ManaCost:U U +Types:Sorcery +A:SP$ Effect | Cost$ U U | StaticAbilities$ STPiracy | AINoRecursiveCheck$ True | SpellDescription$ Until end of turn, you may tap lands you don’t control for mana. Spend this mana only to cast spells. +SVar:STPiracy:Mode$ Continuous | Affected$ You | AddKeyword$ Piracy | Description$ Until end of turn, you may tap lands you don’t control for mana. Spend this mana only to cast spells. +Oracle:Until end of turn, you may tap lands you don’t control for mana. Spend this mana only to cast spells. diff --git a/forge-gui/res/cardsfolder/v/vorinclex_voice_of_hunger.txt b/forge-gui/res/cardsfolder/v/vorinclex_voice_of_hunger.txt index 68b627655f7..2e24c03e087 100644 --- a/forge-gui/res/cardsfolder/v/vorinclex_voice_of_hunger.txt +++ b/forge-gui/res/cardsfolder/v/vorinclex_voice_of_hunger.txt @@ -3,8 +3,8 @@ ManaCost:6 G G Types:Legendary Creature Praetor PT:7/6 K:Trample -T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ You +T:Mode$ TapsForMana | ValidCard$ Land | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You T:Mode$ TapsForMana | ValidCard$ Land.OppCtrl | Execute$ TrigPump | TriggerZones$ Battlefield | TriggerDescription$ Whenever an opponent taps a land for mana, that land doesn't untap during its controller's next untap step. SVar:TrigPump:DB$ Pump | Defined$ TriggeredCard | Permanent$ True | KW$ HIDDEN This card doesn't untap during your next untap step. Oracle:Trample\nWhenever you tap a land for mana, add one mana of any type that land produced.\nWhenever an opponent taps a land for mana, that land doesn't untap during its controller's next untap step. diff --git a/forge-gui/res/cardsfolder/w/winters_night.txt b/forge-gui/res/cardsfolder/w/winters_night.txt index f319bd8a6d7..71885262d3b 100644 --- a/forge-gui/res/cardsfolder/w/winters_night.txt +++ b/forge-gui/res/cardsfolder/w/winters_night.txt @@ -2,7 +2,7 @@ Name:Winter's Night ManaCost:R G W Types:World Enchantment T:Mode$ TapsForMana | ValidCard$ Land.Snow | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a player taps a snow land for mana, that player adds one mana of any type that land produced. That land doesn't untap during its controller's next untap step. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer | SubAbility$ DBPump +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator | SubAbility$ DBPump SVar:DBPump:DB$ Pump | Defined$ TriggeredCard | Permanent$ True | KW$ HIDDEN This card doesn't untap during your next untap step. AI:RemoveDeck:All AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/z/zendikar_resurgent.txt b/forge-gui/res/cardsfolder/z/zendikar_resurgent.txt index 40b8705d514..504ef0569b5 100644 --- a/forge-gui/res/cardsfolder/z/zendikar_resurgent.txt +++ b/forge-gui/res/cardsfolder/z/zendikar_resurgent.txt @@ -1,9 +1,8 @@ Name:Zendikar Resurgent ManaCost:5 G G Types:Enchantment -T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. (The types of mana are white, blue, black, red, green, and colorless.) -SVar:TrigMana:DB$ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer +T:Mode$ TapsForMana | ValidCard$ Land | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a land for mana, add one mana of any type that land produced. (The types of mana are white, blue, black, red, green, and colorless.) +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You T:Mode$ SpellCast | ValidCard$ Creature | ValidActivatingPlayer$ You | Execute$ TrigDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a creature spell, draw a card. -SVar:TrigDraw:DB$Draw | Defined$ You | NumCards$ 1 -SVar:Picture:http://www.wizards.com/global/images/magic/general/zendikar_resurgent.jpg +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 Oracle:Whenever you tap a land for mana, add one mana of any type that land produced. (The types of mana are white, blue, black, red, green, and colorless.)\nWhenever you cast a creature spell, draw a card. diff --git a/forge-gui/res/cardsfolder/z/zhur_taa_ancient.txt b/forge-gui/res/cardsfolder/z/zhur_taa_ancient.txt index 9f5cad19ae7..ae4375d973e 100644 --- a/forge-gui/res/cardsfolder/z/zhur_taa_ancient.txt +++ b/forge-gui/res/cardsfolder/z/zhur_taa_ancient.txt @@ -3,6 +3,5 @@ ManaCost:3 R G Types:Creature Beast PT:7/5 T:Mode$ TapsForMana | ValidCard$ Land | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a player taps a land for mana, that player adds one mana of any type that land produced. -SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | Valid$ Defined.Triggered | ReflectProperty$ Produced | Defined$ TriggeredPlayer -SVar:Picture:http://www.wizards.com/global/images/magic/general/zhur_taa_ancient.jpg +SVar:TrigMana:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ TriggeredActivator Oracle:Whenever a player taps a land for mana, that player adds one mana of any type that land produced. diff --git a/forge-gui/src/main/java/forge/match/input/InputPayMana.java b/forge-gui/src/main/java/forge/match/input/InputPayMana.java index 56276bb5b6d..964f89f34f3 100644 --- a/forge-gui/src/main/java/forge/match/input/InputPayMana.java +++ b/forge-gui/src/main/java/forge/match/input/InputPayMana.java @@ -3,8 +3,6 @@ package forge.match.input; import java.util.*; import forge.GuiBase; -import forge.game.GameActionUtil; -import forge.game.ability.AbilityKey; import forge.game.spellability.SpellAbilityView; import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; @@ -18,15 +16,11 @@ import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.ManaAtom; import forge.game.Game; -import forge.game.ability.ApiType; +import forge.game.GameActionUtil; import forge.game.card.Card; -import forge.game.card.CardUtil; import forge.game.mana.ManaCostBeingPaid; import forge.game.player.Player; import forge.game.player.PlayerView; -import forge.game.replacement.ReplacementEffect; -import forge.game.replacement.ReplacementType; -import forge.game.spellability.AbilityManaPart; import forge.game.spellability.SpellAbility; import forge.player.HumanPlay; import forge.player.PlayerControllerHuman; @@ -77,7 +71,7 @@ public abstract class InputPayMana extends InputSyncronizedBase { // Mobile Forge allows to tap cards underneath the current card even if the current one is tapped if (otherCardsToSelect != null) { for (Card c : otherCardsToSelect) { - for (SpellAbility sa : c.getManaAbilities()) { + for (SpellAbility sa : getAllManaAbilities(c)) { if (sa.canPlay()) { delaySelectCards.add(c); break; @@ -85,16 +79,17 @@ public abstract class InputPayMana extends InputSyncronizedBase { } } } - if (!card.getManaAbilities().isEmpty() && activateManaAbility(card)) { + if (!getAllManaAbilities(card).isEmpty() && activateManaAbility(card)) { return true; } return activateDelayedCard(); } else { + List manaAbilities = getAllManaAbilities(card); // Desktop Forge floating menu functionality - if (card.getManaAbilities().size() == 1) { - activateManaAbility(card, card.getManaAbilities().get(0)); + if (manaAbilities.size() == 1) { + activateManaAbility(card, manaAbilities.get(0)); } else { - SpellAbility spellAbility = getController().getAbilityToPlay(card, Lists.newArrayList(card.getManaAbilities()), triggerEvent); + SpellAbility spellAbility = getController().getAbilityToPlay(card, manaAbilities, triggerEvent); if (spellAbility != null) { activateManaAbility(card, spellAbility); } @@ -103,9 +98,29 @@ public abstract class InputPayMana extends InputSyncronizedBase { } } + protected List getAllManaAbilities(Card card) { + List result = Lists.newArrayList(); + for (SpellAbility sa : card.getManaAbilities()) { + result.add(sa); + result.addAll(GameActionUtil.getAlternativeCosts(sa, player)); + } + final Collection toRemove = Lists.newArrayListWithCapacity(result.size()); + for (final SpellAbility sa : result) { + sa.setActivatingPlayer(player); + // fix things like retrace + // check only if SA can't be cast normally + if (sa.canPlay(true)) { + continue; + } + toRemove.add(sa); + } + result.removeAll(toRemove); + return result; + } + @Override public String getActivateAction(Card card) { - for (SpellAbility sa : card.getManaAbilities()) { + for (SpellAbility sa : getAllManaAbilities(card)) { if (sa.canPlay()) { return "pay mana with card"; } @@ -135,6 +150,7 @@ public abstract class InputPayMana extends InputSyncronizedBase { return false; } + @Deprecated public List getUsefulManaAbilities(Card card) { List abilities = new ArrayList<>(); @@ -151,7 +167,7 @@ public abstract class InputPayMana extends InputSyncronizedBase { if (manaCost.isAnyPartPayableWith((byte) ManaAtom.GENERIC, player.getManaPool())) { colorCanUse |= ManaAtom.GENERIC; } - if (colorCanUse == 0) { // no mana cost or something + if (colorCanUse == 0) { // no mana cost or something return abilities; } @@ -160,15 +176,10 @@ public abstract class InputPayMana extends InputSyncronizedBase { return abilities; } - for (SpellAbility ma : card.getManaAbilities()) { + for (SpellAbility ma : getAllManaAbilities(card)) { ma.setActivatingPlayer(player); - AbilityManaPart m = ma.getManaPartRecursive(); - if (m == null || !ma.canPlay()) { continue; } - if (!abilityProducesManaColor(ma, m, colorCanUse)) { continue; } - if (ma.isAbility() && ma.getRestrictions().isInstantSpeed()) { continue; } - if (!m.meetsManaRestrictions(saPaidFor)) { continue; } - - abilities.add(ma); + if (ma.isManaAbilityFor(saPaidFor, colorCanUse)) + abilities.add(ma); } return abilities; } @@ -189,11 +200,8 @@ public abstract class InputPayMana extends InputSyncronizedBase { System.err.print("Should wait till previous call to playAbility finishes."); return false; } - + // make sure computer's lands aren't selected - if (card.getController() != player) { - return false; - } byte colorCanUse = 0; byte colorNeeded = 0; @@ -206,106 +214,96 @@ public abstract class InputPayMana extends InputSyncronizedBase { colorCanUse |= ManaAtom.GENERIC; } - if (colorCanUse == 0) { // no mana cost or something + if (colorCanUse == 0) { // no mana cost or something return false; } - HashMap abilitiesMap = new HashMap<>(); - // you can't remove unneeded abilities inside a for (am:abilities) loop :( - - final String typeRes = manaCost.getSourceRestriction(); - if (StringUtils.isNotBlank(typeRes) && !card.getType().hasStringType(typeRes)) { - return false; - } - - boolean guessAbilityWithRequiredColors = true; - int amountOfMana = -1; - for (SpellAbility ma : card.getManaAbilities()) { - ma.setActivatingPlayer(player); - - AbilityManaPart m = ma.getManaPartRecursive(); - if (m == null || !ma.canPlay()) { continue; } - if (!abilityProducesManaColor(ma, m, colorCanUse)) { continue; } - if (ma.isAbility() && ma.getRestrictions().isInstantSpeed()) { continue; } - if (!m.meetsManaRestrictions(saPaidFor)) { continue; } - - // If Mana Abilities produce differing amounts of mana, let the player choose - int maAmount = GameActionUtil.amountOfManaGenerated(ma, true); - if (amountOfMana == -1) { - amountOfMana = maAmount; - } else { - if (amountOfMana != maAmount) { - guessAbilityWithRequiredColors = false; - } - } - - abilitiesMap.put(ma.getView(), ma); - - // skip express mana if the ability is not undoable or reusable - if (!ma.isUndoable() || !ma.getPayCosts().isRenewableResource() || ma.getSubAbility() != null) { - guessAbilityWithRequiredColors = false; - } - } - - if (abilitiesMap.isEmpty() || (chosenAbility != null && !abilitiesMap.containsKey(chosenAbility.getView()))) { - return false; - } - - // Store some information about color costs to help with any mana choices - if (colorNeeded == 0) { // only colorless left - if (saPaidFor.getHostCard() != null && saPaidFor.getHostCard().hasSVar("ManaNeededToAvoidNegativeEffect")) { - String[] negEffects = saPaidFor.getHostCard().getSVar("ManaNeededToAvoidNegativeEffect").split(","); - for (String negColor : negEffects) { - byte col = ManaAtom.fromName(negColor); - colorCanUse |= col; - } - } - } - - // If the card has any ability that tracks mana spent, skip express Mana choice - if (saPaidFor.tracksManaSpent()) { - colorCanUse = ColorSet.ALL_COLORS.getColor(); - guessAbilityWithRequiredColors = false; - } - - boolean choice = true; - boolean isPayingGeneric = false; - if (guessAbilityWithRequiredColors) { - // express Mana Choice - if (colorNeeded == 0) { - choice = false; - //avoid unnecessary prompt by pretending we need White - //for the sake of "Add one mana of any color" effects - colorNeeded = MagicColor.WHITE; - isPayingGeneric = true; // for further processing - } - else { - final HashMap colorMatches = new HashMap<>(); - for (SpellAbility sa : abilitiesMap.values()) { - if (abilityProducesManaColor(sa, sa.getManaPartRecursive(), colorNeeded)) { - colorMatches.put(sa.getView(), sa); - } - } - - if (colorMatches.isEmpty()) { - // can only match colorless just grab the first and move on. - // This is wrong. Sometimes all abilities aren't created equal - choice = false; - } - else if (colorMatches.size() < abilitiesMap.size()) { - // leave behind only color matches - abilitiesMap = colorMatches; - } - } - } - - // Exceptions for cards that have conditional abilities which are better handled manually - if (card.getName().equals("Cavern of Souls") && isPayingGeneric) { - choice = true; - } - final SpellAbility chosen; if (chosenAbility == null) { + HashMap abilitiesMap = new HashMap<>(); + // you can't remove unneeded abilities inside a for (am:abilities) loop :( + + final String typeRes = manaCost.getSourceRestriction(); + if (StringUtils.isNotBlank(typeRes) && !card.getType().hasStringType(typeRes)) { + return false; + } + + boolean guessAbilityWithRequiredColors = true; + int amountOfMana = -1; + for (SpellAbility ma : getAllManaAbilities(card)) { + ma.setActivatingPlayer(player); + + if (!ma.isManaAbilityFor(saPaidFor, colorCanUse)) { continue; } + + // If Mana Abilities produce differing amounts of mana, let the player choose + int maAmount = ma.totalAmountOfManaGenerated(saPaidFor, true); + if (amountOfMana == -1) { + amountOfMana = maAmount; + } else { + if (amountOfMana != maAmount) { + guessAbilityWithRequiredColors = false; + } + } + + abilitiesMap.put(ma.getView(), ma); + + // skip express mana if the ability is not undoable or reusable + if (!ma.isUndoable() || !ma.getPayCosts().isRenewableResource() || ma.getSubAbility() != null + || ma.isManaCannotCounter(saPaidFor)) { + guessAbilityWithRequiredColors = false; + } + } + + if (abilitiesMap.isEmpty()) { + return false; + } + + // Store some information about color costs to help with any mana choices + if (colorNeeded == 0) { // only colorless left + if (saPaidFor.getHostCard() != null && saPaidFor.getHostCard().hasSVar("ManaNeededToAvoidNegativeEffect")) { + String[] negEffects = saPaidFor.getHostCard().getSVar("ManaNeededToAvoidNegativeEffect").split(","); + for (String negColor : negEffects) { + byte col = ManaAtom.fromName(negColor); + colorCanUse |= col; + } + } + } + + // If the card has any ability that tracks mana spent, skip express Mana choice + if (saPaidFor.tracksManaSpent()) { + colorCanUse = ColorSet.ALL_COLORS.getColor(); + guessAbilityWithRequiredColors = false; + } + + boolean choice = true; + if (guessAbilityWithRequiredColors) { + // express Mana Choice + if (colorNeeded == 0) { + choice = false; + //avoid unnecessary prompt by pretending we need White + //for the sake of "Add one mana of any color" effects + colorNeeded = MagicColor.WHITE; + } + else { + final HashMap colorMatches = new HashMap<>(); + for (SpellAbility sa : abilitiesMap.values()) { + if (sa.isManaAbilityFor(saPaidFor, colorNeeded)) { + colorMatches.put(sa.getView(), sa); + } + } + + if (colorMatches.isEmpty()) { + // can only match colorless just grab the first and move on. + // This is wrong. Sometimes all abilities aren't created equal + choice = false; + } + else if (colorMatches.size() < abilitiesMap.size()) { + // leave behind only color matches + abilitiesMap = colorMatches; + } + } + } + ArrayList choices = new ArrayList<>(abilitiesMap.keySet()); chosen = abilitiesMap.size() > 1 && choice ? abilitiesMap.get(getController().getGui().one(Localizer.getInstance().getMessage("lblChooseManaAbility"), choices)) : abilitiesMap.get(choices.get(0)); } else { @@ -317,14 +315,13 @@ public abstract class InputPayMana extends InputSyncronizedBase { // Filter the colors for the express choice so that only actually producible colors can be chosen int producedColorMask = 0; for (final byte color : ManaAtom.MANATYPES) { - if (chosen.getManaPartRecursive().getOrigProduced().contains(MagicColor.toShortString(color)) - && colors.hasAnyColor(color)) { + if (chosen.canProduce(MagicColor.toShortString(color)) && colors.hasAnyColor(color)) { producedColorMask |= color; } } ColorSet producedAndNeededColors = ColorSet.fromMask(producedColorMask); - chosen.getManaPartRecursive().setExpressChoice(producedAndNeededColors); + chosen.setManaExpressChoice(producedAndNeededColors); // System.out.println("Chosen sa=" + chosen + " of " + chosen.getHostCard() + " to pay mana"); @@ -344,62 +341,6 @@ public abstract class InputPayMana extends InputSyncronizedBase { return true; } - private static boolean abilityProducesManaColor(final SpellAbility am, AbilityManaPart m, final byte neededColor) { - if (0 != (neededColor & ManaAtom.GENERIC)) { - return true; - } - - if (m.isAnyMana()) { - return true; - } - - // check for produce mana replacement effects - they mess this up, so just use the mana ability - final Card source = am.getHostCard(); - final Player activator = am.getActivatingPlayer(); - final Game g = source.getGame(); - final Map repParams = AbilityKey.newMap(); - repParams.put(AbilityKey.Mana, m.getOrigProduced()); - repParams.put(AbilityKey.Affected, source); - repParams.put(AbilityKey.Player, activator); - repParams.put(AbilityKey.AbilityMana, am); - - for (final Player p : g.getPlayers()) { - for (final Card crd : p.getAllCards()) { - for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) { - if (replacementEffect.requirementsCheck(g) - && replacementEffect.getMode() == ReplacementType.ProduceMana - && replacementEffect.canReplace(repParams) - && replacementEffect.hasParam("ManaReplacement") - && replacementEffect.zonesCheck(g.getZoneOf(crd))) { - return true; - } - } - } - } - - if (am.getApi() == ApiType.ManaReflected) { - final Iterable reflectableColors = CardUtil.getReflectableManaColors(am); - for (final String color : reflectableColors) { - if (0 != (neededColor & ManaAtom.fromName(color))) { - return true; - } - } - } - else { - // treat special mana if it always can be paid - if (m.isSpecialMana()) { - return true; - } - String colorsProduced = m.isComboMana() ? m.getComboColors() : m.mana(); - for (final String color : colorsProduced.split(" ")) { - if (0 != (neededColor & ManaAtom.fromName(color))) { - return true; - } - } - } - return false; - } - protected boolean isAlreadyPaid() { if (manaCost.isPaid()) { bPaid = true; diff --git a/forge-gui/src/main/java/forge/sound/EventVisualizer.java b/forge-gui/src/main/java/forge/sound/EventVisualizer.java index dc4e848b4f4..1269c80fe6a 100644 --- a/forge-gui/src/main/java/forge/sound/EventVisualizer.java +++ b/forge-gui/src/main/java/forge/sound/EventVisualizer.java @@ -33,6 +33,7 @@ import forge.game.event.GameEventTokenCreated; import forge.game.event.GameEventTurnEnded; import forge.game.event.GameEventZone; import forge.game.event.IGameEventVisitor; +import forge.game.spellability.AbilityManaPart; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; import forge.util.TextUtil; @@ -179,10 +180,10 @@ public class EventVisualizer extends IGameEventVisitor.Base imp @Override public SoundEffectType visit(final GameEventLandPlayed event) { SoundEffectType resultSound = null; - return resultSound; + return resultSound; } - + @Override public SoundEffectType visit(GameEventZone event) { Card card = event.card; @@ -208,10 +209,13 @@ public class EventVisualizer extends IGameEventVisitor.Base imp // I want to get all real colors this land can produce - no interest in colorless or devoid StringBuilder fullManaColors = new StringBuilder(); for (final SpellAbility sa : land.getManaAbilities()) { - String currManaColor = sa.getManaPartRecursive().getOrigProduced(); - if(!"C".equals(currManaColor)) { - fullManaColors.append(currManaColor); + for (AbilityManaPart mp : sa.getAllManaParts()) { + String currManaColor = mp.getOrigProduced(); + if(!"C".equals(currManaColor)) { + fullManaColors.append(currManaColor); + } } + } // No interest if "colors together" or "alternative colors" - only interested in colors themselves fullManaColors = new StringBuilder(TextUtil.fastReplace(fullManaColors.toString()," ", ""));