Merge branch 'manaReplacementEffect' into 'master'

AbilityMana: better ReplacementEffect logic

Closes #1266

See merge request core-developers/forge!2650
This commit is contained in:
Michael Kamensky
2021-02-12 08:50:24 +00:00
90 changed files with 1163 additions and 878 deletions

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
@@ -317,6 +319,190 @@ 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<AbilityKey, Object> 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<ReplacementEffect> reList = game.getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other);
List<SpellAbility> replaceMana = Lists.newArrayList();
List<SpellAbility> replaceType = Lists.newArrayList();
List<SpellAbility> 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<AbilityKey, Object> 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,23 +578,13 @@ 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<SpellAbility> 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());
@@ -443,6 +619,21 @@ 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");
@@ -451,7 +642,8 @@ public class ComputerUtilMana {
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
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()) {
@@ -459,7 +651,8 @@ public class ComputerUtilMana {
break;
}
}
if (saList == null || saList.isEmpty()) { // failed to converge, revert to paying generic
if (saList == null || saList.isEmpty()) {
// failed to converge, revert to paying generic
saList = sourcesForShards.get(toPay);
hasConverge = false;
}
@@ -539,23 +732,13 @@ public class ComputerUtilMana {
}
}
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<SpellAbility> 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);
@@ -571,19 +754,9 @@ 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<SpellAbility> 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);
@@ -641,7 +805,8 @@ public class ComputerUtilMana {
List<Mana> manaSpentToPay, final boolean hasConverge) {
// arrange all mana abilities by color produced.
final ListMultimap<Integer, SpellAbility> 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,14 +817,15 @@ public class ComputerUtilMana {
// select which abilities may be used for each shard
ListMultimap<ManaCostShard, SpellAbility> sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost);
if (hasConverge) { // add extra colors for paying converge
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);
}
}
}
@@ -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;
@@ -1387,20 +1553,6 @@ public class ComputerUtilMana {
final ListMultimap<Integer, SpellAbility> manaMap = ArrayListMultimap.create();
final Game game = ai.getGame();
List<ReplacementEffect> 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
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
// setup produce mana replacement effects
String origin = mp.getOrigProduced();
final Map<AbilityKey, Object> repParams = AbilityKey.newMap();
repParams.put(AbilityKey.Mana, mp.getOrigProduced());
repParams.put(AbilityKey.Mana, origin);
repParams.put(AbilityKey.Affected, sourceCard);
repParams.put(AbilityKey.Player, ai);
repParams.put(AbilityKey.AbilityMana, m);
repParams.put(AbilityKey.AbilityMana, m); // RootAbility
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()));
}
mp.setManaReplaceType(repType);
}
}
List<ReplacementEffect> reList = game.getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other);
if (reList.isEmpty()) {
Set<String> reflectedColors = CardUtil.getReflectableManaColors(m);
// find possible colors
if (mp.canProduce("W", m) || reflectedColors.contains(MagicColor.Constant.WHITE)) {
manaMap.get(ManaAtom.WHITE).add(m);
for (byte color : MagicColor.WUBRG) {
if (tail.canThisProduce(MagicColor.toShortString(color)) || reflectedColors.contains(MagicColor.toLongString(color))) {
manaMap.put((int)color, 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 (m.canThisProduce("C") || reflectedColors.contains(MagicColor.Constant.COLORLESS)) {
manaMap.put(ManaAtom.COLORLESS, m);
}
if (mp.canProduce("R", m) || reflectedColors.contains(MagicColor.Constant.RED)) {
manaMap.get(ManaAtom.RED).add(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 (mp.canProduce("G", m) || reflectedColors.contains(MagicColor.Constant.GREEN)) {
manaMap.get(ManaAtom.GREEN).add(m);
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);
}
if (mp.canProduce("C", m) || reflectedColors.contains(MagicColor.Constant.COLORLESS)) {
manaMap.get(ManaAtom.COLORLESS).add(m);
} 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);
}
if (mp.isSnow()) {
manaMap.get(ManaAtom.IS_SNOW).add(m);
}
}
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);
}
}
}
}
tail = tail.getSubAbility();
}
if (m.getHostCard().isSnow()) {
manaMap.put(ManaAtom.IS_SNOW, m);
}
if (DEBUG_MANA_PAYMENT) {
System.out.println("DEBUG_MANA_PAYMENT: groupSourcesByManaColor manaMap = " + manaMap);

View File

@@ -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) {

View File

@@ -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));

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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)) {

View File

@@ -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;
}

