From ff7f81c30789e8f65af72f155254ec53aa01a62a Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sat, 15 May 2021 15:27:15 +0000 Subject: [PATCH] Card property targeted player --- .../src/main/java/forge/ai/AiController.java | 41 +- .../src/main/java/forge/ai/ComputerUtil.java | 2 +- .../main/java/forge/ai/ComputerUtilCard.java | 32 +- .../java/forge/ai/ComputerUtilCombat.java | 45 +- .../main/java/forge/ai/ComputerUtilCost.java | 2 +- .../src/main/java/forge/ai/SpecialCardAi.java | 3 +- .../main/java/forge/ai/ability/AttachAi.java | 2 +- .../java/forge/ai/ability/DamageDealAi.java | 3 +- .../main/java/forge/game/CardTraitBase.java | 2 +- .../java/forge/game/ability/AbilityUtils.java | 2107 ++++++++++++++--- .../game/ability/SpellAbilityEffect.java | 3 +- .../ability/effects/ChooseSourceEffect.java | 4 +- .../ability/effects/DamageEachEffect.java | 7 +- .../game/ability/effects/StoreSVarEffect.java | 7 +- .../java/forge/game/card/CardFactoryUtil.java | 1570 ------------ .../java/forge/game/card/CardProperty.java | 159 +- .../main/java/forge/game/card/CardUtil.java | 19 +- .../java/forge/game/cost/CostAdjustment.java | 15 +- .../spellability/SpellAbilityRestriction.java | 9 +- .../StaticAbilityCantAttackBlock.java | 3 +- .../StaticAbilityCantBeCast.java | 2 +- .../StaticAbilityContinuous.java | 16 +- .../game/trigger/TriggerChangesZone.java | 5 +- .../TriggerSpellAbilityCastOrCopy.java | 2 +- .../cardsfolder/h/hidetsugus_second_rite.txt | 3 +- .../res/cardsfolder/r/robber_of_the_rich.txt | 2 +- .../java/forge/gui/card/CardScriptParser.java | 3 +- 27 files changed, 1959 insertions(+), 2109 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index ad5127eda33..fee407e2c3c 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -47,6 +47,7 @@ import forge.game.Game; import forge.game.GameActionUtil; import forge.game.GameEntity; import forge.game.GlobalRuleChange; +import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.ability.SpellApiBased; @@ -79,6 +80,7 @@ import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; import forge.game.replacement.ReplaceMoved; import forge.game.replacement.ReplacementEffect; +import forge.game.replacement.ReplacementLayer; import forge.game.replacement.ReplacementType; import forge.game.spellability.AbilitySub; import forge.game.spellability.LandAbility; @@ -509,20 +511,35 @@ public class AiController { //try to skip lands that enter the battlefield tapped if (!nonLandsInHand.isEmpty()) { - CardCollection nonTappeddLands = new CardCollection(); + CardCollection nonTappedLands = new CardCollection(); for (Card land : landList) { - // Is this the best way to check if a land ETB Tapped? - if (land.hasSVar("ETBTappedSVar")) { + // check replacement effects if land would enter tapped or not + final Map repParams = AbilityKey.mapFromAffected(land); + repParams.put(AbilityKey.Origin, land.getZone().getZoneType()); + repParams.put(AbilityKey.Destination, ZoneType.Battlefield); + repParams.put(AbilityKey.Source, land); + + boolean foundTapped = false; + for (ReplacementEffect re : player.getGame().getReplacementHandler().getReplacementList(ReplacementType.Moved, repParams, ReplacementLayer.Other)) { + SpellAbility reSA = re.ensureAbility(); + if (reSA == null || !ApiType.Tap.equals(reSA.getApi())) { + continue; + } + reSA.setActivatingPlayer(reSA.getHostCard().getController()); + if (reSA.metConditions()) { + foundTapped = true; + break; + } + } + + if (foundTapped) { continue; } - // Glacial Fortress and friends - if (land.hasSVar("ETBCheckSVar") && CardFactoryUtil.xCount(land, land.getSVar("ETBCheckSVar")) == 0) { - continue; - } - nonTappeddLands.add(land); + + nonTappedLands.add(land); } - if (!nonTappeddLands.isEmpty()) { - landList = nonTappeddLands; + if (!nonTappedLands.isEmpty()) { + landList = nonTappedLands; } } @@ -1779,7 +1796,7 @@ public class AiController { compareTo = Integer.parseInt(strCmpTo); } catch (final Exception ignored) { if (sa == null) { - compareTo = CardFactoryUtil.xCount(hostCard, hostCard.getSVar(strCmpTo)); + compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), effect); } else { compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa); } @@ -1789,7 +1806,7 @@ public class AiController { int left = 0; if (sa == null) { - left = CardFactoryUtil.xCount(hostCard, hostCard.getSVar(svarToCheck)); + left = AbilityUtils.calculateAmount(hostCard, svarToCheck, effect); } else { left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa); } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index d52cfaab894..55ff9777da3 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -1125,7 +1125,7 @@ public class ComputerUtil { creatures2.add(creatures.get(i)); } } - if (((creatures2.size() + CardUtil.getThisTurnCast("Creature.YouCtrl", vengevines.get(0)).size()) > 1) + if (((creatures2.size() + CardUtil.getThisTurnCast("Creature.YouCtrl", vengevines.get(0), null).size()) > 1) && card.isCreature() && card.getManaCost().getCMC() <= 3) { return true; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 05658fb9d49..fe40c155db6 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -982,7 +982,7 @@ public class ComputerUtilCard { for(byte c : MagicColor.WUBRG) { String devotionCode = "Count$Devotion." + MagicColor.toLongString(c); - int devotion = CardFactoryUtil.xCount(sa.getHostCard(), devotionCode); + int devotion = AbilityUtils.calculateAmount(sa.getHostCard(), devotionCode, sa); if (devotion > curDevotion && !CardLists.filter(hand, CardPredicates.isColor(c)).isEmpty()) { curDevotion = devotion; chosenColor = MagicColor.toLongString(c); @@ -1679,37 +1679,27 @@ public class ComputerUtilCard { // remove old boost that might be copied for (final StaticAbility stAb : c.getStaticAbilities()) { vCard.removePTBoost(c.getTimestamp(), stAb.getId()); - final Map params = stAb.getMapParams(); - if (!params.get("Mode").equals("Continuous")) { + if (!stAb.getParam("Mode").equals("Continuous")) { continue; } - if (!params.containsKey("Affected")) { + if (!stAb.hasParam("Affected")) { continue; } - if (!params.containsKey("AddPower") && !params.containsKey("AddToughness")) { + if (!stAb.hasParam("AddPower") && !stAb.hasParam("AddToughness")) { continue; } - final String valid = params.get("Affected"); - if (!vCard.isValid(valid, c.getController(), c, null)) { + if (!vCard.isValid(stAb.getParam("Affected").split(","), c.getController(), c, stAb)) { continue; } int att = 0; - if (params.containsKey("AddPower")) { - String addP = params.get("AddPower"); - if (addP.equals("AffectedX")) { - att = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addP)); - } else { - att = AbilityUtils.calculateAmount(c, addP, stAb); - } + if (stAb.hasParam("AddPower")) { + String addP = stAb.getParam("AddPower"); + att = AbilityUtils.calculateAmount(addP.startsWith("Affected") ? vCard : c, addP, stAb, true); } int def = 0; - if (params.containsKey("AddToughness")) { - String addT = params.get("AddToughness"); - if (addT.equals("AffectedY")) { - def = CardFactoryUtil.xCount(vCard, AbilityUtils.getSVar(stAb, addT)); - } else { - def = AbilityUtils.calculateAmount(c, addT, stAb); - } + if (stAb.hasParam("AddToughness")) { + String addT = stAb.getParam("AddToughness"); + def = AbilityUtils.calculateAmount(addT.startsWith("Affected") ? vCard : c, addT, stAb, true); } vCard.addPTBoost(att, def, c.getTimestamp(), stAb.getId()); } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 3547c6cb4d7..48010975c29 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -33,7 +33,6 @@ import forge.game.ability.ApiType; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; -import forge.game.card.CardFactoryUtil; import forge.game.card.CardLists; import forge.game.card.CardPredicates; import forge.game.card.CardUtil; @@ -938,25 +937,18 @@ public class ComputerUtilCombat { final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command)); for (final Card card : cardList) { for (final StaticAbility stAb : card.getStaticAbilities()) { - final Map params = stAb.getMapParams(); - if (!params.get("Mode").equals("Continuous")) { + if (!stAb.getParam("Mode").equals("Continuous")) { continue; } - if (!params.containsKey("Affected") || !params.get("Affected").contains("blocking")) { + if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("blocking")) { continue; } - final String valid = TextUtil.fastReplace(params.get("Affected"), "blocking", "Creature"); - if (!blocker.isValid(valid, card.getController(), card, null)) { + final String valid = TextUtil.fastReplace(stAb.getParam("Affected"), "blocking", "Creature"); + if (!blocker.isValid(valid, card.getController(), card, stAb)) { continue; } - if (params.containsKey("AddPower")) { - if (params.get("AddPower").equals("X")) { - power += CardFactoryUtil.xCount(card, card.getSVar("X")); - } else if (params.get("AddPower").equals("Y")) { - power += CardFactoryUtil.xCount(card, card.getSVar("Y")); - } else { - power += Integer.valueOf(params.get("AddPower")); - } + if (stAb.hasParam("AddPower")) { + power += AbilityUtils.calculateAmount(card, stAb.getParam("AddPower"), stAb); } } } @@ -1247,25 +1239,18 @@ public class ComputerUtilCombat { final CardCollectionView cardList = CardCollection.combine(game.getCardsIn(ZoneType.Battlefield), game.getCardsIn(ZoneType.Command)); for (final Card card : cardList) { for (final StaticAbility stAb : card.getStaticAbilities()) { - final Map params = stAb.getMapParams(); - if (!params.get("Mode").equals("Continuous")) { + if (!stAb.getParam("Mode").equals("Continuous")) { continue; } - if (!params.containsKey("Affected") || !params.get("Affected").contains("attacking")) { + if (!stAb.hasParam("Affected") || !stAb.getParam("Affected").contains("attacking")) { continue; } - final String valid = TextUtil.fastReplace(params.get("Affected"), "attacking", "Creature"); - if (!attacker.isValid(valid, card.getController(), card, null)) { + final String valid = TextUtil.fastReplace(stAb.getParam("Affected"), "attacking", "Creature"); + if (!attacker.isValid(valid, card.getController(), card, stAb)) { continue; } - if (params.containsKey("AddPower")) { - if (params.get("AddPower").equals("X")) { - power += CardFactoryUtil.xCount(card, card.getSVar("X")); - } else if (params.get("AddPower").equals("Y")) { - power += CardFactoryUtil.xCount(card, card.getSVar("Y")); - } else { - power += Integer.valueOf(params.get("AddPower")); - } + if (stAb.hasParam("AddPower")) { + power += AbilityUtils.calculateAmount(card, stAb.getParam("AddPower"), stAb); } } } @@ -1339,7 +1324,7 @@ public class ComputerUtilCombat { } else if (bonus.contains("TriggeredAttacker$CardToughness")) { bonus = TextUtil.fastReplace(bonus, "TriggeredAttacker$CardToughness", TextUtil.concatNoSpace("Number$", String.valueOf(attacker.getNetToughness()))); } - power += CardFactoryUtil.xCount(source, bonus); + power += AbilityUtils.calculateAmount(source, bonus, sa); } } @@ -1529,7 +1514,7 @@ public class ComputerUtilCombat { } else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1"); } - toughness += CardFactoryUtil.xCount(source, bonus); + toughness += AbilityUtils.calculateAmount(source, bonus, sa); } } else if (ApiType.PumpAll.equals(sa.getApi())) { @@ -1562,7 +1547,7 @@ public class ComputerUtilCombat { } else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1"); } - toughness += CardFactoryUtil.xCount(source, bonus); + toughness += AbilityUtils.calculateAmount(source, bonus, sa); } } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index cc1ef10ecd2..532764b0b1f 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -157,7 +157,7 @@ public class ComputerUtilCost { if (typeList.size() > ai.getMaxHandSize()) { continue; } - int num = AbilityUtils.calculateAmount(source, disc.getAmount(), null); + int num = AbilityUtils.calculateAmount(source, disc.getAmount(), sa); for (int i = 0; i < num; i++) { Card pref = ComputerUtil.getCardPreference(ai, source, "DiscardCost", typeList); diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index 827613d93ee..dcb194d663e 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -39,7 +39,6 @@ import forge.game.ability.ApiType; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; -import forge.game.card.CardFactoryUtil; import forge.game.card.CardLists; import forge.game.card.CardPredicates; import forge.game.card.CardUtil; @@ -1085,7 +1084,7 @@ public class SpecialCardAi { return false; } String prominentColor = ComputerUtilCard.getMostProminentColor(ai.getCardsIn(ZoneType.Battlefield)); - int devotion = CardFactoryUtil.xCount(sa.getHostCard(), "Count$Devotion." + prominentColor); + int devotion = AbilityUtils.calculateAmount(sa.getHostCard(), "Count$Devotion." + prominentColor, sa); int activationCost = sa.getPayCosts().getTotalMana().getCMC() + (sa.getPayCosts().hasTapCost() ? 1 : 0); // do not use this SA if devotion to most prominent color is less than its own activation cost + 1 (to actually get advantage) diff --git a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java index 287f2148b86..e2edae67f9a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java @@ -766,7 +766,7 @@ public class AttachAi extends SpellAbilityAi { int powerBuff = 0; for (StaticAbility stAb : sa.getHostCard().getStaticAbilities()) { if ("Card.EquippedBy".equals(stAb.getParam("Affected")) && stAb.hasParam("AddPower")) { - powerBuff = AbilityUtils.calculateAmount(sa.getHostCard(), stAb.getParam("AddPower"), null); + powerBuff = AbilityUtils.calculateAmount(sa.getHostCard(), stAb.getParam("AddPower"), stAb); } } if (combat != null && combat.isAttacking(equipped) && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS, sa.getActivatingPlayer())) { diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java index 06884107ade..075d1634061 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -30,7 +30,6 @@ import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.Card; import forge.game.card.CardCollection; -import forge.game.card.CardFactoryUtil; import forge.game.card.CardLists; import forge.game.card.CardPredicates; import forge.game.card.CounterEnumType; @@ -132,7 +131,7 @@ public class DamageDealAi extends DamageAiBase { // Set PayX here to maximum value. It will be adjusted later depending on the target. sa.setXManaCostPaid(dmg); } else if (sa.getSVar(damage).contains("InYourHand") && source.isInZone(ZoneType.Hand)) { - dmg = CardFactoryUtil.xCount(source, sa.getSVar(damage)) - 1; // the card will be spent casting the spell, so actual damage is 1 less + dmg = AbilityUtils.calculateAmount(source, damage, sa) - 1; // the card will be spent casting the spell, so actual damage is 1 less } else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) { // cards that deal damage by the number of cards in target player's hand, e.g. Sudden Impact if (sa.getTargetRestrictions().canTgtPlayer()) { diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java index 453daf47e6e..73e6f37bdde 100644 --- a/forge-game/src/main/java/forge/game/CardTraitBase.java +++ b/forge-game/src/main/java/forge/game/CardTraitBase.java @@ -441,7 +441,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView, } if (params.containsKey("WerewolfTransformCondition")) { - if (!CardUtil.getLastTurnCast("Card", this.getHostCard()).isEmpty()) { + if (!CardUtil.getLastTurnCast("Card", this.getHostCard(), this).isEmpty()) { return false; } } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index bdf020b1a7e..492983b16b3 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1,8 +1,12 @@ package forge.game.ability; import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -15,6 +19,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import forge.card.CardStateName; import forge.card.CardType; @@ -37,16 +42,19 @@ import forge.game.card.CardLists; import forge.game.card.CardPredicates; import forge.game.card.CardUtil; import forge.game.card.CounterType; +import forge.game.card.CardPredicates.Presets; import forge.game.cost.Cost; import forge.game.keyword.Keyword; import forge.game.keyword.KeywordInterface; import forge.game.mana.Mana; import forge.game.mana.ManaConversionMatrix; import forge.game.mana.ManaCostBeingPaid; +import forge.game.phase.PhaseHandler; import forge.game.player.Player; import forge.game.player.PlayerCollection; import forge.game.player.PlayerPredicates; import forge.game.spellability.AbilitySub; +import forge.game.spellability.OptionalCost; import forge.game.spellability.Spell; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityRestriction; @@ -58,6 +66,7 @@ import forge.game.trigger.TriggerType; import forge.game.zone.ZoneType; import forge.util.Aggregates; import forge.util.Expressions; +import forge.util.MyRandom; import forge.util.TextUtil; import forge.util.collect.FCollection; import forge.util.collect.FCollectionView; @@ -318,7 +327,7 @@ public class AbilityUtils { origin = null; validFilter = workingCopy[2]; } - for (final Card cl : CardUtil.getThisTurnEntered(destination, origin, validFilter, hostCard)) { + for (final Card cl : CardUtil.getThisTurnEntered(destination, origin, validFilter, hostCard, sa)) { Card gameState = game.getCardState(cl, null); // cards that use this should only care about if it is still in that zone // TODO if all LKI needs to be returned, need to change CardCollection return from this function @@ -509,33 +518,33 @@ public class AbilityUtils { if (calcX[0].startsWith("Count")) { val = AbilityUtils.xCount(card, calcX[1], ability); } else if (calcX[0].startsWith("Number")) { - val = CardFactoryUtil.xCount(card, svarval); + val = AbilityUtils.xCount(card, svarval, ability); } else if (calcX[0].startsWith("SVar")) { final String[] l = calcX[1].split("/"); final String m = CardFactoryUtil.extractOperators(calcX[1]); - val = CardFactoryUtil.doXMath(AbilityUtils.calculateAmount(card, l[0], ability), m, card); + val = doXMath(calculateAmount(card, l[0], ability), m, card, ability); } else if (calcX[0].startsWith("PlayerCount")) { final String hType = calcX[0].substring(11); final FCollection players = new FCollection<>(); if (hType.equals("Players") || hType.equals("")) { players.addAll(game.getPlayers()); - val = CardFactoryUtil.playerXCount(players, calcX[1], card); + val = playerXCount(players, calcX[1], card, ability); } else if (hType.equals("YourTeam")) { players.addAll(player.getYourTeam()); - val = CardFactoryUtil.playerXCount(players, calcX[1], card); + val = playerXCount(players, calcX[1], card, ability); } else if (hType.equals("Opponents")) { players.addAll(player.getOpponents()); - val = CardFactoryUtil.playerXCount(players, calcX[1], card); + val = playerXCount(players, calcX[1], card, ability); } else if (hType.equals("RegisteredOpponents")) { players.addAll(Iterables.filter(game.getRegisteredPlayers(),PlayerPredicates.isOpponentOf(player))); - val = CardFactoryUtil.playerXCount(players, calcX[1], card); + val = playerXCount(players, calcX[1], card, ability); } else if (hType.equals("Other")) { players.addAll(player.getAllOtherPlayers()); - val = CardFactoryUtil.playerXCount(players, calcX[1], card); + val = playerXCount(players, calcX[1], card, ability); } else if (hType.equals("Remembered")) { for (final Object o : card.getRemembered()) { @@ -543,12 +552,12 @@ public class AbilityUtils { players.add((Player) o); } } - val = CardFactoryUtil.playerXCount(players, calcX[1], card); + val = playerXCount(players, calcX[1], card, ability); } else if (hType.equals("NonActive")) { players.addAll(game.getPlayers()); players.remove(game.getPhaseHandler().getPlayerTurn()); - val = CardFactoryUtil.playerXCount(players, calcX[1], card); + val = playerXCount(players, calcX[1], card, ability); } else if (hType.startsWith("PropertyYou")) { if (ability instanceof SpellAbility) { @@ -557,7 +566,7 @@ public class AbilityUtils { } else { players.add(player); } - val = CardFactoryUtil.playerXCount(players, calcX[1], card); + val = playerXCount(players, calcX[1], card, ability); } else if (hType.startsWith("Property")) { String defined = hType.split("Property")[1]; @@ -568,25 +577,16 @@ public class AbilityUtils { players.add(p); } } - val = CardFactoryUtil.playerXCount(players, calcX[1], card); + val = playerXCount(players, calcX[1], card, ability); } else { val = 0; } } - - if (val != null) { - if (maxto) { - val = Math.max(val, 0); - } - return val * multiplier; + else if (calcX[0].equals("OriginalHost")) { + val = xCount(ability.getOriginalHost(), calcX[1], ability); } - - if (calcX[0].equals("OriginalHost")) { - return AbilityUtils.xCount(ability.getOriginalHost(), calcX[1], ability) * multiplier; - } - - if (calcX[0].startsWith("Remembered")) { + else if (calcX[0].startsWith("Remembered")) { // Add whole Remembered list to handlePaid final CardCollection list = new CardCollection(); Card newCard = card; @@ -609,10 +609,9 @@ public class AbilityUtils { } } - return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; + val = handlePaid(list, calcX[1], card, ability); } - - if (calcX[0].startsWith("Imprinted")) { + else if (calcX[0].startsWith("Imprinted")) { // Add whole Imprinted list to handlePaid final CardCollection list = new CardCollection(); Card newCard = card; @@ -629,10 +628,9 @@ public class AbilityUtils { } } - return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; + val = handlePaid(list, calcX[1], card, ability); } - - if (calcX[0].matches("Enchanted")) { + else if (calcX[0].matches("Enchanted")) { // Add whole Enchanted list to handlePaid final CardCollection list = new CardCollection(); if (card.isEnchanting()) { @@ -641,191 +639,187 @@ public class AbilityUtils { list.add(game.getCardState((Card) o)); } } - return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; + val = handlePaid(list, calcX[1], card, ability); } // All the following only work for SpellAbilities - if (!(ability instanceof SpellAbility)) { - return 0; - } - - final SpellAbility sa = (SpellAbility) ability; - if (calcX[0].startsWith("Modes")) { - int chosenModes = 0; - SpellAbility sub = sa; - while(sub != null) { - if (!sub.getSVar("CharmOrder").equals("")) { - chosenModes++; + else if (ability instanceof SpellAbility) { + final SpellAbility sa = (SpellAbility) ability; + if (calcX[0].startsWith("Modes")) { + int chosenModes = 0; + SpellAbility sub = sa; + while(sub != null) { + if (!sub.getSVar("CharmOrder").equals("")) { + chosenModes++; + } + sub = sub.getSubAbility(); } - sub = sub.getSubAbility(); + // Count Math + final String m = CardFactoryUtil.extractOperators(calcX[1]); + val = doXMath(chosenModes, m, card, ability); } - // Count Math - final String m = CardFactoryUtil.extractOperators(calcX[1]); - return CardFactoryUtil.doXMath(chosenModes, m, card) * multiplier; - } - - // Player attribute counting - if (calcX[0].startsWith("TargetedPlayer")) { - final List players = new ArrayList<>(); - final SpellAbility saTargeting = sa.getSATargetingPlayer(); - if (null != saTargeting) { - Iterables.addAll(players, saTargeting.getTargets().getTargetPlayers()); - } - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } - if (calcX[0].startsWith("ThisTargetedPlayer")) { - final List players = new ArrayList<>(); - Iterables.addAll(players, sa.getTargets().getTargetPlayers()); - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } - if (calcX[0].startsWith("TargetedObjects")) { - final List objects = new ArrayList<>(); - // Make list of all targeted objects starting with the root SpellAbility - SpellAbility loopSA = sa.getRootAbility(); - while (loopSA != null) { - if (loopSA.getTargetRestrictions() != null) { - Iterables.addAll(objects, loopSA.getTargets()); + // Player attribute counting + else if (calcX[0].startsWith("TargetedPlayer")) { + final List players = new ArrayList<>(); + final SpellAbility saTargeting = sa.getSATargetingPlayer(); + if (null != saTargeting) { + Iterables.addAll(players, saTargeting.getTargets().getTargetPlayers()); } - loopSA = loopSA.getSubAbility(); + val = playerXCount(players, calcX[1], card, ability); } - return CardFactoryUtil.objectXCount(objects, calcX[1], card) * multiplier; - } - if (calcX[0].startsWith("TargetedController")) { - final List players = new ArrayList<>(); - final CardCollection list = getDefinedCards(card, "Targeted", sa); - final List sas = AbilityUtils.getDefinedSpellAbilities(card, "Targeted", sa); + else if (calcX[0].startsWith("ThisTargetedPlayer")) { + final List players = new ArrayList<>(); + Iterables.addAll(players, sa.getTargets().getTargetPlayers()); + val = playerXCount(players, calcX[1], card, ability); + } + else if (calcX[0].startsWith("TargetedObjects")) { + final List objects = new ArrayList<>(); + // Make list of all targeted objects starting with the root SpellAbility + SpellAbility loopSA = sa.getRootAbility(); + while (loopSA != null) { + if (loopSA.getTargetRestrictions() != null) { + Iterables.addAll(objects, loopSA.getTargets()); + } + loopSA = loopSA.getSubAbility(); + } + val = objectXCount(objects, calcX[1], card, ability); + } + else if (calcX[0].startsWith("TargetedController")) { + final List players = new ArrayList<>(); + final CardCollection list = getDefinedCards(card, "Targeted", sa); + final List sas = AbilityUtils.getDefinedSpellAbilities(card, "Targeted", sa); - for (final Card c : list) { - final Player p = c.getController(); - if (!players.contains(p)) { - players.add(p); + for (final Card c : list) { + final Player p = c.getController(); + if (!players.contains(p)) { + players.add(p); + } + } + for (final SpellAbility s : sas) { + final Player p = s.getHostCard().getController(); + if (!players.contains(p)) { + players.add(p); + } + } + val = playerXCount(players, calcX[1], card, ability); + } + else if (calcX[0].startsWith("TargetedByTarget")) { + final CardCollection tgtList = new CardCollection(); + final List saList = getDefinedSpellAbilities(card, "Targeted", sa); + + for (final SpellAbility s : saList) { + tgtList.addAll(getDefinedCards(s.getHostCard(), "Targeted", s)); + // Check sub-abilities, so that modal cards like Abzan Charm are correctly handled. + // TODO: Should this be done in a more general place, like in getDefinedCards()? + AbilitySub abSub = s.getSubAbility(); + while (abSub != null) { + tgtList.addAll(getDefinedCards(abSub.getHostCard(), "Targeted", abSub)); + abSub = abSub.getSubAbility(); + } + } + val = handlePaid(tgtList, calcX[1], card, ability); + } + else if (calcX[0].startsWith("TriggeredPlayers") || calcX[0].equals("TriggeredCardController")) { + String key = calcX[0]; + if (calcX[0].startsWith("TriggeredPlayers")) { + key = "Triggered" + key.substring(16); + } + val = playerXCount(getDefinedPlayers(card, key, sa), calcX[1], card, ability); + } + else if (calcX[0].startsWith("TriggeredPlayer") || calcX[0].startsWith("TriggeredTarget")) { + final SpellAbility root = sa.getRootAbility(); + Object o = root.getTriggeringObject(AbilityKey.fromString(calcX[0].substring(9))); + val = o instanceof Player ? playerXProperty((Player) o, calcX[1], card, ability) : 0; + } + else if (calcX[0].equals("TriggeredSpellAbility")) { + final SpellAbility root = sa.getRootAbility(); + SpellAbility sat = (SpellAbility) root.getTriggeringObject(AbilityKey.SpellAbility); + val = calculateAmount(sat.getHostCard(), calcX[1], sat); + } + else if (calcX[0].startsWith("TriggerCount")) { + // TriggerCount is similar to a regular Count, but just + // pulls Integer Values from Trigger objects + final SpellAbility root = sa.getRootAbility(); + final String[] l = calcX[1].split("/"); + final String m = CardFactoryUtil.extractOperators(calcX[1]); + final Integer count = (Integer) root.getTriggeringObject(AbilityKey.fromString(l[0])); + + val = doXMath(ObjectUtils.firstNonNull(count, 0), m, card, ability); + } + else if (calcX[0].startsWith("ReplaceCount")) { + // ReplaceCount is similar to a regular Count, but just + // pulls Integer Values from Replacement objects + final SpellAbility root = sa.getRootAbility(); + final String[] l = calcX[1].split("/"); + final String m = CardFactoryUtil.extractOperators(calcX[1]); + final Integer count = (Integer) root.getReplacingObject(AbilityKey.fromString(l[0])); + + val = doXMath(ObjectUtils.firstNonNull(count, 0), m, card, ability); + } else { // these ones only for handling lists + Iterable list = null; + if (calcX[0].startsWith("Sacrificed")) { + list = sa.getRootAbility().getPaidList("Sacrificed"); + } + else if (calcX[0].startsWith("Discarded")) { + final SpellAbility root = sa.getRootAbility(); + list = root.getPaidList("Discarded"); + if ((null == list) && root.isTrigger()) { + list = root.getHostCard().getSpellPermanent().getPaidList("Discarded"); + } + } + else if (calcX[0].startsWith("Exiled")) { + list = sa.getRootAbility().getPaidList("Exiled"); + } + else if (calcX[0].startsWith("Milled")) { + list = sa.getRootAbility().getPaidList("Milled"); + } + else if (calcX[0].startsWith("Tapped")) { + list = sa.getRootAbility().getPaidList("Tapped"); + } + else if (calcX[0].startsWith("Revealed")) { + list = sa.getRootAbility().getPaidList("Revealed"); + } + else if (calcX[0].startsWith("Targeted")) { + list = sa.findTargetedCards(); + } + else if (calcX[0].startsWith("ParentTargeted")) { + SpellAbility parent = sa.getParentTargetingCard(); + if (parent != null) { + list = parent.findTargetedCards(); + } + else { + list = null; + } + } + else if (calcX[0].startsWith("TriggerRemembered")) { + final SpellAbility root = sa.getRootAbility(); + list = Iterables.filter(root.getTriggerRemembered(), Card.class); + } + else if (calcX[0].startsWith("TriggerObjects")) { + final SpellAbility root = sa.getRootAbility(); + list = Iterables.filter((Iterable) root.getTriggeringObject(AbilityKey.fromString(calcX[0].substring(14))), Card.class); + } + else if (calcX[0].startsWith("Triggered")) { + final SpellAbility root = sa.getRootAbility(); + list = new CardCollection((Card) root.getTriggeringObject(AbilityKey.fromString(calcX[0].substring(9)))); + } + else if (calcX[0].startsWith("Replaced")) { + final SpellAbility root = sa.getRootAbility(); + list = new CardCollection((Card) root.getReplacingObject(AbilityKey.fromString(calcX[0].substring(8)))); + } + if (list != null) { + val = handlePaid(list, calcX[1], card, ability); } } - for (final SpellAbility s : sas) { - final Player p = s.getHostCard().getController(); - if (!players.contains(p)) { - players.add(p); - } - } - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; } - if (calcX[0].startsWith("TargetedByTarget")) { - final CardCollection tgtList = new CardCollection(); - final List saList = getDefinedSpellAbilities(card, "Targeted", sa); - for (final SpellAbility s : saList) { - tgtList.addAll(getDefinedCards(s.getHostCard(), "Targeted", s)); - // Check sub-abilities, so that modal cards like Abzan Charm are correctly handled. - // TODO: Should this be done in a more general place, like in getDefinedCards()? - AbilitySub abSub = s.getSubAbility(); - while (abSub != null) { - tgtList.addAll(getDefinedCards(abSub.getHostCard(), "Targeted", abSub)); - abSub = abSub.getSubAbility(); - } + if (val != null) { + if (maxto) { + val = Math.max(val, 0); } - return CardFactoryUtil.handlePaid(tgtList, calcX[1], card) * multiplier; + return val * multiplier; } - if (calcX[0].startsWith("TriggeredPlayers") || calcX[0].equals("TriggeredCardController")) { - String key = calcX[0]; - if (calcX[0].startsWith("TriggeredPlayers")) { - key = "Triggered" + key.substring(16); - } - return CardFactoryUtil.playerXCount(getDefinedPlayers(card, key, sa), calcX[1], card) * multiplier; - } - if (calcX[0].startsWith("TriggeredPlayer") || calcX[0].startsWith("TriggeredTarget")) { - final SpellAbility root = sa.getRootAbility(); - Object o = root.getTriggeringObject(AbilityKey.fromString(calcX[0].substring(9))); - return o instanceof Player ? CardFactoryUtil.playerXProperty((Player) o, calcX[1], card) * multiplier : 0; - } - if (calcX[0].equals("TriggeredSpellAbility")) { - final SpellAbility root = sa.getRootAbility(); - SpellAbility sat = (SpellAbility) root.getTriggeringObject(AbilityKey.SpellAbility); - return calculateAmount(sat.getHostCard(), calcX[1], sat); - } - // Added on 9/30/12 (ArsenalNut) - Ended up not using but might be useful in future - /* - if (calcX[0].startsWith("EnchantedController")) { - final ArrayList players = new ArrayList(); - players.addAll(AbilityFactory.getDefinedPlayers(card, "EnchantedController", ability)); - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } - */ - - Iterable list; - if (calcX[0].startsWith("Sacrificed")) { - list = sa.getRootAbility().getPaidList("Sacrificed"); - } - else if (calcX[0].startsWith("Discarded")) { - final SpellAbility root = sa.getRootAbility(); - list = root.getPaidList("Discarded"); - if ((null == list) && root.isTrigger()) { - list = root.getHostCard().getSpellPermanent().getPaidList("Discarded"); - } - } - else if (calcX[0].startsWith("Exiled")) { - list = sa.getRootAbility().getPaidList("Exiled"); - } - else if (calcX[0].startsWith("Milled")) { - list = sa.getRootAbility().getPaidList("Milled"); - } - else if (calcX[0].startsWith("Tapped")) { - list = sa.getRootAbility().getPaidList("Tapped"); - } - else if (calcX[0].startsWith("Revealed")) { - list = sa.getRootAbility().getPaidList("Revealed"); - } - else if (calcX[0].startsWith("Targeted")) { - list = sa.findTargetedCards(); - } - else if (calcX[0].startsWith("ParentTargeted")) { - SpellAbility parent = sa.getParentTargetingCard(); - if (parent != null) { - list = parent.findTargetedCards(); - } - else { - list = null; - } - } - else if (calcX[0].startsWith("TriggerRemembered")) { - final SpellAbility root = sa.getRootAbility(); - list = Iterables.filter(root.getTriggerRemembered(), Card.class); - } - else if (calcX[0].startsWith("TriggerObjects")) { - final SpellAbility root = sa.getRootAbility(); - list = Iterables.filter((Iterable) root.getTriggeringObject(AbilityKey.fromString(calcX[0].substring(14))), Card.class); - } - else if (calcX[0].startsWith("Triggered")) { - final SpellAbility root = sa.getRootAbility(); - list = new CardCollection((Card) root.getTriggeringObject(AbilityKey.fromString(calcX[0].substring(9)))); - } - else if (calcX[0].startsWith("TriggerCount")) { - // TriggerCount is similar to a regular Count, but just - // pulls Integer Values from Trigger objects - final SpellAbility root = sa.getRootAbility(); - final String[] l = calcX[1].split("/"); - final String m = CardFactoryUtil.extractOperators(calcX[1]); - final Integer count = (Integer) root.getTriggeringObject(AbilityKey.fromString(l[0])); - - return CardFactoryUtil.doXMath(ObjectUtils.firstNonNull(count, 0), m, card) * multiplier; - } - else if (calcX[0].startsWith("Replaced")) { - final SpellAbility root = sa.getRootAbility(); - list = new CardCollection((Card) root.getReplacingObject(AbilityKey.fromString(calcX[0].substring(8)))); - } - else if (calcX[0].startsWith("ReplaceCount")) { - // ReplaceCount is similar to a regular Count, but just - // pulls Integer Values from Replacement objects - final SpellAbility root = sa.getRootAbility(); - final String[] l = calcX[1].split("/"); - final String m = CardFactoryUtil.extractOperators(calcX[1]); - final Integer count = (Integer) root.getReplacingObject(AbilityKey.fromString(l[0])); - - return CardFactoryUtil.doXMath(ObjectUtils.firstNonNull(count, 0), m, card) * multiplier; - } - else { - return 0; - } - return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; + return 0; } /** @@ -1657,6 +1651,26 @@ public class AbilityUtils { final String expr = CardFactoryUtil.extractOperators(s2); final Player player = ctb == null ? null : ctb instanceof SpellAbility ? ((SpellAbility)ctb).getActivatingPlayer() : ctb.getHostCard().getController(); + // accept straight numbers + if (l[0].startsWith("Number$")) { + final String number = l[0].substring(7); + if (number.equals("ChosenNumber")) { // TODO remove in favor of Count ChosenNumber + int x = c.getChosenNumber() == null ? 0 : c.getChosenNumber(); + return doXMath(x, expr, c, ctb); + } + return doXMath(Integer.parseInt(number), expr, c, ctb); + } + + if (l[0].startsWith("Count$")) { + l[0] = l[0].substring(6); + } + + if (l[0].startsWith("SVar$")) { + String n = l[0].substring(5); + String v = ctb == null ? c.getSVar(n) : ctb.getSVar(n); + return doXMath(xCount(c, v, ctb), expr, c, ctb); + } + final String[] sq; sq = l[0].split("\\."); @@ -1668,12 +1682,8 @@ public class AbilityUtils { final String[] compString = sq[0].split(" "); final int lhs = calculateAmount(c, compString[1], ctb); final int rhs = calculateAmount(c, compString[2].substring(2), ctb); - if (Expressions.compare(lhs, compString[2], rhs)) { - return CardFactoryUtil.doXMath(calculateAmount(c, sq[1], ctb), expr, c); - } - else { - return CardFactoryUtil.doXMath(calculateAmount(c, sq[2], ctb), expr, c); - } + boolean v = Expressions.compare(lhs, compString[2], rhs); + return doXMath(calculateAmount(c, sq[v ? 1 : 2], ctb), expr, c, ctb); } if (ctb instanceof SpellAbility) { final SpellAbility sa = (SpellAbility) ctb; @@ -1685,19 +1695,19 @@ public class AbilityUtils { // 107.3i If an object gains an ability, the value of X within that ability is the value defined by that ability, // or 0 if that ability doesn’t define a value of X. This is an exception to rule 107.3h. This may occur with ability-adding effects, text-changing effects, or copy effects. if (root.getXManaCostPaid() != null) { - return CardFactoryUtil.doXMath(root.getXManaCostPaid(), expr, c); + return doXMath(root.getXManaCostPaid(), expr, c, ctb); } if (root.isTrigger()) { Trigger t = root.getTrigger(); if (t == null) { - return CardFactoryUtil.doXMath(0, expr, c); + return doXMath(0, expr, c, ctb); } // ImmediateTrigger should check for the Ability which created the trigger if (t.getSpawningAbility() != null) { root = t.getSpawningAbility().getRootAbility(); - return CardFactoryUtil.doXMath(root.getXManaCostPaid(), expr, c); + return doXMath(root.getXManaCostPaid(), expr, c, ctb); } // 107.3k If an object’s enters-the-battlefield triggered ability or replacement effect refers to X, @@ -1705,68 +1715,54 @@ public class AbilityUtils { // the value of X for that ability is the same as the value of X for that spell, although the value of X for that permanent is 0. if (TriggerType.ChangesZone.equals(t.getMode()) && ZoneType.Battlefield.name().equals(t.getParam("Destination"))) { - return CardFactoryUtil.doXMath(c.getXManaCostPaid(), expr, c); + return doXMath(c.getXManaCostPaid(), expr, c, ctb); } else if (TriggerType.SpellCast.equals(t.getMode())) { // Cast Trigger like Hydroid Krasis SpellAbility castSA = (SpellAbility) root.getTriggeringObject(AbilityKey.SpellAbility); if (castSA == null || castSA.getXManaCostPaid() == null) { - return CardFactoryUtil.doXMath(0, expr, c); + return doXMath(0, expr, c, ctb); } - return CardFactoryUtil.doXMath(castSA.getXManaCostPaid(), expr, c); + return doXMath(castSA.getXManaCostPaid(), expr, c, ctb); } else if (TriggerType.Cycled.equals(t.getMode())) { SpellAbility cycleSA = (SpellAbility) sa.getTriggeringObject(AbilityKey.Cause); if (cycleSA == null || cycleSA.getXManaCostPaid() == null) { - return CardFactoryUtil.doXMath(0, expr, c); + return doXMath(0, expr, c, ctb); } - return CardFactoryUtil.doXMath(cycleSA.getXManaCostPaid(), expr, c); + return doXMath(cycleSA.getXManaCostPaid(), expr, c, ctb); } else if (TriggerType.TurnFaceUp.equals(t.getMode())) { SpellAbility turnupSA = (SpellAbility) sa.getTriggeringObject(AbilityKey.Cause); if (turnupSA == null || turnupSA.getXManaCostPaid() == null) { - return CardFactoryUtil.doXMath(0, expr, c); + return doXMath(0, expr, c, ctb); } - return CardFactoryUtil.doXMath(turnupSA.getXManaCostPaid(), expr, c); + return doXMath(turnupSA.getXManaCostPaid(), expr, c, ctb); } } // If the chosen creature has X in its mana cost, that X is considered to be 0. // The value of X in Altered Ego’s last ability will be whatever value was chosen for X while casting Altered Ego. if (sa.isCopiedTrait() || !sa.getHostCard().equals(c)) { - return CardFactoryUtil.doXMath(0, expr, c); + return doXMath(0, expr, c, ctb); } if (root.isReplacementAbility()) { if (sa.hasParam("ETB")) { - return CardFactoryUtil.doXMath(c.getXManaCostPaid(), expr, c); + return doXMath(c.getXManaCostPaid(), expr, c, ctb); } } - return CardFactoryUtil.doXMath(0, expr, c); + return doXMath(0, expr, c, ctb); } // Count$Kicked.. if (sq[0].startsWith("Kicked")) { boolean kicked = sa.isKicked() || c.getKickerMagnitude() > 0; - return CardFactoryUtil.doXMath(Integer.parseInt(kicked ? sq[1] : sq[2]), expr, c); + return doXMath(Integer.parseInt(kicked ? sq[1] : sq[2]), expr, c, ctb); } - // Count$UrzaLands.. - if (sq[0].startsWith("UrzaLands")) { - return CardFactoryUtil.doXMath(Integer.parseInt(sa.getActivatingPlayer().hasUrzaLands() ? sq[1] : sq[2]), expr, c); - } - - //Count$SearchedLibrary. - if (sq[0].contains("SearchedLibrary")) { - int sum = 0; - for (Player p : AbilityUtils.getDefinedPlayers(c, sq[1], sa)) { - sum += p.getLibrarySearched(); - } - - return sum; - } //Count$HasNumChosenColors. if (sq[0].contains("HasNumChosenColors")) { int sum = 0; - for (Card card : AbilityUtils.getDefinedCards(sa.getHostCard(), sq[1], sa)) { + for (Card card : AbilityUtils.getDefinedCards(c, sq[1], sa)) { sum += CardUtil.getColors(card).getSharedColors(ColorSet.fromNames(c.getChosenColors())).countColors(); } return sum; @@ -1792,25 +1788,12 @@ public class AbilityUtils { } return count; } - // Count$AttachedTo - if (sq[0].startsWith("AttachedTo")) { - final String[] k = l[0].split(" "); - int sum = 0; - for (Card card : AbilityUtils.getDefinedCards(sa.getHostCard(), k[1], sa)) { - // Hateful Eidolon: the script uses LKI so that the attached cards have to be defined - // This card needs the spellability ("Auras You control", you refers to the activating player) - // CardFactoryUtils.xCount doesn't have the sa parameter, SVar:X:TriggeredCard$Valid cannot handle this - CardCollection list = CardLists.getValidCards(card.getAttachedCards(), k[2].split(","), sa.getActivatingPlayer(), c, sa); - sum += list.size(); - } - return sum; - } // Count$Adamant... if (sq[0].startsWith("Adamant")) { final String payingMana = StringUtils.join(sa.getRootAbility().getPayingMana()); final int num = sq[0].length() > 7 ? Integer.parseInt(sq[0].split("_")[1]) : 3; final boolean adamant = StringUtils.countMatches(payingMana, MagicColor.toShortString(sq[1])) >= num; - return CardFactoryUtil.doXMath(Integer.parseInt(sq[adamant ? 2 : 3]), expr, c); + return doXMath(calculateAmount(c,sq[adamant ? 2 : 3], ctb), expr, c, ctb); } if (l[0].startsWith("LastStateBattlefield")) { @@ -1819,13 +1802,13 @@ public class AbilityUtils { if (sa.getLastStateBattlefield() != null) { list = sa.getLastStateBattlefield(); } else { // LastState is Empty - return CardFactoryUtil.doXMath(0, expr, c); + return doXMath(0, expr, c, ctb); } list = CardLists.getValidCards(list, k[1].split(","), sa.getActivatingPlayer(), c, sa); if (k[0].contains("TotalToughness")) { - return CardFactoryUtil.doXMath(Aggregates.sum(list, CardPredicates.Accessors.fnGetNetToughness), expr, c); + return doXMath(Aggregates.sum(list, CardPredicates.Accessors.fnGetNetToughness), expr, c, ctb); } else { - return CardFactoryUtil.doXMath(list.size(), expr, c); + return doXMath(list.size(), expr, c, ctb); } } @@ -1835,26 +1818,14 @@ public class AbilityUtils { if (sa.getLastStateGraveyard() != null) { list = sa.getLastStateGraveyard(); } else { // LastState is Empty - return CardFactoryUtil.doXMath(0, expr, c); + return doXMath(0, expr, c, ctb); } list = CardLists.getValidCards(list, k[1].split(","), sa.getActivatingPlayer(), c, sa); - return CardFactoryUtil.doXMath(list.size(), expr, c); - } - - // Count$TargetedLifeTotal (targeted player's life total) - // Not optimal but since xCount doesn't take SAs, we need to replicate while we have it - // Probably would be best if xCount took an optional SA to use in these circumstances - if (sq[0].contains("TargetedLifeTotal")) { - final SpellAbility saTargeting = sa.getSATargetingPlayer(); - if (saTargeting != null) { - for (final Player tgtP : saTargeting.getTargets().getTargetPlayers()) { - return CardFactoryUtil.doXMath(tgtP.getLife(), expr, c); - } - } + return doXMath(list.size(), expr, c, ctb); } if (sq[0].startsWith("CastTotalManaSpent")) { - return CardFactoryUtil.doXMath(c.getCastSA() != null ? c.getCastSA().getTotalManaSpent() : 0, expr, c); + return doXMath(c.getCastSA() != null ? c.getCastSA().getTotalManaSpent() : 0, expr, c, ctb); } if (sq[0].equals("CastTotalSnowManaSpent")) { @@ -1866,35 +1837,962 @@ public class AbilityUtils { } } } - return CardFactoryUtil.doXMath(v, expr, c); + return doXMath(v, expr, c, ctb); } if (sq[0].equals("ResolvedThisTurn")) { - return CardFactoryUtil.doXMath(sa.getResolvedThisTurn(), expr, c); + return doXMath(sa.getResolvedThisTurn(), expr, c, ctb); + } + } else { + // fallback if ctb isn't a spellability + if (sq[0].startsWith("LastStateBattlefield")) { + final String[] k = l[0].split(" "); + CardCollection list = new CardCollection(game.getLastStateBattlefield()); + list = CardLists.getValidCards(list, k[1].split(","), player, c, ctb); + return doXMath(list.size(), expr, c, ctb); + } + + if (sq[0].startsWith("LastStateGraveyard")) { + final String[] k = l[0].split(" "); + CardCollection list = new CardCollection(game.getLastStateGraveyard()); + list = CardLists.getValidCards(list, k[1].split(","), player, c, ctb); + return doXMath(list.size(), expr, c, ctb); + } + } // end SpellAbility + + // Count$TargetedLifeTotal (targeted player's life total) + // Not optimal but since xCount doesn't take SAs, we need to replicate while we have it + // Probably would be best if xCount took an optional SA to use in these circumstances + if (sq[0].contains("TargetedLifeTotal")) { + for (Player tgtP : AbilityUtils.getDefinedPlayers(c, "TargetedPlayer", ctb)) { + return doXMath(tgtP.getLife(), expr, c, ctb); } } - if (l[0].startsWith("CountersAddedThisTurn")) { - final String[] parts = l[0].split(" "); - CounterType cType = CounterType.getType(parts[1]); - return CardFactoryUtil.doXMath(game.getCounterAddedThisTurn(cType, parts[2], parts[3], c, player, ctb), expr, c); + // Count$DevotionDual.. + // Count$Devotion. + if (sq[0].contains("Devotion")) { + int colorOcurrencices = 0; + String colorName = sq[1]; + if (colorName.contains("Chosen")) { + colorName = MagicColor.toShortString(c.getChosenColor()); + } + byte colorCode = ManaAtom.fromName(colorName); + if (sq[0].equals("DevotionDual")) { + colorCode |= ManaAtom.fromName(sq[2]); + } + for (Card c0 : player.getCardsIn(ZoneType.Battlefield)) { + for (ManaCostShard sh : c0.getManaCost()) { + if (sh.isColor(colorCode)) { + colorOcurrencices++; + } + } + colorOcurrencices += c0.getAmountOfKeyword("Your devotion to each color and each combination of colors is increased by one."); + } + return doXMath(colorOcurrencices, expr, c, ctb); } - // count valid cards in any specified zone/s - if (l[0].startsWith("Valid")) { - String[] lparts = l[0].split(" ", 2); - final String[] rest = lparts[1].split(","); - final CardCollectionView cardsInZones = lparts[0].length() > 5 - ? game.getCardsIn(ZoneType.listValueOf(lparts[0].substring(5))) - : game.getCardsIn(ZoneType.Battlefield); - CardCollection cards = CardLists.getValidCards(cardsInZones, rest, player, c, ctb); - return CardFactoryUtil.doXMath(cards.size(), expr, c); + if (sq[0].startsWith("DamageDoneByPlayerThisTurn")) { + int sum = 0; + for (Player p : AbilityUtils.getDefinedPlayers(c, sq[1], ctb)) { + sum += c.getReceivedDamageByPlayerThisTurn(p); + } + return doXMath(sum, expr, c, ctb); } + } // end ctb != null + + if (sq[0].contains("OppsAtLifeTotal")) { + final int lifeTotal = AbilityUtils.calculateAmount(c, sq[1], ctb); + int number = 0; + for (final Player opp : player.getOpponents()) { + if (opp.getLife() == lifeTotal) { + number++; + } + } + return doXMath(number, expr, c, ctb); } - return CardFactoryUtil.xCount(c, s2); + + //Count$SearchedLibrary. + if (sq[0].contains("SearchedLibrary")) { + int sum = 0; + for (Player p : AbilityUtils.getDefinedPlayers(c, sq[1], ctb)) { + sum += p.getLibrarySearched(); + } + return doXMath(sum, expr, c, ctb); + } + + + //return CardFactoryUtil.xCount(c, s2); + + //////////////////// + // card info + + // Count$CardMulticolor.. + if (sq[0].contains("CardMulticolor")) { + final boolean isMulti = CardUtil.getColors(c).isMulticolor(); + return doXMath(Integer.parseInt(sq[isMulti ? 1 : 2]), expr, c, ctb); + } + // Count$Madness.. + if (sq[0].startsWith("Madness")) { + return doXMath(calculateAmount(c, sq[c.isMadness() ? 1 : 2], ctb), expr, c, ctb); + } + + // Count$Foretold.. + if (sq[0].startsWith("Foretold")) { + return doXMath(calculateAmount(c, sq[c.isForetold() ? 1 : 2], ctb), expr, c, ctb); + } + + if (sq[0].startsWith("Kicked")) { // fallback for not spellAbility + return doXMath(calculateAmount(c, sq[c.getKickerMagnitude() > 0 ? 1 : 2], ctb), expr, c, ctb); + } + if (sq[0].startsWith("Escaped")) { + return doXMath(calculateAmount(c, sq[c.getCastSA() != null && c.getCastSA().isEscape() ? 1 : 2], ctb), expr, c, ctb); + } + if (sq[0].startsWith("AltCost")) { + return doXMath(calculateAmount(c, sq[c.isOptionalCostPaid(OptionalCost.AltCost) ? 1 : 2], ctb), expr, c, ctb); + } + if (l[0].equals("ColorsColorIdentity")) { + return doXMath(c.getController().getCommanderColorID().countColors(), expr, c, ctb); + } + + if (sq[0].equals("TotalDamageDoneByThisTurn")) { + return doXMath(c.getTotalDamageDoneBy(), expr, c, ctb); + } + if (sq[0].equals("TotalDamageReceivedThisTurn")) { + return doXMath(c.getTotalDamageRecievedThisTurn(), expr, c, ctb); + } + + if (sq[0].contains("CardPower")) { + return doXMath(c.getNetPower(), expr, c, ctb); + } + if (sq[0].contains("CardToughness")) { + return doXMath(c.getNetToughness(), expr, c, ctb); + } + if (sq[0].contains("CardSumPT")) { + return doXMath((c.getNetPower() + c.getNetToughness()), expr, c, ctb); + } + + if (sq[0].contains("CardNumColors")) { + return doXMath(CardUtil.getColors(c).countColors(), expr, c, ctb); + } + if (sq[0].contains("CardNumAttacksThisTurn")) { + return doXMath(c.getDamageHistory().getCreatureAttacksThisTurn(), expr, c, ctb); + } + + if (sq[0].contains("CardCounters")) { + // CardCounters.ALL to be used for Kinsbaile Borderguard and anything that cares about all counters + int count = 0; + if (sq[1].equals("ALL")) { + for (Integer i : c.getCounters().values()) { + if (i != null && i > 0) { + count += i; + } + } + } + else { + count = c.getCounters(CounterType.getType(sq[1])); + } + return doXMath(count, expr, c, ctb); + } + + if (sq[0].contains("BushidoPoint")) { + return doXMath(c.getKeywordMagnitude(Keyword.BUSHIDO), expr, c, ctb); + } + if (sq[0].contains("TimesKicked")) { + return doXMath(c.getKickerMagnitude(), expr, c, ctb); + } + if (sq[0].contains("TimesPseudokicked")) { + return doXMath(c.getPseudoKickerMagnitude(), expr, c, ctb); + } + if (sq[0].contains("TimesMutated")) { + return doXMath(c.getTimesMutated(), expr, c, ctb); + } + + if (sq[0].startsWith("DamageDoneByPlayerThisTurn")) { + int sum = 0; + for (Player p : AbilityUtils.getDefinedPlayers(c, sq[1], ctb)) { + sum += c.getReceivedDamageByPlayerThisTurn(p); + } + return doXMath(sum, expr, c, ctb); + } + if (sq[0].equals("DamageDoneThisTurn")) { + return doXMath(c.getDamageDoneThisTurn(), expr, c, ctb); + } + if (sq[0].equals("RegeneratedThisTurn")) { + return doXMath(c.getRegeneratedThisTurn(), expr, c, ctb); + } + + // Count$Converge + if (sq[0].contains("Converge")) { + SpellAbility castSA = c.getCastSA(); + return doXMath(castSA == null ? 0 : castSA.getPayingColors().countColors(), expr, c, ctb); + } + + // Count$wasCastFrom.. + if (sq[0].startsWith("wasCastFrom")) { + boolean zonesMatch = c.getCastFrom() == ZoneType.smartValueOf(sq[0].substring(11)); + return doXMath(calculateAmount(c, sq[zonesMatch ? 1 : 2], ctb), expr, c, ctb); + } + + // Count$Presence_.. + if (sq[0].startsWith("Presence")) { + final String type = sq[0].split("_")[1]; + boolean found = false; + if (c.getCastFrom() != null && c.getCastSA() != null) { + int revealed = AbilityUtils.calculateAmount(c, "Revealed$Valid " + type, c.getCastSA()); + int ctrl = AbilityUtils.calculateAmount(c, "Count$Valid " + type + ".inZoneBattlefield+YouCtrl", c.getCastSA()); + if (revealed + ctrl >= 1) { + found = true; + } + } + return doXMath(calculateAmount(c, sq[found ? 1 : 2], ctb), expr, c, ctb); + } + + if (sq[0].startsWith("Devoured")) { + final String validDevoured = sq[0].split(" ")[1]; + CardCollection cl = CardLists.getValidCards(c.getDevouredCards(), validDevoured.split(","), player, c, ctb); + return doXMath(cl.size(), expr, c, ctb); + } + + if (sq[0].contains("ChosenNumber")) { + Integer i = c.getChosenNumber(); + return doXMath(i == null ? 0 : i, expr, c, ctb); + } + + // Count$IfCastInOwnMainPhase.. // 7/10 + if (sq[0].contains("IfCastInOwnMainPhase")) { + final PhaseHandler cPhase = player.getGame().getPhaseHandler(); + final boolean isMyMain = cPhase.getPhase().isMain() && cPhase.isPlayerTurn(player) && c.getCastFrom() != null; + return doXMath(Integer.parseInt(sq[isMyMain ? 1 : 2]), expr, c, ctb); + } + + + // Count$AttachedTo + if (sq[0].startsWith("AttachedTo")) { + final String[] k = l[0].split(" "); + int sum = 0; + for (Card card : AbilityUtils.getDefinedCards(c, k[1], ctb)) { + // Hateful Eidolon: the script uses LKI so that the attached cards have to be defined + // This card needs the spellability ("Auras You control", you refers to the activating player) + // CardFactoryUtils.xCount doesn't have the sa parameter, SVar:X:TriggeredCard$Valid cannot handle this + sum += CardLists.getValidCardCount(card.getAttachedCards(), k[2], player, c, ctb); + } + return doXMath(sum, expr, c, ctb); + } + + // Count$CardManaCost + if (sq[0].contains("CardManaCost")) { + Card ce; + if (sq[0].contains("Equipped") && c.isEquipping()) { + ce = c.getEquipping(); + } + else if (sq[0].contains("Remembered")) { + ce = (Card) c.getFirstRemembered(); + } + else { + ce = c; + } + + return doXMath(ce == null ? 0 : ce.getCMC(), expr, c, ctb); + } + + if (l[0].startsWith("RememberedSize")) { + return doXMath(c.getRememberedCount(), expr, c, ctb); + } + + if (l[0].startsWith("RememberedNumber")) { + int num = 0; + for (final Object o : c.getRemembered()) { + if (o instanceof Integer) { + num += (Integer) o; + } + } + return doXMath(num, expr, c, ctb); + } + + if (l[0].startsWith("RememberedWithSharedCardType")) { + int maxNum = 1; + for (final Object o : c.getRemembered()) { + if (o instanceof Card) { + int num = 1; + Card firstCard = (Card) o; + for (final Object p : c.getRemembered()) { + if (p instanceof Card) { + Card secondCard = (Card) p; + if (!firstCard.equals(secondCard) && firstCard.sharesCardTypeWith(secondCard)) { + num++; + } + } + } + if (num > maxNum) { + maxNum = num; + } + } + } + return doXMath(maxNum, expr, c, ctb); + } + + + // Count$EnchantedControllerCreatures + if (sq[0].equals("EnchantedControllerCreatures")) { // maybe refactor into a Valid with ControlledBy + int v = 0; + if (c.getEnchantingCard() != null) { + v = CardLists.count(c.getEnchantingCard().getController().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); + } + return doXMath(v, expr, c, ctb); + } + + //////////////////////// + // player info + if (sq[0].equals("Hellbent")) { + return doXMath(calculateAmount(c, sq[player.hasHellbent() ? 1 : 2], ctb), expr, c, ctb); + } + if (sq[0].equals("Metalcraft")) { + return doXMath(calculateAmount(c, sq[player.hasMetalcraft() ? 1 : 2], ctb), expr, c, ctb); + } + if (sq[0].equals("Delirium")) { + return doXMath(calculateAmount(c, sq[player.hasDelirium() ? 1 : 2], ctb), expr, c, ctb); + } + if (sq[0].equals("FatefulHour")) { + return doXMath(calculateAmount(c, sq[player.getLife() <= 5 ? 1 : 2], ctb), expr, c, ctb); + } + if (sq[0].equals("Revolt")) { + return doXMath(calculateAmount(c, sq[player.hasRevolt() ? 1 : 2], ctb), expr, c, ctb); + } + if (sq[0].equals("Landfall")) { + return doXMath(calculateAmount(c, sq[player.hasLandfall() ? 1 : 2], ctb), expr, c, ctb); + } + if (sq[0].equals("Monarch")) { + return doXMath(calculateAmount(c, sq[player.isMonarch() ? 1 : 2], ctb), expr, c, ctb); + } + if (sq[0].equals("Blessing")) { + return doXMath(calculateAmount(c, sq[player.hasBlessing() ? 1 : 2], ctb), expr, c, ctb); + } + if (sq[0].equals("Threshold")) { + return doXMath(calculateAmount(c, sq[player.hasThreshold() ? 1 : 2], ctb), expr, c, ctb); + } + if (sq[0].equals("Averna")) { + String str = "As you cascade, you may put a land card from among the exiled cards onto the " + + "battlefield tapped."; + return doXMath(player.getKeywords().getAmount(str), expr, c, ctb); + } + if (sq[0].equals("YourStartingLife")) { + return doXMath(player.getStartingLife(), expr, c, ctb); + } + + if (sq[0].equals("YourLifeTotal")) { + return doXMath(player.getLife(), expr, c, ctb); + } + if (sq[0].equals("OppGreatestLifeTotal")) { + return doXMath(player.getOpponentsGreatestLifeTotal(), expr, c, ctb); + } + + if (sq[0].equals("YouCycledThisTurn")) { + return doXMath(player.getCycledThisTurn(), expr, c, ctb); + } + + if (sq[0].equals("YouDrewThisTurn")) { + return doXMath(player.getNumDrawnThisTurn(), expr, c, ctb); + } + + if (sq[0].equals("YouSurveilThisTurn")) { + return doXMath(player.getSurveilThisTurn(), expr, c, ctb); + } + + if (sq[0].equals("YouCastThisGame")) { + return doXMath(player.getSpellsCastThisGame(), expr, c, ctb); + } + + if (sq[0].contains("CardControllerTypes")) { + return doXMath(getCardTypesFromList(player.getCardsIn(ZoneType.listValueOf(sq[1]))), expr, c, ctb); + } + + if (l[0].startsWith("CommanderCastFromCommandZone")) { + // only used by Opal Palace, and it does add the trigger to the card + return doXMath(player.getCommanderCast(c), expr, c, ctb); + } + + if (l[0].startsWith("TotalCommanderCastFromCommandZone")) { + return doXMath(player.getTotalCommanderCast(), expr, c, ctb); + } + + if (sq[0].contains("LifeYouLostThisTurn")) { + return doXMath(player.getLifeLostThisTurn(), expr, c, ctb); + } + if (sq[0].contains("LifeYouGainedThisTurn")) { + return doXMath(player.getLifeGainedThisTurn(), expr, c, ctb); + } + if (sq[0].contains("LifeYourTeamGainedThisTurn")) { + return doXMath(player.getLifeGainedByTeamThisTurn(), expr, c, ctb); + } + if (sq[0].contains("LifeYouGainedTimesThisTurn")) { + return doXMath(player.getLifeGainedTimesThisTurn(), expr, c, ctb); + } + if (sq[0].contains("LifeOppsLostThisTurn")) { + return doXMath(player.getOpponentLostLifeThisTurn(), expr, c, ctb); + } + if (sq[0].equals("BloodthirstAmount")) { + return doXMath(player.getBloodthirstAmount(), expr, c, ctb); + } + if (sq[0].equals("YourLandsPlayed")) { + return doXMath(player.getLandsPlayedThisTurn(), expr, c, ctb); + } + + if (sq[0].startsWith("YourCounters")) { + // "YourCountersExperience" or "YourCountersPoison" + String counterType = sq[0].substring(12); + return doXMath(player.getCounters(CounterType.getType(counterType)), expr, c, ctb); + } + + if (sq[0].contains("YourPoisonCounters")) { + return doXMath(player.getPoisonCounters(), expr, c, ctb); + } + if (sq[0].contains("TotalOppPoisonCounters")) { + return doXMath(player.getOpponentsTotalPoisonCounters(), expr, c, ctb); + } + + if (sq[0].equals("YourDamageThisTurn")) { + return doXMath(player.getAssignedDamage(), expr, c, ctb); + } + if (sq[0].equals("TotalOppDamageThisTurn")) { + return doXMath(player.getOpponentsAssignedDamage(), expr, c, ctb); + } + if (sq[0].equals("MaxOppDamageThisTurn")) { + return doXMath(player.getMaxOpponentAssignedDamage(), expr, c, ctb); + } + + if (sq[0].contains("YourDamageSourcesThisTurn")) { + Iterable allSrc = player.getAssignedDamageSources(); + String restriction = sq[0].split(" ")[1]; + return doXMath(CardLists.getValidCardCount(allSrc, restriction, player, c, ctb), expr, c, ctb); + } + + if (sq[0].equals("YourTurns")) { + return doXMath(player.getTurn(), expr, c, ctb); + } + + if (sq[0].contains("OppTypesInGrave")) { + final PlayerCollection opponents = player.getOpponents(); + CardCollection oppCards = new CardCollection(); + oppCards.addAll(opponents.getCardsIn(ZoneType.Graveyard)); + return doXMath(getCardTypesFromList(oppCards), expr, c, ctb); + } + + // Count$TopOfLibraryCMC + if (sq[0].equals("TopOfLibraryCMC")) { + int cmc = player.getCardsIn(ZoneType.Library).isEmpty() ? 0 : + player.getCardsIn(ZoneType.Library).getFirst().getCMC(); + return doXMath(cmc, expr, c, ctb); + } + + if (l[0].contains("ColorsCtrl")) { + final String restriction = l[0].substring(11); + final String[] rest = restriction.split(","); + final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb); + byte n = 0; + for (final Card card : list) { + n |= card.determineColor().getColor(); + } + return doXMath(ColorSet.fromMask(n).countColors(), expr, c, ctb); + } + + // Count$AttackersDeclared + if (sq[0].contains("AttackersDeclared")) { + return doXMath(player.getAttackersDeclaredThisTurn(), expr, c, ctb); + } + + // Count$CardAttackedThisTurn + if (sq[0].contains("CreaturesAttackedThisTurn")) { + final String[] workingCopy = l[0].split(" ", 2); + final String validFilter = workingCopy[1]; + return doXMath(CardLists.getValidCardCount(player.getCreaturesAttackedThisTurn(), validFilter, player, c, ctb), expr, c, ctb); + } + + // Manapool + if (l[0].startsWith("ManaPool")) { + final String color = l[0].split(":")[1]; + int v = 0; + if (color.equals("All")) { + v = player.getManaPool().totalMana(); + } else { + v = player.getManaPool().getAmountOfColor(ManaAtom.fromName(color)); + } + return doXMath(v, expr, c, ctb); + } + + // Count$Domain + if (sq[0].startsWith("Domain")) { + int n = 0; + Player neededPlayer = sq[0].equals("DomainActivePlayer") ? game.getPhaseHandler().getPlayerTurn() : player; + CardCollection someCards = CardLists.filter(neededPlayer.getCardsIn(ZoneType.Battlefield), Presets.LANDS); + for (String basic : MagicColor.Constant.BASIC_LANDS) { + if (!CardLists.getType(someCards, basic).isEmpty()) { + n++; + } + } + return doXMath(n, expr, c, ctb); + } + + //SacrificedThisTurn + if (l[0].startsWith("SacrificedThisTurn")) { + CardCollectionView list = player.getSacrificedThisTurn(); + if (l[0].contains(" ")) { + String[] lparts = l[0].split(" ", 2); + String restrictions = TextUtil.fastReplace(l[0], TextUtil.addSuffix(lparts[0]," "), ""); + final String[] rest = restrictions.split(","); + list = CardLists.getValidCards(list, rest, player, c, ctb); + } + return doXMath(list.size(), expr, c, ctb); + } + + if (sq[0].contains("Party")) { + CardCollection adventurers = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), + "Creature.Cleric,Creature.Rogue,Creature.Warrior,Creature.Wizard", player, c, ctb); + + Set partyTypes = Sets.newHashSet("Cleric", "Rogue", "Warrior", "Wizard"); + int partySize = 0; + + HashMap chosenParty = new HashMap<>(); + List wildcard = Lists.newArrayList(); + HashMap> multityped = new HashMap<>(); + + // Figure out how to count each class separately. + for (Card card : adventurers) { + Set creatureTypes = card.getType().getCreatureTypes(); + boolean anyType = creatureTypes.contains(CardType.AllCreatureTypes); + creatureTypes.retainAll(partyTypes); + + if (anyType || creatureTypes.size() == 4) { + wildcard.add(card); + + if (wildcard.size() >= 4) { + break; + } + continue; + } else if (creatureTypes.size() == 1) { + String type = (String)(creatureTypes.toArray()[0]); + + if (!chosenParty.containsKey(type)) { + chosenParty.put(type, card); + } + } else { + multityped.put(card, creatureTypes); + } + } + + partySize = Math.min(chosenParty.size() + wildcard.size(), 4); + + if (partySize < 4) { + partyTypes.removeAll(chosenParty.keySet()); + + // Here I'm left with just the party types that I haven't selected. + for(Card multi : multityped.keySet()) { + Set types = multityped.get(multi); + types.retainAll(partyTypes); + + for(String type : types) { + chosenParty.put(type, multi); + partyTypes.remove(type); + break; + } + } + } + + partySize = Math.min(chosenParty.size() + wildcard.size(), 4); + + return doXMath(partySize, expr, c, ctb); + } + + // TODO make AI part to understand Sunburst better so this isn't needed + if (sq[0].startsWith("UniqueManaColorsProduced")) { + boolean untappedOnly = sq[1].contains("ByUntappedSources"); + int uniqueColors = 0; + CardCollectionView otb = player.getCardsIn(ZoneType.Battlefield); + outer: for (byte color : MagicColor.WUBRG) { + for (Card card : otb) { + if (!card.isTapped() || !untappedOnly) { + for (SpellAbility ma : card.getManaAbilities()) { + if (ma.canProduce(MagicColor.toShortString(color))) { + uniqueColors++; + continue outer; + } + } + } + } + } + return doXMath(uniqueColors, expr, c, ctb); + } + + + // TODO change into checking SpellAbility + if (sq[0].contains("xColorPaid")) { + String[] attrs = sq[0].split(" "); + StringBuilder colors = new StringBuilder(); + for (int i = 1; i < attrs.length; i++) { + colors.append(attrs[i]); + } + return doXMath(c.getXManaCostPaidCount(colors.toString()), expr, c, ctb); + } + + // Count$UrzaLands.. + if (sq[0].startsWith("UrzaLands")) { + return doXMath(AbilityUtils.calculateAmount(c, sq[player.hasUrzaLands() ? 1 : 2], ctb), expr, c, ctb); + } + + ///////////////// + //game info + // Count$Morbid.. + if (sq[0].startsWith("Morbid")) { + final List res = CardUtil.getThisTurnEntered(ZoneType.Graveyard, ZoneType.Battlefield, "Creature", c, ctb); + return doXMath(calculateAmount(c, sq[res.size() > 0 ? 1 : 2], ctb), expr, c, ctb); + } + + if (l[0].contains("CreatureType")) { + String[] sqparts = l[0].split(" ", 2); + final String[] rest = sqparts[1].split(","); + + final CardCollectionView cardsInZones = sqparts[0].length() > 12 + ? game.getCardsIn(ZoneType.listValueOf(sqparts[0].substring(12))) + : game.getCardsIn(ZoneType.Battlefield); + + CardCollection cards = CardLists.getValidCards(cardsInZones, rest, player, c, ctb); + final Set creatTypes = Sets.newHashSet(); + + for (Card card : cards) { + Iterables.addAll(creatTypes, card.getType().getCreatureTypes()); + } + // filter out fun types? + int n = creatTypes.contains(CardType.AllCreatureTypes) ? CardType.getAllCreatureTypes().size() : creatTypes.size(); + return doXMath(n, expr, c, ctb); + } + + // Count$Chroma. + if (sq[0].contains("Chroma")) { + ZoneType sourceZone = sq[0].contains("ChromaInGrave") ? ZoneType.Graveyard : ZoneType.Battlefield; + final CardCollectionView cards; + if (sq[0].contains("ChromaSource")) { // Runs Chroma for passed in Source card + cards = new CardCollection(c); + } + else { + cards = player.getCardsIn(sourceZone); + } + + int colorOcurrencices = 0; + byte colorCode = ManaAtom.fromName(sq[1]); + for (Card c0 : cards) { + for (ManaCostShard sh : c0.getManaCost()){ + if (sh.isColor(colorCode)) + colorOcurrencices++; + } + } + return doXMath(colorOcurrencices, expr, c, ctb); + } + + if (l[0].contains("ExactManaCost")) { + String[] sqparts = l[0].split(" ", 2); + final String[] rest = sqparts[1].split(","); + + final CardCollectionView cardsInZones = sqparts[0].length() > 13 + ? game.getCardsIn(ZoneType.listValueOf(sqparts[0].substring(13))) + : game.getCardsIn(ZoneType.Battlefield); + + CardCollection cards = CardLists.getValidCards(cardsInZones, rest, player, c, ctb); + final Set manaCost = Sets.newHashSet(); + + for (Card card : cards) { + manaCost.add(card.getManaCost().getShortString()); + } + + return doXMath(manaCost.size(), expr, c, ctb); + } + + if (sq[0].equals("StormCount")) { + return doXMath(game.getStack().getSpellsCastThisTurn().size() - 1, expr, c, ctb); + } + + if (l[0].startsWith("RolledThisTurn")) { + return game.getPhaseHandler().getPlanarDiceRolledthisTurn(); + } + + if (sq[0].contains("CardTypes")) { + return doXMath(getCardTypesFromList(game.getCardsIn(ZoneType.smartValueOf(sq[1]))), expr, c, ctb); + } + + if (sq[0].equals("TotalTurns")) { + return doXMath(game.getPhaseHandler().getTurn(), expr, c, ctb); + } + + if (sq[0].equals("MaxDistinctOnStack")) { + return doXMath(game.getStack().getMaxDistinctSources(), expr, c, ctb); + } + + + //Count$Random.. + if (sq[0].equals("Random")) { + int min = AbilityUtils.calculateAmount(c, sq[1], ctb); + int max = AbilityUtils.calculateAmount(c, sq[2], ctb); + + return MyRandom.getRandom().nextInt(1+max-min) + min; + } + + + // Count$SumPower_valid + if (l[0].contains("SumPower")) { + final String[] restrictions = l[0].split("_"); + final String[] rest = restrictions[1].split(","); + CardCollection filteredCards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb); + return doXMath(Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetNetPower), expr, c, ctb); + } + + // Count$SumCMC_valid + if (sq[0].contains("SumCMC")) { + ZoneType zone = ZoneType.Battlefield; + //graveyard support for Inferno Project (may need other zones or multi-zone in future) + if (sq[0].contains("Graveyard")) + zone = ZoneType.Graveyard; + final String[] restrictions = l[0].split("_"); + final String[] rest = restrictions[1].split(","); + CardCollectionView cardsonbattlefield = game.getCardsIn(zone); + CardCollection filteredCards = CardLists.getValidCards(cardsonbattlefield, rest, player, c, ctb); + return Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetCmc); + } + + + + + // Count$TotalCounters._ + if (sq[0].contains("TotalCounters")) { + final String[] restrictions = l[0].split("_"); + final CounterType cType = CounterType.getType(restrictions[1]); + final String[] validFilter = restrictions[2].split(","); + CardCollectionView validCards = game.getCardsIn(ZoneType.Battlefield); + validCards = CardLists.getValidCards(validCards, validFilter, player, c, ctb); + int cCount = 0; + for (final Card card : validCards) { + cCount += card.getCounters(cType); + } + return doXMath(cCount, expr, c, ctb); + } + + // Count$ThisTurnCast + // Count$LastTurnCast + if (sq[0].contains("ThisTurnCast") || sq[0].contains("LastTurnCast")) { + + final String[] workingCopy = l[0].split("_"); + final String validFilter = workingCopy[1]; + + List res = Lists.newArrayList(); + if (workingCopy[0].contains("This")) { + res = CardUtil.getThisTurnCast(validFilter, c, ctb); + } else { + res = CardUtil.getLastTurnCast(validFilter, c, ctb); + } + + return doXMath(res.size(), expr, c, ctb); + } + + // Count$ThisTurnEntered [from ] + if (sq[0].contains("ThisTurnEntered")) { + final String[] workingCopy = l[0].split("_"); + + ZoneType destination = ZoneType.smartValueOf(workingCopy[1]); + final boolean hasFrom = workingCopy[2].equals("from"); + ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null; + String validFilter = workingCopy[hasFrom ? 4 : 2] ; + + final List res = CardUtil.getThisTurnEntered(destination, origin, validFilter, c, ctb); + if (origin == null) { // Remove cards on the battlefield that changed controller + res.removeAll(CardUtil.getThisTurnEntered(destination, destination, validFilter, c, ctb)); + } + return doXMath(res.size(), expr, c, ctb); + } + + // Count$LastTurnEntered [from ] + if (sq[0].contains("LastTurnEntered")) { + final String[] workingCopy = l[0].split("_"); + + ZoneType destination = ZoneType.smartValueOf(workingCopy[1]); + final boolean hasFrom = workingCopy[2].equals("from"); + ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null; + String validFilter = workingCopy[hasFrom ? 4 : 2] ; + + final List res = CardUtil.getLastTurnEntered(destination, origin, validFilter, c, ctb); + if (origin == null) { // Remove cards on the battlefield that changed controller + res.removeAll(CardUtil.getLastTurnEntered(destination, destination, validFilter, c, ctb)); + } + return doXMath(res.size(), expr, c, ctb); + } + + if (l[0].startsWith("CountersAddedThisTurn")) { + final String[] parts = l[0].split(" "); + CounterType cType = CounterType.getType(parts[1]); + + return doXMath(game.getCounterAddedThisTurn(cType, parts[2], parts[3], c, player, ctb), expr, c, ctb); + } + + // count valid cards in any specified zone/s + if (l[0].startsWith("Valid")) { + String[] lparts = l[0].split(" ", 2); + + final CardCollectionView cardsInZones = lparts[0].length() > 5 + ? game.getCardsIn(ZoneType.listValueOf(lparts[0].substring(5))) + : game.getCardsIn(ZoneType.Battlefield); + + return doXMath(CardLists.getValidCardCount(cardsInZones, lparts[1], player, c, ctb), expr, c, ctb); + } + + if (l[0].startsWith("GreatestPower")) { + final String[] lparts = l[0].split("_", 2); + final String[] rest = lparts[1].split(","); + final CardCollectionView cardsInZones = lparts[0].length() > 13 + ? game.getCardsIn(ZoneType.listValueOf(lparts[0].substring(13))) + : game.getCardsIn(ZoneType.Battlefield); + CardCollection list = CardLists.getValidCards(cardsInZones, rest, player, c, ctb); + int highest = 0; + for (final Card crd : list) { + if (crd.getNetPower() > highest) { + highest = crd.getNetPower(); + } + } + return highest; + } + + if (l[0].startsWith("GreatestToughness_")) { + final String restriction = l[0].substring(18); + final String[] rest = restriction.split(","); + CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb); + int highest = 0; + for (final Card crd : list) { + if (crd.getNetToughness() > highest) { + highest = crd.getNetToughness(); + } + } + return highest; + } + + if (l[0].startsWith("HighestCMC_")) { + final String restriction = l[0].substring(11); + final String[] rest = restriction.split(","); + CardCollection list = CardLists.getValidCards(game.getCardsInGame(), rest, player, c, ctb); + int highest = 0; + for (final Card crd : list) { + // dont check for Split card anymore + if (crd.getCMC() > highest) { + highest = crd.getCMC(); + } + } + return highest; + } + + if (l[0].startsWith("MostCardName")) { + String[] lparts = l[0].split(" ", 2); + final String[] rest = lparts[1].split(","); + + final CardCollectionView cardsInZones = lparts[0].length() > 12 + ? game.getCardsIn(ZoneType.listValueOf(lparts[0].substring(12))) + : game.getCardsIn(ZoneType.Battlefield); + + CardCollection cards = CardLists.getValidCards(cardsInZones, rest, player, c, ctb); + final Map map = Maps.newHashMap(); + for (final Card card : cards) { + // Remove Duplicated types + final String name = card.getName(); + Integer count = map.get(name); + map.put(name, count == null ? 1 : count + 1); + } + int max = 0; + for (final Entry entry : map.entrySet()) { + if (max < entry.getValue()) { + max = entry.getValue(); + } + } + return max; + } + + if (l[0].startsWith("DifferentCardNames_")) { + final List crdname = Lists.newArrayList(); + final String restriction = l[0].substring(19); + final String[] rest = restriction.split(","); + CardCollection list = CardLists.getValidCards(game.getCardsInGame(), rest, player, c, ctb); + for (final Card card : list) { + String name = card.getName(); + // CR 201.2b Those objects have different names only if each of them has at least one name and no two objects in that group have a name in common + if (!crdname.contains(name) && !name.isEmpty()) { + crdname.add(name); + } + } + return doXMath(crdname.size(), expr, c, ctb); + } + + if (l[0].startsWith("DifferentPower_")) { + final List powers = Lists.newArrayList(); + final String restriction = l[0].substring(15); + final String[] rest = restriction.split(","); + CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb); + for (final Card card : list) { + Integer pow = card.getNetPower(); + if (!powers.contains(pow)) { + powers.add(pow); + } + } + return doXMath(powers.size(), expr, c, ctb); + } + + + if (l[0].startsWith("MostProminentCreatureType")) { + String restriction = l[0].split(" ")[1]; + CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb); + return doXMath(CardFactoryUtil.getMostProminentCreatureTypeSize(list), expr, c, ctb); + } + + if (l[0].startsWith("SecondMostProminentColor")) { + String restriction = l[0].split(" ")[1]; + CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb); + int[] colorSize = CardFactoryUtil.SortColorsFromList(list); + return doXMath(colorSize[colorSize.length - 2], expr, c, ctb); + } + + if (l[0].contains("ColorsCtrl")) { + final String restriction = l[0].substring(11); + final String[] rest = restriction.split(","); + final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb); + byte n = 0; + for (final Card card : list) { + n |= card.determineColor().getColor(); + } + return doXMath(ColorSet.fromMask(n).countColors(), expr, c, ctb); + } + + if (l[0].contains("CreatureType")) { + String[] sqparts = l[0].split(" ", 2); + final String[] rest = sqparts[1].split(","); + + final CardCollectionView cardsInZones = sqparts[0].length() > 12 + ? game.getCardsIn(ZoneType.listValueOf(sqparts[0].substring(12))) + : game.getCardsIn(ZoneType.Battlefield); + + CardCollection cards = CardLists.getValidCards(cardsInZones, rest, player, c, ctb); + final Set creatTypes = Sets.newHashSet(); + + for (Card card : cards) { + Iterables.addAll(creatTypes, card.getType().getCreatureTypes()); + } + int n = creatTypes.contains(CardType.AllCreatureTypes) ? CardType.getAllCreatureTypes().size() : creatTypes.size(); + return doXMath(n, expr, c, ctb); + } + // Complex counting methods + CardCollectionView someCards = getCardListForXCount(c, player, sq, ctb); + + // 1/10 - Count$MaxCMCYouCtrl + if (sq[0].contains("MaxCMC")) { + int mmc = Aggregates.max(someCards, CardPredicates.Accessors.fnGetCmc); + return doXMath(mmc, expr, c, ctb); + } + + return doXMath(someCards.size(), expr, c, ctb); } public static final void applyManaColorConversion(ManaConversionMatrix matrix, final Map params) { @@ -2134,4 +3032,645 @@ public class AbilityUtils { sa.setDescription(sa.getDescription() + " (Splicing " + c + " onto it)"); sa.addSplicedCards(c); } + + public static int doXMath(final int num, final String operators, final Card c, CardTraitBase ctb) { + if (operators == null || operators.equals("none")) { + return num; + } + + final String[] s = operators.split("\\."); + int secondaryNum = 0; + + try { + if (s.length == 2) { + secondaryNum = Integer.parseInt(s[1]); + } + } catch (final Exception e) { + secondaryNum = AbilityUtils.calculateAmount(c, s[1], ctb); + } + + if (s[0].contains("Plus")) { + return num + secondaryNum; + } else if (s[0].contains("NMinus")) { + return secondaryNum - num; + } else if (s[0].contains("Minus")) { + return num - secondaryNum; + } else if (s[0].contains("Twice")) { + return num * 2; + } else if (s[0].contains("Thrice")) { + return num * 3; + } else if (s[0].contains("HalfUp")) { + return (int) (Math.ceil(num / 2.0)); + } else if (s[0].contains("HalfDown")) { + return (int) (Math.floor(num / 2.0)); + } else if (s[0].contains("ThirdUp")) { + return (int) (Math.ceil(num / 3.0)); + } else if (s[0].contains("ThirdDown")) { + return (int) (Math.floor(num / 3.0)); + } else if (s[0].contains("Negative")) { + return num * -1; + } else if (s[0].contains("Times")) { + return num * secondaryNum; + } else if (s[0].contains("DivideEvenlyDown")) { + if (secondaryNum == 0) { + return 0; + } else { + return num / secondaryNum; + } + } else if (s[0].contains("Mod")) { + return num % secondaryNum; + } else if (s[0].contains("Abs")) { + return Math.abs(num); + } else if (s[0].contains("LimitMax")) { + if (num < secondaryNum) { + return num; + } else { + return secondaryNum; + } + } else if (s[0].contains("LimitMin")) { + if (num > secondaryNum) { + return num; + } else { + return secondaryNum; + } + + } else { + return num; + } + } + + /** + *

+ * Parse player targeted X variables. + *

+ * + * @param players + * a {@link java.util.ArrayList} object. + * @param s + * a {@link java.lang.String} object. + * @param source + * a {@link forge.game.card.Card} object. + * @return a int. + */ + public static int playerXCount(final List players, final String s, final Card source, CardTraitBase ctb) { + if (players.size() == 0) { + return 0; + } + + final String[] l = s.split("/"); + final String m = CardFactoryUtil.extractOperators(s); + + int n = 0; + + if (l[0].startsWith("TotalCommanderCastFromCommandZone")) { + int totCast = 0; + for (Player p : players) { + totCast += p.getTotalCommanderCast(); + } + return doXMath(totCast, m, source, ctb); + } + + // methods for getting the highest/lowest playerXCount from a range of players + if (l[0].startsWith("Highest")) { + for (final Player player : players) { + final int current = playerXProperty(player, TextUtil.fastReplace(s, "Highest", ""), source, ctb); + if (current > n) { + n = current; + } + } + + return doXMath(n, m, source, ctb); + } + + if (l[0].startsWith("Lowest")) { + n = 99999; // if no players have fewer than 99999 valids, the game is frozen anyway + for (final Player player : players) { + final int current = playerXProperty(player, TextUtil.fastReplace(s, "Lowest", ""), source, ctb); + if (current < n) { + n = current; + } + } + return doXMath(n, m, source, ctb); + } + + if (l[0].startsWith("TiedForHighestLife")) { + int maxLife = Integer.MIN_VALUE; + for (final Player player : players) { + int highestTotal = playerXProperty(player, "LifeTotal", source, ctb); + if (highestTotal > maxLife) { + maxLife = highestTotal; + } + } + int numTied = 0; + for (final Player player : players) { + if (player.getLife() == maxLife) { + numTied++; + } + } + return doXMath(numTied, m, source, ctb); + } + + if (l[0].startsWith("TiedForLowestLife")) { + int minLife = Integer.MAX_VALUE; + for (final Player player : players) { + int lowestTotal = playerXProperty(player, "LifeTotal", source, ctb); + if (lowestTotal < minLife) { + minLife = lowestTotal; + } + } + int numTied = 0; + for (final Player player : players) { + if (player.getLife() == minLife) { + numTied++; + } + } + return doXMath(numTied, m, source, ctb); + } + + final String[] sq; + sq = l[0].split("\\."); + + // the number of players passed in + if (sq[0].equals("Amount")) { + return doXMath(players.size(), m, source, ctb); + } + + if (sq[0].startsWith("HasProperty")) { + int totPlayer = 0; + String property = sq[0].substring(11); + for (Player p : players) { + if (p.hasProperty(property, source.getController(), source, ctb)) { + totPlayer++; + } + } + return doXMath(totPlayer, m, source, ctb); + } + + if (sq[0].contains("DamageThisTurn")) { + int totDmg = 0; + for (Player p : players) { + totDmg += p.getAssignedDamage(); + } + return doXMath(totDmg, m, source, ctb); + } else if (sq[0].contains("LifeLostThisTurn")) { + int totDmg = 0; + for (Player p : players) { + totDmg += p.getLifeLostThisTurn(); + } + return doXMath(totDmg, m, source, ctb); + } + + if (players.size() > 0) { + int totCount = 0; + for (Player p : players) { + totCount += playerXProperty(p, s, source, ctb); + } + return totCount; + } + + return doXMath(n, m, source, ctb); + } + + public static int playerXProperty(final Player player, final String s, final Card source, CardTraitBase ctb) { + final String[] l = s.split("/"); + final String m = CardFactoryUtil.extractOperators(s); + + final Game game = player.getGame(); + + // count valid cards in any specified zone/s + if (l[0].startsWith("Valid") && !l[0].contains("Valid ")) { + String[] lparts = l[0].split(" ", 2); + final List vZone = ZoneType.listValueOf(lparts[0].split("Valid")[1]); + String restrictions = TextUtil.fastReplace(l[0], TextUtil.addSuffix(lparts[0]," "), ""); + final String[] rest = restrictions.split(","); + CardCollection cards = CardLists.getValidCards(game.getCardsIn(vZone), rest, player, source, ctb); + return doXMath(cards.size(), m, source, ctb); + } + + // count valid cards on the battlefield + if (l[0].startsWith("Valid ")) { + final String restrictions = l[0].substring(6); + final String[] rest = restrictions.split(","); + CardCollection cardsonbattlefield = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, source, ctb); + return doXMath(cardsonbattlefield.size(), m, source, ctb); + } + + final String[] sq = l[0].split("\\."); + final String value = sq[0]; + + if (value.contains("CardsInHand")) { + return doXMath(player.getCardsIn(ZoneType.Hand).size(), m, source, ctb); + } + + if (value.contains("NumPowerSurgeLands")) { + return doXMath(player.getNumPowerSurgeLands(), m, source, ctb); + } + + if (value.contains("DomainPlayer")) { + int n = 0; + final CardCollectionView someCards = player.getCardsIn(ZoneType.Battlefield); + final List basic = MagicColor.Constant.BASIC_LANDS; + + for (int i = 0; i < basic.size(); i++) { + if (!CardLists.getType(someCards, basic.get(i)).isEmpty()) { + n++; + } + } + return doXMath(n, m, source, ctb); + } + + if (value.contains("CardsInLibrary")) { + return doXMath(player.getCardsIn(ZoneType.Library).size(), m, source, ctb); + } + + if (value.contains("CardsInGraveyard")) { + return doXMath(player.getCardsIn(ZoneType.Graveyard).size(), m, source, ctb); + } + if (value.contains("LandsInGraveyard")) { + return doXMath(CardLists.getType(player.getCardsIn(ZoneType.Graveyard), "Land").size(), m, source, ctb); + } + + if (value.contains("CreaturesInPlay")) { + return doXMath(player.getCreaturesInPlay().size(), m, source, ctb); + } + + if (value.contains("CardsInPlay")) { + return doXMath(player.getCardsIn(ZoneType.Battlefield).size(), m, source, ctb); + } + + if (value.contains("StartingLife")) { + return doXMath(player.getStartingLife(), m, source, ctb); + } + + if (value.contains("LifeTotal")) { + return doXMath(player.getLife(), m, source, ctb); + } + + if (value.contains("LifeLostThisTurn")) { + return doXMath(player.getLifeLostThisTurn(), m, source, ctb); + } + + if (value.contains("LifeLostLastTurn")) { + return doXMath(player.getLifeLostLastTurn(), m, source, ctb); + } + + if (value.contains("LifeGainedThisTurn")) { + return doXMath(player.getLifeGainedThisTurn(), m, source, ctb); + } + + if (value.contains("LifeGainedByTeamThisTurn")) { + return doXMath(player.getLifeGainedByTeamThisTurn(), m, source, ctb); + } + + if (value.contains("LifeStartedThisTurnWith")) { + return doXMath(player.getLifeStartedThisTurnWith(), m, source, ctb); + } + + if (value.contains("PoisonCounters")) { + return doXMath(player.getPoisonCounters(), m, source, ctb); + } + + if (value.contains("TopOfLibraryCMC")) { + return doXMath(Aggregates.sum(player.getCardsIn(ZoneType.Library, 1), CardPredicates.Accessors.fnGetCmc), m, source, ctb); + } + + if (value.contains("LandsPlayed")) { + return doXMath(player.getLandsPlayedThisTurn(), m, source, ctb); + } + + if (value.contains("CardsDrawn")) { + return doXMath(player.getNumDrawnThisTurn(), m, source, ctb); + } + + if (value.contains("CardsDiscardedThisTurn")) { + return doXMath(player.getNumDiscardedThisTurn(), m, source, ctb); + } + + if (value.contains("TokensCreatedThisTurn")) { + return doXMath(player.getNumTokensCreatedThisTurn(), m, source, ctb); + } + + if (value.contains("AttackersDeclared")) { + return doXMath(player.getAttackersDeclaredThisTurn(), m, source, ctb); + } + + if (value.equals("DamageDoneToPlayerBy")) { + return doXMath(source.getDamageDoneToPlayerBy(player.getName()), m, source, ctb); + } + + if (value.contains("DamageToOppsThisTurn")) { + int oppDmg = 0; + for (Player opp : player.getOpponents()) { + oppDmg += opp.getAssignedDamage(); + } + return doXMath(oppDmg, m, source, ctb); + } + + if (value.contains("NonCombatDamageDealtThisTurn")) { + return doXMath(player.getAssignedDamage() - player.getAssignedCombatDamage(), m, source, ctb); + } + + if (value.equals("OpponentsAttackedThisTurn")) { + return doXMath(player.getAttackedOpponentsThisTurn().size(), m, source, ctb); + } + + return doXMath(0, m, source, ctb); + } + + /** + *

+ * Parse player targeted X variables. + *

+ * + * @param objects + * a {@link java.util.ArrayList} object. + * @param s + * a {@link java.lang.String} object. + * @param source + * a {@link forge.game.card.Card} object. + * @return a int. + */ + public static int objectXCount(final List objects, final String s, final Card source, CardTraitBase ctb) { + if (objects.isEmpty()) { + return 0; + } + + if (s.startsWith("Valid")) { + return handlePaid(Iterables.filter(objects, Card.class), s, source, ctb); + } + + int n = s.startsWith("Amount") ? objects.size() : 0; + return doXMath(n, CardFactoryUtil.extractOperators(s), source, ctb); + } + + + /** + *

+ * handlePaid. + *

+ * + * @param paidList + * a {@link forge.game.card.CardCollectionView} object. + * @param string + * a {@link java.lang.String} object. + * @param source + * a {@link forge.game.card.Card} object. + * @return a int. + */ + public static int handlePaid(final Iterable paidList, final String string, final Card source, CardTraitBase ctb) { + if (paidList == null) { + if (string.contains(".")) { + final String[] splitString = string.split("\\.", 2); + return doXMath(0, splitString[1], source, ctb); + } else { + return 0; + } + } + if (string.startsWith("Amount")) { + int size = Iterables.size(paidList); + if (string.contains(".")) { + final String[] splitString = string.split("\\.", 2); + return doXMath(size, splitString[1], source, ctb); + } else { + return size; + } + + } + + if (string.startsWith("DifferentCMC")) { + final Set diffCMC = new HashSet<>(); + for (final Card card : paidList) { + diffCMC.add(card.getCMC()); + } + return diffCMC.size(); + } + + if (string.startsWith("SumCMC")) { + int sumCMC = 0; + for(Card c : paidList) { + sumCMC += c.getCMC(); + } + return sumCMC; + } + + if (string.startsWith("Valid")) { + + final String[] splitString = string.split("/", 2); + String valid = splitString[0].substring(6); + final List list = CardLists.getValidCardsAsList(paidList, valid, source.getController(), source, ctb); + return doXMath(list.size(), splitString.length > 1 ? splitString[1] : null, source, ctb); + } + + String filteredString = string; + Iterable filteredList = paidList; + final String[] filter = filteredString.split("_"); + + if (string.startsWith("FilterControlledBy")) { + final String pString = filter[0].substring(18); + FCollectionView controllers = AbilityUtils.getDefinedPlayers(source, pString, ctb); + filteredList = CardLists.filterControlledByAsList(filteredList, controllers); + filteredString = TextUtil.fastReplace(filteredString, pString, ""); + filteredString = TextUtil.fastReplace(filteredString, "FilterControlledBy_", ""); + } + + int tot = 0; + for (final Card c : filteredList) { + tot += xCount(c, filteredString, ctb); + } + + return tot; + } + + + private static CardCollectionView getCardListForXCount(final Card c, final Player cc, final String[] sq, CardTraitBase ctb) { + final List opps = cc.getOpponents(); + CardCollection someCards = new CardCollection(); + final Game game = c.getGame(); + + // Generic Zone-based counting + // Count$QualityAndZones.Subquality + + // build a list of cards in each possible specified zone + + if (sq[0].contains("YouCtrl")) { + someCards.addAll(cc.getCardsIn(ZoneType.Battlefield)); + } + + if (sq[0].contains("InYourYard")) { + someCards.addAll(cc.getCardsIn(ZoneType.Graveyard)); + } + + if (sq[0].contains("InYourLibrary")) { + someCards.addAll(cc.getCardsIn(ZoneType.Library)); + } + + if (sq[0].contains("InYourHand")) { + someCards.addAll(cc.getCardsIn(ZoneType.Hand)); + } + + if (sq[0].contains("InYourSideboard")) { + someCards.addAll(cc.getCardsIn(ZoneType.Sideboard)); + } + + if (sq[0].contains("OppCtrl")) { + for (final Player p : opps) { + someCards.addAll(p.getZone(ZoneType.Battlefield).getCards()); + } + } + + if (sq[0].contains("InOppYard")) { + for (final Player p : opps) { + someCards.addAll(p.getCardsIn(ZoneType.Graveyard)); + } + } + + if (sq[0].contains("InOppHand")) { + for (final Player p : opps) { + someCards.addAll(p.getCardsIn(ZoneType.Hand)); + } + } + + if (sq[0].contains("InChosenHand")) { + if (c.getChosenPlayer() != null) { + someCards.addAll(c.getChosenPlayer().getCardsIn(ZoneType.Hand)); + } + } + + if (sq[0].contains("InRememberedHand")) { + if (c.getRemembered() != null) { + for (final Object o : c.getRemembered()) { + if (o instanceof Player) { + Player remPlayer = (Player) o; + someCards.addAll(remPlayer.getCardsIn(ZoneType.Hand)); + } + } + } + } + + if (sq[0].contains("InChosenYard")) { + if (c.getChosenPlayer() != null) { + someCards.addAll(c.getChosenPlayer().getCardsIn(ZoneType.Graveyard)); + } + } + + if (sq[0].contains("OnBattlefield")) { + someCards.addAll(game.getCardsIn(ZoneType.Battlefield)); + } + + if (sq[0].contains("InAllYards")) { + someCards.addAll(game.getCardsIn(ZoneType.Graveyard)); + } + + if (sq[0].contains("SpellsOnStack")) { + someCards.addAll(game.getCardsIn(ZoneType.Stack)); + } + + if (sq[0].contains("InAllHands")) { + someCards.addAll(game.getCardsIn(ZoneType.Hand)); + } + + // Count$InTargetedHand (targeted player's cards in hand) + if (sq[0].contains("InTargetedHand")) { + for (Player tgtP : AbilityUtils.getDefinedPlayers(c, "TargetedPlayer", ctb)) { + someCards.addAll(tgtP.getCardsIn(ZoneType.Hand)); + } + } + + // Count$InTargetedHand (targeted player's cards in hand) + if (sq[0].contains("InTargetedLibrary")) { + for (Player tgtP : AbilityUtils.getDefinedPlayers(c, "TargetedPlayer", ctb)) { + someCards.addAll(tgtP.getCardsIn(ZoneType.Library)); + } + } + + // Count$InTargetedHand (targeted player's cards in hand) + if (sq[0].contains("InEnchantedHand")) { + GameEntity o = c.getEntityAttachedTo(); + Player controller = null; + if (o instanceof Card) { + controller = ((Card) o).getController(); + } + else { + controller = (Player) o; + } + if (controller != null) { + someCards.addAll(controller.getCardsIn(ZoneType.Hand)); + } + } + if (sq[0].contains("InEnchantedYard")) { + GameEntity o = c.getEntityAttachedTo(); + Player controller = null; + if (o instanceof Card) { + controller = ((Card) o).getController(); + } + else { + controller = (Player) o; + } + if (controller != null) { + someCards.addAll(controller.getCardsIn(ZoneType.Graveyard)); + } + } + + // filter lists based on the specified quality + + // "Clerics you control" - Count$TypeYouCtrl.Cleric + if (sq[0].contains("Type")) { + someCards = CardLists.filter(someCards, CardPredicates.isType(sq[1])); + } + + // "Named in all graveyards" - Count$NamedAllYards. + + if (sq[0].contains("Named")) { + if (sq[1].equals("CARDNAME")) { + sq[1] = c.getName(); + } + someCards = CardLists.filter(someCards, CardPredicates.nameEquals(sq[1])); + } + + // Refined qualities + + // "Untapped Lands" - Count$UntappedTypeYouCtrl.Land + // if (sq[0].contains("Untapped")) { someCards = CardLists.filter(someCards, Presets.UNTAPPED); } + + // if (sq[0].contains("Tapped")) { someCards = CardLists.filter(someCards, Presets.TAPPED); } + +// String sq0 = sq[0].toLowerCase(); +// for (String color : MagicColor.Constant.ONLY_COLORS) { +// if (sq0.contains(color)) +// someCards = someCards.filter(CardListFilter.WHITE); +// } + // "White Creatures" - Count$WhiteTypeYouCtrl.Creature + // if (sq[0].contains("White")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.WHITE)); + // if (sq[0].contains("Blue")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.BLUE)); + // if (sq[0].contains("Black")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.BLACK)); + // if (sq[0].contains("Red")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.RED)); + // if (sq[0].contains("Green")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.GREEN)); + + if (sq[0].contains("Multicolor")) { + someCards = CardLists.filter(someCards, new Predicate() { + @Override + public boolean apply(final Card c) { + return CardUtil.getColors(c).isMulticolor(); + } + }); + } + + if (sq[0].contains("Monocolor")) { + someCards = CardLists.filter(someCards, new Predicate() { + @Override + public boolean apply(final Card c) { + return CardUtil.getColors(c).isMonoColor(); + } + }); + } + return someCards; + } + + public static int getCardTypesFromList(final CardCollectionView list) { + EnumSet types = EnumSet.noneOf(CardType.CoreType.class); + for (Card c1 : list) { + Iterables.addAll(types, c1.getType().getCoreTypes()); + } + return types.size(); + } } diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java index 98e0ffb58ea..162fe084056 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -21,7 +21,6 @@ import forge.game.GameObject; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; -import forge.game.card.CardFactoryUtil; import forge.game.card.CardZoneTable; import forge.game.combat.Combat; import forge.game.player.Player; @@ -123,7 +122,7 @@ public abstract class SpellAbilityEffect { if (sa.hasParam("Announce")) { String svar = sa.getParam("Announce"); - int amount = CardFactoryUtil.xCount(sa.getHostCard(), sa.getSVar(svar)); + int amount = AbilityUtils.calculateAmount(sa.getHostCard(), svar, sa); sb.append(" "); sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace(svar,"=",String.valueOf(amount)))); } else{ diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseSourceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseSourceEffect.java index 2f7ceac1850..02cdadca1c4 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseSourceEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseSourceEffect.java @@ -5,11 +5,11 @@ import java.util.List; import org.apache.commons.lang3.StringUtils; import forge.game.Game; +import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; -import forge.game.card.CardFactoryUtil; import forge.game.card.CardLists; import forge.game.player.Player; import forge.game.spellability.SpellAbility; @@ -127,7 +127,7 @@ public class ChooseSourceEffect extends SpellAbilityEffect { } final String numericAmount = sa.getParamOrDefault("Amount", "1"); - final int validAmount = StringUtils.isNumeric(numericAmount) ? Integer.parseInt(numericAmount) : CardFactoryUtil.xCount(host, host.getSVar(numericAmount)); + final int validAmount = StringUtils.isNumeric(numericAmount) ? Integer.parseInt(numericAmount) : AbilityUtils.calculateAmount(host, numericAmount, sa); for (final Player p : tgtPlayers) { final CardCollection chosen = new CardCollection(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java index 4dd49bb9c1b..975f7532695 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java @@ -8,7 +8,6 @@ import forge.game.GameObject; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardDamageMap; -import forge.game.card.CardFactoryUtil; import forge.game.card.CardLists; import forge.game.player.Player; import forge.game.spellability.SpellAbility; @@ -90,7 +89,7 @@ public class DamageEachEffect extends DamageBaseEffect { final Card sourceLKI = game.getChangeZoneLKIInfo(source); // TODO shouldn't that be using Num or something first? - final int dmg = CardFactoryUtil.xCount(source, sa.getSVar("X")); + final int dmg = AbilityUtils.calculateAmount(source, "X", sa); // System.out.println(source+" deals "+dmg+" damage to "+o.toString()); if (o instanceof Card) { @@ -113,14 +112,14 @@ public class DamageEachEffect extends DamageBaseEffect { for (final Card source : sources) { final Card sourceLKI = game.getChangeZoneLKIInfo(source); - final int dmg = CardFactoryUtil.xCount(source, card.getSVar("X")); + final int dmg = AbilityUtils.calculateAmount(source, "X", sa); // System.out.println(source+" deals "+dmg+" damage to "+source); source.addDamage(dmg, sourceLKI, damageMap, preventMap, counterTable, sa); } } if (sa.getParam("DefinedCards").equals("Remembered")) { for (final Card source : sources) { - final int dmg = CardFactoryUtil.xCount(source, card.getSVar("X")); + final int dmg = AbilityUtils.calculateAmount(source, "X", sa); final Card sourceLKI = source.getGame().getChangeZoneLKIInfo(source); for (final Object o : sa.getHostCard().getRemembered()) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/StoreSVarEffect.java b/forge-game/src/main/java/forge/game/ability/effects/StoreSVarEffect.java index 8d9a969f068..f0b27f5d712 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/StoreSVarEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/StoreSVarEffect.java @@ -4,7 +4,6 @@ import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; -import forge.game.card.CardFactoryUtil; import forge.game.spellability.SpellAbility; import forge.util.TextUtil; @@ -50,12 +49,12 @@ public class StoreSVarEffect extends SpellAbilityEffect { int exprMath = AbilityUtils.calculateAmount(source, exprMathVar, sa); expr = TextUtil.fastReplace(expr, exprMathVar, Integer.toString(exprMath)); } - value = CardFactoryUtil.xCount(source, "SVar$" + expr); + value = AbilityUtils.xCount(source, "SVar$" + expr, sa); } else if (type.equals("Targeted")) { - value = CardFactoryUtil.handlePaid(sa.findTargetedCards(), expr, source); + value = AbilityUtils.handlePaid(sa.findTargetedCards(), expr, source, sa); } else if (type.equals("Triggered")) { Card trigCard = (Card)sa.getTriggeringObject(AbilityKey.Card); - value = CardFactoryUtil.xCount(trigCard, expr); + value = AbilityUtils.xCount(trigCard, expr, sa); } else if (type.equals("Calculate")) { value = AbilityUtils.calculateAmount(source, expr, sa); } diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index 7ecb4585f14..2901cd12597 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -19,8 +19,6 @@ package forge.game.card; import java.util.Arrays; import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -41,24 +39,18 @@ import forge.card.CardType; import forge.card.ColorSet; import forge.card.ICardFace; import forge.card.MagicColor; -import forge.card.mana.ManaAtom; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostParser; -import forge.card.mana.ManaCostShard; import forge.game.Game; -import forge.game.GameEntity; import forge.game.GameEntityCounterTable; import forge.game.GameLogEntryType; import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; -import forge.game.card.CardPredicates.Presets; import forge.game.cost.Cost; import forge.game.keyword.Keyword; import forge.game.keyword.KeywordInterface; -import forge.game.phase.PhaseHandler; import forge.game.player.Player; -import forge.game.player.PlayerCollection; import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementLayer; @@ -74,10 +66,8 @@ import forge.game.staticability.StaticAbilityCantBeCast; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerHandler; import forge.game.zone.ZoneType; -import forge.util.Aggregates; import forge.util.Lang; import forge.util.TextUtil; -import forge.util.collect.FCollectionView; import io.sentry.Sentry; import io.sentry.event.BreadcrumbBuilder; @@ -320,1566 +310,6 @@ public class CardFactoryUtil { return l.length > 1 ? l[1] : null; } - /** - *

- * Parse player targeted X variables. - *

- * - * @param objects - * a {@link java.util.ArrayList} object. - * @param s - * a {@link java.lang.String} object. - * @param source - * a {@link forge.game.card.Card} object. - * @return a int. - */ - public static int objectXCount(final List objects, final String s, final Card source) { - if (objects.isEmpty()) { - return 0; - } - - if (s.startsWith("Valid")) { - return CardFactoryUtil.handlePaid(Iterables.filter(objects, Card.class), s, source); - } - - int n = s.startsWith("Amount") ? objects.size() : 0; - return doXMath(n, extractOperators(s), source); - } - - /** - *

- * Parse player targeted X variables. - *

- * - * @param players - * a {@link java.util.ArrayList} object. - * @param s - * a {@link java.lang.String} object. - * @param source - * a {@link forge.game.card.Card} object. - * @return a int. - */ - public static int playerXCount(final List players, final String s, final Card source) { - if (players.size() == 0) { - return 0; - } - - final String[] l = s.split("/"); - final String m = extractOperators(s); - - int n = 0; - - if (l[0].startsWith("TotalCommanderCastFromCommandZone")) { - int totCast = 0; - for (Player p : players) { - totCast += p.getTotalCommanderCast(); - } - return doXMath(totCast, m, source); - } - - // methods for getting the highest/lowest playerXCount from a range of players - if (l[0].startsWith("Highest")) { - for (final Player player : players) { - final int current = playerXProperty(player, TextUtil.fastReplace(s, "Highest", ""), source); - if (current > n) { - n = current; - } - } - - return doXMath(n, m, source); - } - - if (l[0].startsWith("Lowest")) { - n = 99999; // if no players have fewer than 99999 valids, the game is frozen anyway - for (final Player player : players) { - final int current = playerXProperty(player, TextUtil.fastReplace(s, "Lowest", ""), source); - if (current < n) { - n = current; - } - } - return doXMath(n, m, source); - } - - if (l[0].startsWith("TiedForHighestLife")) { - int maxLife = Integer.MIN_VALUE; - for (final Player player : players) { - int highestTotal = playerXProperty(player, "LifeTotal", source); - if (highestTotal > maxLife) { - maxLife = highestTotal; - } - } - int numTied = 0; - for (final Player player : players) { - if (player.getLife() == maxLife) { - numTied++; - } - } - return doXMath(numTied, m, source); - } - - if (l[0].startsWith("TiedForLowestLife")) { - int minLife = Integer.MAX_VALUE; - for (final Player player : players) { - int lowestTotal = playerXProperty(player, "LifeTotal", source); - if (lowestTotal < minLife) { - minLife = lowestTotal; - } - } - int numTied = 0; - for (final Player player : players) { - if (player.getLife() == minLife) { - numTied++; - } - } - return doXMath(numTied, m, source); - } - - final String[] sq; - sq = l[0].split("\\."); - - // the number of players passed in - if (sq[0].equals("Amount")) { - return doXMath(players.size(), m, source); - } - - if (sq[0].startsWith("HasProperty")) { - int totPlayer = 0; - String property = sq[0].substring(11); - for (Player p : players) { - if (p.hasProperty(property, source.getController(), source, null)) { - totPlayer++; - } - } - return doXMath(totPlayer, m, source); - } - - if (sq[0].contains("DamageThisTurn")) { - int totDmg = 0; - for (Player p : players) { - totDmg += p.getAssignedDamage(); - } - return doXMath(totDmg, m, source); - } else if (sq[0].contains("LifeLostThisTurn")) { - int totDmg = 0; - for (Player p : players) { - totDmg += p.getLifeLostThisTurn(); - } - return doXMath(totDmg, m, source); - } - - if (players.size() > 0) { - int totCount = 0; - for (Player p : players) { - totCount += playerXProperty(p, s, source); - } - return totCount; - } - - return doXMath(n, m, source); - } - - public static int playerXProperty(final Player player, final String s, final Card source) { - final String[] l = s.split("/"); - final String m = extractOperators(s); - - final Game game = player.getGame(); - - // count valid cards in any specified zone/s - if (l[0].startsWith("Valid") && !l[0].contains("Valid ")) { - String[] lparts = l[0].split(" ", 2); - final List vZone = ZoneType.listValueOf(lparts[0].split("Valid")[1]); - String restrictions = TextUtil.fastReplace(l[0], TextUtil.addSuffix(lparts[0]," "), ""); - final String[] rest = restrictions.split(","); - CardCollection cards = CardLists.getValidCards(game.getCardsIn(vZone), rest, player, source, null); - return doXMath(cards.size(), m, source); - } - - // count valid cards on the battlefield - if (l[0].startsWith("Valid ")) { - final String restrictions = l[0].substring(6); - final String[] rest = restrictions.split(","); - CardCollection cardsonbattlefield = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, source, null); - return doXMath(cardsonbattlefield.size(), m, source); - } - - final String[] sq = l[0].split("\\."); - final String value = sq[0]; - - if (value.contains("CardsInHand")) { - return doXMath(player.getCardsIn(ZoneType.Hand).size(), m, source); - } - - if (value.contains("NumPowerSurgeLands")) { - return doXMath(player.getNumPowerSurgeLands(), m, source); - } - - if (value.contains("DomainPlayer")) { - int n = 0; - final CardCollectionView someCards = player.getCardsIn(ZoneType.Battlefield); - final List basic = MagicColor.Constant.BASIC_LANDS; - - for (int i = 0; i < basic.size(); i++) { - if (!CardLists.getType(someCards, basic.get(i)).isEmpty()) { - n++; - } - } - return doXMath(n, m, source); - } - - if (value.contains("CardsInLibrary")) { - return doXMath(player.getCardsIn(ZoneType.Library).size(), m, source); - } - - if (value.contains("CardsInGraveyard")) { - return doXMath(player.getCardsIn(ZoneType.Graveyard).size(), m, source); - } - if (value.contains("LandsInGraveyard")) { - return doXMath(CardLists.getType(player.getCardsIn(ZoneType.Graveyard), "Land").size(), m, source); - } - - if (value.contains("CreaturesInPlay")) { - return doXMath(player.getCreaturesInPlay().size(), m, source); - } - - if (value.contains("CardsInPlay")) { - return doXMath(player.getCardsIn(ZoneType.Battlefield).size(), m, source); - } - - if (value.contains("StartingLife")) { - return doXMath(player.getStartingLife(), m, source); - } - - if (value.contains("LifeTotal")) { - return doXMath(player.getLife(), m, source); - } - - if (value.contains("LifeLostThisTurn")) { - return doXMath(player.getLifeLostThisTurn(), m, source); - } - - if (value.contains("LifeLostLastTurn")) { - return doXMath(player.getLifeLostLastTurn(), m, source); - } - - if (value.contains("LifeGainedThisTurn")) { - return doXMath(player.getLifeGainedThisTurn(), m, source); - } - - if (value.contains("LifeGainedByTeamThisTurn")) { - return doXMath(player.getLifeGainedByTeamThisTurn(), m, source); - } - - if (value.contains("LifeStartedThisTurnWith")) { - return doXMath(player.getLifeStartedThisTurnWith(), m, source); - } - - if (value.contains("PoisonCounters")) { - return doXMath(player.getPoisonCounters(), m, source); - } - - if (value.contains("TopOfLibraryCMC")) { - return doXMath(Aggregates.sum(player.getCardsIn(ZoneType.Library, 1), CardPredicates.Accessors.fnGetCmc), m, source); - } - - if (value.contains("LandsPlayed")) { - return doXMath(player.getLandsPlayedThisTurn(), m, source); - } - - if (value.contains("CardsDrawn")) { - return doXMath(player.getNumDrawnThisTurn(), m, source); - } - - if (value.contains("CardsDiscardedThisTurn")) { - return doXMath(player.getNumDiscardedThisTurn(), m, source); - } - - if (value.contains("TokensCreatedThisTurn")) { - return doXMath(player.getNumTokensCreatedThisTurn(), m, source); - } - - if (value.contains("AttackersDeclared")) { - return doXMath(player.getAttackersDeclaredThisTurn(), m, source); - } - - if (value.equals("DamageDoneToPlayerBy")) { - return doXMath(source.getDamageDoneToPlayerBy(player.getName()), m, source); - } - - if (value.contains("DamageToOppsThisTurn")) { - int oppDmg = 0; - for (Player opp : player.getOpponents()) { - oppDmg += opp.getAssignedDamage(); - } - return doXMath(oppDmg, m, source); - } - - if (value.contains("NonCombatDamageDealtThisTurn")) { - return doXMath(player.getAssignedDamage() - player.getAssignedCombatDamage(), m, source); - } - - if (value.equals("OpponentsAttackedThisTurn")) { - return doXMath(player.getAttackedOpponentsThisTurn().size(), m, source); - } - - return doXMath(0, m, source); - } - - /** - *

- * Parse non-mana X variables. - *

- * - * @param c - * a {@link forge.game.card.Card} object. - * @param expression - * a {@link java.lang.String} object. - * @return a int. - */ - public static int xCount(final Card c, final String expression) { - if (StringUtils.isBlank(expression) || c == null) { - return 0; - } - if (StringUtils.isNumeric(expression)) { - return Integer.parseInt(expression); - } - - final Player cc = c.getController(); - final Game game = c.getGame(); - final Player activePlayer = game.getPhaseHandler().getPlayerTurn(); - - final String[] l = expression.split("/"); - final String m = extractOperators(expression); - - // accept straight numbers - if (l[0].startsWith("Number$")) { - final String number = l[0].substring(7); - if (number.equals("ChosenNumber")) { - int x = c.getChosenNumber() == null ? 0 : c.getChosenNumber(); - return doXMath(x, m, c); - } - return doXMath(Integer.parseInt(number), m, c); - } - - if (l[0].startsWith("Count$")) { - l[0] = l[0].substring(6); - } - - if (l[0].startsWith("SVar$")) { - return doXMath(xCount(c, c.getSVar(l[0].substring(5))), m, c); - } - - if (l[0].startsWith("Controller$")) { - return playerXProperty(cc, l[0].substring(11), c); - } - - // Manapool - if (l[0].startsWith("ManaPool")) { - final String color = l[0].split(":")[1]; - if (color.equals("All")) { - return cc.getManaPool().totalMana(); - } - return cc.getManaPool().getAmountOfColor(ManaAtom.fromName(color)); - } - - // count valid cards in any specified zone/s - if (l[0].startsWith("Valid")) { - String[] lparts = l[0].split(" ", 2); - final String[] rest = lparts[1].split(","); - - final CardCollectionView cardsInZones = lparts[0].length() > 5 - ? game.getCardsIn(ZoneType.listValueOf(lparts[0].substring(5))) - : game.getCardsIn(ZoneType.Battlefield); - - CardCollection cards = CardLists.getValidCards(cardsInZones, rest, cc, c, null); - return doXMath(cards.size(), m, c); - } - - if (l[0].startsWith("ImprintedCardManaCost") && !c.getImprintedCards().isEmpty()) { - return c.getImprintedCards().get(0).getCMC(); - } - - if (l[0].startsWith("GreatestPower")) { - final String[] lparts = l[0].split("_", 2); - final String[] rest = lparts[1].split(","); - final CardCollectionView cardsInZones = lparts[0].length() > 13 - ? game.getCardsIn(ZoneType.listValueOf(lparts[0].substring(13))) - : game.getCardsIn(ZoneType.Battlefield); - CardCollection list = CardLists.getValidCards(cardsInZones, rest, cc, c, null); - int highest = 0; - for (final Card crd : list) { - if (crd.getNetPower() > highest) { - highest = crd.getNetPower(); - } - } - return highest; - } - - if (l[0].startsWith("GreatestToughness_")) { - final String restriction = l[0].substring(18); - final String[] rest = restriction.split(","); - CardCollection list = CardLists.getValidCards(cc.getGame().getCardsIn(ZoneType.Battlefield), rest, cc, c, null); - int highest = 0; - for (final Card crd : list) { - if (crd.getNetToughness() > highest) { - highest = crd.getNetToughness(); - } - } - return highest; - } - - if (l[0].startsWith("HighestCMC_")) { - final String restriction = l[0].substring(11); - final String[] rest = restriction.split(","); - CardCollection list = CardLists.getValidCards(cc.getGame().getCardsInGame(), rest, cc, c, null); - int highest = 0; - for (final Card crd : list) { - // dont check for Split card anymore - if (crd.getCMC() > highest) { - highest = crd.getCMC(); - } - } - return highest; - } - - if (l[0].startsWith("MostCardName")) { - String[] lparts = l[0].split(" ", 2); - final String[] rest = lparts[1].split(","); - - final CardCollectionView cardsInZones = lparts[0].length() > 12 - ? game.getCardsIn(ZoneType.listValueOf(lparts[0].substring(12))) - : game.getCardsIn(ZoneType.Battlefield); - - CardCollection cards = CardLists.getValidCards(cardsInZones, rest, cc, c, null); - final Map map = Maps.newHashMap(); - for (final Card card : cards) { - // Remove Duplicated types - final String name = card.getName(); - Integer count = map.get(name); - map.put(name, count == null ? 1 : count + 1); - } - int max = 0; - for (final Entry entry : map.entrySet()) { - if (max < entry.getValue()) { - max = entry.getValue(); - } - } - return max; - } - - if (l[0].startsWith("DifferentCardNames_")) { - final List crdname = Lists.newArrayList(); - final String restriction = l[0].substring(19); - final String[] rest = restriction.split(","); - CardCollection list = CardLists.getValidCards(cc.getGame().getCardsInGame(), rest, cc, c, null); - for (final Card card : list) { - String name = card.getName(); - // CR 201.2b Those objects have different names only if each of them has at least one name and no two objects in that group have a name in common - if (!crdname.contains(name) && !name.isEmpty()) { - crdname.add(name); - } - } - return doXMath(crdname.size(), m, c); - } - - if (l[0].startsWith("DifferentPower_")) { - final List powers = Lists.newArrayList(); - final String restriction = l[0].substring(15); - final String[] rest = restriction.split(","); - CardCollection list = CardLists.getValidCards(cc.getGame().getCardsIn(ZoneType.Battlefield), rest, cc, c, null); - for (final Card card : list) { - Integer pow = card.getNetPower(); - if (!powers.contains(pow)) { - powers.add(pow); - } - } - return doXMath(powers.size(), m, c); - } - - if (l[0].startsWith("RememberedSize")) { - return doXMath(c.getRememberedCount(), m, c); - } - - if (l[0].startsWith("RememberedNumber")) { - int num = 0; - for (final Object o : c.getRemembered()) { - if (o instanceof Integer) { - num += (Integer) o; - } - } - return doXMath(num, m, c); - } - - if (l[0].startsWith("RememberedWithSharedCardType")) { - int maxNum = 1; - for (final Object o : c.getRemembered()) { - if (o instanceof Card) { - int num = 1; - Card firstCard = (Card) o; - for (final Object p : c.getRemembered()) { - if (p instanceof Card) { - Card secondCard = (Card) p; - if (!firstCard.equals(secondCard) && firstCard.sharesCardTypeWith(secondCard)) { - num++; - } - } - } - if (num > maxNum) { - maxNum = num; - } - } - } - return doXMath(maxNum, m, c); - } - - if (l[0].startsWith("CommanderCastFromCommandZone")) { - // only used by Opal Palace, and it does add the trigger to the card - return doXMath(cc.getCommanderCast(c), m, c); - } - - if (l[0].startsWith("TotalCommanderCastFromCommandZone")) { - return doXMath(cc.getTotalCommanderCast(), m, c); - } - - if (l[0].startsWith("MostProminentCreatureType")) { - String restriction = l[0].split(" ")[1]; - CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, cc, c, null); - return doXMath(getMostProminentCreatureTypeSize(list), m, c); - } - - if (l[0].startsWith("SecondMostProminentColor")) { - String restriction = l[0].split(" ")[1]; - CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, cc, c, null); - int[] colorSize = SortColorsFromList(list); - return doXMath(colorSize[colorSize.length - 2], m, c); - } - - if (l[0].startsWith("RolledThisTurn")) { - return game.getPhaseHandler().getPlanarDiceRolledthisTurn(); - } - - //SacrificedThisTurn - if (l[0].startsWith("SacrificedThisTurn")) { - CardCollectionView list = cc.getSacrificedThisTurn(); - if (l[0].contains(" ")) { - String[] lparts = l[0].split(" ", 2); - String restrictions = TextUtil.fastReplace(l[0], TextUtil.addSuffix(lparts[0]," "), ""); - final String[] rest = restrictions.split(","); - list = CardLists.getValidCards(list, rest, cc, c, null); - } - return doXMath(list.size(), m, c); - } - - final String[] sq; - sq = l[0].split("\\."); - - if (sq[0].contains("xPaid")) { - return doXMath(c.getXManaCostPaid(), m, c); - } - - if (sq[0].contains("xColorPaid")) { - String[] attrs = sq[0].split(" "); - StringBuilder colors = new StringBuilder(); - for (int i = 1; i < attrs.length; i++) { - colors.append(attrs[i]); - } - return doXMath(c.getXManaCostPaidCount(colors.toString()), m, c); - } - - if (sq[0].equals("YouCycledThisTurn")) { - return doXMath(cc.getCycledThisTurn(), m, c); - } - - if (sq[0].equals("YouDrewThisTurn")) { - return doXMath(cc.getNumDrawnThisTurn(), m, c); - } - - if (sq[0].equals("YouSurveilThisTurn")) { - return doXMath(cc.getSurveilThisTurn(), m, c); - } - - if (sq[0].equals("YouCastThisGame")) { - return doXMath(cc.getSpellsCastThisGame(), m, c); - } - - if (sq[0].equals("StormCount")) { - return doXMath(game.getStack().getSpellsCastThisTurn().size() - 1, m, c); - } - if (sq[0].startsWith("DamageDoneByPlayerThisTurn")) { - int sum = 0; - for (Player p : AbilityUtils.getDefinedPlayers(c, sq[1], null)) { - sum += c.getReceivedDamageByPlayerThisTurn(p); - } - return doXMath(sum, m, c); - } - if (sq[0].equals("DamageDoneThisTurn")) { - return doXMath(c.getDamageDoneThisTurn(), m, c); - } - if (sq[0].equals("BloodthirstAmount")) { - return doXMath(cc.getBloodthirstAmount(), m, c); - } - if (sq[0].equals("RegeneratedThisTurn")) { - return doXMath(c.getRegeneratedThisTurn(), m, c); - } - - if (sq[0].contains("YourStartingLife")) { - return doXMath(cc.getStartingLife(), m, c); - } - - if (sq[0].contains("YourLifeTotal")) { - return doXMath(cc.getLife(), m, c); - } - if (sq[0].contains("OppGreatestLifeTotal")) { - return doXMath(cc.getOpponentsGreatestLifeTotal(), m, c); - } - if (sq[0].contains("OppsAtLifeTotal")) { - final int lifeTotal = xCount(c, sq[1]); - int number = 0; - for (final Player opp : cc.getOpponents()) { - if (opp.getLife() == lifeTotal) { - number++; - } - } - return doXMath(number, m, c); - } - - // Count$TargetedLifeTotal (targeted player's life total) - if (sq[0].contains("TargetedLifeTotal")) { - // This doesn't work in some circumstances, since the active SA isn't passed through - for (final SpellAbility sa : c.getCurrentState().getNonManaAbilities()) { - final SpellAbility saTargeting = sa.getSATargetingPlayer(); - if (saTargeting != null) { - for (final Player tgtP : saTargeting.getTargets().getTargetPlayers()) { - return doXMath(tgtP.getLife(), m, c); - } - } - } - } - - if (sq[0].contains("LifeYouLostThisTurn")) { - return doXMath(cc.getLifeLostThisTurn(), m, c); - } - if (sq[0].contains("LifeYouGainedThisTurn")) { - return doXMath(cc.getLifeGainedThisTurn(), m, c); - } - if (sq[0].contains("LifeYourTeamGainedThisTurn")) { - return doXMath(cc.getLifeGainedByTeamThisTurn(), m, c); - } - if (sq[0].contains("LifeYouGainedTimesThisTurn")) { - return doXMath(cc.getLifeGainedTimesThisTurn(), m, c); - } - if (sq[0].contains("LifeOppsLostThisTurn")) { - return doXMath(cc.getOpponentLostLifeThisTurn(), m, c); - } - if (sq[0].equals("TotalDamageDoneByThisTurn")) { - return doXMath(c.getTotalDamageDoneBy(), m, c); - } - if (sq[0].equals("TotalDamageReceivedThisTurn")) { - return doXMath(c.getTotalDamageRecievedThisTurn(), m, c); - } - - if (sq[0].startsWith("YourCounters")) { - // "YourCountersExperience" or "YourCountersPoison" - String counterType = sq[0].substring(12); - return doXMath(cc.getCounters(CounterType.getType(counterType)), m, c); - } - - if (sq[0].contains("YourPoisonCounters")) { - return doXMath(cc.getPoisonCounters(), m, c); - } - if (sq[0].contains("TotalOppPoisonCounters")) { - return doXMath(cc.getOpponentsTotalPoisonCounters(), m, c); - } - - if (sq[0].contains("YourDamageThisTurn")) { - return doXMath(cc.getAssignedDamage(), m, c); - } - if (sq[0].contains("TotalOppDamageThisTurn")) { - return doXMath(cc.getOpponentsAssignedDamage(), m, c); - } - if (sq[0].contains("MaxOppDamageThisTurn")) { - return doXMath(cc.getMaxOpponentAssignedDamage(), m, c); - } - - // Count$YourTypeDamageThisTurn Type - if (sq[0].contains("YourTypeDamageThisTurn")) { - return doXMath(cc.getAssignedDamage(sq[0].split(" ")[1]), m, c); - } - if (sq[0].contains("YourDamageSourcesThisTurn")) { - Iterable allSrc = cc.getAssignedDamageSources(); - String restriction = sq[0].split(" ")[1]; - CardCollection filtered = CardLists.getValidCards(allSrc, restriction, cc, c, null); - return doXMath(filtered.size(), m, c); - } - - if (sq[0].contains("YourLandsPlayed")) { - return doXMath(cc.getLandsPlayedThisTurn(), m, c); - } - - // Count$TopOfLibraryCMC - if (sq[0].contains("TopOfLibraryCMC")) { - int cmc = cc.getCardsIn(ZoneType.Library).isEmpty() ? 0 : - cc.getCardsIn(ZoneType.Library).getFirst().getCMC(); - return doXMath(cmc, m, c); - } - - // Count$EnchantedControllerCreatures - if (sq[0].contains("EnchantedControllerCreatures")) { - if (c.getEnchantingCard() != null) { - return CardLists.count(c.getEnchantingCard().getController().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); - } - return 0; - } - - // Count$Chroma. - if (sq[0].contains("Chroma")) { - ZoneType sourceZone = sq[0].contains("ChromaInGrave") ? ZoneType.Graveyard : ZoneType.Battlefield; - final CardCollectionView cards; - if (sq[0].contains("ChromaSource")) { // Runs Chroma for passed in Source card - cards = new CardCollection(c); - } - else { - cards = cc.getCardsIn(sourceZone); - } - - int colorOcurrencices = 0; - byte colorCode = ManaAtom.fromName(sq[1]); - for (Card c0 : cards) { - for (ManaCostShard sh : c0.getManaCost()){ - if ((sh.getColorMask() & colorCode) != 0) - colorOcurrencices++; - } - } - return doXMath(colorOcurrencices, m, c); - } - // Count$DevotionDual.. - // Count$Devotion. - if (sq[0].contains("Devotion")) { - int colorOcurrencices = 0; - String colorName = sq[1]; - if (colorName.contains("Chosen")) { - colorName = MagicColor.toShortString(c.getChosenColor()); - } - byte colorCode = ManaAtom.fromName(colorName); - if (sq[0].equals("DevotionDual")) { - colorCode |= ManaAtom.fromName(sq[2]); - } - for (Card c0 : cc.getCardsIn(ZoneType.Battlefield)) { - for (ManaCostShard sh : c0.getManaCost()) { - if ((sh.getColorMask() & colorCode) != 0) { - colorOcurrencices++; - } - } - colorOcurrencices += c0.getAmountOfKeyword("Your devotion to each color and each combination of colors is increased by one."); - } - return doXMath(colorOcurrencices, m, c); - } - - if (sq[0].contains("ColorsCtrl")) { - final String restriction = l[0].substring(11); - final String[] rest = restriction.split(","); - final CardCollection list = CardLists.getValidCards(cc.getCardsIn(ZoneType.Battlefield), rest, cc, c, null); - byte n = 0; - for (final Card card : list) { - n |= card.determineColor().getColor(); - } - return doXMath(ColorSet.fromMask(n).countColors(), m, c); - } - - if (sq[0].equals("ColorsColorIdentity")) { - return doXMath(c.getController().getCommanderColorID().countColors(), m, c); - } - - if (sq[0].contains("CreatureType")) { - String[] sqparts = l[0].split(" ", 2); - final String[] rest = sqparts[1].split(","); - - final CardCollectionView cardsInZones = sqparts[0].length() > 12 - ? game.getCardsIn(ZoneType.listValueOf(sqparts[0].substring(12))) - : game.getCardsIn(ZoneType.Battlefield); - - CardCollection cards = CardLists.getValidCards(cardsInZones, rest, cc, c, null); - final Set creatTypes = Sets.newHashSet(); - - for (Card card : cards) { - Iterables.addAll(creatTypes, card.getType().getCreatureTypes()); - } - int n = creatTypes.contains(CardType.AllCreatureTypes) ? CardType.getAllCreatureTypes().size() : creatTypes.size(); - return doXMath(n, m, c); - } - - if (sq[0].contains("ExactManaCost")) { - String[] sqparts = l[0].split(" ", 2); - final String[] rest = sqparts[1].split(","); - - final CardCollectionView cardsInZones = sqparts[0].length() > 13 - ? game.getCardsIn(ZoneType.listValueOf(sqparts[0].substring(13))) - : game.getCardsIn(ZoneType.Battlefield); - - CardCollection cards = CardLists.getValidCards(cardsInZones, rest, cc, c, null); - final Set manaCost = Sets.newHashSet(); - - for (Card card : cards) { - manaCost.add(card.getManaCost().getShortString()); - } - - return doXMath(manaCost.size(), m, c); - } - - if (sq[0].contains("Hellbent")) { - return doXMath(Integer.parseInt(sq[cc.hasHellbent() ? 1 : 2]), m, c); - } - if (sq[0].contains("Metalcraft")) { - return doXMath(Integer.parseInt(sq[cc.hasMetalcraft() ? 1 : 2]), m, c); - } - if (sq[0].contains("Delirium")) { - return doXMath(Integer.parseInt(sq[cc.hasDelirium() ? 1 : 2]), m, c); - } - if (sq[0].contains("FatefulHour")) { - return doXMath(Integer.parseInt(sq[cc.getLife() <= 5 ? 1 : 2]), m, c); - } - if (sq[0].contains("Revolt")) { - return doXMath(Integer.parseInt(sq[cc.hasRevolt() ? 1 : 2]), m, c); - } - if (sq[0].contains("Landfall")) { - return doXMath(Integer.parseInt(sq[cc.hasLandfall() ? 1 : 2]), m, c); - } - if (sq[0].contains("Monarch")) { - return doXMath(Integer.parseInt(sq[cc.isMonarch() ? 1 : 2]), m, c); - } - if (sq[0].contains("Blessing")) { - return doXMath(Integer.parseInt(sq[cc.hasBlessing() ? 1 : 2]), m, c); - } - if (sq[0].contains("Threshold")) { - return doXMath(Integer.parseInt(sq[cc.hasThreshold() ? 1 : 2]), m, c); - } - if (sq[0].contains("Averna")) { - String str = "As you cascade, you may put a land card from among the exiled cards onto the " + - "battlefield tapped."; - return cc.getKeywords().getAmount(str); - } - if (sq[0].startsWith("Kicked")) { - return doXMath(Integer.parseInt(sq[c.getKickerMagnitude() > 0 ? 1 : 2]), m, c); - } - if (sq[0].startsWith("Escaped")) { - return doXMath(Integer.parseInt(sq[c.getCastSA() != null && c.getCastSA().isEscape() ? 1 : 2]), m, c); - } - if (sq[0].startsWith("AltCost")) { - return doXMath(Integer.parseInt(sq[c.isOptionalCostPaid(OptionalCost.AltCost) ? 1 : 2]), m, c); - } - - // Count$wasCastFrom.. - if (sq[0].startsWith("wasCastFrom")) { - boolean zonesMatch = c.getCastFrom() == ZoneType.smartValueOf(sq[0].substring(11)); - return doXMath(Integer.parseInt(sq[zonesMatch ? 1 : 2]), m, c); - } - - if (sq[0].startsWith("Devoured")) { - final String validDevoured = l[0].split(" ")[1]; - CardCollection cl = CardLists.getValidCards(c.getDevouredCards(), validDevoured.split(","), cc, c, null); - return doXMath(cl.size(), m, c); - } - - if (sq[0].contains("Party")) { - CardCollection adventurers = CardLists.getValidCards(cc.getCardsIn(ZoneType.Battlefield), - "Creature.Cleric,Creature.Rogue,Creature.Warrior,Creature.Wizard", cc, c, null); - - Set partyTypes = new HashSet<>(Arrays.asList(new String[]{"Cleric", "Rogue", "Warrior", "Wizard"})); - int partySize = 0; - - HashMap chosenParty = new HashMap<>(); - List wildcard = Lists.newArrayList(); - HashMap> multityped = new HashMap<>(); - - // Figure out how to count each class separately. - for (Card card : adventurers) { - Set creatureTypes = card.getType().getCreatureTypes(); - boolean anyType = creatureTypes.contains(CardType.AllCreatureTypes); - creatureTypes.retainAll(partyTypes); - - if (anyType || creatureTypes.size() == 4) { - wildcard.add(card); - - if (wildcard.size() >= 4) { - break; - } - continue; - } else if (creatureTypes.size() == 1) { - String type = (String)(creatureTypes.toArray()[0]); - - if (!chosenParty.containsKey(type)) { - chosenParty.put(type, card); - } - } else { - multityped.put(card, creatureTypes); - } - } - - partySize = Math.min(chosenParty.size() + wildcard.size(), 4); - - if (partySize < 4) { - partyTypes.removeAll(chosenParty.keySet()); - - // Here I'm left with just the party types that I haven't selected. - for(Card multi : multityped.keySet()) { - Set types = multityped.get(multi); - types.retainAll(partyTypes); - - for(String type : types) { - chosenParty.put(type, multi); - partyTypes.remove(type); - break; - } - } - } - - partySize = Math.min(chosenParty.size() + wildcard.size(), 4); - - return doXMath(partySize, m, c); - } - - if (sq[0].contains("CardPower")) { - return doXMath(c.getNetPower(), m, c); - } - if (sq[0].contains("CardToughness")) { - return doXMath(c.getNetToughness(), m, c); - } - if (sq[0].contains("CardSumPT")) { - return doXMath((c.getNetPower() + c.getNetToughness()), m, c); - } - - // Count$SumPower_valid - if (sq[0].contains("SumPower")) { - final String[] restrictions = l[0].split("_"); - final String[] rest = restrictions[1].split(","); - CardCollection filteredCards = CardLists.getValidCards(cc.getGame().getCardsIn(ZoneType.Battlefield), rest, cc, c, null); - return doXMath(Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetNetPower), m, c); - } - // Count$CardManaCost - if (sq[0].contains("CardManaCost")) { - Card ce; - if (sq[0].contains("Equipped") && c.isEquipping()) { - ce = c.getEquipping(); - } - else if (sq[0].contains("Remembered")) { - ce = (Card) c.getFirstRemembered(); - } - else { - ce = c; - } - - return doXMath(ce == null ? 0 : ce.getCMC(), m, c); - } - // Count$SumCMC_valid - if (sq[0].contains("SumCMC")) { - ZoneType zone = ZoneType.Battlefield; - //graveyard support for Inferno Project (may need other zones or multi-zone in future) - if (sq[0].contains("Graveyard")) - zone = ZoneType.Graveyard; - final String[] restrictions = l[0].split("_"); - final String[] rest = restrictions[1].split(","); - CardCollectionView cardsonbattlefield = game.getCardsIn(zone); - CardCollection filteredCards = CardLists.getValidCards(cardsonbattlefield, rest, cc, c, null); - return Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetCmc); - } - - if (sq[0].contains("CardNumColors")) { - return doXMath(CardUtil.getColors(c).countColors(), m, c); - } - if (sq[0].contains("CardNumAttacksThisTurn")) { - return doXMath(c.getDamageHistory().getCreatureAttacksThisTurn(), m, c); - } - if (sq[0].contains("ChosenNumber")) { - Integer i = c.getChosenNumber(); - return doXMath(i == null ? 0 : i, m, c); - } - if (sq[0].contains("CardCounters")) { - // CardCounters.ALL to be used for Kinsbaile Borderguard and anything that cares about all counters - int count = 0; - if (sq[1].equals("ALL")) { - for (Integer i : c.getCounters().values()) { - if (i != null && i > 0) { - count += i; - } - } - } - else { - count = c.getCounters(CounterType.getType(sq[1])); - } - return doXMath(count, m, c); - } - - // Count$TotalCounters._ - if (sq[0].contains("TotalCounters")) { - final String[] restrictions = l[0].split("_"); - final CounterType cType = CounterType.getType(restrictions[1]); - final String[] validFilter = restrictions[2].split(","); - CardCollectionView validCards = game.getCardsIn(ZoneType.Battlefield); - validCards = CardLists.getValidCards(validCards, validFilter, cc, c, null); - int cCount = 0; - for (final Card card : validCards) { - cCount += card.getCounters(cType); - } - return doXMath(cCount, m, c); - } - - if (sq[0].contains("CardControllerTypes")) { - return doXMath(getCardTypesFromList(cc.getCardsIn(ZoneType.listValueOf(sq[1]))), m, c); - } - - if (sq[0].contains("CardTypes")) { - return doXMath(getCardTypesFromList(game.getCardsIn(ZoneType.smartValueOf(sq[1]))), m, c); - } - - if (sq[0].contains("OppTypesInGrave")) { - final PlayerCollection opponents = cc.getOpponents(); - CardCollection oppCards = new CardCollection(); - oppCards.addAll(opponents.getCardsIn(ZoneType.Graveyard)); - return doXMath(getCardTypesFromList(oppCards), m, c); - } - - if (sq[0].contains("BushidoPoint")) { - return doXMath(c.getKeywordMagnitude(Keyword.BUSHIDO), m, c); - } - if (sq[0].contains("TimesKicked")) { - return doXMath(c.getKickerMagnitude(), m, c); - } - if (sq[0].contains("TimesPseudokicked")) { - return doXMath(c.getPseudoKickerMagnitude(), m, c); - } - if (sq[0].contains("TimesMutated")) { - return doXMath(c.getTimesMutated(), m, c); - } - - // Count$IfCastInOwnMainPhase.. // 7/10 - if (sq[0].contains("IfCastInOwnMainPhase")) { - final PhaseHandler cPhase = cc.getGame().getPhaseHandler(); - final boolean isMyMain = cPhase.getPhase().isMain() && cPhase.getPlayerTurn().equals(cc) && c.getCastFrom() != null; - return doXMath(Integer.parseInt(sq[isMyMain ? 1 : 2]), m, c); - } - - // Count$ThisTurnEntered [from ] - if (sq[0].contains("ThisTurnEntered")) { - final String[] workingCopy = l[0].split("_"); - - ZoneType destination = ZoneType.smartValueOf(workingCopy[1]); - final boolean hasFrom = workingCopy[2].equals("from"); - ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null; - String validFilter = workingCopy[hasFrom ? 4 : 2] ; - - final List res = CardUtil.getThisTurnEntered(destination, origin, validFilter, c); - if (origin == null) { // Remove cards on the battlefield that changed controller - res.removeAll(CardUtil.getThisTurnEntered(destination, destination, validFilter, c)); - } - return doXMath(res.size(), m, c); - } - - // Count$LastTurnEntered [from ] - if (sq[0].contains("LastTurnEntered")) { - final String[] workingCopy = l[0].split("_"); - - ZoneType destination = ZoneType.smartValueOf(workingCopy[1]); - final boolean hasFrom = workingCopy[2].equals("from"); - ZoneType origin = hasFrom ? ZoneType.smartValueOf(workingCopy[3]) : null; - String validFilter = workingCopy[hasFrom ? 4 : 2] ; - - final List res = CardUtil.getLastTurnEntered(destination, origin, validFilter, c); - if (origin == null) { // Remove cards on the battlefield that changed controller - res.removeAll(CardUtil.getLastTurnEntered(destination, destination, validFilter, c)); - } - return doXMath(res.size(), m, c); - } - - // Count$AttackersDeclared - if (sq[0].contains("AttackersDeclared")) { - return doXMath(cc.getAttackersDeclaredThisTurn(), m, c); - } - - // Count$CardAttackedThisTurn_ - if (sq[0].contains("CreaturesAttackedThisTurn")) { - final String[] workingCopy = l[0].split("_"); - final String validFilter = workingCopy[1]; - return doXMath(CardLists.getType(cc.getCreaturesAttackedThisTurn(), validFilter).size(), m, c); - } - - // Count$ThisTurnCast - // Count$LastTurnCast - if (sq[0].contains("ThisTurnCast") || sq[0].contains("LastTurnCast")) { - - final String[] workingCopy = l[0].split("_"); - final String validFilter = workingCopy[1]; - - List res = Lists.newArrayList(); - if (workingCopy[0].contains("This")) { - res = CardUtil.getThisTurnCast(validFilter, c); - } - else { - res = CardUtil.getLastTurnCast(validFilter, c); - } - - final int ret = doXMath(res.size(), m, c); - return ret; - } - - // Count$Morbid.. - if (sq[0].startsWith("Morbid")) { - final List res = CardUtil.getThisTurnEntered(ZoneType.Graveyard, ZoneType.Battlefield, "Creature", c); - return doXMath(Integer.parseInt(sq[res.size() > 0 ? 1 : 2]), m, c); - } - - // Count$Madness.. - if (sq[0].startsWith("Madness")) { - String v = c.isMadness() ? sq[1] : sq[2]; - // TODO move this to AbilityUtils - return doXMath(StringUtils.isNumeric(v) ? Integer.parseInt(v) : xCount(c, c.getSVar(v)), m, c); - } - - // Count$Foretold.. - if (sq[0].startsWith("Foretold")) { - String v = c.isForetold() ? sq[1] : sq[2]; - // TODO move this to AbilityUtils - return doXMath(StringUtils.isNumeric(v) ? Integer.parseInt(v) : xCount(c, c.getSVar(v)), m, c); - } - - // Count$Presence_.. - if (sq[0].startsWith("Presence")) { - final String type = sq[0].split("_")[1]; - - if (c.getCastFrom() != null && c.getCastSA() != null) { - int revealed = AbilityUtils.calculateAmount(c, "Revealed$Valid " + type, c.getCastSA()); - int ctrl = AbilityUtils.calculateAmount(c, "Count$Valid " + type + ".inZoneBattlefield+YouCtrl", c.getCastSA()); - if (revealed + ctrl >= 1) { - return doXMath(StringUtils.isNumeric(sq[1]) ? Integer.parseInt(sq[1]) : xCount(c, c.getSVar(sq[1])), m, c); - } - } - return doXMath(StringUtils.isNumeric(sq[2]) ? Integer.parseInt(sq[2]) : xCount(c, c.getSVar(sq[2])), m, c); - } - - if (sq[0].startsWith("LastStateBattlefield")) { - final String[] k = l[0].split(" "); - CardCollection list = new CardCollection(game.getLastStateBattlefield()); - list = CardLists.getValidCards(list, k[1].split(","), cc, c, null); - return CardFactoryUtil.doXMath(list.size(), m, c); - } - - if (sq[0].startsWith("LastStateGraveyard")) { - final String[] k = l[0].split(" "); - CardCollection list = new CardCollection(game.getLastStateGraveyard()); - list = CardLists.getValidCards(list, k[1].split(","), cc, c, null); - return CardFactoryUtil.doXMath(list.size(), m, c); - } - - if (sq[0].equals("YourTurns")) { - return doXMath(cc.getTurn(), m, c); - } - - if (sq[0].equals("TotalTurns")) { - // Sorry for the Singleton use, replace this once this function has game passed into it - return doXMath(game.getPhaseHandler().getTurn(), m, c); - } - - if (sq[0].equals("MaxDistinctOnStack")) { - return game.getStack().getMaxDistinctSources(); - } - - //Count$Random.. - if (sq[0].equals("Random")) { - int min = StringUtils.isNumeric(sq[1]) ? Integer.parseInt(sq[1]) : xCount(c, c.getSVar(sq[1])); - int max = StringUtils.isNumeric(sq[2]) ? Integer.parseInt(sq[2]) : xCount(c, c.getSVar(sq[2])); - - return forge.util.MyRandom.getRandom().nextInt(1+max-min) + min; - } - - // Count$Domain - if (sq[0].startsWith("Domain")) { - int n = 0; - Player neededPlayer = sq[0].equals("DomainActivePlayer") ? activePlayer : cc; - CardCollection someCards = CardLists.filter(neededPlayer.getCardsIn(ZoneType.Battlefield), Presets.LANDS); - for (String basic : MagicColor.Constant.BASIC_LANDS) { - if (!CardLists.getType(someCards, basic).isEmpty()) { - n++; - } - } - return doXMath(n, m, c); - } - if (sq[0].startsWith("UniqueManaColorsProduced")) { - boolean untappedOnly = sq[1].contains("ByUntappedSources"); - int uniqueColors = 0; - CardCollectionView otb = cc.getCardsIn(ZoneType.Battlefield); - outer: for (byte color : MagicColor.WUBRG) { - for (Card card : otb) { - if (!card.isTapped() || !untappedOnly) { - for (SpellAbility ma : card.getManaAbilities()) { - if (ma.canProduce(MagicColor.toShortString(color))) { - uniqueColors++; - continue outer; - } - } - } - } - } - return doXMath(uniqueColors, m, c); - } - // Count$Converge - if (sq[0].contains("Converge")) { - SpellAbility castSA = c.getCastSA(); - return doXMath(castSA == null ? 0 : castSA.getPayingColors().countColors(), m, c); - } - - // Count$CardMulticolor.. - if (sq[0].contains("CardMulticolor")) { - final boolean isMulti = CardUtil.getColors(c).isMulticolor(); - return doXMath(Integer.parseInt(sq[isMulti ? 1 : 2]), m, c); - } - - // Complex counting methods - CardCollectionView someCards = getCardListForXCount(c, cc, sq); - - // 1/10 - Count$MaxCMCYouCtrl - if (sq[0].contains("MaxCMC")) { - int mmc = Aggregates.max(someCards, CardPredicates.Accessors.fnGetCmc); - return doXMath(mmc, m, c); - } - - return doXMath(someCards.size(), m, c); - } - - private static CardCollectionView getCardListForXCount(final Card c, final Player cc, final String[] sq) { - final List opps = cc.getOpponents(); - CardCollection someCards = new CardCollection(); - final Game game = c.getGame(); - - // Generic Zone-based counting - // Count$QualityAndZones.Subquality - - // build a list of cards in each possible specified zone - - if (sq[0].contains("YouCtrl")) { - someCards.addAll(cc.getCardsIn(ZoneType.Battlefield)); - } - - if (sq[0].contains("InYourYard")) { - someCards.addAll(cc.getCardsIn(ZoneType.Graveyard)); - } - - if (sq[0].contains("InYourLibrary")) { - someCards.addAll(cc.getCardsIn(ZoneType.Library)); - } - - if (sq[0].contains("InYourHand")) { - someCards.addAll(cc.getCardsIn(ZoneType.Hand)); - } - - if (sq[0].contains("InYourSideboard")) { - someCards.addAll(cc.getCardsIn(ZoneType.Sideboard)); - } - - if (sq[0].contains("OppCtrl")) { - for (final Player p : opps) { - someCards.addAll(p.getZone(ZoneType.Battlefield).getCards()); - } - } - - if (sq[0].contains("InOppYard")) { - for (final Player p : opps) { - someCards.addAll(p.getCardsIn(ZoneType.Graveyard)); - } - } - - if (sq[0].contains("InOppHand")) { - for (final Player p : opps) { - someCards.addAll(p.getCardsIn(ZoneType.Hand)); - } - } - - if (sq[0].contains("InChosenHand")) { - if (c.getChosenPlayer() != null) { - someCards.addAll(c.getChosenPlayer().getCardsIn(ZoneType.Hand)); - } - } - - if (sq[0].contains("InRememberedHand")) { - if (c.getRemembered() != null) { - for (final Object o : c.getRemembered()) { - if (o instanceof Player) { - Player remPlayer = (Player) o; - someCards.addAll(remPlayer.getCardsIn(ZoneType.Hand)); - } - } - } - } - - if (sq[0].contains("InChosenYard")) { - if (c.getChosenPlayer() != null) { - someCards.addAll(c.getChosenPlayer().getCardsIn(ZoneType.Graveyard)); - } - } - - if (sq[0].contains("OnBattlefield")) { - someCards.addAll(game.getCardsIn(ZoneType.Battlefield)); - } - - if (sq[0].contains("InAllYards")) { - someCards.addAll(game.getCardsIn(ZoneType.Graveyard)); - } - - if (sq[0].contains("SpellsOnStack")) { - someCards.addAll(game.getCardsIn(ZoneType.Stack)); - } - - if (sq[0].contains("InAllHands")) { - someCards.addAll(game.getCardsIn(ZoneType.Hand)); - } - - // Count$InTargetedHand (targeted player's cards in hand) - if (sq[0].contains("InTargetedHand")) { - for (final SpellAbility sa : c.getCurrentState().getNonManaAbilities()) { - final SpellAbility saTargeting = sa.getSATargetingPlayer(); - if (saTargeting != null) { - for (final Player tgtP : saTargeting.getTargets().getTargetPlayers()) { - someCards.addAll(tgtP.getCardsIn(ZoneType.Hand)); - } - } - } - } - - // Count$InTargetedHand (targeted player's cards in hand) - if (sq[0].contains("InTargetedLibrary")) { - for (final SpellAbility sa : c.getCurrentState().getNonManaAbilities()) { - final SpellAbility saTargeting = sa.getSATargetingPlayer(); - if (saTargeting != null) { - for (final Player tgtP : saTargeting.getTargets().getTargetPlayers()) { - someCards.addAll(tgtP.getCardsIn(ZoneType.Library)); - } - } - } - } - - // Count$InTargetedHand (targeted player's cards in hand) - if (sq[0].contains("InEnchantedHand")) { - GameEntity o = c.getEntityAttachedTo(); - Player controller = null; - if (o instanceof Card) { - controller = ((Card) o).getController(); - } - else { - controller = (Player) o; - } - if (controller != null) { - someCards.addAll(controller.getCardsIn(ZoneType.Hand)); - } - } - if (sq[0].contains("InEnchantedYard")) { - GameEntity o = c.getEntityAttachedTo(); - Player controller = null; - if (o instanceof Card) { - controller = ((Card) o).getController(); - } - else { - controller = (Player) o; - } - if (controller != null) { - someCards.addAll(controller.getCardsIn(ZoneType.Graveyard)); - } - } - - // filter lists based on the specified quality - - // "Clerics you control" - Count$TypeYouCtrl.Cleric - if (sq[0].contains("Type")) { - someCards = CardLists.filter(someCards, CardPredicates.isType(sq[1])); - } - - // "Named in all graveyards" - Count$NamedAllYards. - - if (sq[0].contains("Named")) { - if (sq[1].equals("CARDNAME")) { - sq[1] = c.getName(); - } - someCards = CardLists.filter(someCards, CardPredicates.nameEquals(sq[1])); - } - - // Refined qualities - - // "Untapped Lands" - Count$UntappedTypeYouCtrl.Land - // if (sq[0].contains("Untapped")) { someCards = CardLists.filter(someCards, Presets.UNTAPPED); } - - // if (sq[0].contains("Tapped")) { someCards = CardLists.filter(someCards, Presets.TAPPED); } - -// String sq0 = sq[0].toLowerCase(); -// for (String color : MagicColor.Constant.ONLY_COLORS) { -// if (sq0.contains(color)) -// someCards = someCards.filter(CardListFilter.WHITE); -// } - // "White Creatures" - Count$WhiteTypeYouCtrl.Creature - // if (sq[0].contains("White")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.WHITE)); - // if (sq[0].contains("Blue")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.BLUE)); - // if (sq[0].contains("Black")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.BLACK)); - // if (sq[0].contains("Red")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.RED)); - // if (sq[0].contains("Green")) someCards = CardLists.filter(someCards, CardPredicates.isColor(MagicColor.GREEN)); - - if (sq[0].contains("Multicolor")) { - someCards = CardLists.filter(someCards, new Predicate() { - @Override - public boolean apply(final Card c) { - return CardUtil.getColors(c).isMulticolor(); - } - }); - } - - if (sq[0].contains("Monocolor")) { - someCards = CardLists.filter(someCards, new Predicate() { - @Override - public boolean apply(final Card c) { - return CardUtil.getColors(c).isMonoColor(); - } - }); - } - return someCards; - } - - public static int doXMath(final int num, final String operators, final Card c) { - if (operators == null || operators.equals("none")) { - return num; - } - - final String[] s = operators.split("\\."); - int secondaryNum = 0; - - try { - if (s.length == 2) { - secondaryNum = Integer.parseInt(s[1]); - } - } catch (final Exception e) { - secondaryNum = xCount(c, c.getSVar(s[1])); - } - - if (s[0].contains("Plus")) { - return num + secondaryNum; - } else if (s[0].contains("NMinus")) { - return secondaryNum - num; - } else if (s[0].contains("Minus")) { - return num - secondaryNum; - } else if (s[0].contains("Twice")) { - return num * 2; - } else if (s[0].contains("Thrice")) { - return num * 3; - } else if (s[0].contains("HalfUp")) { - return (int) (Math.ceil(num / 2.0)); - } else if (s[0].contains("HalfDown")) { - return (int) (Math.floor(num / 2.0)); - } else if (s[0].contains("ThirdUp")) { - return (int) (Math.ceil(num / 3.0)); - } else if (s[0].contains("ThirdDown")) { - return (int) (Math.floor(num / 3.0)); - } else if (s[0].contains("Negative")) { - return num * -1; - } else if (s[0].contains("Times")) { - return num * secondaryNum; - } else if (s[0].contains("DivideEvenlyDown")) { - if (secondaryNum == 0) { - return 0; - } else { - return num / secondaryNum; - } - } else if (s[0].contains("Mod")) { - return num % secondaryNum; - } else if (s[0].contains("Abs")) { - return Math.abs(num); - } else if (s[0].contains("LimitMax")) { - if (num < secondaryNum) { - return num; - } else { - return secondaryNum; - } - } else if (s[0].contains("LimitMin")) { - if (num > secondaryNum) { - return num; - } else { - return secondaryNum; - } - - } else { - return num; - } - } - - /** - *

- * handlePaid. - *

- * - * @param paidList - * a {@link forge.game.card.CardCollectionView} object. - * @param string - * a {@link java.lang.String} object. - * @param source - * a {@link forge.game.card.Card} object. - * @return a int. - */ - public static int handlePaid(final Iterable paidList, final String string, final Card source) { - if (paidList == null) { - if (string.contains(".")) { - final String[] splitString = string.split("\\.", 2); - return doXMath(0, splitString[1], source); - } else { - return 0; - } - } - if (string.startsWith("Amount")) { - int size = Iterables.size(paidList); - if (string.contains(".")) { - final String[] splitString = string.split("\\.", 2); - return doXMath(size, splitString[1], source); - } else { - return size; - } - - } - - if (string.startsWith("DifferentCMC")) { - final Set diffCMC = new HashSet<>(); - for (final Card card : paidList) { - diffCMC.add(card.getCMC()); - } - return diffCMC.size(); - } - - if (string.startsWith("SumCMC")) { - int sumCMC = 0; - for(Card c : paidList) { - sumCMC += c.getCMC(); - } - return sumCMC; - } - - if (string.startsWith("Valid")) { - - final String[] splitString = string.split("/", 2); - String valid = splitString[0].substring(6); - final List list = CardLists.getValidCardsAsList(paidList, valid, source.getController(), source, null); - return doXMath(list.size(), splitString.length > 1 ? splitString[1] : null, source); - } - - String filteredString = string; - Iterable filteredList = paidList; - final String[] filter = filteredString.split("_"); - - if (string.startsWith("FilterControlledBy")) { - final String pString = filter[0].substring(18); - FCollectionView controllers = AbilityUtils.getDefinedPlayers(source, pString, null); - filteredList = CardLists.filterControlledByAsList(filteredList, controllers); - filteredString = TextUtil.fastReplace(filteredString, pString, ""); - filteredString = TextUtil.fastReplace(filteredString, "FilterControlledBy_", ""); - } - - int tot = 0; - for (final Card c : filteredList) { - tot += xCount(c, filteredString); - } - - return tot; - } - /** *

* isMostProminentColor. diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 8d08a469332..50478243f19 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -219,55 +219,22 @@ public class CardProperty { return false; } } else if (property.equals("TargetedPlayerCtrl")) { - boolean foundTargetingSA = false; - for (final SpellAbility sa : source.getCurrentState().getNonManaAbilities()) { - final SpellAbility saTargeting = sa.getSATargetingPlayer(); - if (saTargeting != null) { - foundTargetingSA = true; - for (final Player p : saTargeting.getTargets().getTargetPlayers()) { - if (!controller.equals(p)) { - return false; - } - } - } - } - if (!foundTargetingSA) { - // FIXME: Something went wrong with detecting the SA that has a target, this can happen - // e.g. when activating a SA on a card from another player's hand (e.g. opponent's Chandra's Fury - // activated via Sen Triplets). Needs further investigation as to why this is happening, it might - // cause issues elsewhere too. - System.err.println("Warning: could not deduce a player target for TargetedPlayerCtrl for " + source + ", trying to locate it via CastSA..."); - SpellAbility castSA = source.getCastSA(); - while (castSA != null) { - if (!Iterables.isEmpty(castSA.getTargets().getTargetPlayers())) { - foundTargetingSA = true; - for (final Player p : castSA.getTargets().getTargetPlayers()) { - if (!controller.equals(p)) { - return false; - } - } - } - castSA = castSA.getSubAbility(); - } - if (!foundTargetingSA) { - System.err.println("Warning: checking targets in CastSA did not yield any results as well, TargetedPlayerCtrl check failed."); - } + if (!AbilityUtils.getDefinedPlayers(source, "TargetedPlayer", spellAbility).contains(controller)) { + return false; } } else if (property.equals("TargetedControllerCtrl")) { - for (final SpellAbility sa : source.getCurrentState().getNonManaAbilities()) { - final CardCollectionView cards = AbilityUtils.getDefinedCards(source, "Targeted", sa); - final List sas = AbilityUtils.getDefinedSpellAbilities(source, "Targeted", sa); - for (final Card c : cards) { - final Player p = c.getController(); - if (!controller.equals(p)) { - return false; - } + final CardCollectionView cards = AbilityUtils.getDefinedCards(source, "Targeted", spellAbility); + final List sas = AbilityUtils.getDefinedSpellAbilities(source, "Targeted", spellAbility); + for (final Card c : cards) { + final Player p = c.getController(); + if (!controller.equals(p)) { + return false; } - for (final SpellAbility s : sas) { - final Player p = s.getHostCard().getController(); - if (!controller.equals(p)) { - return false; - } + } + for (final SpellAbility s : sas) { + final Player p = s.getHostCard().getController(); + if (!controller.equals(p)) { + return false; } } } else if (property.startsWith("ActivePlayerCtrl")) { @@ -291,36 +258,8 @@ public class CardProperty { return false; } } else if (property.equals("TargetedPlayerOwn")) { - boolean foundTargetingSA = false; - for (final SpellAbility sa : source.getCurrentState().getNonManaAbilities()) { - final SpellAbility saTargeting = sa.getSATargetingPlayer(); - if (saTargeting != null) { - foundTargetingSA = true; - for (final Player p : saTargeting.getTargets().getTargetPlayers()) { - if (!card.getOwner().equals(p)) { - return false; - } - } - } - } - if (!foundTargetingSA) { - // FIXME: Something went wrong with detecting the SA that has a target, needs investigation - System.err.println("Warning: could not deduce a player target for TargetedPlayerOwn for " + source + ", trying to locate it via CastSA..."); - SpellAbility castSA = source.getCastSA(); - while (castSA != null) { - if (!Iterables.isEmpty(castSA.getTargets().getTargetPlayers())) { - foundTargetingSA = true; - for (final Player p : castSA.getTargets().getTargetPlayers()) { - if (!card.getOwner().equals(p)) { - return false; - } - } - } - castSA = castSA.getSubAbility(); - } - if (!foundTargetingSA) { - System.err.println("Warning: checking targets in CastSA did not yield any results as well, TargetedPlayerOwn check failed."); - } + if (!AbilityUtils.getDefinedPlayers(source, "TargetedPlayer", spellAbility).contains(card.getOwner())) { + return false; } } else if (property.startsWith("OwnedBy")) { final String valid = property.substring(8); @@ -475,14 +414,9 @@ public class CardProperty { } break; case "Targeted": - for (final SpellAbility sa : source.getCurrentState().getNonManaAbilities()) { - final SpellAbility saTargeting = sa.getSATargetingCard(); - if (saTargeting != null) { - for (final Card c : saTargeting.getTargets().getTargetCards()) { - if (!card.isEnchantedBy(c) && !card.equals(c.getEntityAttachedTo())) { - return false; - } - } + for (final Card c : AbilityUtils.getDefinedCards(source, "Targeted", spellAbility)) { + if (!card.isEnchantedBy(c) && !card.equals(c.getEntityAttachedTo())) { + return false; } } break; @@ -497,14 +431,9 @@ public class CardProperty { } } else if (property.startsWith("NotEnchantedBy")) { if (property.substring(14).equals("Targeted")) { - for (final SpellAbility sa : source.getCurrentState().getNonManaAbilities()) { - final SpellAbility saTargeting = sa.getSATargetingCard(); - if (saTargeting != null) { - for (final Card c : saTargeting.getTargets().getTargetCards()) { - if (card.isEnchantedBy(c)) { - return false; - } - } + for (final Card c : AbilityUtils.getDefinedCards(source, "Targeted", spellAbility)) { + if (card.isEnchantedBy(c)) { + return false; } } } else { @@ -528,14 +457,9 @@ public class CardProperty { } } else if (property.startsWith("CanBeEnchantedBy")) { if (property.substring(16).equals("Targeted")) { - for (final SpellAbility sa : source.getCurrentState().getNonManaAbilities()) { - final SpellAbility saTargeting = sa.getSATargetingCard(); - if (saTargeting != null) { - for (final Card c : saTargeting.getTargets().getTargetCards()) { - if (!card.canBeAttached(c)) { - return false; - } - } + for (final Card c : AbilityUtils.getDefinedCards(source, "Targeted", spellAbility)) { + if (!card.canBeAttached(c)) { + return false; } } } else if (property.substring(16).equals("AllRemembered")) { @@ -554,14 +478,9 @@ public class CardProperty { } } else if (property.startsWith("EquippedBy")) { if (property.substring(10).equals("Targeted")) { - for (final SpellAbility sa : source.getCurrentState().getNonManaAbilities()) { - final SpellAbility saTargeting = sa.getSATargetingCard(); - if (saTargeting != null) { - for (final Card c : saTargeting.getTargets().getTargetCards()) { - if (!card.hasCardAttachment(c)) { - return false; - } - } + for (final Card c : AbilityUtils.getDefinedCards(source, "Targeted", spellAbility)) { + if (!card.hasCardAttachment(c)) { + return false; } } } else if (property.substring(10).equals("Enchanted")) { @@ -697,18 +616,6 @@ public class CardProperty { if (!card.getDealtDamageToThisTurn().containsKey(source)) { return false; } - } else if (property.startsWith("IsTargetingSource")) { - for (final SpellAbility sa : card.getCurrentState().getNonManaAbilities()) { - final SpellAbility saTargeting = sa.getSATargetingCard(); - if (saTargeting != null) { - for (final Card c : saTargeting.getTargets().getTargetCards()) { - if (c.equals(source)) { - return true; - } - } - } - } - return false; } else if (property.startsWith("SharesCMCWith")) { if (property.equals("SharesCMCWith")) { if (!card.sharesCMCWith(source)) { @@ -916,10 +823,10 @@ public class CardProperty { } else if (restriction.equals(ZoneType.Battlefield.toString())) { return Iterables.any(game.getCardsIn(ZoneType.Battlefield), CardPredicates.sharesNameWith(card)); } else if (restriction.equals("ThisTurnCast")) { - return Iterables.any(CardUtil.getThisTurnCast("Card", source), CardPredicates.sharesNameWith(card)); + return Iterables.any(CardUtil.getThisTurnCast("Card", source, spellAbility), CardPredicates.sharesNameWith(card)); } else if (restriction.equals("MovedToGrave")) { - for (final SpellAbility sa : source.getCurrentState().getNonManaAbilities()) { - final SpellAbility root = sa.getRootAbility(); + if (!(spellAbility instanceof SpellAbility)) { + final SpellAbility root = ((SpellAbility)spellAbility).getRootAbility(); if (root != null && (root.getPaidList("MovedToGrave") != null) && !root.getPaidList("MovedToGrave").isEmpty()) { final CardCollectionView cards = root.getPaidList("MovedToGrave"); @@ -1010,7 +917,7 @@ public class CardProperty { } } } else if (property.startsWith("SecondSpellCastThisTurn")) { - final List cards = CardUtil.getThisTurnCast("Card", source); + final List cards = CardUtil.getThisTurnCast("Card", source, spellAbility); if (cards.size() < 2) { return false; } @@ -1018,7 +925,7 @@ public class CardProperty { return false; } } else if (property.equals("ThisTurnCast")) { - for (final Card c : CardUtil.getThisTurnCast("Card", source)) { + for (final Card c : CardUtil.getThisTurnCast("Card", source, spellAbility)) { if (card.equals(c)) { return true; } @@ -1067,7 +974,7 @@ public class CardProperty { return false; } - List cards = CardUtil.getThisTurnEntered(ZoneType.Graveyard, ZoneType.Hand, "Card", source); + List cards = CardUtil.getThisTurnEntered(ZoneType.Graveyard, ZoneType.Hand, "Card", source, spellAbility); if (!cards.contains(card) && !card.getMadnessWithoutCast()) { return false; } diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index 2edd8fce95b..ed54a5921c9 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -32,6 +32,7 @@ import forge.card.CardStateName; import forge.card.CardType; import forge.card.ColorSet; import forge.card.MagicColor; +import forge.game.CardTraitBase; import forge.game.Game; import forge.game.GameEntity; import forge.game.GameObject; @@ -115,7 +116,7 @@ public final class CardUtil { * @param src a Card object * @return a CardCollection that matches the given criteria */ - public static List getThisTurnEntered(final ZoneType to, final ZoneType from, final String valid, final Card src) { + public static List getThisTurnEntered(final ZoneType to, final ZoneType from, final String valid, final Card src, final CardTraitBase ctb) { List res = Lists.newArrayList(); final Game game = src.getGame(); if (to != ZoneType.Stack) { @@ -126,8 +127,7 @@ public final class CardUtil { else { res.addAll(game.getStackZone().getCardsAddedThisTurn(from)); } - res = CardLists.getValidCardsAsList(res, valid, src.getController(), src, null); - return res; + return CardLists.getValidCardsAsList(res, valid, src.getController(), src, ctb); } /** @@ -139,7 +139,7 @@ public final class CardUtil { * @param src a Card object * @return a CardCollection that matches the given criteria */ - public static List getLastTurnEntered(final ZoneType to, final ZoneType from, final String valid, final Card src) { + public static List getLastTurnEntered(final ZoneType to, final ZoneType from, final String valid, final Card src, final CardTraitBase ctb) { List res = Lists.newArrayList(); final Game game = src.getGame(); if (to != ZoneType.Stack) { @@ -150,16 +150,15 @@ public final class CardUtil { else { res.addAll(game.getStackZone().getCardsAddedLastTurn(from)); } - res = CardLists.getValidCardsAsList(res, valid, src.getController(), src, null); - return res; + return CardLists.getValidCardsAsList(res, valid, src.getController(), src, ctb); } - public static List getThisTurnCast(final String valid, final Card src) { - return CardLists.getValidCardsAsList(src.getGame().getStack().getSpellsCastThisTurn(), valid, src.getController(), src, null); + public static List getThisTurnCast(final String valid, final Card src, final CardTraitBase ctb) { + return CardLists.getValidCardsAsList(src.getGame().getStack().getSpellsCastThisTurn(), valid, src.getController(), src, ctb); } - public static List getLastTurnCast(final String valid, final Card src) { - return CardLists.getValidCardsAsList(src.getGame().getStack().getSpellsCastLastTurn(), valid, src.getController(), src, null); + public static List getLastTurnCast(final String valid, final Card src, final CardTraitBase ctb) { + return CardLists.getValidCardsAsList(src.getGame().getStack().getSpellsCastLastTurn(), valid, src.getController(), src, ctb); } diff --git a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java index 76844f950ef..88ad8460560 100644 --- a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java +++ b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java @@ -18,7 +18,6 @@ import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; -import forge.game.card.CardFactoryUtil; import forge.game.card.CardLists; import forge.game.card.CardPredicates; import forge.game.card.CardUtil; @@ -139,9 +138,9 @@ public class CostAdjustment { count = Integer.parseInt(amount); } else { if (st.hasParam("AffectedAmount")) { - count = CardFactoryUtil.xCount(card, hostCard.getSVar(amount)); + count = AbilityUtils.calculateAmount(card, amount, st); } else { - count = AbilityUtils.calculateAmount(hostCard, amount, sa); + count = AbilityUtils.calculateAmount(hostCard, amount, st); } } } @@ -380,7 +379,7 @@ public class CostAdjustment { int value; if ("AffectedX".equals(amount)) { - value = CardFactoryUtil.xCount(card, hostCard.getSVar(amount)); + value = AbilityUtils.calculateAmount(card, amount, staticAbility); } else if ("Undaunted".equals(amount)) { value = card.getController().getOpponents().size(); } else if (staticAbility.hasParam("Relative")) { @@ -429,13 +428,13 @@ public class CostAdjustment { final Card card = sa.getHostCard(); final Game game = hostCard.getGame(); - if (st.hasParam("ValidCard") && !st.matchesValid(card, st.getParam("ValidCard").split(","))) { + if (!st.matchesValidParam("ValidCard", card)) { return false; } - if (st.hasParam("ValidSpell") && !st.matchesValid(sa, st.getParam("ValidSpell").split(","))) { + if (!st.matchesValidParam("ValidSpell", sa)) { return false; } - if (st.hasParam("Activator") && !st.matchesValid(activator, st.getParam("Activator").split(","))) { + if (!st.matchesValidParam("Activator", activator)) { return false; } if (st.hasParam("NonActivatorTurn") && ((activator == null) @@ -455,7 +454,7 @@ public class CostAdjustment { } List list; if (st.hasParam("ValidCard")) { - list = CardUtil.getThisTurnCast(st.getParam("ValidCard"), hostCard); + list = CardUtil.getThisTurnCast(st.getParam("ValidCard"), hostCard, st); } else { list = game.getStack().getSpellsCastThisTurn(); } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java index d6c25a23ff9..2b42837ed70 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java @@ -30,7 +30,6 @@ import forge.game.GameType; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollection; -import forge.game.card.CardFactoryUtil; import forge.game.card.CardLists; import forge.game.card.CardPlayOption; import forge.game.card.CardUtil; @@ -455,13 +454,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { life = activator.getOpponentsSmallestLifeTotal(); } - int right = 1; - final String rightString = this.getLifeAmount().substring(2); - if (rightString.equals("X")) { - right = CardFactoryUtil.xCount(sa.getHostCard(), sa.getHostCard().getSVar("X")); - } else { - right = Integer.parseInt(this.getLifeAmount().substring(2)); - } + int right =AbilityUtils.calculateAmount(sa.getHostCard(), this.getLifeAmount().substring(2), sa); if (!Expressions.compare(life, this.getLifeAmount(), right)) { return false; diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java index b20b7329a45..343a88240e6 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java @@ -23,7 +23,6 @@ import forge.game.GameEntity; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollectionView; -import forge.game.card.CardFactoryUtil; import forge.game.card.CardPredicates; import forge.game.cost.Cost; import forge.game.keyword.KeywordInterface; @@ -199,7 +198,7 @@ public class StaticAbilityCantAttackBlock { } String costString = stAb.getParam("Cost"); if (stAb.hasSVar(costString)) { - costString = Integer.toString(CardFactoryUtil.xCount(hostCard, stAb.getSVar(costString))); + costString = Integer.toString(AbilityUtils.calculateAmount(hostCard, costString, stAb)); } return new Cost(costString, true); diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java index 964e3134506..9c84816a495 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java @@ -132,7 +132,7 @@ public class StaticAbilityCantBeCast { if (stAb.hasParam("NumLimitEachTurn") && activator != null) { int limit = Integer.parseInt(stAb.getParam("NumLimitEachTurn")); String valid = stAb.hasParam("ValidCard") ? stAb.getParam("ValidCard") : "Card"; - List thisTurnCast = CardUtil.getThisTurnCast(valid, card); + List thisTurnCast = CardUtil.getThisTurnCast(valid, card, stAb); if (CardLists.filterControlledBy(thisTurnCast, activator).size() < limit) { return false; } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java index 2d435b4f920..4fb1f95f63b 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -638,11 +638,11 @@ public final class StaticAbilityContinuous { if (layer == StaticAbilityLayer.SETPT) { if ((setPower != Integer.MAX_VALUE) || (setToughness != Integer.MAX_VALUE)) { // non CharacteristicDefining - if (setP.startsWith("AffectedX")) { - setPower = CardFactoryUtil.xCount(affectedCard, AbilityUtils.getSVar(stAb, setP)); + if (setP.startsWith("Affected")) { + setPower = AbilityUtils.calculateAmount(affectedCard, setP, stAb, true); } - if (setT.startsWith("AffectedX")) { - setToughness = CardFactoryUtil.xCount(affectedCard, AbilityUtils.getSVar(stAb, setT)); + if (setT.startsWith("Affected")) { + setToughness = AbilityUtils.calculateAmount(affectedCard, setT, stAb, true); } affectedCard.addNewPT(setPower, setToughness, hostCard.getTimestamp(), stAb.hasParam("CharacteristicDefining")); @@ -651,11 +651,11 @@ public final class StaticAbilityContinuous { // add P/T bonus if (layer == StaticAbilityLayer.MODIFYPT) { - if (addP.startsWith("AffectedX")) { - powerBonus = CardFactoryUtil.xCount(affectedCard, AbilityUtils.getSVar(stAb, addP)); + if (addP.startsWith("Affected")) { + powerBonus = AbilityUtils.calculateAmount(affectedCard, addP, stAb, true); } - if (addT.startsWith("AffectedX")) { - toughnessBonus = CardFactoryUtil.xCount(affectedCard, AbilityUtils.getSVar(stAb, addT)); + if (addT.startsWith("Affected")) { + toughnessBonus = AbilityUtils.calculateAmount(affectedCard, addT, stAb, true); } affectedCard.addPTBoost(powerBonus, toughnessBonus, se.getTimestamp(), stAb.getId()); } diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java b/forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java index 73294ffc9c6..291d872775d 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java @@ -30,7 +30,6 @@ import com.google.common.collect.Sets; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.card.Card; -import forge.game.card.CardFactoryUtil; import forge.game.card.CardLists; import forge.game.card.CardPredicates; import forge.game.card.CardUtil; @@ -139,7 +138,7 @@ public class TriggerChangesZone extends Trigger { final String comparator = condition.length < 2 ? "GE1" : condition[1]; final int referenceValue = AbilityUtils.calculateAmount(host, comparator.substring(2), this); final Card triggered = (Card) runParams.get(AbilityKey.Card); - final int actualValue = CardFactoryUtil.xCount(triggered, host.getSVar(condition[0])); + final int actualValue = AbilityUtils.calculateAmount(triggered, condition[0], this); if (!Expressions.compare(actualValue, comparator.substring(0, 2), referenceValue)) { return false; } @@ -179,7 +178,7 @@ public class TriggerChangesZone extends Trigger { /* this trigger only activates for the nth spell you cast this turn */ if (hasParam("ConditionYouCastThisTurn")) { final String compare = getParam("ConditionYouCastThisTurn"); - List thisTurnCast = CardUtil.getThisTurnCast("Card", getHostCard()); + List thisTurnCast = CardUtil.getThisTurnCast("Card", getHostCard(), this); thisTurnCast = CardLists.filterControlledByAsList(thisTurnCast, getHostCard().getController()); // checks which card this spell was the castSA diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCastOrCopy.java b/forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCastOrCopy.java index 58b017c51f4..e0383f67b41 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCastOrCopy.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCastOrCopy.java @@ -109,7 +109,7 @@ public class TriggerSpellAbilityCastOrCopy extends Trigger { if (hasParam("ActivatorThisTurnCast")) { final String compare = getParam("ActivatorThisTurnCast"); final String valid = hasParam("ValidCard") ? getParam("ValidCard") : "Card"; - List thisTurnCast = CardUtil.getThisTurnCast(valid, getHostCard()); + List thisTurnCast = CardUtil.getThisTurnCast(valid, getHostCard(), this); thisTurnCast = CardLists.filterControlledBy(thisTurnCast, si.getSpellAbility(true).getActivatingPlayer()); int left = thisTurnCast.size(); int right = Integer.parseInt(compare.substring(2)); diff --git a/forge-gui/res/cardsfolder/h/hidetsugus_second_rite.txt b/forge-gui/res/cardsfolder/h/hidetsugus_second_rite.txt index a400eb74dd2..3173a9140a8 100644 --- a/forge-gui/res/cardsfolder/h/hidetsugus_second_rite.txt +++ b/forge-gui/res/cardsfolder/h/hidetsugus_second_rite.txt @@ -5,5 +5,4 @@ A:SP$ DealDamage | Cost$ 3 R | ValidTgts$ Player | TgtPrompt$ Select target play SVar:X:TargetedPlayer$LifeTotal SVar:Y:Count$OppsAtLifeTotal.10 SVar:NeedsToPlayVar:Y GE1 -SVar:Picture:http://resources.wizards.com/magic/cards/sok/en-us/card88818.jpg -Oracle:If target player has exactly 10 life, Hidetsugu's Second Rite deals 10 damage to that player. +Oracle:If target player has exactly 10 life, Hidetsugu's Second Rite deals 10 damage to that player. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/r/robber_of_the_rich.txt b/forge-gui/res/cardsfolder/r/robber_of_the_rich.txt index b68d4cf32c6..d2b762e432e 100644 --- a/forge-gui/res/cardsfolder/r/robber_of_the_rich.txt +++ b/forge-gui/res/cardsfolder/r/robber_of_the_rich.txt @@ -11,5 +11,5 @@ SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayIgnoreColor$ True | Effect SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$ValidHand Card.DefenderCtrl SVar:Y:Count$InYourHand -SVar:Z:Count$CreaturesAttackedThisTurn_Rogue +SVar:Z:Count$CreaturesAttackedThisTurn Creature.Rogue Oracle:Reach, haste\nWhenever Robber of the Rich attacks, if defending player has more cards in hand than you, exile the top card of their library. During any turn you attacked with a Rogue, you may cast that card and you may spend mana as though it were mana of any color to cast that spell. diff --git a/forge-gui/src/main/java/forge/gui/card/CardScriptParser.java b/forge-gui/src/main/java/forge/gui/card/CardScriptParser.java index d65aadfce23..d02b173a8ce 100644 --- a/forge-gui/src/main/java/forge/gui/card/CardScriptParser.java +++ b/forge-gui/src/main/java/forge/gui/card/CardScriptParser.java @@ -412,8 +412,7 @@ public final class CardScriptParser { "notTributed", "madness", "Paired", "NotPaired", "PairedWith", "Above", "DirectlyAbove", "TopGraveyardCreature", "BottomGraveyard", "TopLibrary", "Cloned", "DamagedBy", "Damaged", - "IsTargetingSource", "sharesPermanentTypeWith", - "canProduceSameManaTypeWith", "SecondSpellCastThisTurn", + "sharesPermanentTypeWith", "canProduceSameManaTypeWith", "SecondSpellCastThisTurn", "ThisTurnCast", "withFlashback", "tapped", "untapped", "faceDown", "faceUp", "hasLevelUp", "DrawnThisTurn", "notDrawnThisTurn", "firstTurnControlled", "notFirstTurnControlled",