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

@@ -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 int amountOfManaGenerated(final SpellAbility sa, boolean multiply) {
// Calculate generated mana here for stack description and resolving
int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa) : 1;
AbilityManaPart abMana = sa.getManaPartRecursive();
if (sa.hasParam("Bonus")) {
// For mana abilities that get a bonus
// Bonus currently MULTIPLIES the base amount. Base Amounts should
// ALWAYS be Base
int bonus = 0;
if (sa.getParam("Bonus").equals("UrzaLands")) {
if (hasUrzaLands(sa.getActivatingPlayer())) {
bonus = Integer.parseInt(sa.getParam("BonusProduced"));
}
public static String generatedTotalMana(final SpellAbility sa) {
StringBuilder sb = new StringBuilder();
SpellAbility tail = sa;
while (tail != null) {
String value = generatedMana(tail);
if (!value.isEmpty() && !"0".equals(value)) {
sb.append(value).append(" ");
}
amount += bonus;
}
if (!multiply || abMana.isAnyMana() || abMana.isComboMana() || abMana.isSpecialMana()) {
return amount;
} else {
// For cards that produce like {C}{R} vs cards that produce {R}{R}.
return abMana.mana().split(" ").length * amount;
tail = tail.getSubAbility();
}
return sb.toString().trim();
}
public static String generatedMana(final SpellAbility sa) {
int amount = amountOfManaGenerated(sa, false);
int amount = sa.amountOfManaGenerated(false);
AbilityManaPart abMana = sa.getManaPart();
String baseMana;

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,46 +43,26 @@ public class ManaEffect extends SpellAbilityEffect {
return;
}
if (sa.hasParam("DoubleManaInPool")) {
for (final Player player : tgtPlayers) {
for (byte color : ManaAtom.MANATYPES) {
int amountColor = player.getManaPool().getAmountOfColor(color);
for (int i = 0; i < amountColor; i++) {
abMana.produceMana(MagicColor.toShortString(color), player, sa);
}
}
for (Player p : tgtPlayers) {
if (sa.usesTargeting() && !p.canBeTargetedBy(sa)) {
// Illegal target. Skip.
continue;
}
}
if (sa.hasParam("ProduceNoOtherMana")) {
return;
}
if (abMana.isComboMana()) {
for (Player p : tgtPlayers) {
if (abMana.isComboMana()) {
int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa) : 1;
if (tgt != null && !p.canBeTargetedBy(sa)) {
// Illegal target. Skip.
continue;
}
Player activator = sa.getActivatingPlayer();
String express = abMana.getExpressChoice();
String[] colorsProduced = abMana.getComboColors().split(" ");
final StringBuilder choiceString = new StringBuilder();
ColorSet colorOptions = null;
ColorSet colorOptions = ColorSet.fromNames(colorsProduced);
String[] colorsNeeded = express.isEmpty() ? null : express.split(" ");
if (!abMana.isAnyMana()) {
colorOptions = ColorSet.fromNames(colorsProduced);
} else {
colorOptions = ColorSet.fromNames(MagicColor.Constant.ONLY_COLORS);
}
boolean differentChoice = abMana.getOrigProduced().contains("Different");
ColorSet fullOptions = colorOptions;
for (int nMana = 0; nMana < amount; nMana++) {
String choice = "";
if (colorsNeeded != null && colorsNeeded.length > nMana) { // select from express choices if possible
if (colorsNeeded != null && colorsNeeded.length > nMana) { // select from express choices if possible
colorOptions = ColorSet
.fromMask(fullOptions.getColor() & ManaAtom.fromName(colorsNeeded[nMana]));
}
@@ -93,10 +71,10 @@ public class ManaEffect extends SpellAbilityEffect {
// just use the first possible color.
choice = colorsProduced[differentChoice ? nMana : 0];
} else {
byte chosenColor = activator.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa,
byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa,
differentChoice ? fullOptions : colorOptions);
if (chosenColor == 0)
throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + activator + " color mana choice is empty for " + card.getName());
throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + p + " color mana choice is empty for " + card.getName());
fullOptions = ColorSet.fromMask(fullOptions.getMyColor() - chosenColor);
choice = MagicColor.toShortString(chosenColor);
@@ -116,18 +94,10 @@ public class ManaEffect extends SpellAbilityEffect {
return;
}
game.action.nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", activator.getName(), choiceString), activator);
game.getAction().nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), choiceString), p);
abMana.setExpressChoice(choiceString.toString());
}
}
else if (abMana.isAnyMana()) {
for (Player p : tgtPlayers) {
if (tgt != null && !p.canBeTargetedBy(sa)) {
// Illegal target. Skip.
continue;
}
Player act = sa.getActivatingPlayer();
else if (abMana.isAnyMana()) {
// AI color choice is set in ComputerUtils so only human players need to make a choice
String colorsNeeded = abMana.getExpressChoice();
@@ -142,20 +112,14 @@ public class ManaEffect extends SpellAbilityEffect {
colorMenu = mask == 0 ? ColorSet.ALL_COLORS : ColorSet.fromMask(mask);
byte val = p.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu);
if (0 == val) {
throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + act + " color mana choice is empty for " + card.getName());
throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + p + " color mana choice is empty for " + card.getName());
}
choice = MagicColor.toShortString(val);
game.action.nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", act.getName(), choice), act);
game.getAction().nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), choice), p);
abMana.setExpressChoice(choice);
}
}
else if (abMana.isSpecialMana()) {
for (Player p : tgtPlayers) {
if (tgt != null && !p.canBeTargetedBy(sa)) {
// Illegal target. Skip.
continue;
}
else if (abMana.isSpecialMana()) {
String type = abMana.getOrigProduced().split("Special ")[1];
@@ -177,7 +141,7 @@ public class ManaEffect extends SpellAbilityEffect {
if (cs.isMonoColor())
sb.append(MagicColor.toShortString(s.getColorMask()));
else /* (cs.isMulticolor()) */ {
byte chosenColor = sa.getActivatingPlayer().getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
sb.append(MagicColor.toShortString(chosenColor));
}
}
@@ -210,34 +174,36 @@ public class ManaEffect extends SpellAbilityEffect {
abMana.setExpressChoice(ColorSet.fromMask(colors));
} else if (type.startsWith("EachColoredManaSymbol")) {
final String res = type.split("_")[1];
final CardCollection list = AbilityUtils.getDefinedCards(card, res, sa);
StringBuilder sb = new StringBuilder();
for (Card c : list) {
String mana = c.getManaCost().toString();
for (int i = 0; i < mana.length(); i++) {
char symbol = mana.charAt(i);
switch (symbol) {
case 'W':
case 'U':
case 'B':
case 'R':
case 'G':
sb.append(symbol).append(' ');
break;
for (Card c : AbilityUtils.getDefinedCards(card, res, sa)) {
for (ManaCostShard s : c.getManaCost()) {
ColorSet cs = ColorSet.fromMask(s.getColorMask());
if(cs.isColorless())
continue;
sb.append(' ');
if (cs.isMonoColor())
sb.append(MagicColor.toShortString(s.getColorMask()));
else /* (cs.isMulticolor()) */ {
byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
sb.append(MagicColor.toShortString(chosenColor));
}
}
}
abMana.setExpressChoice(sb.toString().trim());
} else if (type.startsWith("DoubleManaInPool")) {
StringBuilder sb = new StringBuilder();
for (byte color : ManaAtom.MANATYPES) {
sb.append(StringUtils.repeat(MagicColor.toShortString(color), " ", p.getManaPool().getAmountOfColor(color))).append(" ");
}
abMana.setExpressChoice(sb.toString().trim());
}
if (abMana.getExpressChoice().isEmpty()) {
System.out.println("AbilityFactoryMana::manaResolve() - special mana effect is empty for " + sa.getHostCard().getName());
}
}
}
for (final Player player : tgtPlayers) {
abMana.produceMana(GameActionUtil.generatedMana(sa), player, sa);
abMana.produceMana(GameActionUtil.generatedMana(sa), p, sa);
}
// Only clear express choice after mana has been produced

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,28 +399,30 @@ public final class CardUtil {
maxChoices++;
}
CardCollection cards = null;
CardCollection cards;
// Reuse AF_Defined in a slightly different way
if (validCard.startsWith("Defined.")) {
cards = AbilityUtils.getDefinedCards(card, TextUtil.fastReplace(validCard, "Defined.", ""), abMana);
} else {
if (sa.getActivatingPlayer() == null) {
sa.setActivatingPlayer(sa.getHostCard().getController());
if (sa.hasParam("Valid")) {
final String validCard = sa.getParam("Valid");
// Reuse AF_Defined in a slightly different way
if (validCard.startsWith("Defined.")) {
cards = AbilityUtils.getDefinedCards(card, TextUtil.fastReplace(validCard, "Defined.", ""), abMana);
} else {
if (sa.getActivatingPlayer() == null) {
sa.setActivatingPlayer(sa.getHostCard().getController());
}
final Game game = sa.getActivatingPlayer().getGame();
cards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), validCard, abMana.getActivatingPlayer(), card);
}
final Game game = sa.getActivatingPlayer().getGame();
cards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), validCard, abMana.getActivatingPlayer(), card);
}
// remove anything cards that is already in parents
for (final Card p : parents) {
cards.remove(p);
}
// remove anything cards that is already in parents
cards.removeAll(parents);
if ((cards.size() == 0) && !reflectProperty.equals("Produced")) {
return colors;
if (cards.isEmpty()) {
return colors;
}
} else {
cards = new CardCollection();
}
if (reflectProperty.equals("Is")) { // Meteor Crater
for (final Card card1 : cards) {
// For each card, go through all the colors and if the card is that color, add
@@ -436,7 +437,7 @@ public final class CardUtil {
}
} else if (reflectProperty.equals("Produced")) {
// Why is this name so similar to the one below?
final String producedColors = abMana instanceof AbilitySub ? (String) abMana.getRootAbility().getTriggeringObject(AbilityKey.Produced) : (String) abMana.getTriggeringObject(AbilityKey.Produced);
final String producedColors = (String) abMana.getRootAbility().getTriggeringObject(AbilityKey.Produced);
for (final String col : MagicColor.Constant.ONLY_COLORS) {
final String s = MagicColor.toShortString(col);
if (producedColors.contains(s)) {
@@ -469,7 +470,7 @@ public final class CardUtil {
}
continue;
}
colors = canProduce(maxChoices, ab.getManaPart(), colors);
colors = canProduce(maxChoices, ab, colors);
if (!parents.contains(ab.getHostCard())) {
parents.add(ab.getHostCard());
}
@@ -486,19 +487,18 @@ public final class CardUtil {
return colors;
}
public static Set<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,23 +166,27 @@ 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()) {
if (tryPayCostWithMana(saPaidFor, manaCost, mana, false)) {
saPaidFor.getPayingMana().add(0, mana);
// need to get all mana from all ManaAbilities of the SpellAbility
for (AbilityManaPart mp : saPayment.getAllManaParts()) {
for (final Mana mana : mp.getLastManaProduced()) {
if (tryPayCostWithMana(saPaidFor, manaCost, mana, false)) {
saPaidFor.getPayingMana().add(0, mana);
}
}
}
}
@@ -216,8 +220,12 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<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);
String message = chosenRE.getDescription();
if ( !StringUtils.isEmpty(message))
if (chosenRE.getHostCard() != null) {
message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName());
}
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
// Updated Replacements need to be logged elsewhere because its otherwise in the wrong order
if (res != ReplacementResult.Updated) {
String message = chosenRE.getDescription();
if ( !StringUtils.isEmpty(message))
if (chosenRE.getHostCard() != null) {
message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName());
}
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
}
return res;
}
@@ -344,25 +348,11 @@ public class ReplacementHandler {
Player player = host.getController();
if (mapParams.containsKey("ManaReplacement")) {
final SpellAbility manaAb = (SpellAbility) runParams.get(AbilityKey.AbilityMana);
final Player player1 = (Player) runParams.get(AbilityKey.Player);
final String rep = (String) runParams.get(AbilityKey.Mana);
// Replaced mana type
final Card repHost = host;
String repType = repHost.getSVar(mapParams.get("ManaReplacement"));
if (repType.contains("Chosen") && repHost.hasChosenColor()) {
repType = TextUtil.fastReplace(repType, "Chosen", MagicColor.toShortString(repHost.getChosenColor()));
}
manaAb.getManaPart().setManaReplaceType(repType);
manaAb.getManaPart().produceMana(rep, player1, manaAb);
} else {
player.getController().playSpellAbilityNoStack(effectSA, true);
// if the spellability is a replace effect then its some new logic
// if ReplacementResult is set in run params use that instead
if (runParams.containsKey(AbilityKey.ReplacementResult)) {
return (ReplacementResult) runParams.get(AbilityKey.ReplacementResult);
}
player.getController().playSpellAbilityNoStack(effectSA, true);
// if the spellability is a replace effect then its some new logic
// if ReplacementResult is set in run params use that instead
if (runParams.containsKey(AbilityKey.ReplacementResult)) {
return (ReplacementResult) runParams.get(AbilityKey.ReplacementResult);
}
return ReplacementResult.Replaced;
@@ -407,6 +397,10 @@ public class ReplacementHandler {
ret.setActiveZone(EnumSet.copyOf(ZoneType.listValueOf(activeZones)));
}
if (mapParams.containsKey("ReplaceWith")) {
ret.setOverridingAbility(AbilityFactory.getAbility(host, mapParams.get("ReplaceWith"), ret));
}
return ret;
}
}

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()) {
player.setTappedLandForManaThisTurn(true);
if (source.isLand() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost() ) {
player.setTappedLandForManaThisTurn(true);
}
// Clear Mana replacement
this.manaReplaceType = "";
} // end produceMana(String)
/**
@@ -256,7 +268,15 @@ public class AbilityManaPart implements java.io.Serializable {
* @return a {@link java.lang.String} object.
*/
public String getManaRestrictions() {
return this.manaRestrictions;
return manaRestrictions;
}
public void setExtraManaRestriction(String str) {
this.extraManaRestrictions = str;
}
public boolean meetsManaRestrictions(final SpellAbility sa) {
return meetsManaRestrictions(sa, this.manaRestrictions) && meetsManaRestrictions(sa, this.extraManaRestrictions);
}
/**
@@ -268,14 +288,14 @@ public class AbilityManaPart implements java.io.Serializable {
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
public boolean meetsManaRestrictions(final SpellAbility sa) {
public boolean meetsManaRestrictions(final SpellAbility sa, String restrictions) {
// No restrictions
if (this.manaRestrictions.isEmpty()) {
if (restrictions.isEmpty()) {
return true;
}
// Loop over restrictions
for (String restriction : this.manaRestrictions.split(",")) {
for (String restriction : restrictions.split(",")) {
if (restriction.equals("nonSpell")) {
return !sa.isSpell();
}
@@ -306,9 +326,9 @@ public class AbilityManaPart implements java.io.Serializable {
if (sa.isValid(restriction, this.getSourceCard().getController(), this.getSourceCard(), null)) {
return true;
}
if (restriction.equals("CantPayGenericCosts")) {
return true;
return true;
}
if (sa.isAbility()) {
@@ -331,7 +351,7 @@ public class AbilityManaPart implements java.io.Serializable {
return false;
}
/**
* <p>
* meetsManaShardRestrictions.
@@ -340,44 +360,44 @@ public class AbilityManaPart implements java.io.Serializable {
* @param shard
* a {@link forge.card.mana.ManaCostShard} object.
* @param color
* the color of mana being paid
* the color of mana being paid
* @return a boolean.
*/
public boolean meetsManaShardRestrictions(final ManaCostShard shard, final byte color) {
if (this.manaRestrictions.isEmpty()) {
if (this.manaRestrictions.isEmpty()) {
return true;
}
for (String restriction : this.manaRestrictions.split(",")) {
if (restriction.equals("CantPayGenericCosts")) {
if (shard.isGeneric()) {
if (shard.isOr2Generic() && shard.isColor(color)) {
continue;
} else {
return false;
}
} else {
continue;
}
}
if (restriction.equals("CantPayGenericCosts")) {
if (shard.isGeneric()) {
if (shard.isOr2Generic() && shard.isColor(color)) {
continue;
} else {
return false;
}
} else {
continue;
}
}
}
return true;
}
/**
* <p>
* meetsSpellAndShardRestrictions.
* </p>
*
*
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param shard
* a {@link forge.card.mana.ManaCostShard} object.
* @param color
* the color of mana being paid
* the color of mana being paid
* @return a boolean.
*/
public boolean meetsSpellAndShardRestrictions(final SpellAbility sa, final ManaCostShard shard, final byte color) {
return this.meetsManaRestrictions(sa) && this.meetsManaShardRestrictions(shard, color);
return this.meetsManaRestrictions(sa) && this.meetsManaShardRestrictions(shard, color);
}
/**
@@ -465,10 +485,6 @@ public class AbilityManaPart implements java.io.Serializable {
return this.getOrigProduced().contains("Special");
}
public final boolean canProduce(final String s) {
return canProduce(s, null);
}
/**
* <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;
}
/**
* <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 (isAnyMana()) {
return true;
}
if (manaReplace.startsWith("Any")) {
// Replace any type and amount
String replaced = manaReplace.split("->")[1];
if (replaced.equals("Any")) {
byte rs = MagicColor.GREEN;
if (act != null) {
rs = act.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS);
// check for produce mana replacement effects - they mess this up, so just use the mana ability
final Card source = am.getHostCard();
final Player activator = am.getActivatingPlayer();
final Game g = source.getGame();
final Map<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;
}
}
replaced = MagicColor.toShortString(rs);
}
return replaced;
}
final Pattern splitter = Pattern.compile("->");
// Replace any type
for (String part : manaReplace.split(" & ")) {
final String[] v = splitter.split(part, 2);
// TODO Colorless mana replacement is probably different now?
if (v[0].equals("Colorless")) {
repMap.put("[0-9][0-9]?", v.length > 1 ? v[1].trim() : "");
} else {
repMap.put(v[0], v.length > 1 ? v[1].trim() : "");
}
}
// Handle different replacement simultaneously
Pattern pattern = Pattern.compile(StringUtils.join(repMap.keySet().iterator(), "|"));
Matcher m = pattern.matcher(original);
StringBuffer sb = new StringBuffer();
while(m.find()) {
if (m.group().matches("[0-9][0-9]?")) {
final String rep = StringUtils.repeat(repMap.get("[0-9][0-9]?") + " ",
Integer.parseInt(m.group())).trim();
m.appendReplacement(sb, rep);
} else {
m.appendReplacement(sb, repMap.get(m.group()));
if (am.getApi() == ApiType.ManaReflected) {
final Iterable<String> reflectableColors = CardUtil.getReflectableManaColors(am);
for (final String color : reflectableColors) {
if (0 != (neededColor & ManaAtom.fromName(color))) {
return true;
}
}
}
m.appendTail(sb);
String replaced = sb.toString();
while (replaced.contains("Any")) {
byte rs = MagicColor.GREEN;
if (act != null) {
rs = act.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS);
else {
// treat special mana if it always can be paid
if (isSpecialMana()) {
return true;
}
String colorsProduced = isComboMana() ? getComboColors() : mana();
for (final String color : colorsProduced.split(" ")) {
if (0 != (neededColor & ManaAtom.fromName(color))) {
return true;
}
}
replaced = replaced.replaceFirst("Any", MagicColor.toShortString(rs));
}
return replaced;
return false;
}
} // end class AbilityMana

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;
}
tail = tail.getSubAbility();
public final List<AbilityManaPart> getAllManaParts() {
AbilityManaPart mp = getManaPart();
if (mp == null && subAbility == null) {
return ImmutableList.of();
}
return null;
List<AbilityManaPart> result = Lists.newArrayList();
if (mp != null) {
result.add(mp);
}
if (subAbility != null) {
result.addAll(subAbility.getAllManaParts());
}
return result;
}
public final boolean isManaAbility() {
@@ -266,7 +348,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return false;
}
return getManaPartRecursive() != null;
SpellAbility tail = this;
while (tail != null) {
if (tail.manaPart != null) {
return true;
}
tail = tail.getSubAbility();
}
return false;
}
protected final void setManaPart(AbilityManaPart manaPart0) {
@@ -464,6 +553,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
conditions = condition;
}
public boolean metConditions() {
return getConditions() != null && getConditions().areMet(this);
}
public List<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