View File

@@ -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<CardTraitBase> isHostCard(final Card host) {
return new Predicate<CardTraitBase>() {
@Override
public boolean apply(final CardTraitBase sa) {
return host.equals(sa.getHostCard());
}
};
}
public static final Predicate<CardTraitBase> hasParam(final String name) {
return new Predicate<CardTraitBase>() {
@Override

View File

@@ -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")) {

View File

@@ -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 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(" ");
}
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"));
tail = tail.getSubAbility();
}
return sb.toString().trim();
}
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;
}
}
public static String generatedMana(final SpellAbility sa) {
int amount = amountOfManaGenerated(sa, false);
int amount = sa.amountOfManaGenerated(false);
AbilityManaPart abMana = sa.getManaPart();
String baseMana;

View File

@@ -77,4 +77,5 @@ public abstract class TriggerReplacementBase extends CardTraitBase implements II
this.overridingAbility = overridingAbility0;
}
abstract public SpellAbility ensureAbility();
}

View File

@@ -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<String, String> 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);
}
}

View File

@@ -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.<numHB>.<numNotHB>
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.<numHB>.<numNotHB>
if (sq[0].startsWith("UrzaLands")) {
return CardFactoryUtil.doXMath(Integer.parseInt(sa.getActivatingPlayer().hasUrzaLands() ? sq[1] : sq[2]), expr, c);
}
//Count$SearchedLibrary.<DefinedPlayer>
if (sq[0].contains("SearchedLibrary")) {
int sum = 0;

View File

@@ -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),

View File

@@ -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);
}
}

View File

@@ -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<Player> tgtPlayers = getTargetPlayers(sa);
final TargetRestrictions tgt = sa.getTargetRestrictions();
final boolean optional = sa.hasParam("Optional");
final Game game = sa.getActivatingPlayer().getGame();
@@ -45,41 +43,21 @@ 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);
}
}
}
}
if (sa.hasParam("ProduceNoOtherMana")) {
return;
}
if (abMana.isComboMana()) {
for (Player p : tgtPlayers) {
int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa) : 1;
if (tgt != null && !p.canBeTargetedBy(sa)) {
if (sa.usesTargeting() && !p.canBeTargetedBy(sa)) {
// Illegal target. Skip.
continue;
}
Player activator = sa.getActivatingPlayer();
if (abMana.isComboMana()) {
int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa) : 1;
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++) {
@@ -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();
// 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;
}
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

View File

@@ -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<String> colors = CardUtil.getReflectableManaColors(sa);
final List<Player> 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);
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>) sa.getReplacingObject(AbilityKey.OriginalParams);
Map<AbilityKey, Object> 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<AbilityKey, Object> 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;
}
}
}

View File

@@ -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);

View File

@@ -2353,7 +2353,7 @@ public class Card extends GameEntity implements Comparable<Card>, 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<Card>, IHasSVars {
return true;
}
} else {
if (mana.getManaPart().canProduce(MagicColor.toShortString(s))) {
if (mana.canProduce(MagicColor.toShortString(s))) {
return true;
}
}

View File

@@ -358,12 +358,12 @@ public class CardFactory {
// Name first so Senty has the Card name
c.setName(face.getName());
for (Entry<String, String> 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<String, String> v : face.getVariables()) c.setSVar(v.getKey(), v.getValue());
// keywords not before variables
c.addIntrinsicKeywords(face.getKeywords(), false);

View File

@@ -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;
}

View File

@@ -912,7 +912,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;
}
}

View File

@@ -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,8 +399,10 @@ public final class CardUtil {
maxChoices++;
}
CardCollection cards = null;
CardCollection cards;
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);
@@ -414,14 +415,14 @@ public final class CardUtil {
}
// remove anything cards that is already in parents
for (final Card p : parents) {
cards.remove(p);
}
cards.removeAll(parents);
if ((cards.size() == 0) && !reflectProperty.equals("Produced")) {
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<String> canProduce(final int maxChoices, final AbilityManaPart ab,
public static Set<String> canProduce(final int maxChoices, final SpellAbility sa,
final Set<String> 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);
}

View File

@@ -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<ManaCostShard, ShardCount> unpaidShards = new HashMap<>();
private final Map<ManaCostShard, ShardCount> unpaidShards = Maps.newHashMap();
private Map<String, Integer> 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<ManaCostShard> 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<ManaCostShard> shards = Lists.newArrayList(unpaidShards.keySet());
if (nGeneric > 0) {
if (nGeneric <= 20) {

View File

@@ -166,26 +166,30 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
owner.updateManaForView();
}
private void removeMana(final Mana mana) {
Collection<Mana> 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<SpellAbility> 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()) {
// 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);
}
}
}
}
public boolean tryPayCostWithColor(byte colorCode, SpellAbility saPaidFor, ManaCostBeingPaid manaCost) {
Mana manaFound = null;
@@ -216,8 +220,12 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
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;
}

View File

@@ -3528,4 +3528,11 @@ public class Player extends GameEntity implements Comparable<Player> {
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")));
}
}

View File

@@ -31,10 +31,10 @@ public class ReplaceProduceMana extends ReplacementEffect {
*/
@Override
public boolean canReplace(Map<AbilityKey, Object> 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<AbilityKey, Object> runParams, SpellAbility sa) {
sa.setReplacingObject(AbilityKey.Mana, runParams.get(AbilityKey.Mana));
}
}

View File

@@ -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;
}
}

View File

@@ -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);
// 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,26 +348,12 @@ 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);
}
}
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;
}
}

View File

@@ -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;
/**
* <p>
@@ -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<Mana> 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<AbilityKey, Object> 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<AbilityKey, Object> 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()) {
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();
}
@@ -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);
}
/**
* <p>
* 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);
}
/**
* <p>
* isBasic.
* </p>
*
* @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;
if (isAnyMana()) {
return true;
}
/**
* <p>
* applyManaReplacement.
* </p>
* @return a String
*/
public static String applyManaReplacement(final SpellAbility sa, final String original) {
final Map<String, String> 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 (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);
}
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() : "");
// 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<AbilityKey, Object> 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;
}
}
// 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()));
}
}
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);
if (am.getApi() == ApiType.ManaReflected) {
final Iterable<String> reflectableColors = CardUtil.getReflectableManaColors(am);
for (final String color : reflectableColors) {
if (0 != (neededColor & ManaAtom.fromName(color))) {
return true;
}
replaced = replaced.replaceFirst("Any", MagicColor.toShortString(rs));
}
return replaced;
}
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;
}
}
}
return false;
}
} // end class AbilityMana

View File

@@ -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);
}
}

View File

@@ -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;
public final List<AbilityManaPart> getAllManaParts() {
AbilityManaPart mp = getManaPart();
if (mp == null && subAbility == null) {
return ImmutableList.of();
}
tail = tail.getSubAbility();
List<AbilityManaPart> result = Lists.newArrayList();
if (mp != null) {
result.add(mp);
}
return null;
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<Mana> 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;
}

View File

@@ -169,13 +169,7 @@ public class TriggerHandler {
if (wt.getTriggers() != null)
continue;
List<Trigger> 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<Trigger> getActiveTrigger(final TriggerType mode, final Map<AbilityKey, Object> runParams) {
List<Trigger> trigger = Lists.newArrayList();
for (final Trigger t : activeTriggers) {
if (canRunTrigger(t, mode, runParams)) {
trigger.add(t);
}
}
return trigger;
}
}

View File

@@ -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<AbilityKey, Object> runParams) {
sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card, AbilityKey.Player, AbilityKey.Produced);
sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card, AbilityKey.Player, AbilityKey.Produced, AbilityKey.Activator);
}
@Override

View File

@@ -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.

View File

@@ -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 lands 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 lands ability causes you to add one or more mana of the chosen color, add one additional mana of that color.

View File

@@ -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

View File

@@ -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.

View File

@@ -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}.
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
SVar:Picture:http://www.wizards.com/global/images/magic/general/crypt_ghast.jpg
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}.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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}.
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
SVar:Picture:http://www.wizards.com/global/images/magic/general/nirkana_revenant.jpg
Oracle:Whenever you tap a Swamp for mana, add an additional {B}.\n{B}: Nirkana Revenant gets +1/+1 until end of turn.

View File

@@ -2,7 +2,7 @@ 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}.
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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.
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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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 dont 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 dont control for mana. Spend this mana only to cast spells.
Oracle:Until end of turn, you may tap lands you dont control for mana. Spend this mana only to cast spells.

View File

@@ -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.

View File

@@ -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

View File

@@ -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
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.

View File

@@ -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.

View File

@@ -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<SpellAbility> 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<SpellAbility> getAllManaAbilities(Card card) {
List<SpellAbility> result = Lists.newArrayList();
for (SpellAbility sa : card.getManaAbilities()) {
result.add(sa);
result.addAll(GameActionUtil.getAlternativeCosts(sa, player));
}
final Collection<SpellAbility> 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<SpellAbility> getUsefulManaAbilities(Card card) {
List<SpellAbility> abilities = new ArrayList<>();
@@ -160,14 +176,9 @@ 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; }
if (ma.isManaAbilityFor(saPaidFor, colorCanUse))
abilities.add(ma);
}
return abilities;
@@ -191,9 +202,6 @@ public abstract class InputPayMana extends InputSyncronizedBase {
}
// make sure computer's lands aren't selected
if (card.getController() != player) {
return false;
}
byte colorCanUse = 0;
byte colorNeeded = 0;
@@ -210,6 +218,8 @@ public abstract class InputPayMana extends InputSyncronizedBase {
return false;
}
final SpellAbility chosen;
if (chosenAbility == null) {
HashMap<SpellAbilityView, SpellAbility> abilitiesMap = new HashMap<>();
// you can't remove unneeded abilities inside a for (am:abilities) loop :(
@@ -220,17 +230,13 @@ public abstract class InputPayMana extends InputSyncronizedBase {
boolean guessAbilityWithRequiredColors = true;
int amountOfMana = -1;
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; }
if (!ma.isManaAbilityFor(saPaidFor, colorCanUse)) { continue; }
// If Mana Abilities produce differing amounts of mana, let the player choose
int maAmount = GameActionUtil.amountOfManaGenerated(ma, true);
int maAmount = ma.totalAmountOfManaGenerated(saPaidFor, true);
if (amountOfMana == -1) {
amountOfMana = maAmount;
} else {
@@ -242,12 +248,13 @@ public abstract class InputPayMana extends InputSyncronizedBase {
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) {
if (!ma.isUndoable() || !ma.getPayCosts().isRenewableResource() || ma.getSubAbility() != null
|| ma.isManaCannotCounter(saPaidFor)) {
guessAbilityWithRequiredColors = false;
}
}
if (abilitiesMap.isEmpty() || (chosenAbility != null && !abilitiesMap.containsKey(chosenAbility.getView()))) {
if (abilitiesMap.isEmpty()) {
return false;
}
@@ -269,7 +276,6 @@ public abstract class InputPayMana extends InputSyncronizedBase {
}
boolean choice = true;
boolean isPayingGeneric = false;
if (guessAbilityWithRequiredColors) {
// express Mana Choice
if (colorNeeded == 0) {
@@ -277,12 +283,11 @@ public abstract class InputPayMana extends InputSyncronizedBase {
//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<SpellAbilityView, SpellAbility> colorMatches = new HashMap<>();
for (SpellAbility sa : abilitiesMap.values()) {
if (abilityProducesManaColor(sa, sa.getManaPartRecursive(), colorNeeded)) {
if (sa.isManaAbilityFor(saPaidFor, colorNeeded)) {
colorMatches.put(sa.getView(), sa);
}
}
@@ -299,13 +304,6 @@ public abstract class InputPayMana extends InputSyncronizedBase {
}
}
// 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) {
ArrayList<SpellAbilityView> 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<AbilityKey, Object> 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<String> 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;

View File

@@ -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;
@@ -208,11 +209,14 @@ public class EventVisualizer extends IGameEventVisitor.Base<SoundEffectType> 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();
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()," ", ""));