diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 2dabc751d06..7421fa0cffb 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -751,7 +751,7 @@ public class AiController { if (xPay <= 0) { return AiPlayDecision.CantAffordX; } - card.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } else if (mana.isZero()) { // if mana is zero, but card mana cost does have X, then something is wrong ManaCost cardCost = card.getManaCost(); @@ -1061,8 +1061,8 @@ public class AiController { } else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) { return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa); } else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) { - final int CMC = Integer.parseInt(sourceCard.getSVar("PayX")); - CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(CMC)); + final int cmc = Integer.parseInt(sa.getSVar("PayX")); + CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc)); if (discards.isEmpty()) { return null; } else { @@ -1802,6 +1802,9 @@ public class AiController { throw new UnsupportedOperationException(); } CardCollection result = new CardCollection(); + if (sa.hasParam("AIMaxAmount")) { + max = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("AIMaxAmount"), sa); + } switch(sa.getApi()) { case TwoPiles: // TODO: improve AI diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java index c2b8c9cf691..f5c43be501f 100644 --- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java +++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java @@ -4,12 +4,15 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import org.apache.commons.lang3.ObjectUtils; + import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Lists; import forge.card.CardType; import forge.game.Game; +import forge.game.GameEntityCounterTable; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollection; @@ -20,6 +23,7 @@ import forge.game.card.CardPredicates.Presets; import forge.game.card.CounterEnumType; import forge.game.card.CounterType; import forge.game.cost.*; +import forge.game.keyword.Keyword; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; @@ -31,7 +35,7 @@ import forge.util.collect.FCollectionView; public class AiCostDecision extends CostDecisionMakerBase { private final SpellAbility ability; private final Card source; - + private final CardCollection discarded; private final CardCollection tapped; @@ -47,11 +51,11 @@ public class AiCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(CostAddMana cost) { Integer c = cost.convertAmount(); - + if (c == null) { c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability); } - + return PaymentDecision.number(c); } @@ -321,17 +325,17 @@ public class AiCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(CostGainLife cost) { final List oppsThatCanGainLife = Lists.newArrayList(); - + for (final Player opp : cost.getPotentialTargets(player, source)) { if (opp.canGainLife()) { oppsThatCanGainLife.add(opp); } } - + if (oppsThatCanGainLife.size() == 0) { return null; } - + return PaymentDecision.players(oppsThatCanGainLife); } @@ -587,7 +591,7 @@ public class AiCostDecision extends CostDecisionMakerBase { public PaymentDecision visit(CostReturn cost) { if (cost.payCostFromSource()) return PaymentDecision.card(source); - + Integer c = cost.convertAmount(); if (c == null) { c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability); @@ -616,7 +620,7 @@ public class AiCostDecision extends CostDecisionMakerBase { if (cost.getType().equals("SameColor")) { return null; } - + hand = CardLists.getValidCards(hand, type.split(";"), player, source, ability); Integer c = cost.convertAmount(); if (c == null) { @@ -632,60 +636,162 @@ public class AiCostDecision extends CostDecisionMakerBase { return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability)); } + protected int removeCounter(GameEntityCounterTable table, List prefs, CounterEnumType cType, int stillToRemove) { + int removed = 0; + if (!prefs.isEmpty() && stillToRemove > 0) { + Collections.sort(prefs, CardPredicates.compareByCounterType(cType)); + + for (Card prefCard : prefs) { + // already enough removed + if (stillToRemove <= removed) { + break; + } + int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove); + if (thisRemove > 0) { + removed += thisRemove; + table.put(prefCard, CounterType.get(cType), thisRemove); + } + } + } + return removed; + } + @Override public PaymentDecision visit(CostRemoveAnyCounter cost) { final String amount = cost.getAmount(); final int c = AbilityUtils.calculateAmount(source, amount, ability); - final String type = cost.getType(); + final Card originalHost = ObjectUtils.defaultIfNull(ability.getOriginalHost(), source); - CardCollectionView typeList = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"), player, source, ability); + if (c <= 0) { + return null; + } + + CardCollectionView typeList = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source, ability); + // only cards with counters are of interest + typeList = CardLists.filter(typeList, CardPredicates.hasCounters()); // no target if (typeList.isEmpty()) { return null; } + // TODO fill up a GameEntityCounterTable + // cost now has counter type or null + // the amount might be different from 1, could be X + // currently if amount is bigger than one, + // it tries to remove all counters from one source and type at once + + + int toRemove = 0; + final GameEntityCounterTable table = new GameEntityCounterTable(); + + // currently the only one using remove any counter using a type uses p1p1 + // the first things are benefit from removing counters - // try to remove +1/+1 counter from undying creature - List prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.P1P1, c), - CardPredicates.hasKeyword("Undying")); - - if (!prefs.isEmpty()) { - Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.P1P1)); - PaymentDecision result = PaymentDecision.card(prefs); - result.ct = CounterType.get(CounterEnumType.P1P1); - return result; - } - // try to remove -1/-1 counter from persist creature - prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.M1M1, c), - CardPredicates.hasKeyword("Persist")); + if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.M1M1))) { + List prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.M1M1), CardPredicates.hasKeyword(Keyword.PERSIST)); - if (!prefs.isEmpty()) { - Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.M1M1)); - PaymentDecision result = PaymentDecision.card(prefs); - result.ct = CounterType.get(CounterEnumType.M1M1); - return result; + toRemove += removeCounter(table, prefs, CounterEnumType.M1M1, c - toRemove); } - // try to remove Time counter from Chronozoa, it will generate more - prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.TIME, c), - CardPredicates.nameEquals("Chronozoa")); + // try to remove +1/+1 counter from undying creature + if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.P1P1))) { + List prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.P1P1), CardPredicates.hasKeyword(Keyword.UNDYING)); - if (!prefs.isEmpty()) { - Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.TIME)); - PaymentDecision result = PaymentDecision.card(prefs); - result.ct = CounterType.get(CounterEnumType.TIME); - return result; + toRemove += removeCounter(table, prefs, CounterEnumType.P1P1, c - toRemove); + } + + if (c > toRemove && cost.counter == null && originalHost.hasSVar("AIRemoveCounterCostPriority") && !"ANY".equalsIgnoreCase(originalHost.getSVar("AIRemoveCounterCostPriority"))) { + String[] counters = TextUtil.split(originalHost.getSVar("AIRemoveCounterCostPriority"), ','); + + for (final String ctr : counters) { + CounterType ctype = CounterType.getType(ctr); + // ctype == null means any type + // any type is just used to return null for this + + for (Card card : CardLists.filter(typeList, CardPredicates.hasCounter(ctype))) { + int thisRemove = Math.min(card.getCounters(ctype), c - toRemove); + if (thisRemove > 0) { + toRemove += thisRemove; + table.put(card, ctype, thisRemove); + } + } + } + } + + // filter for negative counters + if (c > toRemove && cost.counter == null) { + List negatives = CardLists.filter(typeList, new Predicate() { + @Override + public boolean apply(final Card crd) { + for (CounterType cType : table.filterToRemove(crd).keySet()) { + if (ComputerUtil.isNegativeCounter(cType, crd)) { + return true; + } + } + return false; + } + }); + + if (!negatives.isEmpty()) { + // TODO sort negatives to remove from best Cards first? + for (final Card crd : negatives) { + for (Map.Entry e : table.filterToRemove(crd).entrySet()) { + if (ComputerUtil.isNegativeCounter(e.getKey(), crd)) { + int over = Math.min(e.getValue(), c - toRemove); + if (over > 0) { + toRemove += over; + table.put(crd, e.getKey(), over); + } + } + } + } + } + } + + // filter for useless counters + // they have no effect on the card, if they are there or removed + if (c > toRemove && cost.counter == null) { + List useless = CardLists.filter(typeList, new Predicate() { + @Override + public boolean apply(final Card crd) { + for (CounterType ctype : table.filterToRemove(crd).keySet()) { + if (ComputerUtil.isUselessCounter(ctype, crd)) { + return true; + } + } + return false; + } + }); + + if (!useless.isEmpty()) { + for (final Card crd : useless) { + for (Map.Entry e : table.filterToRemove(crd).entrySet()) { + if (ComputerUtil.isUselessCounter(e.getKey(), crd)) { + int over = Math.min(e.getValue(), c - toRemove); + if (over > 0) { + toRemove += over; + table.put(crd, e.getKey(), over); + } + } + } + } + } + } + + // try to remove Time counter from Chronozoa, it will generate more token + if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.TIME))) { + List prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.TIME), CardPredicates.nameEquals("Chronozoa")); + + toRemove += removeCounter(table, prefs, CounterEnumType.TIME, c - toRemove); } // try to remove Quest counter on something with enough counters for the // effect to continue - prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.QUEST, c)); - - if (!prefs.isEmpty()) { - prefs = CardLists.filter(prefs, new Predicate() { + if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.QUEST))) { + List prefs = CardLists.filter(typeList, new Predicate() { @Override public boolean apply(final Card crd) { // a Card without MaxQuestEffect doesn't need any Quest @@ -694,130 +800,65 @@ public class AiCostDecision extends CostDecisionMakerBase { if (crd.hasSVar("MaxQuestEffect")) { e = Integer.parseInt(crd.getSVar("MaxQuestEffect")); } - return crd.getCounters(CounterEnumType.QUEST) >= e + c; + return crd.getCounters(CounterEnumType.QUEST) > e; } }); Collections.sort(prefs, Collections.reverseOrder(CardPredicates.compareByCounterType(CounterEnumType.QUEST))); - PaymentDecision result = PaymentDecision.card(prefs); - result.ct = CounterType.get(CounterEnumType.QUEST); - return result; + + for (final Card crd : prefs) { + int e = 0; + if (crd.hasSVar("MaxQuestEffect")) { + e = Integer.parseInt(crd.getSVar("MaxQuestEffect")); + } + int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove); + if (over > 0) { + toRemove += over; + table.put(crd, CounterType.get(CounterEnumType.QUEST), over); + } + } } - // filter for only cards with enough counters - typeList = CardLists.filter(typeList, new Predicate() { - @Override - public boolean apply(final Card crd) { - for (Integer i : crd.getCounters().values()) { - if (i >= c) { - return true; + // remove Lore counters from Sagas to keep them longer + if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.LORE))) { + List prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.LORE), CardPredicates.isType("Saga")); + // TODO add Svars and other stuff to keep the Sagas on specific levels + // also add a way for the AI to respond to the last Chapter ability to keep the Saga on the field if wanted + toRemove += removeCounter(table, prefs, CounterEnumType.LORE, c - toRemove); + } + + + // TODO add logic to remove positive counters? + if (c > toRemove && cost.counter != null) { + // TODO add logic for Ooze Flux, should probably try to make a token as big as possible + // without killing own non undying creatures in the process + // the amount of X should probably be tweaked for this + List withCtr = CardLists.filter(typeList, CardPredicates.hasCounter(cost.counter)); + for (Card card : withCtr) { + int thisRemove = Math.min(card.getCounters(cost.counter), c - toRemove); + if (thisRemove > 0) { + toRemove += thisRemove; + table.put(card, cost.counter, thisRemove); + } + } + } + + // Used to not return null + // Special part for CostPriority Any + if (c > toRemove && cost.counter == null && originalHost.hasSVar("AIRemoveCounterCostPriority") && "ANY".equalsIgnoreCase(originalHost.getSVar("AIRemoveCounterCostPriority"))) { + for (Card card : typeList) { + // TODO try not to remove to much positive counters from the same card + for (Map.Entry e : table.filterToRemove(card).entrySet()) { + int thisRemove = Math.min(e.getValue(), c - toRemove); + if (thisRemove > 0) { + toRemove += thisRemove; + table.put(card, e.getKey(), thisRemove); } } - return false; - } - }); - - // nothing with enough counters of any type - if (typeList.isEmpty()) { - return null; - } - - // filter for negative counters - List negatives = CardLists.filter(typeList, new Predicate() { - @Override - public boolean apply(final Card crd) { - for (Map.Entry e : crd.getCounters().entrySet()) { - if (e.getValue() >= c && ComputerUtil.isNegativeCounter(e.getKey(), crd)) { - return true; - } - } - return false; - } - }); - - if (!negatives.isEmpty()) { - final Card card = ComputerUtilCard.getBestAI(negatives); - PaymentDecision result = PaymentDecision.card(card); - - for (Map.Entry e : card.getCounters().entrySet()) { - if (e.getValue() >= c && ComputerUtil.isNegativeCounter(e.getKey(), card)) { - result.ct = e.getKey(); - break; - } - } - return result; - } - - // filter for useless counters - // they have no effect on the card, if they are there or removed - List useless = CardLists.filter(typeList, new Predicate() { - @Override - public boolean apply(final Card crd) { - for (Map.Entry e : crd.getCounters().entrySet()) { - if (e.getValue() >= c && ComputerUtil.isUselessCounter(e.getKey())) { - return true; - } - } - return false; - } - }); - - if (!useless.isEmpty()) { - final Card card = useless.get(0); - PaymentDecision result = PaymentDecision.card(card); - - for (Map.Entry e : card.getCounters().entrySet()) { - if (e.getValue() >= c && ComputerUtil.isUselessCounter(e.getKey())) { - result.ct = e.getKey(); - break; - } - } - return result; - } - - // try a way to pay unless cost - if ("Chisei, Heart of Oceans".equals(ComputerUtilAbility.getAbilitySourceName(ability))) { - final Card card = ComputerUtilCard.getWorstAI(typeList); - PaymentDecision result = PaymentDecision.card(card); - for (Map.Entry e : card.getCounters().entrySet()) { - if (e.getValue() >= c) { - result.ct = e.getKey(); - break; - } - } - return result; - } - - // check if the card defines its own priorities for counter removal as cost - if (source.hasSVar("AIRemoveCounterCostPriority")) { - String[] counters = TextUtil.split(source.getSVar("AIRemoveCounterCostPriority"), ','); - - for (final String ctr : counters) { - List withCtr = CardLists.filter(typeList, new Predicate() { - @Override - public boolean apply(final Card crd) { - for (Map.Entry e : crd.getCounters().entrySet()) { - if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey().equals(CounterType.getType(ctr)))) { - return true; - } - } - return false; - } - }); - if (!withCtr.isEmpty()) { - final Card card = withCtr.get(0); - PaymentDecision result = PaymentDecision.card(card); - - for (Map.Entry e : card.getCounters().entrySet()) { - if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey().equals(CounterType.getType(ctr)))) { - result.ct = e.getKey(); - break; - } - } - return result; - } } } - return null; + + // if table is empty, than no counter was removed + return table.isEmpty() ? null : PaymentDecision.counters(table); } @Override @@ -843,7 +884,7 @@ public class AiCostDecision extends CostDecisionMakerBase { } } } else if (sVar.equals("Count$xPaid")) { - c = AbilityUtils.calculateAmount(source, "PayX", null); + c = AbilityUtils.calculateAmount(source, "PayX", ability); } else { c = AbilityUtils.calculateAmount(source, amount, ability); } @@ -898,7 +939,7 @@ public class AiCostDecision extends CostDecisionMakerBase { System.out.println("Couldn't find a valid card to untap for: " + source.getName()); return null; } - + return PaymentDecision.card(list); } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 29ca893d82f..8a8a4734bed 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -161,8 +161,6 @@ public class ComputerUtil { // Play higher costing spells first? final Cost cost = sa.getPayCosts(); - // Convert cost to CMC - // String totalMana = source.getSVar("PayX"); // + cost.getCMC() // Consider the costs here for relative "scoring" if (hasDiscardHandCost(cost)) { @@ -2809,7 +2807,17 @@ public class ComputerUtil { } // this countertypes has no effect - public static boolean isUselessCounter(CounterType type) { + public static boolean isUselessCounter(CounterType type, Card c) { + + // Quest counter on a card without MaxQuestEffect are useless + if (type.is(CounterEnumType.QUEST)) { + int e = 0; + if ( c.hasSVar("MaxQuestEffect")) { + e = Integer.parseInt(c.getSVar("MaxQuestEffect")); + } + return c.getCounters(type) > e; + } + return type.is(CounterEnumType.AWAKENING) || type.is(CounterEnumType.MANIFESTATION) || type.is(CounterEnumType.PETRIFICATION) || type.is(CounterEnumType.TRAINING); } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index ccf63277425..c5e428cd87d 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -19,6 +19,8 @@ import forge.game.zone.ZoneType; import forge.util.MyRandom; import forge.util.TextUtil; import forge.util.collect.FCollectionView; + +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import java.util.List; @@ -53,9 +55,6 @@ public class ComputerUtilCost { return true; } - public static boolean checkRemoveCounterCost(final Cost cost, final Card source) { - return checkRemoveCounterCost(cost, source, null); - } /** * Check remove counter cost. * @@ -69,6 +68,7 @@ public class ComputerUtilCost { if (cost == null) { return true; } + final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa); for (final CostPart part : cost.getCostParts()) { if (part instanceof CostRemoveCounter) { final CostRemoveCounter remCounter = (CostRemoveCounter) part; @@ -88,17 +88,17 @@ public class ComputerUtilCost { // Remove X counters - set ChosenX to max possible value here, the SAs should correct that // value later as the AI decides what to do (in checkApiLogic / checkAiLogic) - if (sa != null && sa.hasSVar(remCounter.getAmount())) { + if (sa.hasSVar(remCounter.getAmount())) { final String sVar = sa.getSVar(remCounter.getAmount()); if (sVar.equals("XChoice") && !sa.hasSVar("ChosenX")) { sa.setSVar("ChosenX", String.valueOf(source.getCounters(type))); + } else if (sVar.equals("Count$xPaid") && sa.hasSVar("PayX")) { + sa.setSVar("PayX", Integer.toString(Math.min(Integer.valueOf(sa.getSVar("PayX")), source.getCounters(type)))); } } - // check the sa what the PaymentDecision is. // ignore Loyality abilities with Zero as Cost - if (sa != null && !type.is(CounterEnumType.LOYALTY)) { - final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa); + if (!type.is(CounterEnumType.LOYALTY)) { PaymentDecision pay = decision.visit(remCounter); if (pay == null || pay.c <= 0) { return false; @@ -111,14 +111,10 @@ public class ComputerUtilCost { return false; } } else if (part instanceof CostRemoveAnyCounter) { - if (sa != null) { - final CostRemoveAnyCounter remCounter = (CostRemoveAnyCounter) part; + final CostRemoveAnyCounter remCounter = (CostRemoveAnyCounter) part; - PaymentDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa).visit(remCounter); - return decision != null; - } - - return false; + PaymentDecision pay = decision.visit(remCounter); + return pay != null; } } return true; @@ -677,4 +673,26 @@ public class ComputerUtilCost { } return false; } + + public static int getMaxXValue(SpellAbility sa, Player ai) { + final Cost abCost = sa.getPayCosts(); + if (abCost == null || !abCost.hasXInAnyCostPart()) { + return 0; + } + + Integer val = null; + + if (sa.costHasManaX()) { + val = ComputerUtilMana.determineLeftoverMana(sa, ai); + } + + if (sa.usesTargeting()) { + // if announce is used as min targets, check what the max possible number would be + if ("X".equals(sa.getTargetRestrictions().getMinTargets())) { + val = ObjectUtils.min(val, CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa).size()); + } + } + + return ObjectUtils.defaultIfNull(ObjectUtils.min(val, abCost.getMaxForNonManaX(sa, ai)), 0); + } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index d0685de143f..cda5239a2b1 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -4,7 +4,6 @@ import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.*; import forge.ai.ability.AnimateAi; -import forge.card.CardStateName; import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.ManaAtom; @@ -119,10 +118,6 @@ public class ComputerUtilMana { return score; } - private static void sortManaAbilities(final Multimap manaAbilityMap) { - sortManaAbilities(manaAbilityMap, null); - } - private static void sortManaAbilities(final Multimap manaAbilityMap, final SpellAbility sa) { final Map manaCardMap = Maps.newHashMap(); final List orderedCards = Lists.newArrayList(); @@ -1194,11 +1189,11 @@ public class ComputerUtilMana { } else { // For Count$xPaid set PayX in the AFs then use that here // Else calculate it as appropriate. - final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X"; - if (!sa.getSVar(xSvar).isEmpty() || card.hasSVar(xSvar) || card.getState(CardStateName.Original).hasSVar(xSvar)) { - if (xSvar.equals("PayX") && (card.hasSVar(xSvar) || card.getState(CardStateName.Original).hasSVar(xSvar))) { + final String xSvar = sa.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X"; + if (sa.hasSVar(xSvar)) { + if (xSvar.equals("PayX")) { // X SVar may end up being an empty string when copying a spell with no cost (e.g. Jhoira Avatar) - String xValue = card.hasSVar(xSvar) ? card.getSVar(xSvar) : card.getState(CardStateName.Original).getSVar(xSvar); + String xValue = sa.getSVar(xSvar); manaToAdd = xValue.isEmpty() ? 0 : Integer.parseInt(xValue) * cost.getXcounter(); // X } else { manaToAdd = AbilityUtils.calculateAmount(card, xSvar, sa) * cost.getXcounter(); @@ -1206,9 +1201,7 @@ public class ComputerUtilMana { } } - String manaXColor = sa.getParam("XColor"); - ManaCostShard shardToGrow = ManaCostShard.parseNonGeneric(manaXColor == null ? "1" : manaXColor); - cost.increaseShard(shardToGrow, manaToAdd); + cost.increaseShard(ManaCostShard.parseNonGeneric(sa.getParamOrDefault("XColor", "1")), manaToAdd); if (!test) { sa.setXManaCostPaid(manaToAdd / cost.getXcounter()); diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 2105a24592f..3e2590964c0 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -957,9 +957,7 @@ public class PlayerControllerAi extends PlayerController { final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } }; emptyAbility.setActivatingPlayer(player); emptyAbility.setTriggeringObjects(sa.getTriggeringObjects()); - for (String sVar : sa.getSVars()) { - emptyAbility.setSVar(sVar, sa.getSVar(sVar)); - } + emptyAbility.setSVars(sa.getSVars()); if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) { ComputerUtil.playNoStack(player, emptyAbility, getGame()); // AI needs something to resolve to pay that cost return true; diff --git a/forge-ai/src/main/java/forge/ai/SpecialAiLogic.java b/forge-ai/src/main/java/forge/ai/SpecialAiLogic.java index 6a378ea6d0f..c757133f256 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialAiLogic.java +++ b/forge-ai/src/main/java/forge/ai/SpecialAiLogic.java @@ -1,6 +1,10 @@ package forge.ai; +import java.util.List; + import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; + import forge.ai.ability.TokenAi; import forge.game.Game; import forge.game.ability.AbilityUtils; @@ -12,8 +16,6 @@ import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; -import forge.game.zone.ZoneType; import forge.util.Aggregates; /* @@ -31,60 +33,83 @@ public class SpecialAiLogic { Card source = sa.getHostCard(); Game game = source.getGame(); PhaseHandler ph = game.getPhaseHandler(); - TargetRestrictions tgt = sa.getTargetRestrictions(); + boolean isDestroy = ApiType.Destroy.equals(sa.getApi()); + SpellAbility tokenSA = sa.findSubAbilityByType(ApiType.Token); + if (tokenSA == null) { + // Used wrong AI logic? + return false; + } - CardCollection listOpp = CardLists.getValidCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, source, sa); - listOpp = CardLists.getTargetableCards(listOpp, sa); + List targetable = CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa); - Card choice = ComputerUtilCard.getMostExpensivePermanentAI(listOpp); + CardCollection listOpp = CardLists.filterControlledBy(targetable, ai.getOpponents()); + if (isDestroy) { + listOpp = CardLists.getNotKeyword(listOpp, Keyword.INDESTRUCTIBLE); + // TODO add handling for cards like targeting dies + } - final Card token = choice != null ? TokenAi.spawnToken(choice.getController(), sa.getSubAbility()) : null; - if (token == null || !token.isCreature() || token.getNetToughness() < 1) { - return true; // becomes Terminate - } else if (choice != null && choice.isPlaneswalker()) { - if (choice.getCurrentLoyalty() * 35 > ComputerUtilCard.evaluateCreature(token)) { - sa.resetTargets(); - sa.getTargets().add(choice); - return true; - } else { - return false; + Card choice = null; + if (!listOpp.isEmpty()) { + choice = ComputerUtilCard.getMostExpensivePermanentAI(listOpp); + // can choice even be null? + + if (choice != null) { + final Card token = TokenAi.spawnToken(choice.getController(), tokenSA); + if (!token.isCreature() || token.getNetToughness() < 1) { + sa.resetTargets(); + sa.getTargets().add(choice); + return true; + } + if (choice.isPlaneswalker()) { + if (choice.getCurrentLoyalty() * 35 > ComputerUtilCard.evaluateCreature(token)) { + sa.resetTargets(); + sa.getTargets().add(choice); + return true; + } else { + return false; + } + } + if ((!choice.isCreature() || choice.isTapped()) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && ph.isPlayerTurn(ai) // prevent surprise combatant + || ComputerUtilCard.evaluateCreature(choice) < 1.5 * ComputerUtilCard.evaluateCreature(token)) { + choice = null; + } } - } else { - boolean hasOppTarget = true; - if (choice != null - && ((!choice.isCreature() || choice.isTapped()) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && ph.getPlayerTurn() == ai) // prevent surprise combatant - || ComputerUtilCard.evaluateCreature(choice) < 1.5 * ComputerUtilCard.evaluateCreature(token)) { + } - hasOppTarget = false; + // See if we have anything we can upgrade + if (choice == null) { + CardCollection listOwn = CardLists.filterControlledBy(targetable, ai); + final Card token = TokenAi.spawnToken(ai, tokenSA); + + Card bestOwnCardToUpgrade = null; + if (isDestroy) { + // just choose any Indestructible + // TODO maybe filter something that doesn't like to be targeted, or does something benefit by targeting + bestOwnCardToUpgrade = Iterables.getFirst(CardLists.getKeyword(listOwn, Keyword.INDESTRUCTIBLE), null); } - - // See if we have anything we can upgrade - if (!hasOppTarget) { - CardCollection listOwn = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, source, sa); - listOwn = CardLists.getTargetableCards(listOwn, sa); - - Card bestOwnCardToUpgrade = ComputerUtilCard.getWorstCreatureAI(CardLists.filter(listOwn, new Predicate() { + if (bestOwnCardToUpgrade == null) { + bestOwnCardToUpgrade = ComputerUtilCard.getWorstCreatureAI(CardLists.filter(listOwn, new Predicate() { @Override public boolean apply(Card card) { return card.isCreature() && (ComputerUtilCard.isUselessCreature(ai, card) || ComputerUtilCard.evaluateCreature(token) > 2 * ComputerUtilCard.evaluateCreature(card)); } })); - if (bestOwnCardToUpgrade != null) { - if (ComputerUtilCard.isUselessCreature(ai, bestOwnCardToUpgrade) || (ph.getPhase().isAfter(PhaseType.COMBAT_END) || ph.getPlayerTurn() != ai)) { - sa.resetTargets(); - sa.getTargets().add(bestOwnCardToUpgrade); - return true; - } - } - } else { - sa.resetTargets(); - sa.getTargets().add(choice); - return true; } - - return hasOppTarget; + if (bestOwnCardToUpgrade != null) { + if (ComputerUtilCard.isUselessCreature(ai, bestOwnCardToUpgrade) || (ph.getPhase().isAfter(PhaseType.COMBAT_END) || !ph.isPlayerTurn(ai))) { + choice = bestOwnCardToUpgrade; + } + } } + + if (choice != null) { + sa.resetTargets(); + sa.getTargets().add(choice); + return true; + } + + return false; } // A logic for cards that say "Sacrifice a creature: CARDNAME gets +X/+X until EOT" diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index f5932462c3a..96978de7320 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -875,7 +875,7 @@ public class SpecialCardAi { tokenSize = 11; } - source.setSVar("PayX", Integer.toString(tokenSize)); + sa.setSVar("PayX", Integer.toString(tokenSize)); return true; } diff --git a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java index 665e8881f4f..fa33c11087a 100644 --- a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java @@ -78,16 +78,14 @@ public abstract class SpellAbilityAi { } } - if (sa.hasParam("AITgtBeforeCostEval")) { - // Cost payment requires a valid target to be specified, e.g. Quillmane Baku, so run the API logic first - // to set the target, then decide on paying costs (slower, so only use for cards where it matters) - return checkApiLogic(ai, sa) && (cost == null || willPayCosts(ai, sa, cost, source)); + if (!checkApiLogic(ai, sa)) { + return false; } - + // needs to be after API logic because needs to check possible X Cost? if (cost != null && !willPayCosts(ai, sa, cost, source)) { return false; } - return checkApiLogic(ai, sa); + return true; } protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) { diff --git a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java index 95d1835c9a6..7fcc06b0507 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java @@ -14,7 +14,6 @@ import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbilityContinuous; import forge.game.staticability.StaticAbilityLayer; @@ -38,15 +37,13 @@ import forge.game.ability.effects.AnimateEffectBase; public class AnimateAi extends SpellAbilityAi { @Override protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); - final Card source = sa.getHostCard(); final Game game = ai.getGame(); final PhaseHandler ph = game.getPhaseHandler(); if ("Attacking".equals(aiLogic)) { // Launch the Fleet if (ph.getPlayerTurn().isOpponentOf(ai) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) { return false; } - List list = CardLists.getValidCards(ai.getCreaturesInPlay(), tgt.getValidTgts(), ai, source, sa); + List list = CardLists.getTargetableCards(ai.getCreaturesInPlay(), sa); for (Card c : list) { if (ComputerUtilCard.doesCreatureAttackAI(ai, c)) { sa.getTargets().add(c); @@ -224,10 +221,7 @@ public class AnimateAi extends SpellAbilityAi { } else if (sa.usesTargeting() && mandatory) { // fallback if animate is mandatory sa.resetTargets(); - final TargetRestrictions tgt = sa.getTargetRestrictions(); - final Card source = sa.getHostCard(); - CardCollectionView list = aiPlayer.getGame().getCardsIn(tgt.getZone()); - list = CardLists.getValidCards(list, tgt.getValidTgts(), aiPlayer, source, sa); + List list = CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa); if (list.isEmpty()) { return false; } @@ -253,12 +247,8 @@ public class AnimateAi extends SpellAbilityAi { // something is used for animate into creature if (types.isCreature()) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); - final Card source = sa.getHostCard(); - CardCollectionView list = ai.getGame().getCardsIn(tgt.getZone()); - list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source, sa); - // need to targetable - list = CardLists.getTargetableCards(list, sa); + final Game game = ai.getGame(); + CardCollectionView list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa); // Filter AI-specific targets if provided list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false); 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 5473029db9f..09624b037d4 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java @@ -93,7 +93,7 @@ public class AttachAi extends SpellAbilityAi { return false; } - if (abCost.getTotalMana().countX() > 0 && source.getSVar("X").equals("Count$xPaid")) { + if (abCost.getTotalMana().countX() > 0 && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. (Endless Scream and Venarian // Gold) final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); @@ -102,7 +102,7 @@ public class AttachAi extends SpellAbilityAi { return false; } - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Chained to the Rocks")) { diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index 468ed9ccf72..3ca3777d814 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -339,10 +339,10 @@ public class ChangeZoneAi extends SpellAbilityAi { String type = sa.getParam("ChangeType"); if (type != null) { - if (type.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (type.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); type = type.replace("X", Integer.toString(xPay)); } } @@ -384,11 +384,11 @@ public class ChangeZoneAi extends SpellAbilityAi { String num = sa.getParam("ChangeNum"); if (num != null) { - if (num.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (num.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); xPay = Math.min(xPay, list.size()); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } } @@ -474,8 +474,6 @@ public class ChangeZoneAi extends SpellAbilityAi { // Fetching should occur fairly often as it helps cast more spells, and // have access to more mana - final Card source = sa.getHostCard(); - if (sa.hasParam("AILogic")) { if (sa.getParam("AILogic").equals("Never")) { /* @@ -496,10 +494,10 @@ public class ChangeZoneAi extends SpellAbilityAi { // this works for hidden because the mana is paid first. final String type = sa.getParam("ChangeType"); - if (type != null && type.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (type != null && type.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } Iterable pDefined; @@ -1353,29 +1351,13 @@ public class ChangeZoneAi extends SpellAbilityAi { } final Card source = sa.getHostCard(); - final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0); final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination")); final TargetRestrictions tgt = sa.getTargetRestrictions(); - CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(origin), tgt.getValidTgts(), ai, source, sa); + CardCollection list = CardLists.getValidCards(ai.getGame().getCardsIn(tgt.getZone()), tgt.getValidTgts(), ai, source, sa); + list = CardLists.getTargetableCards(list, sa); - // Narrow down the list: - if (origin.equals(ZoneType.Battlefield)) { - // filter out untargetables - list = CardLists.getTargetableCards(list, sa); - - // if Destination is hand, either bounce opponents dangerous stuff - // or save my about to die stuff - - // if Destination is exile, filter out my cards - } - else if (origin.equals(ZoneType.Graveyard)) { - // Retrieve from Graveyard to: - } - - for (final Card c : sa.getTargets().getTargetCards()) { - list.remove(c); - } + list.removeAll(sa.getTargets().getTargetCards()); if (list.isEmpty()) { return false; @@ -1387,12 +1369,13 @@ public class ChangeZoneAi extends SpellAbilityAi { Card choice = null; if (!list.isEmpty()) { - if (ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false).isCreature() - && (destination.equals(ZoneType.Battlefield) || origin.equals(ZoneType.Battlefield))) { + Card mostExpensivePermanent = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false); + if (mostExpensivePermanent.isCreature() + && (destination.equals(ZoneType.Battlefield) || tgt.getZone().contains(ZoneType.Battlefield))) { // if a creature is most expensive take the best choice = ComputerUtilCard.getBestCreatureToBounceAI(list); - } else if (destination.equals(ZoneType.Battlefield) || origin.equals(ZoneType.Battlefield)) { - choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false); + } else if (destination.equals(ZoneType.Battlefield) || tgt.getZone().contains(ZoneType.Battlefield)) { + choice = mostExpensivePermanent; } else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) { List nonLands = CardLists.getNotType(list, "Land"); // Prefer to pull a creature, generally more useful for AI. @@ -1845,7 +1828,7 @@ public class ChangeZoneAi extends SpellAbilityAi { int toPay = 0; boolean setPayX = false; - if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) { + if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) { setPayX = true; toPay = ComputerUtilMana.determineLeftoverMana(sa, ai); } else { @@ -1861,7 +1844,7 @@ public class ChangeZoneAi extends SpellAbilityAi { } if (setPayX) { - source.setSVar("PayX", Integer.toString(toPay)); + sa.setSVar("PayX", Integer.toString(toPay)); } } } diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java index f8a48d2ec25..461e6b2e60c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java @@ -130,6 +130,12 @@ public class ChooseCardAi extends SpellAbilityAi { return (ComputerUtilCard.evaluateCreatureList(aiCreatures) + minGain) < ComputerUtilCard .evaluateCreatureList(oppCreatures); + } else if (aiLogic.equals("OwnCard")) { + CardCollectionView ownChoices = CardLists.filter(choices, CardPredicates.isController(ai)); + if (ownChoices.isEmpty()) { + ownChoices = CardLists.filter(choices, CardPredicates.isControlledByAnyOf(ai.getAllies())); + } + return !ownChoices.isEmpty(); } return true; } @@ -156,6 +162,12 @@ public class ChooseCardAi extends SpellAbilityAi { choice = ComputerUtilCard.getBestAI(options); } else if ("WorstCard".equals(logic)) { choice = ComputerUtilCard.getWorstAI(options); + } else if ("OwnCard".equals(logic)) { + CardCollectionView ownChoices = CardLists.filter(options, CardPredicates.isController(ai)); + if (ownChoices.isEmpty()) { + ownChoices = CardLists.filter(options, CardPredicates.isControlledByAnyOf(ai.getAllies())); + } + choice = ComputerUtilCard.getBestAI(ownChoices); } else if (logic.equals("BestBlocker")) { if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) { options = CardLists.filter(options, Presets.UNTAPPED); diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseColorAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseColorAi.java index e086a64803e..cefb6cfe3db 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseColorAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseColorAi.java @@ -44,7 +44,7 @@ public class ChooseColorAi extends SpellAbilityAi { } // Set PayX here to maximum value. int x = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(x)); + sa.setSVar("PayX", Integer.toString(x)); return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java index 0f2b2730321..7781490d425 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java @@ -60,7 +60,7 @@ public class ChooseSourceAi extends SpellAbilityAi { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) { + if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) { return false; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java index 7c14df50863..2e4a66fbcef 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java @@ -280,7 +280,7 @@ public class ControlGainAi extends SpellAbilityAi { @Override public boolean chkAIDrawback(SpellAbility sa, final Player ai) { final Game game = ai.getGame(); - if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { + if (!sa.usesTargeting()) { if (sa.hasParam("AllValid")) { CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents()); tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa); diff --git a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java index 41edaa0d49a..a60fbe69051 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java @@ -103,7 +103,7 @@ public class CounterAi extends SpellAbilityAi { int toPay = 0; boolean setPayX = false; - if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) { + if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) { setPayX = true; toPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), usableManaSources + 1); } else { @@ -123,7 +123,7 @@ public class CounterAi extends SpellAbilityAi { } if (setPayX) { - source.setSVar("PayX", Integer.toString(toPay)); + sa.setSVar("PayX", Integer.toString(toPay)); } } @@ -267,7 +267,7 @@ public class CounterAi extends SpellAbilityAi { int toPay = 0; boolean setPayX = false; - if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) { + if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) { setPayX = true; toPay = ComputerUtilMana.determineLeftoverMana(sa, ai); } else { @@ -289,7 +289,7 @@ public class CounterAi extends SpellAbilityAi { } if (setPayX) { - source.setSVar("PayX", Integer.toString(toPay)); + sa.setSVar("PayX", Integer.toString(toPay)); } } } diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java index e1d76e9c0db..8de73d6a071 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -338,7 +338,7 @@ public class CountersPutAi extends SpellAbilityAi { } if (amountStr.equals("X")) { - if (source.getSVar(amountStr).equals("Count$xPaid")) { + if (sa.getSVar(amountStr).equals("Count$xPaid")) { // By default, set PayX here to maximum value (used for most SAs of this type). amount = ComputerUtilMana.determineLeftoverMana(sa, ai); @@ -359,7 +359,7 @@ public class CountersPutAi extends SpellAbilityAi { } } - source.setSVar("PayX", Integer.toString(amount)); + sa.setSVar("PayX", Integer.toString(amount)); } else if ("ExiledCreatureFromGraveCMC".equals(logic)) { // e.g. Necropolis amount = Aggregates.max(CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES), CardPredicates.Accessors.fnGetCmc); @@ -698,8 +698,8 @@ public class CountersPutAi extends SpellAbilityAi { list = new CardCollection(AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)); if (amountStr.equals("X") - && !source.hasSVar("PayX") /* SubAbility on something that already had set PayX, e.g. Endless One ETB counters */ - && ((sa.hasParam(amountStr) && sa.getSVar(amountStr).equals("Count$xPaid")) || source.getSVar(amountStr).equals("Count$xPaid") )) { + && !sa.hasSVar("PayX") /* SubAbility on something that already had set PayX, e.g. Endless One ETB counters */ + && ((sa.hasParam(amountStr) && sa.getSVar(amountStr).equals("Count$xPaid")))) { // detect if there's more than one X in the cost (Hangarback Walker, Walking Ballista, etc.) SpellAbility testSa = sa; @@ -725,7 +725,7 @@ public class CountersPutAi extends SpellAbilityAi { // Account for the multiple X in cost if (countX > 1) { payX /= countX; } - source.setSVar("PayX", Integer.toString(payX)); + sa.setSVar("PayX", Integer.toString(payX)); } if (!mandatory) { @@ -1031,7 +1031,7 @@ public class CountersPutAi extends SpellAbilityAi { } } else { for (CounterType type : options) { - if (!ComputerUtil.isNegativeCounter(type, c) && !ComputerUtil.isUselessCounter(type)) { + if (!ComputerUtil.isNegativeCounter(type, c) && !ComputerUtil.isUselessCounter(type, c)) { return type; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java index 8c883637620..4fc300b6c1b 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java @@ -82,10 +82,10 @@ public class CountersPutAllAi extends SpellAbilityAi { // TODO improve X value to don't overpay when extra mana won't do // anything more useful final int amount; - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. amount = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(amount)); + sa.setSVar("PayX", Integer.toString(amount)); } else { amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); } diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java index a261270dec9..70e66e15c59 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java @@ -157,7 +157,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi { if (!ComputerUtil.isNegativeCounter(aType, best)) { sa.getTargets().add(best); return true; - } else if (!ComputerUtil.isUselessCounter(aType)) { + } else if (!ComputerUtil.isUselessCounter(aType, best)) { // whould remove positive counter if (best.getCounters(aType) <= amount) { sa.getTargets().add(best); diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java index 1d4737a83ea..8ce1b1a369c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java @@ -143,7 +143,7 @@ public class CountersRemoveAi extends SpellAbilityAi { // variable amount for Hex Parasite int amount; boolean xPay = false; - if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar("X").equals("Count$xPaid")) { final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai); if (manaLeft == 0) { @@ -167,7 +167,7 @@ public class CountersRemoveAi extends SpellAbilityAi { if (amount >= ice) { sa.getTargets().add(depth); if (xPay) { - source.setSVar("PayX", Integer.toString(ice)); + sa.setSVar("PayX", Integer.toString(ice)); } return true; } @@ -186,7 +186,7 @@ public class CountersRemoveAi extends SpellAbilityAi { Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList); sa.getTargets().add(best); if (xPay) { - source.setSVar("PayX", Integer.toString(best.getCurrentLoyalty())); + sa.setSVar("PayX", Integer.toString(best.getCurrentLoyalty())); } return true; } @@ -297,7 +297,7 @@ public class CountersRemoveAi extends SpellAbilityAi { int amount; boolean xPay = false; // Timecrafting has X R - if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar("X").equals("Count$xPaid")) { final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai); if (manaLeft == 0) { @@ -317,7 +317,7 @@ public class CountersRemoveAi extends SpellAbilityAi { int timeCount = best.getCounters(CounterEnumType.TIME); sa.getTargets().add(best); if (xPay) { - source.setSVar("PayX", Integer.toString(timeCount)); + sa.setSVar("PayX", Integer.toString(timeCount)); } return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java index 79e83ac65a4..15e5c05c1a9 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java @@ -83,7 +83,7 @@ public class DamageAllAi extends SpellAbilityAi { if (best_x > 0) { if (sa.getSVar(damage).equals("Count$xPaid")) { - source.setSVar("PayX", Integer.toString(best_x)); + sa.setSVar("PayX", Integer.toString(best_x)); } if (damage.equals("ChosenX")) { source.setSVar("ChosenX", "Number$" + best_x); @@ -203,7 +203,7 @@ public class DamageAllAi extends SpellAbilityAi { if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) { // Set PayX here to maximum value. dmg = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(dmg)); + sa.setSVar("PayX", Integer.toString(dmg)); } if (sa.hasParam("ValidPlayers")) { @@ -285,7 +285,7 @@ public class DamageAllAi extends SpellAbilityAi { if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) { // Set PayX here to maximum value. dmg = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(dmg)); + sa.setSVar("PayX", Integer.toString(dmg)); } if (sa.hasParam("ValidPlayers")) { 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 92c213ca2e3..20bbb1a8e5a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -12,7 +12,6 @@ import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.*; import forge.game.cost.Cost; -import forge.game.cost.CostPart; import forge.game.cost.CostPartMana; import forge.game.cost.CostRemoveCounter; import forge.game.keyword.Keyword; @@ -75,8 +74,8 @@ public class DamageDealAi extends DamageAiBase { } // Set PayX here to maximum value. - dmg = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(dmg)); + dmg = ComputerUtilCost.getMaxXValue(sa, ai); + sa.setSVar("PayX", Integer.toString(dmg)); } else if (sa.getSVar(damage).equals("Count$CardsInYourHand") && source.isInZone(ZoneType.Hand)) { dmg--; // the card will be spent casting the spell, so actual damage is 1 less } @@ -96,7 +95,7 @@ public class DamageDealAi extends DamageAiBase { if (damage.equals("X")) { if (sa.getSVar(damage).equals("Count$xPaid") || sourceName.equals("Crater's Claws")) { - dmg = ComputerUtilMana.determineLeftoverMana(sa, ai); + dmg = ComputerUtilCost.getMaxXValue(sa, ai); // Try not to waste spells like Blaze or Fireball on early targets, try to do more damage with them if possible if (ai.getController().isAI()) { @@ -113,7 +112,7 @@ public class DamageDealAi extends DamageAiBase { } // Set PayX here to maximum value. It will be adjusted later depending on the target. - source.setSVar("PayX", Integer.toString(dmg)); + sa.setSVar("PayX", Integer.toString(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 } else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) { @@ -225,7 +224,7 @@ public class DamageDealAi extends DamageAiBase { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) { + if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) { return false; } @@ -266,7 +265,7 @@ public class DamageDealAi extends DamageAiBase { } } - if ((damage.equals("X") && source.getSVar(damage).equals("Count$xPaid")) || + if ((damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) || sourceName.equals("Crater's Claws")){ // If I can kill my target by paying less mana, do it if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) { @@ -281,25 +280,13 @@ public class DamageDealAi extends DamageAiBase { if (sourceName.equals("Crater's Claws") && ai.hasFerocious()) { actualPay = actualPay > 2 ? actualPay - 2 : 0; } - source.setSVar("PayX", Integer.toString(actualPay)); - } - } - - if ("XCountersDamage".equals(logic)) { - // Check to ensure that we have enough counters to remove per the defined PayX - for (CostPart part : sa.getPayCosts().getCostParts()) { - if (part instanceof CostRemoveCounter) { - if (source.getCounters(((CostRemoveCounter) part).counter) < Integer.valueOf(source.getSVar("PayX"))) { - return false; - } - break; - } + sa.setSVar("PayX", Integer.toString(actualPay)); } } if ("DiscardCMCX".equals(sa.getParam("AILogic"))) { - final int CMC = Integer.parseInt(source.getSVar("PayX")); - return !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(CMC)).isEmpty(); + final int cmc = Integer.parseInt(sa.getSVar("PayX")); + return !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc)).isEmpty(); } return true; @@ -990,7 +977,7 @@ public class DamageDealAi extends DamageAiBase { if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) { // Set PayX here to maximum value. dmg = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(dmg)); + sa.setSVar("PayX", Integer.toString(dmg)); } final TargetRestrictions tgt = sa.getTargetRestrictions(); @@ -1002,7 +989,7 @@ public class DamageDealAi extends DamageAiBase { return false; } - if (damage.equals("X") && source.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) { + if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) { // If I can kill my target by paying less mana, do it int actualPay = 0; final boolean noPrevention = sa.hasParam("NoPrevention"); @@ -1018,7 +1005,7 @@ public class DamageDealAi extends DamageAiBase { } } - source.setSVar("PayX", Integer.toString(actualPay)); + sa.setSVar("PayX", Integer.toString(actualPay)); } } @@ -1078,7 +1065,7 @@ public class DamageDealAi extends DamageAiBase { saTgt.resetTargets(); saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent); - source.setSVar("PayX", Integer.toString(dmg)); + sa.setSVar("PayX", Integer.toString(dmg)); return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java b/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java index 2a171dbbb07..6e5259f25cb 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java @@ -46,7 +46,7 @@ public class DamagePreventAi extends SpellAbilityAi { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) { + if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard, sa)) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DamagePreventAllAi.java b/forge-ai/src/main/java/forge/ai/ability/DamagePreventAllAi.java index f6f8b73f2e2..f62a7b51c25 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamagePreventAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamagePreventAllAi.java @@ -34,7 +34,7 @@ public class DamagePreventAllAi extends SpellAbilityAi { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) { + if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard, sa)) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java b/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java index 3d7b045108f..b41018c9d35 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java @@ -50,7 +50,7 @@ public class DebuffAi extends SpellAbilityAi { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) { + if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) { return false; } @@ -67,7 +67,7 @@ public class DebuffAi extends SpellAbilityAi { } } - if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) { + if (!sa.usesTargeting()) { List cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); @@ -93,7 +93,7 @@ public class DebuffAi extends SpellAbilityAi { @Override public boolean chkAIDrawback(SpellAbility sa, Player ai) { - if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { + if (!sa.usesTargeting()) { // TODO - copied from AF_Pump.pumpDrawbackAI() - what should be // here? } else { diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java index dc0093e7a90..1c8c72f6e32 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java @@ -13,7 +13,6 @@ import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType; public class DestroyAi extends SpellAbilityAi { @@ -23,89 +22,19 @@ public class DestroyAi extends SpellAbilityAi { } @Override - protected boolean canPlayAI(final Player ai, SpellAbility sa) { - // AI needs to be expanded, since this function can be pretty complex - // based on what the expected targets could be - final Cost abCost = sa.getPayCosts(); - final TargetRestrictions abTgt = sa.getTargetRestrictions(); + protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) { final Card source = sa.getHostCard(); - final boolean noRegen = sa.hasParam("NoRegen"); - final String logic = sa.getParam("AILogic"); - boolean hasXCost = false; - - CardCollection list; - - if (abCost != null) { - if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) { - return false; - } - - if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) { - return false; - } - - if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) { - return false; - } - - hasXCost = abCost.getCostMana() != null && abCost.getCostMana().getAmountOfX() > 0; - } - - if ("AtOpponentsCombatOrAfter".equals(sa.getParam("AILogic"))) { - PhaseHandler ph = ai.getGame().getPhaseHandler(); - if (ph.getPlayerTurn() == ai || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) { - return false; - } - } else if ("AtEOT".equals(sa.getParam("AILogic"))) { - PhaseHandler ph = ai.getGame().getPhaseHandler(); - if (!ph.is(PhaseType.END_OF_TURN)) { - return false; - } - } else if ("AtEOTIfNotAttacking".equals(sa.getParam("AILogic"))) { - PhaseHandler ph = ai.getGame().getPhaseHandler(); - if (!ph.is(PhaseType.END_OF_TURN) || !ai.getCreaturesAttackedThisTurn().isEmpty()) { - return false; - } - } - - if (ComputerUtil.preventRunAwayActivations(sa)) { - return false; - } - - // Ability that's intended to destroy own useless token to trigger Grave Pacts - // should be fired at end of turn or when under attack after blocking to make opponent sac something - boolean havepact = false; - - // TODO replace it with look for a dies -> sacrifice trigger check - havepact |= ai.isCardInPlay("Grave Pact"); - havepact |= ai.isCardInPlay("Butcher of Malakir"); - havepact |= ai.isCardInPlay("Dictate of Erebos"); - if ("Pactivator".equals(logic) && havepact) { - if ((!ai.getGame().getPhaseHandler().isPlayerTurn(ai)) - && ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)) || (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))) - && (ai.getOpponents().getCreaturesInPlay().size() > 0)) { - ai.getController().chooseTargetsFor(sa); - return true; - } - } - - // Targeting - if (abTgt != null) { + if (sa.usesTargeting()) { sa.resetTargets(); - if (sa.hasParam("TargetingPlayer")) { - Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0); - sa.setTargetingPlayer(targetingPlayer); - return targetingPlayer.getController().chooseTargetsFor(sa); - } - if ("MadSarkhanDragon".equals(logic)) { + if ("MadSarkhanDragon".equals(aiLogic)) { return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa); - } else if (logic != null && logic.startsWith("MinLoyalty.")) { - int minLoyalty = Integer.parseInt(logic.substring(logic.indexOf(".") + 1)); + } else if (aiLogic.startsWith("MinLoyalty.")) { + int minLoyalty = Integer.parseInt(aiLogic.substring(aiLogic.indexOf(".") + 1)); if (source.getCounters(CounterEnumType.LOYALTY) < minLoyalty) { return false; } - } else if ("Polymorph".equals(logic)) { - list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa); + } else if ("Polymorph".equals(aiLogic)) { + CardCollection list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa); if (list.isEmpty()) { return false; } @@ -124,7 +53,108 @@ public class DestroyAi extends SpellAbilityAi { } sa.getTargets().add(worst); return true; + } else if ("Pongify".equals(aiLogic)) { + return SpecialAiLogic.doPongifyLogic(ai, sa); } + } + return super.checkAiLogic(ai, sa, aiLogic); + } + + protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph, + final String logic) { + if ("AtOpponentsCombatOrAfter".equals(logic)) { + if (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) { + return false; + } + } else if ("AtEOT".equals(logic)) { + if (!ph.is(PhaseType.END_OF_TURN)) { + return false; + } + } else if ("AtEOTIfNotAttacking".equals(logic)) { + if (!ph.is(PhaseType.END_OF_TURN) || !ai.getCreaturesAttackedThisTurn().isEmpty()) { + return false; + } + } else if ("Pactivator".equals(logic)) { + // Ability that's intended to destroy own useless token to trigger Grave Pacts + // should be fired at end of turn or when under attack after blocking to make opponent sac something + boolean havepact = false; + + // TODO replace it with look for a dies -> sacrifice trigger check + havepact |= ai.isCardInPlay("Grave Pact"); + havepact |= ai.isCardInPlay("Butcher of Malakir"); + havepact |= ai.isCardInPlay("Dictate of Erebos"); + if (havepact) { + if ((!ph.isPlayerTurn(ai)) + && ((ph.is(PhaseType.END_OF_TURN)) || (ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS))) + && (ai.getOpponents().getCreaturesInPlay().size() > 0)) { + CardCollection list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa); + Card worst = ComputerUtilCard.getWorstAI(list); + if (worst != null) { + sa.getTargets().add(worst); + return true; + } + return false; + } + } + } + + return true; + } + + @Override + protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { + final Card source = sa.getHostCard(); + final boolean noRegen = sa.hasParam("NoRegen"); + final String logic = sa.getParam("AILogic"); + + CardCollection list; + + + + if (ComputerUtil.preventRunAwayActivations(sa)) { + return false; + } + + + // Targeting + if (sa.usesTargeting()) { + // Assume there where already enough targets chosen by AI Logic Above + if (!sa.canAddMoreTarget() && sa.isTargetNumberValid()) { + return true; + } + + // reset targets before AI Logic part + sa.resetTargets(); + int maxTargets; + + if (sa.costHasManaX()) { + // TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement. + maxTargets = ComputerUtilCost.getMaxXValue(sa, ai); + // need to set XPaid to get the right number for + sa.setXManaCostPaid(maxTargets); + // need to check for maxTargets + maxTargets = Math.min(maxTargets, sa.getMaxTargets()); + } else { + maxTargets = sa.getMaxTargets(); + } + if (sa.hasParam("AIMaxTgtsCount")) { + // Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified + // TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable? + maxTargets = Math.min(CardFactoryUtil.xCount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount")), maxTargets); + } + + if (maxTargets == 0) { + // can't afford X or otherwise target anything + return false; + } + + if (sa.hasParam("TargetingPlayer")) { + Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0); + sa.setTargetingPlayer(targetingPlayer); + return targetingPlayer.getController().chooseTargetsFor(sa); + } + + // AI doesn't destroy own cards if it isn't defined in AI logic list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa); if ("FatalPush".equals(logic)) { final int cmcMax = ai.hasRevolt() ? 4 : 2; @@ -184,33 +214,12 @@ public class DestroyAi extends SpellAbilityAi { return false; } - int maxTargets = abTgt.getMaxTargets(sa.getHostCard(), sa); - - if (hasXCost) { - // TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement. - maxTargets = Math.min(ComputerUtilMana.determineMaxAffordableX(ai, sa), abTgt.getMaxTargets(sa.getHostCard(), sa)); - // X can't be more than the lands we have in our hand for "discard X lands"! - if ("ScorchedEarth".equals(logic)) { - int lands = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size(); - maxTargets = Math.min(maxTargets, lands); - } - } - if (sa.hasParam("AIMaxTgtsCount")) { - // Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified - // TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable? - maxTargets = Math.min(CardFactoryUtil.xCount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount")), maxTargets); - } - - if (maxTargets == 0) { - // can't afford X or otherwise target anything - return false; - } // target loop + // TODO use can add more Targets while (sa.getTargets().size() < maxTargets) { if (list.isEmpty()) { - if ((sa.getTargets().size() < abTgt.getMinTargets(sa.getHostCard(), sa)) - || (sa.getTargets().size() == 0)) { + if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { sa.resetTargets(); return false; } else { @@ -222,10 +231,6 @@ public class DestroyAi extends SpellAbilityAi { Card choice = null; // If the targets are only of one type, take the best if (CardLists.getNotType(list, "Creature").isEmpty()) { - if ("Pongify".equals(logic)) { - return SpecialAiLogic.doPongifyLogic(ai, sa); - } - choice = ComputerUtilCard.getBestCreatureAI(list); if ("OppDestroyYours".equals(logic)) { Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay()); @@ -246,15 +251,14 @@ public class DestroyAi extends SpellAbilityAi { choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true); } //option to hold removal instead only applies for single targeted removal - if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) { + if (!sa.isTrigger() && sa.getMaxTargets() == 1) { if (choice == null || !ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) { return false; } } if (choice == null) { // can't find anything left - if ((sa.getTargets().size() < abTgt.getMinTargets(sa.getHostCard(), sa)) - || (sa.getTargets().size() == 0)) { + if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { sa.resetTargets(); return false; } else { @@ -298,22 +302,19 @@ public class DestroyAi extends SpellAbilityAi { @Override protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); - final Card source = sa.getHostCard(); final boolean noRegen = sa.hasParam("NoRegen"); - if (tgt != null) { + if (sa.usesTargeting()) { sa.resetTargets(); CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa); - list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa); + + if (list.isEmpty() || list.size() < sa.getMinTargets()) { + return false; + } // Try to avoid targeting creatures that are dead on board list = ComputerUtil.filterCreaturesThatWillDieThisTurn(ai, list, sa); - if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) { - return false; - } - CardCollection preferred = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE); preferred = CardLists.filterControlledBy(preferred, ai.getOpponents()); if (CardLists.getNotType(preferred, "Creature").isEmpty()) { @@ -344,10 +345,9 @@ public class DestroyAi extends SpellAbilityAi { return false; } - while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) { + while (sa.canAddMoreTarget()) { if (preferred.isEmpty()) { - if (sa.getTargets().size() == 0 - || sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) { + if (!sa.isMinTargetChosen()) { if (!mandatory) { sa.resetTargets(); return false; @@ -371,7 +371,7 @@ public class DestroyAi extends SpellAbilityAi { } } - while (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) { + while (sa.canAddMoreTarget()) { if (list.isEmpty()) { break; } else { @@ -392,7 +392,7 @@ public class DestroyAi extends SpellAbilityAi { } } - return sa.getTargets().size() >= tgt.getMinTargets(sa.getHostCard(), sa); + return sa.isTargetNumberValid(); } else { return mandatory; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java index 9a837bc1601..9fbe5427ccf 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java @@ -1,18 +1,20 @@ package forge.ai.ability; import com.google.common.base.Predicate; - +import com.google.common.base.Predicates; import forge.ai.*; +import forge.card.MagicColor; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardLists; +import forge.game.card.CardPredicates; +import forge.game.combat.Combat; import forge.game.cost.Cost; import forge.game.keyword.Keyword; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; -import forge.game.combat.Combat; public class DestroyAllAi extends SpellAbilityAi { @@ -78,10 +80,10 @@ public class DestroyAllAi extends SpellAbilityAi { valid = sa.getParam("ValidCards"); } - if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); valid = valid.replace("X", Integer.toString(xPay)); } @@ -106,6 +108,14 @@ public class DestroyAllAi extends SpellAbilityAi { } } + // Special handling for Raiding Party + if (logic.equals("RaidingParty")) { + int numAiCanSave = Math.min(CardLists.filter(ai.getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, ailist.size()); + int numOppsCanSave = Math.min(CardLists.filter(ai.getOpponents().getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, opplist.size()); + + return numOppsCanSave < opplist.size() && (ailist.size() - numAiCanSave < opplist.size() - numOppsCanSave); + } + // If effect is destroying creatures and AI is about to lose, activate effect anyway no matter what! if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) && (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) { diff --git a/forge-ai/src/main/java/forge/ai/ability/DigAi.java b/forge-ai/src/main/java/forge/ai/ability/DigAi.java index 822423e4f48..d7aaed7d3ab 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DigAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DigAi.java @@ -75,9 +75,9 @@ public class DigAi extends SpellAbilityAi { final String num = sa.getParam("DigNum"); final boolean payXLogic = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayX"); - if (num != null && (num.equals("X") && host.getSVar(num).equals("Count$xPaid")) || payXLogic) { + if (num != null && (num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) || payXLogic) { // By default, set PayX here to maximum value. - if (!(sa instanceof AbilitySub) || host.getSVar("PayX").equals("")) { + if (!(sa instanceof AbilitySub) || sa.getSVar("PayX").equals("")) { int manaToSave = 0; // Special logic that asks the AI to conserve a certain amount of mana when paying X @@ -89,7 +89,7 @@ public class DigAi extends SpellAbilityAi { if (numCards <= 0) { return false; } - host.setSVar("PayX", Integer.toString(numCards)); + sa.setSVar("PayX", Integer.toString(numCards)); } } diff --git a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java index f18dc3c7784..5eb12be3a17 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java @@ -76,14 +76,14 @@ public class DigUntilAi extends SpellAbilityAi { } final String num = sa.getParam("Amount"); - if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) { + if ((num != null) && num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) { // Set PayX here to maximum value. - if (!(sa instanceof AbilitySub) || source.getSVar("PayX").equals("")) { + if (!(sa instanceof AbilitySub) || sa.getSVar("PayX").equals("")) { int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai); if (numCards <= 0) { return false; } - source.setSVar("PayX", Integer.toString(numCards)); + sa.setSVar("PayX", Integer.toString(numCards)); } } diff --git a/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java b/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java index d7dad6b293d..8bac65be1b4 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java @@ -41,7 +41,7 @@ public class DiscardAi extends SpellAbilityAi { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) { + if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) { return false; } @@ -85,14 +85,14 @@ public class DiscardAi extends SpellAbilityAi { } if (sa.hasParam("NumCards")) { - if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) { + if (sa.getParam("NumCards").equals("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getWeakestOpponent() .getCardsIn(ZoneType.Hand).size()); if (cardsToDiscard < 1) { return false; } - source.setSVar("PayX", Integer.toString(cardsToDiscard)); + sa.setSVar("PayX", Integer.toString(cardsToDiscard)); } else { if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) { return false; diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java index 4047d12ee77..579d731d625 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -238,17 +238,17 @@ public class DrawAi extends SpellAbilityAi { boolean xPaid = false; final String num = sa.getParam("NumCards"); if (num != null && num.equals("X")) { - if (source.getSVar(num).equals("Count$xPaid")) { + if (sa.getSVar(num).equals("Count$xPaid")) { // Set PayX here to maximum value. - if (drawback && !source.getSVar("PayX").equals("")) { - numCards = Integer.parseInt(source.getSVar("PayX")); + if (drawback && !sa.getSVar("PayX").equals("")) { + numCards = Integer.parseInt(sa.getSVar("PayX")); } else { numCards = ComputerUtilMana.determineLeftoverMana(sa, ai); // try not to overdraw int safeDraw = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3); if (sa.getHostCard().isInstant() || sa.getHostCard().isSorcery()) { safeDraw++; } // card will be spent numCards = Math.min(numCards, safeDraw); - source.setSVar("PayX", Integer.toString(numCards)); + sa.setSVar("PayX", Integer.toString(numCards)); assumeSafeX = true; } xPaid = true; @@ -340,7 +340,7 @@ public class DrawAi extends SpellAbilityAi { // for drawing and losing life if (numCards >= oppA.getLife()) { if (xPaid) { - source.setSVar("PayX", Integer.toString(oppA.getLife())); + sa.setSVar("PayX", Integer.toString(oppA.getLife())); } sa.getTargets().add(oppA); return true; @@ -400,7 +400,7 @@ public class DrawAi extends SpellAbilityAi { } if (xPaid) { - source.setSVar("PayX", Integer.toString(numCards)); + sa.setSVar("PayX", Integer.toString(numCards)); } } @@ -411,7 +411,7 @@ public class DrawAi extends SpellAbilityAi { if (sa.getHostCard().isInZone(ZoneType.Hand)) { numCards++; // the card will be spent } - source.setSVar("PayX", Integer.toString(numCards)); + sa.setSVar("PayX", Integer.toString(numCards)); } else { // Don't draw too many cards and then risk discarding // cards at EOT diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java index 5ad2c283477..359490e75fd 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java @@ -49,7 +49,7 @@ public class LifeGainAi extends SpellAbilityAi { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) { + if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) { return false; } } else { @@ -125,10 +125,10 @@ public class LifeGainAi extends SpellAbilityAi { final String amountStr = sa.getParam("LifeAmount"); int lifeAmount = 0; boolean activateForCost = ComputerUtil.activateForCost(sa, ai); - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); lifeAmount = xPay; } else { lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); @@ -213,12 +213,11 @@ public class LifeGainAi extends SpellAbilityAi { } } - final Card source = sa.getHostCard(); final String amountStr = sa.getParam("LifeAmount"); - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } return true; diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java index 23ee0a4712e..de4bf22b32d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java @@ -34,14 +34,14 @@ public class LifeLoseAi extends SpellAbilityAi { final Card source = sa.getHostCard(); final String amountStr = sa.getParam("LifeAmount"); int amount = 0; - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // something already set PayX - if (source.hasSVar("PayX")) { - amount = Integer.parseInt(source.getSVar("PayX")); + if (sa.hasSVar("PayX")) { + amount = Integer.parseInt(sa.getSVar("PayX")); } else { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); amount = xPay; } } else { @@ -72,10 +72,9 @@ public class LifeLoseAi extends SpellAbilityAi { final String amountStr = sa.getParam("LifeAmount"); int amount = 0; - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. amount = ComputerUtilMana.determineLeftoverMana(sa, ai); - // source.setSVar("PayX", Integer.toString(amount)); } else { amount = AbilityUtils.calculateAmount(source, amountStr, sa); } @@ -101,10 +100,10 @@ public class LifeLoseAi extends SpellAbilityAi { final String amountStr = sa.getParam("LifeAmount"); int amount = 0; - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. amount = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(amount)); + sa.setSVar("PayX", Integer.toString(amount)); } else { amount = AbilityUtils.calculateAmount(source, amountStr, sa); } @@ -172,10 +171,10 @@ public class LifeLoseAi extends SpellAbilityAi { final Card source = sa.getHostCard(); final String amountStr = sa.getParam("LifeAmount"); int amount = 0; - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); amount = xPay; } else { amount = AbilityUtils.calculateAmount(source, amountStr, sa); diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java index 2afc4b47137..d51395a7026 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java @@ -16,8 +16,6 @@ public class LifeSetAi extends SpellAbilityAi { @Override protected boolean canPlayAI(Player ai, SpellAbility sa) { - // Ability_Cost abCost = sa.getPayCosts(); - final Card source = sa.getHostCard(); final int myLife = ai.getLife(); final Player opponent = ai.getWeakestOpponent(); final int hlife = opponent.getLife(); @@ -42,10 +40,10 @@ public class LifeSetAi extends SpellAbilityAi { // would be paid int amount; // we shouldn't have to worry too much about PayX for SetLife - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); amount = xPay; } else { amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); @@ -114,10 +112,10 @@ public class LifeSetAi extends SpellAbilityAi { final String amountStr = sa.getParam("LifeAmount"); int amount; - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); amount = xPay; } else { amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); diff --git a/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java b/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java index b3d205c4958..b6608c17988 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java @@ -80,7 +80,7 @@ public class ManifestAi extends SpellAbilityAi { // Handle either Manifest X cards, or Manifest 1 card and give it X P1P1s // Set PayX here to maximum value. int x = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(x)); + sa.setSVar("PayX", Integer.toString(x)); if (x <= 0) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/MillAi.java b/forge-ai/src/main/java/forge/ai/ability/MillAi.java index 52f0e8cda25..a469cc108be 100644 --- a/forge-ai/src/main/java/forge/ai/ability/MillAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/MillAi.java @@ -75,7 +75,6 @@ public class MillAi extends SpellAbilityAi { * - check for Laboratory Maniac effect (needs to check for actual * effect due to possibility of "lose abilities" effect) */ - final Card source = sa.getHostCard(); if (ComputerUtil.preventRunAwayActivations(sa)) { return false; // prevents mill 0 infinite loop? } @@ -90,10 +89,10 @@ public class MillAi extends SpellAbilityAi { } if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z")) - && source.getSVar("X").startsWith("Count$xPaid")) { + && sa.getSVar("X").startsWith("Count$xPaid")) { // Set PayX here to maximum value. final int cardsToDiscard = getNumToDiscard(ai, sa); - source.setSVar("PayX", Integer.toString(cardsToDiscard)); + sa.setSVar("PayX", Integer.toString(cardsToDiscard)); return cardsToDiscard > 0; } return true; @@ -182,11 +181,10 @@ public class MillAi extends SpellAbilityAi { return false; } - final Card source = sa.getHostCard(); - if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) { + if (sa.getParam("NumCards").equals("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. final int cardsToDiscard = getNumToDiscard(aiPlayer, sa); - source.setSVar("PayX", Integer.toString(cardsToDiscard)); + sa.setSVar("PayX", Integer.toString(cardsToDiscard)); } return true; diff --git a/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java b/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java index cf9ecdf6555..9557f3204eb 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java @@ -164,7 +164,7 @@ public class ProtectAi extends SpellAbilityAi { @Override protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { - if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { + if (!sa.usesTargeting()) { final List cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); if (cards.size() == 0) { return false; @@ -359,12 +359,7 @@ public class ProtectAi extends SpellAbilityAi { @Override public boolean chkAIDrawback(SpellAbility sa, Player ai) { - final Card host = sa.getHostCard(); - if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { - if (host.isCreature()) { - // TODO - } - } else { + if (sa.usesTargeting()) { return protectTgtAI(ai, sa, false); } diff --git a/forge-ai/src/main/java/forge/ai/ability/ProtectAllAi.java b/forge-ai/src/main/java/forge/ai/ability/ProtectAllAi.java index 7fb827cd033..8e60b3d397c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ProtectAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ProtectAllAi.java @@ -33,7 +33,7 @@ public class ProtectAllAi extends SpellAbilityAi { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) { + if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard, sa)) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java index 9d4ec101c8e..2da1bfb5662 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -10,8 +10,6 @@ import forge.game.ability.ApiType; import forge.game.card.*; import forge.game.card.CardPredicates.Presets; import forge.game.cost.Cost; -import forge.game.cost.CostPart; -import forge.game.cost.CostRemoveCounter; import forge.game.cost.CostTapType; import forge.game.keyword.Keyword; import forge.game.phase.PhaseHandler; @@ -288,19 +286,19 @@ public class PumpAi extends PumpAiBase { } } - if (source.getSVar("X").equals("Count$xPaid")) { - source.setSVar("PayX", ""); + if (sa.getSVar("X").equals("Count$xPaid")) { + sa.setSVar("PayX", ""); } int defense; - if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. - int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); + int xPay = ComputerUtilCost.getMaxXValue(sa, ai); if (sourceName.equals("Necropolis Fiend")) { xPay = Math.min(xPay, sa.getActivatingPlayer().getCardsIn(ZoneType.Graveyard).size()); sa.setSVar("X", Integer.toString(xPay)); } - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); defense = xPay; if (numDefense.equals("-X")) { defense = -xPay; @@ -313,13 +311,13 @@ public class PumpAi extends PumpAiBase { } int attack; - if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (numAttack.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. - final String toPay = source.getSVar("PayX"); + final String toPay = sa.getSVar("PayX"); if (toPay.equals("")) { - final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + final int xPay = ComputerUtilCost.getMaxXValue(sa, ai); + sa.setSVar("PayX", Integer.toString(xPay)); attack = xPay; } else { attack = Integer.parseInt(toPay); @@ -353,7 +351,7 @@ public class PumpAi extends PumpAiBase { } //Untargeted - if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { + if (!sa.usesTargeting()) { final List cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); if (cards.isEmpty()) { @@ -398,19 +396,10 @@ public class PumpAi extends PumpAiBase { return false; } - if ("DebuffForXCounters".equals(sa.getParam("AILogic")) && sa.getTargetCard() != null) { - // e.g. Skullmane Baku - CounterType ctrType = CounterType.get(CounterEnumType.KI); - for (CostPart part : sa.getPayCosts().getCostParts()) { - if (part instanceof CostRemoveCounter) { - ctrType = ((CostRemoveCounter)part).counter; - break; - } - } - + if (!sa.getSVar("PayX").isEmpty() && sa.getTargetCard() != null) { // Do not pay more counters than necessary to kill the targeted creature - int chosenX = Math.min(source.getCounters(ctrType), sa.getTargetCard().getNetToughness()); - sa.setSVar("ChosenX", String.valueOf(chosenX)); + int chosenX = Math.min(sa.getSVarInt("PayX"), sa.getTargetCard().getNetToughness()); + sa.setSVar("PayX", String.valueOf(chosenX)); } return true; @@ -573,12 +562,12 @@ public class PumpAi extends PumpAiBase { } } - while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) { + while (sa.canAddMoreTarget()) { Card t = null; // boolean goodt = false; if (list.isEmpty()) { - if (sa.getTargets().size() < tgt.getMinTargets(source, sa) || sa.getTargets().size() == 0) { + if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { if (mandatory || ComputerUtil.activateForCost(sa, ai)) { return pumpMandatoryTarget(ai, sa); } @@ -678,28 +667,27 @@ public class PumpAi extends PumpAiBase { @Override protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { - final Card source = sa.getHostCard(); final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : ""; final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : ""; int defense; - if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. - final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + final int xPay = ComputerUtilCost.getMaxXValue(sa, ai); + sa.setSVar("PayX", Integer.toString(xPay)); defense = xPay; } else { defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa); } int attack; - if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (numAttack.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. - final String toPay = source.getSVar("PayX"); + final String toPay = sa.getSVar("PayX"); if (toPay.equals("")) { - final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + final int xPay = ComputerUtilCost.getMaxXValue(sa, ai); + sa.setSVar("PayX", Integer.toString(xPay)); attack = xPay; } else { attack = Integer.parseInt(toPay); @@ -708,7 +696,7 @@ public class PumpAi extends PumpAiBase { attack = AbilityUtils.calculateAmount(sa.getHostCard(), numAttack, sa); } - if (sa.getTargetRestrictions() == null) { + if (!sa.usesTargeting()) { if (mandatory) { return true; } @@ -749,28 +737,28 @@ public class PumpAi extends PumpAiBase { return false; } - int defense; - if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) { - defense = Integer.parseInt(source.getSVar("PayX")); - } else { - defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa); - } - int attack; - if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) { - if (source.getSVar("PayX").equals("")) { + if (numAttack.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { + if (sa.getSVar("PayX").equals("")) { // X is not set yet - final int xPay = ComputerUtilMana.determineLeftoverMana(sa.getRootAbility(), ai); - source.setSVar("PayX", Integer.toString(xPay)); + final int xPay = ComputerUtilCost.getMaxXValue(sa, ai); + sa.setSVar("PayX", Integer.toString(xPay)); attack = xPay; } else { - attack = Integer.parseInt(source.getSVar("PayX")); + attack = Integer.parseInt(sa.getSVar("PayX")); } } else { attack = AbilityUtils.calculateAmount(sa.getHostCard(), numAttack, sa); } - if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { + int defense; + if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { + defense = Integer.parseInt(sa.getSVar("PayX")); + } else { + defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa); + } + + if (!sa.usesTargeting()) { if (source.isCreature()) { if (!source.hasKeyword(Keyword.INDESTRUCTIBLE) && source.getNetToughness() + defense <= source.getDamage()) { return false; diff --git a/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java b/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java index 7a37733b284..53be584b138 100644 --- a/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java @@ -2,22 +2,18 @@ package forge.ai.ability; import forge.ai.*; -import forge.game.card.Card; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; public class RepeatAi extends SpellAbilityAi { @Override protected boolean canPlayAI(Player ai, SpellAbility sa) { - final Card source = sa.getHostCard(); - final TargetRestrictions tgt = sa.getTargetRestrictions(); final Player opp = ai.getWeakestOpponent(); - if (tgt != null) { + if (sa.usesTargeting()) { if (!opp.canBeTargetedBy(sa)) { return false; } @@ -31,7 +27,7 @@ public class RepeatAi extends SpellAbilityAi { } // Set PayX here to maximum value. final int max = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(max)); + sa.setSVar("PayX", Integer.toString(max)); return max > 0; } return true; diff --git a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java index 78081283c46..18a2cf4e13e 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java @@ -103,10 +103,10 @@ public class SacrificeAi extends SpellAbilityAi { return false; } - if (num.equals("X") && source.getSVar(num).equals("Count$xPaid")) { + if (num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), amount); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } final int half = (amount / 2) + (amount % 2); // Half of amount @@ -135,7 +135,7 @@ public class SacrificeAi extends SpellAbilityAi { final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1"; int amount = AbilityUtils.calculateAmount(source, num, sa); - if (num.equals("X") && source.getSVar(num).equals("Count$xPaid")) { + if (num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) { // Set PayX here to maximum value. amount = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), amount); } diff --git a/forge-ai/src/main/java/forge/ai/ability/SacrificeAllAi.java b/forge-ai/src/main/java/forge/ai/ability/SacrificeAllAi.java index 8032c127eed..5f0e3de1931 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SacrificeAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SacrificeAllAi.java @@ -28,10 +28,10 @@ public class SacrificeAllAi extends SpellAbilityAi { valid = sa.getParam("ValidCards"); } - if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); valid = TextUtil.fastReplace(valid, "X", Integer.toString(xPay)); } diff --git a/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java b/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java index 2abada6610d..b7560a63b37 100644 --- a/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java @@ -32,7 +32,7 @@ public class StoreSVarAi extends SpellAbilityAi { // Set PayX here to half the remaining mana to allow for Main 2 and other combat shenanigans. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai) / 2; if (xPay == 0) { return false; } - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } final String logic = sa.getParam("AILogic"); diff --git a/forge-ai/src/main/java/forge/ai/ability/TapAi.java b/forge-ai/src/main/java/forge/ai/ability/TapAi.java index 54da422ee87..5bd753c2d4d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TapAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TapAi.java @@ -3,18 +3,11 @@ package forge.ai.ability; import forge.ai.*; import forge.game.ability.AbilityUtils; import forge.game.card.Card; -import forge.game.card.CounterEnumType; -import forge.game.card.CounterType; import forge.game.cost.Cost; -import forge.game.cost.CostPart; -import forge.game.cost.CostRemoveCounter; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; - -import java.util.List; public class TapAi extends TapAiBase { @Override @@ -48,7 +41,6 @@ public class TapAi extends TapAiBase { return false; } - final TargetRestrictions tgt = sa.getTargetRestrictions(); final Card source = sa.getHostCard(); final Cost abCost = sa.getPayCosts(); if (abCost != null) { @@ -57,32 +49,27 @@ public class TapAi extends TapAiBase { } } - if (tgt == null) { - final List defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa); - + if (!sa.usesTargeting()) { boolean bFlag = false; - for (final Card c : defined) { + for (final Card c : AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)) { bFlag |= c.isUntapped(); } return bFlag; } else { - if ("TapForXCounters".equals(sa.getParam("AILogic"))) { - // e.g. Waxmane Baku - CounterType ctrType = CounterType.get(CounterEnumType.KI); - for (CostPart part : sa.getPayCosts().getCostParts()) { - if (part instanceof CostRemoveCounter) { - ctrType = ((CostRemoveCounter)part).counter; - break; - } - } + // X controls the minimum targets + if ("X".equals(sa.getTargetRestrictions().getMinTargets()) && sa.getSVar("X").equals("Count$xPaid")) { + // Set PayX here to maximum value. + int xPay = ComputerUtilCost.getMaxXValue(sa, ai); + sa.setSVar("PayX", Integer.toString(xPay)); - int numTargetable = Math.min(sa.getHostCard().getCounters(ctrType), ai.getOpponents().getCreaturesInPlay().size()); - sa.setSVar("ChosenX", String.valueOf(numTargetable)); + // TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore? + sa.setXManaCostPaid(xPay); + // TODO since change of PayX. the shouldCastLessThanMax logic might be faulty } sa.resetTargets(); - return tapPrefTargeting(ai, source, tgt, sa, false); + return tapPrefTargeting(ai, source, sa, false); } } diff --git a/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java b/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java index 98734bc4f2f..e198d6f26ee 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java @@ -18,12 +18,11 @@ import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType; import java.util.List; -public abstract class TapAiBase extends SpellAbilityAi { +public abstract class TapAiBase extends SpellAbilityAi { /** *

@@ -41,21 +40,18 @@ public abstract class TapAiBase extends SpellAbilityAi { */ private boolean tapTargetList(final Player ai, final SpellAbility sa, final CardCollection tapList, final boolean mandatory) { final Card source = sa.getHostCard(); - final TargetRestrictions tgt = sa.getTargetRestrictions(); - for (final Card c : sa.getTargets().getTargetCards()) { - tapList.remove(c); - } + tapList.removeAll(sa.getTargets().getTargetCards()); if (tapList.isEmpty()) { return false; } - while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) { + while (sa.canAddMoreTarget()) { Card choice = null; - if (tapList.size() == 0) { - if (sa.getTargets().size() < tgt.getMinTargets(source, sa) || sa.getTargets().size() == 0) { + if (tapList.isEmpty()) { + if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { if (!mandatory) { sa.resetTargets(); } @@ -76,7 +72,7 @@ public abstract class TapAiBase extends SpellAbilityAi { } if (choice == null) { // can't find anything left - if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) { + if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { if (!mandatory) { sa.resetTargets(); } @@ -111,11 +107,10 @@ public abstract class TapAiBase extends SpellAbilityAi { * a boolean. * @return a boolean. */ - protected boolean tapPrefTargeting(final Player ai, final Card source, final TargetRestrictions tgt, final SpellAbility sa, final boolean mandatory) { + protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) { final Player opp = ai.getWeakestOpponent(); final Game game = ai.getGame(); CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents()); - tapList = CardLists.getValidCards(tapList, tgt.getValidTgts(), source.getController(), source, sa); tapList = CardLists.getTargetableCards(tapList, sa); tapList = CardLists.filter(tapList, Presets.UNTAPPED); tapList = CardLists.filter(tapList, new Predicate() { @@ -136,10 +131,9 @@ public abstract class TapAiBase extends SpellAbilityAi { //use broader approach when the cost is a positive thing if (tapList.isEmpty() && ComputerUtil.activateForCost(sa, ai)) { - tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents()); - tapList = CardLists.getValidCards(tapList, tgt.getValidTgts(), source.getController(), source, sa); + tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents()); tapList = CardLists.getTargetableCards(tapList, sa); - tapList = CardLists.filter(tapList, new Predicate() { + tapList = CardLists.filter(tapList, new Predicate() { @Override public boolean apply(final Card c) { if (c.isCreature()) { @@ -162,15 +156,15 @@ public abstract class TapAiBase extends SpellAbilityAi { tapList.removeAll(toExclude); if (tapList.isEmpty()) { - return false; + return false; } boolean goodTargets = false; - while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) { + while (sa.canAddMoreTarget()) { Card choice = null; if (tapList.isEmpty()) { - if (sa.getTargets().size() < tgt.getMinTargets(source, sa) || sa.getTargets().size() == 0) { + if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { if (!mandatory) { sa.resetTargets(); } @@ -186,8 +180,8 @@ public abstract class TapAiBase extends SpellAbilityAi { PhaseHandler phase = game.getPhaseHandler(); Card primeTarget = ComputerUtil.getKilledByTargeting(sa, tapList); if (primeTarget != null) { - choice = primeTarget; - goodTargets = true; + choice = primeTarget; + goodTargets = true; } else if (phase.isPlayerTurn(ai) && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) { // Tap creatures possible blockers before combat during AI's turn. List attackers; @@ -231,7 +225,7 @@ public abstract class TapAiBase extends SpellAbilityAi { } if (choice == null) { // can't find anything left - if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) { + if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { if (!mandatory) { sa.resetTargets(); } @@ -265,19 +259,17 @@ public abstract class TapAiBase extends SpellAbilityAi { */ protected boolean tapUnpreferredTargeting(final Player ai, final SpellAbility sa, final boolean mandatory) { final Card source = sa.getHostCard(); - final TargetRestrictions tgt = sa.getTargetRestrictions(); final Game game = ai.getGame(); - CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), source.getController(), source, sa); - list = CardLists.getTargetableCards(list, sa); - + CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa); + // try to tap anything controlled by the computer CardCollection tapList = CardLists.filterControlledBy(list, ai.getOpponents()); if (tapTargetList(ai, sa, tapList, mandatory)) { return true; } - if (sa.getTargets().size() >= tgt.getMinTargets(sa.getHostCard(), sa)) { + if (sa.isMinTargetChosen()) { return true; } @@ -296,7 +288,7 @@ public abstract class TapAiBase extends SpellAbilityAi { return true; } - if (sa.getTargets().size() >= tgt.getMinTargets(sa.getHostCard(), sa)) { + if (sa.isMinTargetChosen()) { return true; } @@ -308,11 +300,9 @@ public abstract class TapAiBase extends SpellAbilityAi { @Override protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { - - final TargetRestrictions tgt = sa.getTargetRestrictions(); final Card source = sa.getHostCard(); - if (tgt == null) { + if (!sa.usesTargeting()) { if (mandatory) { return true; } @@ -322,7 +312,7 @@ public abstract class TapAiBase extends SpellAbilityAi { return true; } else { sa.resetTargets(); - if (tapPrefTargeting(ai, source, tgt, sa, mandatory)) { + if (tapPrefTargeting(ai, source, sa, mandatory)) { return true; } else if (mandatory) { // not enough preferred targets, but mandatory so keep going: @@ -335,17 +325,14 @@ public abstract class TapAiBase extends SpellAbilityAi { @Override public boolean chkAIDrawback(SpellAbility sa, Player ai) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); final Card source = sa.getHostCard(); boolean randomReturn = true; - if (tgt == null) { - // either self or defined, either way should be fine - } else { + if (sa.usesTargeting()) { // target section, maybe pull this out? sa.resetTargets(); - if (!tapPrefTargeting(ai, source, tgt, sa, false)) { + if (!tapPrefTargeting(ai, source, sa, false)) { return false; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/TapOrUntapAi.java b/forge-ai/src/main/java/forge/ai/ability/TapOrUntapAi.java index b2655c16ff5..949bb217141 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TapOrUntapAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TapOrUntapAi.java @@ -4,11 +4,8 @@ import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.player.Player; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; import forge.util.MyRandom; -import java.util.List; - public class TapOrUntapAi extends TapAiBase { /* (non-Javadoc) @@ -16,19 +13,17 @@ public class TapOrUntapAi extends TapAiBase { */ @Override protected boolean canPlayAI(Player ai, SpellAbility sa) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); final Card source = sa.getHostCard(); boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); - if (tgt == null) { + if (!sa.usesTargeting()) { // assume we are looking to tap human's stuff // TODO - check for things with untap abilities, and don't tap // those. - final List defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa); boolean bFlag = false; - for (final Card c : defined) { + for (final Card c : AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)) { bFlag |= c.isUntapped(); } @@ -37,7 +32,7 @@ public class TapOrUntapAi extends TapAiBase { } } else { sa.resetTargets(); - if (!tapPrefTargeting(ai, source, tgt, sa, false)) { + if (!tapPrefTargeting(ai, source, sa, false)) { return false; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java index f6a62f567ef..7a6eb371c7c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java @@ -85,10 +85,10 @@ public class TokenAi extends SpellAbilityAi { if (source.getSVar("X").equals("Count$Converge")) { x = ComputerUtilMana.getConvergeCount(sa, ai); } - if (source.getSVar("X").equals("Count$xPaid")) { + if (sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. x = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(x)); + sa.setSVar("PayX", Integer.toString(x)); } if (x <= 0) { return false; // 0 tokens or 0 toughness token(s) @@ -261,10 +261,10 @@ public class TokenAi extends SpellAbilityAi { if ("X".equals(tokenAmount) || "X".equals(tokenPower) || "X".equals(tokenToughness)) { int x = AbilityUtils.calculateAmount(source, tokenAmount, sa); - if (source.getSVar("X").equals("Count$xPaid")) { + if (sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. x = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(x)); + sa.setSVar("PayX", Integer.toString(x)); } if (x <= 0) { return false; diff --git a/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java b/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java index 4c877974ff2..a6dd8205ec6 100644 --- a/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java @@ -46,7 +46,7 @@ public class UnattachAllAi extends SpellAbilityAi { return false; } - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) diff --git a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java index 5145906e259..ecb45dd2ec8 100644 --- a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java @@ -51,27 +51,24 @@ public class UntapAi extends SpellAbilityAi { @Override protected boolean checkApiLogic(Player ai, SpellAbility sa) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); final Card source = sa.getHostCard(); if (ComputerUtil.preventRunAwayActivations(sa)) { return false; } - if (tgt == null) { + if (!sa.usesTargeting()) { final List pDefined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa); return pDefined == null || !pDefined.get(0).isUntapped() || pDefined.get(0).getController() != ai; } else { - return untapPrefTargeting(ai, tgt, sa, false); + return untapPrefTargeting(ai, sa, false); } } @Override protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); - - if (tgt == null) { + if (!sa.usesTargeting()) { if (mandatory) { return true; } @@ -80,7 +77,7 @@ public class UntapAi extends SpellAbilityAi { final List pDefined = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); return pDefined == null || !pDefined.get(0).isUntapped() || pDefined.get(0).getController() != ai; } else { - if (untapPrefTargeting(ai, tgt, sa, mandatory)) { + if (untapPrefTargeting(ai, sa, mandatory)) { return true; } else if (mandatory) { // not enough preferred targets, but mandatory so keep going: @@ -100,7 +97,7 @@ public class UntapAi extends SpellAbilityAi { if (tgt == null) { // who cares if its already untapped, it's only a subability? } else { - if (!untapPrefTargeting(ai, tgt, sa, false)) { + if (!untapPrefTargeting(ai, sa, false)) { return false; } } @@ -113,15 +110,13 @@ public class UntapAi extends SpellAbilityAi { * untapPrefTargeting. *

* - * @param tgt - * a {@link forge.game.spellability.TargetRestrictions} object. * @param sa * a {@link forge.game.spellability.SpellAbility} object. * @param mandatory * a boolean. * @return a boolean. */ - private static boolean untapPrefTargeting(final Player ai, final TargetRestrictions tgt, final SpellAbility sa, final boolean mandatory) { + private static boolean untapPrefTargeting(final Player ai, final SpellAbility sa, final boolean mandatory) { final Card source = sa.getHostCard(); Player targetController = ai; @@ -131,7 +126,6 @@ public class UntapAi extends SpellAbilityAi { } CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa); - list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa); if (list.isEmpty()) { return false; @@ -175,7 +169,7 @@ public class UntapAi extends SpellAbilityAi { untapList.removeAll(toExclude); sa.resetTargets(); - while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) { + while (sa.canAddMoreTarget()) { Card choice = null; if (untapList.isEmpty()) { @@ -183,7 +177,7 @@ public class UntapAi extends SpellAbilityAi { if (sa.getSubAbility() != null && sa.getSubAbility().getApi() == ApiType.Animate && !list.isEmpty() && ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) { choice = ComputerUtilCard.getWorstPermanentAI(list, false, false, false, false); - } else if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) { + } else if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { sa.resetTargets(); return false; } else { @@ -204,7 +198,7 @@ public class UntapAi extends SpellAbilityAi { } if (choice == null) { // can't find anything left - if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) { + if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { sa.resetTargets(); return false; } else { diff --git a/forge-core/src/main/java/forge/card/CardDb.java b/forge-core/src/main/java/forge/card/CardDb.java index f672c12e09a..6703f58075b 100644 --- a/forge-core/src/main/java/forge/card/CardDb.java +++ b/forge-core/src/main/java/forge/card/CardDb.java @@ -50,8 +50,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { private final Map alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); private final Map artIds = new HashMap<>(); - private final List allCards = new ArrayList<>(); - private final List roAllCards = Collections.unmodifiableList(allCards); + private final Collection roAllCards = Collections.unmodifiableCollection(allCardsByName.values()); private final CardEdition.Collection editions; public enum SetPreference { @@ -247,10 +246,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { private void reIndex() { uniqueCardsByName.clear(); - allCards.clear(); for (Entry> kv : allCardsByName.asMap().entrySet()) { uniqueCardsByName.put(kv.getKey(), getFirstWithImage(kv.getValue())); - allCards.addAll(kv.getValue()); } } @@ -537,7 +534,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { } @Override - public List getAllCards() { + public Collection getAllCards() { return roAllCards; } diff --git a/forge-core/src/main/java/forge/card/ICardDatabase.java b/forge-core/src/main/java/forge/card/ICardDatabase.java index a6256207fc7..a83faea5b4d 100644 --- a/forge-core/src/main/java/forge/card/ICardDatabase.java +++ b/forge-core/src/main/java/forge/card/ICardDatabase.java @@ -24,9 +24,9 @@ public interface ICardDatabase extends Iterable { int getArtCount(String cardName, String edition); Collection getUniqueCards(); - List getAllCards(); - List getAllCards(String cardName); - List getAllCards(Predicate predicate); + Collection getAllCards(); + Collection getAllCards(String cardName); + Collection getAllCards(Predicate predicate); List getAllCardsFromEdition(CardEdition edition); diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java index b8e2dce51cd..cf98aedb023 100644 --- a/forge-game/src/main/java/forge/game/CardTraitBase.java +++ b/forge-game/src/main/java/forge/game/CardTraitBase.java @@ -21,15 +21,17 @@ import org.apache.commons.lang3.StringUtils; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; -/** +/** * Base class for Triggers,ReplacementEffects and StaticAbilities. - * + * */ -public abstract class CardTraitBase extends GameObject implements IHasCardView { +public abstract class CardTraitBase extends GameObject implements IHasCardView, IHasSVars { /** The host card. */ protected Card hostCard; + private Card grantorCard = null; // card which grants the ability (equipment or owner of static ability that gave this one) + /** The map params. */ protected Map originalMapParams = Maps.newHashMap(), mapParams = Maps.newHashMap(); @@ -40,7 +42,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { /** The suppressed. */ protected boolean suppressed = false; - protected Map sVars = Maps.newHashMap(); + protected Map sVars = Maps.newTreeMap(); protected Map intrinsicChangedTextColors = Maps.newHashMap(); protected Map intrinsicChangedTextTypes = Maps.newHashMap(); @@ -82,7 +84,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { public boolean hasParam(String key) { return mapParams.containsKey(key); - } + } /** *

* Getter for the field mapParams. @@ -110,7 +112,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { *

* Getter for the field hostCard. *

- * + * * @return a {@link forge.game.card.Card} object. */ public Card getHostCard() { @@ -133,7 +135,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { *

* isSecondary. *

- * + * * @return a boolean. */ public final boolean isSecondary() { @@ -144,7 +146,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { *

* matchesValid. *

- * + * * @param o * a {@link java.lang.Object} object. * @param valids @@ -164,7 +166,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { /** * Sets the suppressed. - * + * * @param supp * the new suppressed */ @@ -174,7 +176,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { /** * Checks if is suppressed. - * + * * @return true, if is suppressed */ public final boolean isSuppressed() { @@ -184,7 +186,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { protected boolean meetsCommonRequirements(Map params) { final Player hostController = this.getHostCard().getController(); final Game game = hostController.getGame(); - + if (params.containsKey("Metalcraft")) { if ("True".equalsIgnoreCase(params.get("Metalcraft")) != hostController.hasMetalcraft()) return false; } @@ -212,7 +214,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { if (params.containsKey("Blessing")) { if ("True".equalsIgnoreCase(params.get("Blessing")) != hostController.hasBlessing()) return false; } - + if (params.containsKey("Adamant")) { if (hostCard.getCastSA() == null) { return false; @@ -307,18 +309,18 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { } } list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), null); - + final String rightString = presentCompare.substring(2); int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this); final int left = list.size(); - + if (!Expressions.compare(left, presentCompare, right)) { return false; } - + } - + if (params.containsKey("IsPresent2")) { final String sIsPresent = params.get("IsPresent2"); String presentCompare = "GE1"; @@ -342,13 +344,13 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { list.addAll(p.getCardsIn(presentZone)); } } - + list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), null); - + final String rightString = presentCompare.substring(2); int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this); final int left = list.size(); - + if (!Expressions.compare(left, presentCompare, right)) { return false; } @@ -371,7 +373,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { return false; } } - + if (params.containsKey("CheckSVar")) { final int sVar = AbilityUtils.calculateAmount(game.getCardState(this.getHostCard()), params.get("CheckSVar"), this); String comparator = "GE1"; @@ -385,7 +387,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { return false; } } - + if (params.containsKey("ManaSpent")) { SpellAbility castSA = getHostCard().getCastSA(); if (castSA == null) { @@ -462,28 +464,29 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { return CardView.get(hostCard); } - public String getSvarWithFallback(final String name) { - String var = sVars.get(name); - if (var == null) { - var = hostCard.getSVar(name); + protected IHasSVars getSVarFallback() { + if (getOriginalHost() != null) { + return getOriginalHost(); } - return var; + return getHostCard(); } + @Override public String getSVar(final String name) { - String var = sVars.get(name); - if (var == null) { - var = ""; + if (sVars.containsKey(name)) { + return sVars.get(name); + } else { + return getSVarFallback().getSVar(name); } - return var; } + @Override public boolean hasSVar(final String name) { - return sVars.containsKey(name); + return sVars.containsKey(name) || getSVarFallback().hasSVar(name); } public Integer getSVarInt(final String name) { - String var = sVars.get(name); + String var = this.getSVar(name); if (var != null) { try { return Integer.parseInt(var); @@ -493,12 +496,32 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { return null; } + @Override public final void setSVar(final String name, final String value) { sVars.put(name, value); } - public Set getSVars() { - return sVars.keySet(); + @Override + public Map getSVars() { + return sVars; + } + + @Override + public void setSVars(Map newSVars) { + sVars = Maps.newTreeMap(); + sVars.putAll(newSVars); + } + + @Override + public void removeSVar(String var) { + sVars.remove(var); + } + + public Card getOriginalHost() { + return grantorCard; + } + public void setOriginalHost(final Card c) { + grantorCard = c; } public Map getChangedTextColors() { @@ -553,7 +576,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { protected void copyHelper(CardTraitBase copy, Card host) { copy.originalMapParams = Maps.newHashMap(originalMapParams); copy.mapParams = Maps.newHashMap(originalMapParams); - copy.sVars = Maps.newHashMap(sVars); + copy.setSVars(sVars); // dont use setHostCard to not trigger the not copied parts yet copy.hostCard = host; } diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java index 06a3d41adee..49409689a32 100644 --- a/forge-game/src/main/java/forge/game/ForgeScript.java +++ b/forge-game/src/main/java/forge/game/ForgeScript.java @@ -126,7 +126,7 @@ public class ForgeScript { } else if (property.equals("nonManaAbility")) { return !sa.isManaAbility(); } else if (property.equals("withoutXCost")) { - return !sa.isXCost(); + return !sa.costHasManaX(); } else if (property.equals("Buyback")) { return sa.isBuyBackAbility(); } else if (property.equals("Cycling")) { diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index 2886947a065..991a219048d 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -439,10 +439,10 @@ public class Game { } public synchronized void setGameOver(GameEndReason reason) { - age = GameStage.GameOver; for (Player p : allPlayers) { p.clearController(); } + age = GameStage.GameOver; for (Player p : getPlayers()) { p.onGameOver(); diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 853a1a35758..c41f9023d5c 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -227,12 +227,6 @@ public class GameAction { // ensure that any leftover keyword/type changes are cleared in the state view copied.updateStateForView(); - // Clean up temporary variables such as Sunburst value or announced PayX value - if (!(zoneTo.is(ZoneType.Stack) || zoneTo.is(ZoneType.Battlefield))) { - copied.clearTemporaryVars(); - } - - if (!suppress) { if (zoneFrom == null) { copied.getOwner().addInboundToken(copied); diff --git a/forge-game/src/main/java/forge/game/GameEntityCounterTable.java b/forge-game/src/main/java/forge/game/GameEntityCounterTable.java index d8c7b9b3403..6e8c7051552 100644 --- a/forge-game/src/main/java/forge/game/GameEntityCounterTable.java +++ b/forge-game/src/main/java/forge/game/GameEntityCounterTable.java @@ -31,15 +31,43 @@ public class GameEntityCounterTable extends ForwardingTable filterToRemove(GameEntity ge) { + Map result = Maps.newHashMap(); + if (!containsRow(ge)) { + result.putAll(ge.getCounters()); + return result; + } + Map alreadyRemoved = row(ge); + for (Map.Entry e : ge.getCounters().entrySet()) { + Integer rest = e.getValue() - (alreadyRemoved.containsKey(e.getKey()) ? alreadyRemoved.get(e.getKey()) : 0); + if (rest > 0) { + result.put(e.getKey(), rest); + } + } + return result; } public Map filterTable(CounterType type, String valid, Card host, SpellAbility sa) { Map result = Maps.newHashMap(); for (Map.Entry e : column(type).entrySet()) { - if (e.getKey().isValid(valid, host.getController(), host, sa)) { + if (e.getValue() > 0 && e.getKey().isValid(valid, host.getController(), host, sa)) { result.put(e.getKey(), e.getValue()); } } diff --git a/forge-game/src/main/java/forge/game/IHasSVars.java b/forge-game/src/main/java/forge/game/IHasSVars.java new file mode 100644 index 00000000000..f6effefb3f0 --- /dev/null +++ b/forge-game/src/main/java/forge/game/IHasSVars.java @@ -0,0 +1,20 @@ +package forge.game; + +import java.util.Map; + +public interface IHasSVars { + + public String getSVar(final String name); + + public boolean hasSVar(final String name); + //public Integer getSVarInt(final String name); + + public void setSVar(final String name, final String value); + public void setSVars(final Map newSVars); + + //public Set getSVars(); + + public Map getSVars(); + + public void removeSVar(final String var); +} diff --git a/forge-game/src/main/java/forge/game/TriggerReplacementBase.java b/forge-game/src/main/java/forge/game/TriggerReplacementBase.java index 8552db9f0ff..1ff5b94e3ae 100644 --- a/forge-game/src/main/java/forge/game/TriggerReplacementBase.java +++ b/forge-game/src/main/java/forge/game/TriggerReplacementBase.java @@ -19,13 +19,23 @@ public abstract class TriggerReplacementBase extends CardTraitBase implements II @Override public void setHostCard(final Card c) { - this.hostCard = c; + super.setHostCard(c); if (overridingAbility != null) { overridingAbility.setHostCard(c); } } + @Override + public void setOriginalHost(Card c) { + super.setOriginalHost(c); + if (overridingAbility != null) { + overridingAbility.setOriginalHost(c); + } + } + + + public Set getActiveZone() { return validHostZones; } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java index 2ddddf62ee5..120149338b0 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -21,6 +21,7 @@ import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import forge.card.CardStateName; +import forge.game.IHasSVars; import forge.game.ability.effects.CharmEffect; import forge.game.card.Card; import forge.game.card.CardState; @@ -102,7 +103,10 @@ public final class AbilityFactory { } public static final SpellAbility getAbility(final String abString, final Card card) { - return getAbility(abString, card.getCurrentState(), null); + return getAbility(abString, card, card.getCurrentState()); + } + public static final SpellAbility getAbility(final String abString, final Card card, final IHasSVars sVarHolder) { + return getAbility(abString, card.getCurrentState(), sVarHolder); } /** *

@@ -116,10 +120,10 @@ public final class AbilityFactory { * @return a {@link forge.game.spellability.SpellAbility} object. */ public static final SpellAbility getAbility(final String abString, final CardState state) { - return getAbility(abString, state, null); + return getAbility(abString, state, state); } - private static final SpellAbility getAbility(final String abString, final CardState state, final SpellAbility parent) { + private static final SpellAbility getAbility(final String abString, final CardState state, final IHasSVars sVarHolder) { Map mapParams; try { mapParams = AbilityFactory.getMapParams(abString); @@ -134,7 +138,7 @@ public final class AbilityFactory { throw new RuntimeException("AbilityFactory : getAbility -- no API in " + source + ": " + abString); } try { - return getAbility(mapParams, type, state, parent); + return getAbility(mapParams, type, state, sVarHolder); } catch (Error | Exception ex) { String msg = "AbilityFactory:getAbility: crash when trying to create ability "; Sentry.getContext().recordBreadcrumb( @@ -146,28 +150,24 @@ public final class AbilityFactory { } public static final SpellAbility getAbility(final Card hostCard, final String svar) { - return getAbility(hostCard.getCurrentState(), svar, null); + return getAbility(hostCard, svar, hostCard.getCurrentState()); } - public static final SpellAbility getAbility(final CardState state, final String svar) { - return getAbility(state, svar, null); + public static final SpellAbility getAbility(final Card hostCard, final String svar, final IHasSVars sVarHolder) { + return getAbility(hostCard.getCurrentState(), svar, sVarHolder); } - private static final SpellAbility getAbility(final CardState state, final String svar, final SpellAbility parent) { - if (!state.hasSVar(svar)) { + public static final SpellAbility getAbility(final CardState state, final String svar, final IHasSVars sVarHolder) { + if (!sVarHolder.hasSVar(svar)) { String source = state.getCard().getName(); throw new RuntimeException("AbilityFactory : getAbility -- " + source + " has no SVar: " + svar); } else { - return getAbility(state.getSVar(svar), state, parent); + return getAbility(sVarHolder.getSVar(svar), state, sVarHolder); } } - - public static final SpellAbility getAbility(final Map mapParams, AbilityRecordType type, final Card card, final SpellAbility parent) { - return getAbility(mapParams, type, card.getCurrentState(), parent); - } - public static final SpellAbility getAbility(final Map mapParams, AbilityRecordType type, final CardState state, final SpellAbility parent) { - return getAbility(type, type.getApiTypeOf(mapParams), mapParams, parseAbilityCost(state, mapParams, type), state, parent); + public static final SpellAbility getAbility(final Map mapParams, AbilityRecordType type, final CardState state, final IHasSVars sVarHolder) { + return getAbility(type, type.getApiTypeOf(mapParams), mapParams, parseAbilityCost(state, mapParams, type), state, sVarHolder); } @@ -184,12 +184,7 @@ public final class AbilityFactory { } public static final SpellAbility getAbility(AbilityRecordType type, ApiType api, Map mapParams, - Cost abCost,final Card card, final SpellAbility parent) { - return getAbility(type, api, mapParams, abCost, card.getCurrentState(), parent); - } - - public static final SpellAbility getAbility(AbilityRecordType type, ApiType api, Map mapParams, - Cost abCost,final CardState state, final SpellAbility parent) { + Cost abCost,final CardState state, final IHasSVars sVarHolder) { final Card hostCard = state.getCard(); TargetRestrictions abTgt = mapParams.containsKey("ValidTgts") ? readTarget(mapParams) : null; @@ -218,41 +213,36 @@ public final class AbilityFactory { if (spellAbility == null) { final StringBuilder msg = new StringBuilder(); msg.append("AbilityFactory : SpellAbility was not created for "); - msg.append(state.getName()); + msg.append(state.toString()); msg.append(". Looking for API: ").append(api); throw new RuntimeException(msg.toString()); } - // need to set Parent Early - if (parent != null && spellAbility instanceof AbilitySub) { - ((AbilitySub)spellAbility).setParent(parent); - } - // ********************************************* // set universal properties of the SpellAbility if (mapParams.containsKey("References")) { for (String svar : mapParams.get("References").split(",")) { - spellAbility.setSVar(svar, state.getSVar(svar)); + spellAbility.setSVar(svar, sVarHolder.getSVar(svar)); } } if (api == ApiType.DelayedTrigger && mapParams.containsKey("Execute")) { - spellAbility.setSVar(mapParams.get("Execute"), state.getSVar(mapParams.get("Execute"))); + spellAbility.setSVar(mapParams.get("Execute"), sVarHolder.getSVar(mapParams.get("Execute"))); } if (mapParams.containsKey("PreventionSubAbility")) { - spellAbility.setSVar(mapParams.get("PreventionSubAbility"), state.getSVar(mapParams.get("PreventionSubAbility"))); + spellAbility.setSVar(mapParams.get("PreventionSubAbility"), sVarHolder.getSVar(mapParams.get("PreventionSubAbility"))); } if (mapParams.containsKey("SubAbility")) { final String name = mapParams.get("SubAbility"); - spellAbility.setSubAbility(getSubAbility(state, name, spellAbility)); + spellAbility.setSubAbility(getSubAbility(state, name, sVarHolder)); } for (final String key : additionalAbilityKeys) { if (mapParams.containsKey(key) && spellAbility.getAdditionalAbility(key) == null) { - spellAbility.setAdditionalAbility(key, getSubAbility(state, mapParams.get(key), spellAbility)); + spellAbility.setAdditionalAbility(key, getSubAbility(state, mapParams.get(key), sVarHolder)); } } @@ -260,11 +250,10 @@ public final class AbilityFactory { final String key = "Choices"; if (mapParams.containsKey(key)) { List names = Lists.newArrayList(mapParams.get(key).split(",")); - final SpellAbility sap = spellAbility; spellAbility.setAdditionalAbilityList(key, Lists.transform(names, new Function() { @Override public AbilitySub apply(String input) { - return getSubAbility(state, input, sap); + return getSubAbility(state, input, sVarHolder); } })); } @@ -277,8 +266,8 @@ public final class AbilityFactory { } else if (mapParams.containsKey("SpellDescription")) { final StringBuilder sb = new StringBuilder(); - if (type != AbilityRecordType.SubAbility) { // SubAbilities don't have Costs or Cost - // descriptors + if (type != AbilityRecordType.SubAbility) { + // SubAbilities don't have Costs or Cost descriptors sb.append(spellAbility.getCostDescription()); } @@ -418,10 +407,10 @@ public final class AbilityFactory { * * @return a {@link forge.game.spellability.AbilitySub} object. */ - private static final AbilitySub getSubAbility(CardState state, String sSub, final SpellAbility parent) { + private static final AbilitySub getSubAbility(CardState state, String sSub, final IHasSVars sVarHolder) { - if (state.hasSVar(sSub)) { - return (AbilitySub) AbilityFactory.getAbility(state, sSub, parent); + if (sVarHolder.hasSVar(sSub)) { + return (AbilitySub) AbilityFactory.getAbility(state, sSub, sVarHolder); } System.out.println("SubAbility '"+ sSub +"' not found for: " + state.getName()); @@ -463,7 +452,7 @@ public final class AbilityFactory { SpellAbility rightAbility = rightState.getFirstAbility(); Map rightMap = Maps.newHashMap(rightAbility.getMapParams()); - AbilityRecordType rightType = AbilityRecordType.getRecordType(leftMap); + AbilityRecordType rightType = AbilityRecordType.getRecordType(rightMap); ApiType rightApi = leftType.getApiTypeOf(rightMap); rightMap.put("StackDescription", rightMap.get("SpellDescription")); rightMap.put("SpellDescription", ""); @@ -471,8 +460,8 @@ public final class AbilityFactory { Cost totalCost = parseAbilityCost(leftState, leftMap, leftType); totalCost.add(parseAbilityCost(rightState, rightMap, rightType)); - final SpellAbility left = getAbility(leftType, leftApi, leftMap, totalCost, leftState, null); - final AbilitySub right = (AbilitySub) getAbility(AbilityRecordType.SubAbility, rightApi, rightMap, null, rightState, left); + final SpellAbility left = getAbility(leftType, leftApi, leftMap, totalCost, leftState, leftState); + final AbilitySub right = (AbilitySub) getAbility(AbilityRecordType.SubAbility, rightApi, rightMap, null, rightState, rightState); left.appendSubAbility(right); return left; } 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 7c3dd5d85e7..34422ae5467 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1901,16 +1901,6 @@ public class AbilityUtils { public static final String getSVar(final CardTraitBase ability, final String sVarName) { String val = ability.getSVar(sVarName); - if (StringUtils.isEmpty(val)) { - Card host = null; - if (ability instanceof SpellAbility) { - host = ((SpellAbility) ability).getOriginalHost(); - } - if (host == null) { - host = ability.getHostCard(); - } - val = host.getSVar(sVarName); - } if (!ability.isIntrinsic() || StringUtils.isEmpty(val)) { return val; } @@ -2016,9 +2006,8 @@ public class AbilityUtils { SpellAbility firstSpell = c.getFirstSpellAbility(); Map params = Maps.newHashMap(firstSpell.getMapParams()); - AbilityRecordType rc = AbilityRecordType.getRecordType(params); - ApiType api = rc.getApiTypeOf(params); - AbilitySub subAbility = (AbilitySub) AbilityFactory.getAbility(AbilityRecordType.SubAbility, api, params, null, c, null); + ApiType api = AbilityRecordType.getRecordType(params).getApiTypeOf(params); + AbilitySub subAbility = (AbilitySub) AbilityFactory.getAbility(AbilityRecordType.SubAbility, api, params, null, c.getCurrentState(), null); subAbility.setActivatingPlayer(sa.getActivatingPlayer()); subAbility.setHostCard(sa.getHostCard()); diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java index ae6ffa205f3..68b50dfae39 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java @@ -151,14 +151,17 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect { // give abilities final List addedAbilities = Lists.newArrayList(); for (final String s : abilities) { - addedAbilities.add(AbilityFactory.getAbility(AbilityUtils.getSVar(sa, s), c)); + SpellAbility sSA = AbilityFactory.getAbility(c, s, sa); + sSA.setOriginalHost(source); + addedAbilities.add(sSA); } // Grant triggers final List addedTriggers = Lists.newArrayList(); for (final String s : triggers) { final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false); - parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(AbilityUtils.getSVar(sa, parsedTrigger.getParam("Execute")), c)); + parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(c, parsedTrigger.getParam("Execute"), sa)); + parsedTrigger.setOriginalHost(source); addedTriggers.add(parsedTrigger); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java index 911eab6e8dc..253031c67b5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java @@ -226,8 +226,6 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { } } - game.getTriggerHandler().resetActiveTriggers(false); - triggerList.triggerChangesZoneAll(game); // if Shuffle parameter exists, and any amount of cards were owned by diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index e938a9f9973..2a7956c90ea 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -947,10 +947,12 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (!decider.getController().confirmAction(tgtSA, null, Localizer.getInstance().getMessage("lblDoYouWantPlayCard", CardTranslation.getTranslatedName(tgtCard.getName())))) { continue; } + tgtSA.setSVar("IsCastFromPlayEffect", "True"); // if played, that card cannot be found if (decider.getController().playSaFromPlayEffect(tgtSA)) { fetchList.remove(tgtCard); } + //some kind of reset here? } } final Map runParams = AbilityKey.newMap(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java index 3d55ced65bc..2b3e8aa16e2 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java @@ -135,5 +135,10 @@ public class ChooseCardEffect extends SpellAbilityEffect { host.removeRemembered(rem); } } + if (sa.hasParam("ImprintChosen")) { + for (final Card imp : chosen) { + host.addImprintedCard(imp); + } + } } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java index 60dc63538e0..081b0045a23 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java @@ -155,9 +155,7 @@ public class EffectEffect extends SpellAbilityEffect { // Grant abilities if (effectAbilities != null) { for (final String s : effectAbilities) { - final String actualAbility = AbilityUtils.getSVar(sa, s); - - final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, eff); + final SpellAbility grantedAbility = AbilityFactory.getAbility(eff, s, sa); eff.addSpellAbility(grantedAbility); grantedAbility.setIntrinsic(true); } @@ -166,11 +164,8 @@ public class EffectEffect extends SpellAbilityEffect { // Grant triggers if (effectTriggers != null) { for (final String s : effectTriggers) { - final String actualTrigger = AbilityUtils.getSVar(sa, s); - - final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, eff, true); - final String ability = AbilityUtils.getSVar(sa, parsedTrigger.getParam("Execute")); - parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(ability, eff)); + final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), eff, true); + parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(eff, parsedTrigger.getParam("Execute"), sa)); parsedTrigger.setActiveZone(EnumSet.of(ZoneType.Command)); parsedTrigger.setIntrinsic(true); eff.addTrigger(parsedTrigger); diff --git a/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java index e16f59a92c5..b8e20cb1ba5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/FlipCoinEffect.java @@ -224,12 +224,7 @@ public class FlipCoinEffect extends SpellAbilityEffect { } public static int getFilpMultiplier(final Player flipper) { - int i = 0; - for (String kw : flipper.getKeywords()) { - if (kw.startsWith("If you would flip a coin")) { - i++; - } - } - return 1 << i; + String str = "If you would flip a coin, instead flip two coins and ignore one."; + return 1 + flipper.getKeywords().getAmount(str); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/PermanentNoncreatureEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PermanentNoncreatureEffect.java index 6295abb75b5..c5e8fa78f8b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PermanentNoncreatureEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PermanentNoncreatureEffect.java @@ -13,6 +13,6 @@ public class PermanentNoncreatureEffect extends PermanentEffect { public String getStackDescription(final SpellAbility sa) { final Card sourceCard = sa.getHostCard(); //CardView toString return translated name,don't need call CardTranslation.getTranslatedName in this. - return sourceCard.toString(); + return sourceCard.getName(); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java index 65ef6e1f37a..5d8d4782172 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java @@ -7,6 +7,7 @@ import org.apache.commons.lang3.StringUtils; import com.google.common.base.Predicate; import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -76,15 +77,12 @@ public class PlayEffect extends SpellAbilityEffect { CardCollection showCards = new CardCollection(); if (sa.hasParam("Valid")) { - ZoneType zone = ZoneType.Hand; - if (sa.hasParam("ValidZone")) { - zone = ZoneType.smartValueOf(sa.getParam("ValidZone")); - } + List zones = sa.hasParam("ValidZone") ? ZoneType.listValueOf(sa.getParam("ValidZone")) : ImmutableList.of(ZoneType.Hand); tgtCards = new CardCollection( - AbilityUtils.filterListByType(game.getCardsIn(zone), sa.getParam("Valid"), sa) + AbilityUtils.filterListByType(game.getCardsIn(zones), sa.getParam("Valid"), sa) ); if ( sa.hasParam("ShowCards") ) { - showCards = new CardCollection(AbilityUtils.filterListByType(game.getCardsIn(zone), sa.getParam("ShowCards"), sa)); + showCards = new CardCollection(AbilityUtils.filterListByType(game.getCardsIn(zones), sa.getParam("ShowCards"), sa)); } } else if (sa.hasParam("AnySupportedCard")) { diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 1fa7a61ec7d..c7c65583eab 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -80,7 +80,7 @@ import io.sentry.event.BreadcrumbBuilder; * @author Forge * @version $Id$ */ -public class Card extends GameEntity implements Comparable { +public class Card extends GameEntity implements Comparable, IHasSVars { private final Game game; private final IPaperCard paperCard; @@ -124,7 +124,8 @@ public class Card extends GameEntity implements Comparable { private final PlayerCollection mayLookFaceDownExile = new PlayerCollection(); private final PlayerCollection mayLookTemp = new PlayerCollection(); - private final Multimap cantHaveKeywords = MultimapBuilder.hashKeys().enumSetValues(Keyword.class).build(); + // don't use Enum Set Values or it causes a slow down + private final Multimap cantHaveKeywords = MultimapBuilder.hashKeys().hashSetValues().build(); private final Map counterTypeTimestamps = Maps.newHashMap(); @@ -1437,6 +1438,7 @@ public class Card extends GameEntity implements Comparable { } public final void setChosenNumber(final int i) { chosenNumber = i; + view.updateChosenNumber(this); } public final Card getExiledWith() { @@ -6034,6 +6036,17 @@ public class Card extends GameEntity implements Comparable { abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player)); } } + // Add Modal Spells + if (isModal() && hasState(CardStateName.Modal)) { + for (SpellAbility sa : getState(CardStateName.Modal).getSpellAbilities()) { + //add alternative costs as additional spell abilities + // only add Spells there + if (sa.isSpell()) { + abilities.add(sa); + abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player)); + } + } + } final Collection toRemove = Lists.newArrayListWithCapacity(abilities.size()); for (final SpellAbility sa : abilities) { @@ -6049,7 +6062,11 @@ public class Card extends GameEntity implements Comparable { } abilities.removeAll(toRemove); - if (getState(CardStateName.Original).getType().isLand() && !getLastKnownZone().is(ZoneType.Battlefield)) { + // Land Abilities below, move them to CardFactory after MayPlayRefactor + if (getLastKnownZone().is(ZoneType.Battlefield)) { + return abilities; + } + if (getState(CardStateName.Original).getType().isLand()) { LandAbility la = new LandAbility(this, player, null); if (la.canPlay()) { abilities.add(la); @@ -6091,7 +6108,7 @@ public class Card extends GameEntity implements Comparable { } if (isModal() && hasState(CardStateName.Modal)) { - if (getState(CardStateName.Modal).getType().isLand() && !getLastKnownZone().is(ZoneType.Battlefield)) { + if (getState(CardStateName.Modal).getType().isLand()) { LandAbility la = new LandAbility(this, player, null); la.setCardState(CardStateName.Modal); @@ -6409,17 +6426,6 @@ public class Card extends GameEntity implements Comparable { return changed; } - public final void clearTemporaryVars() { - // Add cleanup for all variables that are set temporarily but that need - // to be restored to their original value if a card changes zones - - removeSVar("PayX"); // Temporary AI X announcement variable - removeSVar("IsCastFromPlayEffect"); // Temporary SVar indicating that the spell is cast indirectly via AF Play - setXManaCostPaidByColor(null); - setKickerMagnitude(0); - setPseudoMultiKickerMagnitude(0); - } - public final int getFinalChapterNr() { int n = 0; for (final Trigger t : getTriggers()) { diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index c2cab603c52..5dbfabc2740 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -392,11 +392,18 @@ public class CardFactory { } // SpellPermanent only for Original State - if (c.getCurrentStateName() == CardStateName.Original) { + if (c.getCurrentStateName() == CardStateName.Original || c.getCurrentStateName() == CardStateName.Modal) { // this is the "default" spell for permanents like creatures and artifacts if (c.isPermanent() && !c.isAura() && !c.isLand()) { - c.addSpellAbility(new SpellPermanent(c)); + SpellAbility sa = new SpellPermanent(c); + + // Currently only for Modal, might react different when state is always set + if (c.getCurrentStateName() == CardStateName.Modal) { + sa.setCardState(c.getCurrentStateName()); + } + c.addSpellAbility(sa); } + // TODO add LandAbility there when refactor MayPlay } CardFactoryUtil.addAbilityFactoryAbilities(c, face.getAbilities()); @@ -540,9 +547,6 @@ public class CardFactory { to.setActivatingPlayer(p, lki); } - for (String sVar : from.getSVars()) { - to.setSVar(sVar, from.getSVar(sVar)); - } //to.changeText(); } 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 1af50662bd9..fba73308497 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -51,7 +51,6 @@ import forge.game.spellability.*; import forge.game.staticability.StaticAbility; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerHandler; -import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.util.Aggregates; import forge.util.Expressions; @@ -227,50 +226,6 @@ public class CardFactoryUtil { return AbilityFactory.getAbility(ab, sourceCard); } - /** - *

- * isTargetStillValid. - *

- * - * @param ability - * a {@link forge.game.spellability.SpellAbility} object. - * @param target - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public static boolean isTargetStillValid(final SpellAbility ability, final Card target) { - Zone zone = target.getGame().getZoneOf(target); - if (zone == null) { - return false; // for tokens that disappeared - } - - final Card source = ability.getHostCard(); - final TargetRestrictions tgt = ability.getTargetRestrictions(); - if (tgt != null) { - // Reconfirm the Validity of a TgtValid, or if the Creature is still - // a Creature - if (tgt.doesTarget() - && !target.isValid(tgt.getValidTgts(), ability.getActivatingPlayer(), ability.getHostCard(), ability)) { - return false; - } - - // Check if the target is in the zone it needs to be in to be targeted - if (!tgt.getZone().contains(zone.getZoneType())) { - return false; - } - } - else { - // If an Aura's target is removed before it resolves, the Aura - // fizzles - if (source.isAura() && !target.isInZone(ZoneType.Battlefield)) { - return false; - } - } - - // Make sure it's still targetable as well - return ability.canTarget(target); - } - // does "target" have protection from "card"? /** *

@@ -1100,7 +1055,7 @@ public class CardFactoryUtil { if (sq[0].contains("ColorsCtrl")) { final String restriction = l[0].substring(11); final String[] rest = restriction.split(","); - final CardCollection list = CardLists.getValidCards(cc.getGame().getCardsInGame(), rest, cc, c, null); + 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(); @@ -1176,14 +1131,9 @@ public class CardFactoryUtil { return doXMath(Integer.parseInt(sq[cc.hasThreshold() ? 1 : 2]), m, c); } if (sq[0].contains("Averna")) { - int kwcount = 0; - for (String kw : cc.getKeywords()) { - if (kw.equals("As you cascade, you may put a land card from among the exiled cards onto the " + - "battlefield tapped.")) { - kwcount++; - } - } - return kwcount; + 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); @@ -1540,15 +1490,6 @@ public class CardFactoryUtil { SpellAbility castSA = c.getCastSA(); return doXMath(castSA == null ? 0 : castSA.getPayingColors().countColors(), m, c); } - // Count$ColoredCreatures *a DOMAIN for creatures* - if (sq[0].contains("ColoredCreatures")) { - int mask = 0; - CardCollection someCards = CardLists.filter(cc.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); - for (final Card crd : someCards) { - mask |= CardUtil.getColors(crd).getColor(); - } - return doXMath(ColorSet.fromMask(mask).countColors(), m, c); - } // Count$CardMulticolor.. if (sq[0].contains("CardMulticolor")) { 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 13f747c7fd9..9d89c04eba1 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -1360,7 +1360,7 @@ public class CardProperty { } } else if (property.startsWith("hasXCost")) { SpellAbility sa1 = card.getFirstSpellAbility(); - if (sa1 != null && !sa1.isXCost()) { + if (sa1 != null && !sa1.costHasManaX()) { return false; } } else if (property.startsWith("suspended")) { diff --git a/forge-game/src/main/java/forge/game/card/CardState.java b/forge-game/src/main/java/forge/game/card/CardState.java index 6563317225b..f304cfb409d 100644 --- a/forge-game/src/main/java/forge/game/card/CardState.java +++ b/forge-game/src/main/java/forge/game/card/CardState.java @@ -26,6 +26,7 @@ import forge.card.mana.ManaCostParser; import forge.game.CardTraitBase; import forge.game.ForgeScript; import forge.game.GameObject; +import forge.game.IHasSVars; import forge.game.card.CardView.CardStateView; import forge.game.keyword.Keyword; import forge.game.keyword.KeywordCollection; @@ -46,7 +47,7 @@ import java.util.Map; import io.sentry.Sentry; import io.sentry.event.BreadcrumbBuilder; -public class CardState extends GameObject { +public class CardState extends GameObject implements IHasSVars { private String name = ""; private CardType type = new CardType(false); private ManaCost manaCost = ManaCost.NO_COST; diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index 16c7ab0210d..2a3fb86f417 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -306,6 +306,13 @@ public class CardView extends GameEntityView { set(TrackableProperty.ChosenType, c.getChosenType()); } + public String getChosenNumber() { + return get(TrackableProperty.ChosenNumber); + } + void updateChosenNumber(Card c) { + set(TrackableProperty.ChosenNumber, c.getChosenNumber().toString()); + } + public List getChosenColors() { return get(TrackableProperty.ChosenColors); } @@ -392,6 +399,7 @@ public class CardView extends GameEntityView { if (viewers == null || Iterables.isEmpty(viewers)) { return true; } return Iterables.any(viewers, new Predicate() { + @Override public final boolean apply(final PlayerView input) { return canBeShownTo(input); } @@ -456,14 +464,17 @@ public class CardView extends GameEntityView { } public boolean canFaceDownBeShownToAny(final Iterable viewers) { + if (viewers == null || Iterables.isEmpty(viewers)) { return true; } + return Iterables.any(viewers, new Predicate() { - @Override public final boolean apply(final PlayerView input) { - return canFaceDownBeShownTo(input); + @Override + public final boolean apply(final PlayerView input) { + return canFaceDownBeShownTo(input, false); } }); } - private boolean canFaceDownBeShownTo(final PlayerView viewer) { + private boolean canFaceDownBeShownTo(final PlayerView viewer, boolean skip) { if (!isFaceDown()) { return true; } @@ -472,13 +483,15 @@ public class CardView extends GameEntityView { if (mayPlayerLook(viewer)) { return true; } - final PlayerView controller = getController(); - //if viewer is controlled by another player, also check if face can be shown to that player - final PlayerView mindSlaveMaster = viewer.getMindSlaveMaster(); - if (mindSlaveMaster != null && mindSlaveMaster != controller && canFaceDownBeShownTo(mindSlaveMaster)) { - return true; + if (!skip) { + //if viewer is controlled by another player, also check if face can be shown to that player + final PlayerView mindSlaveMaster = viewer.getMindSlaveMaster(); + if (mindSlaveMaster != null) { + return canFaceDownBeShownTo(mindSlaveMaster, true); + } } - return isInZone(EnumSet.of(ZoneType.Battlefield, ZoneType.Stack, ZoneType.Sideboard)) && controller.equals(viewer); + + return isInZone(EnumSet.of(ZoneType.Battlefield, ZoneType.Stack, ZoneType.Sideboard)) && getController().equals(viewer); } public FCollectionView getEncodedCards() { @@ -1120,6 +1133,7 @@ public class CardView extends GameEntityView { public boolean hasDeathtouch() { return get(TrackableProperty.HasDeathtouch); } public boolean hasDevoid() { return get(TrackableProperty.HasDevoid); } public boolean hasDefender() { return get(TrackableProperty.HasDefender); } + public boolean hasDivideDamage() { return get(TrackableProperty.HasDivideDamage); } public boolean hasDoubleStrike() { return get(TrackableProperty.HasDoubleStrike); } public boolean hasFirstStrike() { return get(TrackableProperty.HasFirstStrike); } public boolean hasFlying() { return get(TrackableProperty.HasFlying); } @@ -1145,6 +1159,9 @@ public class CardView extends GameEntityView { public boolean hasStorm() { return get(TrackableProperty.HasStorm); } + public boolean hasLandwalk() { + return get(TrackableProperty.HasLandwalk); + } public String getAbilityText() { return get(TrackableProperty.AbilityText); @@ -1157,6 +1174,8 @@ public class CardView extends GameEntityView { set(TrackableProperty.HasDeathtouch, c.hasKeyword(Keyword.DEATHTOUCH, state)); set(TrackableProperty.HasDevoid, c.hasKeyword(Keyword.DEVOID, state)); set(TrackableProperty.HasDefender, c.hasKeyword(Keyword.DEFENDER, state)); + set(TrackableProperty.HasDivideDamage, c.hasKeyword("You may assign CARDNAME's combat damage divided as " + + "you choose among defending player and/or any number of creatures they control.")); set(TrackableProperty.HasDoubleStrike, c.hasKeyword(Keyword.DOUBLE_STRIKE, state)); set(TrackableProperty.HasFirstStrike, c.hasKeyword(Keyword.FIRST_STRIKE, state)); set(TrackableProperty.HasFlying, c.hasKeyword(Keyword.FLYING, state)); @@ -1175,6 +1194,7 @@ public class CardView extends GameEntityView { set(TrackableProperty.HasHaste, c.hasKeyword(Keyword.HASTE, state)); set(TrackableProperty.HasInfect, c.hasKeyword(Keyword.INFECT, state)); set(TrackableProperty.HasStorm, c.hasKeyword(Keyword.STORM, state)); + set(TrackableProperty.HasLandwalk, c.hasKeyword(Keyword.LANDWALK, state)); updateAbilityText(c, state); //set protectionKey for Icons set(TrackableProperty.ProtectionKey, c.getProtectionKey()); diff --git a/forge-game/src/main/java/forge/game/card/CounterEnumType.java b/forge-game/src/main/java/forge/game/card/CounterEnumType.java index 722180488e4..dc6b0409aa7 100644 --- a/forge-game/src/main/java/forge/game/card/CounterEnumType.java +++ b/forge-game/src/main/java/forge/game/card/CounterEnumType.java @@ -123,6 +123,8 @@ public enum CounterEnumType { GEM("GEM", 255, 99, 251), + GHOSTFORM("GHSTF", 223, 0, 254), + GLYPH("GLYPH", 184, 202, 199), GOLD("GOLD", 248, 191, 0), diff --git a/forge-game/src/main/java/forge/game/card/CounterType.java b/forge-game/src/main/java/forge/game/card/CounterType.java index 0cf41244e4f..d64222b1691 100644 --- a/forge-game/src/main/java/forge/game/card/CounterType.java +++ b/forge-game/src/main/java/forge/game/card/CounterType.java @@ -45,6 +45,9 @@ public class CounterType implements Comparable, Serializable { } public static CounterType getType(String name) { + if ("Any".equalsIgnoreCase(name)) { + return null; + } try { return get(CounterEnumType.getType(name)); } catch (final IllegalArgumentException ex) { diff --git a/forge-game/src/main/java/forge/game/combat/Combat.java b/forge-game/src/main/java/forge/game/combat/Combat.java index 4c85ccd5cac..992fce3b3bf 100644 --- a/forge-game/src/main/java/forge/game/combat/Combat.java +++ b/forge-game/src/main/java/forge/game/combat/Combat.java @@ -20,21 +20,18 @@ package forge.game.combat; import com.google.common.base.Function; import com.google.common.collect.*; import forge.game.Game; -import forge.game.GameEntity; -import forge.game.GameEntityCounterTable; -import forge.game.GameLogEntryType; -import forge.game.GameObjectMap; +import forge.game.*; import forge.game.ability.AbilityKey; -import forge.game.card.Card; -import forge.game.card.CardCollection; -import forge.game.card.CardCollectionView; -import forge.game.card.CardDamageMap; +import forge.game.card.*; import forge.game.keyword.Keyword; import forge.game.player.Player; import forge.game.spellability.SpellAbilityStackInstance; import forge.game.trigger.TriggerType; +import forge.game.zone.ZoneType; +import forge.util.CardTranslation; import forge.util.collect.FCollection; import forge.util.collect.FCollectionView; +import forge.util.Localizer; import org.apache.commons.lang3.tuple.Pair; import java.util.*; @@ -64,6 +61,7 @@ public class Combat { private Map blockersOrderedForDamageAssignment = Maps.newHashMap(); private Map lkiCache = Maps.newHashMap(); private CardDamageMap dealtDamageTo = new CardDamageMap(); + private boolean dividedToPlayer = false; // List holds creatures who have dealt 1st strike damage to disallow them deal damage on regular basis (unless they have double-strike KW) private CardCollection combatantsThatDealtFirstStrikeDamage = new CardCollection(); @@ -373,7 +371,7 @@ public class Combat { blocker.updateBlockingForView(); } - // remove blocked from specific attacker + // remove blocker from specific attacker public final void removeBlockAssignment(final Card attacker, final Card blocker) { AttackingBand band = getBandOfAttackerNotNull(attacker); Collection cc = blockedBands.get(band); @@ -400,6 +398,15 @@ public class Combat { return result; } + public final CardCollection getDefendersCreatures() { + CardCollection result = new CardCollection(); + for (Card attacker : getAttackers()) { + CardCollection cc = getDefenderPlayerByAttacker(attacker).getCreaturesInPlay(); + result.addAll(cc); + } + return result; + } + public final CardCollection getBlockers(final AttackingBand band) { Collection blockers = blockedBands.get(band); return blockers == null ? new CardCollection() : new CardCollection(blockers); @@ -716,8 +723,26 @@ public class Combat { continue; } + boolean divideCombatDamageAsChoose = (getDefendersCreatures().size() > 0 && + attacker.hasKeyword("You may assign CARDNAME's combat damage divided as you choose among " + + "defending player and/or any number of creatures they control.") + && attacker.getController().getController().confirmAction(null, null, + Localizer.getInstance().getMessage("lblAssignCombatDamageAsChoose", + CardTranslation.getTranslatedName(attacker.getName())))); boolean trampler = attacker.hasKeyword(Keyword.TRAMPLE); orderedBlockers = blockersOrderedForDamageAssignment.get(attacker); + if (divideCombatDamageAsChoose) { + if (orderedBlockers == null || orderedBlockers.isEmpty()) { + orderedBlockers = getDefendersCreatures(); + } + else { + for (Card c : getDefendersCreatures()) { + if (!orderedBlockers.contains(c)) { + orderedBlockers.add(c); + } + } + } + } assignedDamage = true; // If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender if (orderedBlockers == null || orderedBlockers.isEmpty()) { @@ -730,6 +755,10 @@ public class Combat { Player assigningPlayer = getAttackingPlayer(); // Defensive Formation is very similar to Banding with Blockers // It allows the defending player to assign damage instead of the attacking player + if (defender instanceof Card && divideCombatDamageAsChoose) { + defender = getDefenderPlayerByAttacker(attacker); + dividedToPlayer = true; + } if (defender instanceof Player && defender.hasKeyword("You assign combat damage of each creature attacking you.")) { assigningPlayer = (Player)defender; } @@ -737,7 +766,8 @@ public class Combat { assigningPlayer = orderedBlockers.get(0).getController(); } - Map map = assigningPlayer.getController().assignCombatDamage(attacker, orderedBlockers, damageDealt, defender, getAttackingPlayer() != assigningPlayer); + Map map = assigningPlayer.getController().assignCombatDamage(attacker, orderedBlockers, + damageDealt, defender, divideCombatDamageAsChoose || getAttackingPlayer() != assigningPlayer); for (Entry dt : map.entrySet()) { if (dt.getKey() == null) { if (dt.getValue() > 0) @@ -767,7 +797,7 @@ public class Combat { private final void addDefendingDamage(final int n, final Card source) { final GameEntity ge = getDefenderByAttacker(source); - if (ge instanceof Card) { + if (ge instanceof Card && !dividedToPlayer) { final Card planeswalker = (Card) ge; planeswalker.addAssignedDamage(n, source); return; @@ -805,6 +835,9 @@ public class Combat { // This function handles both Regular and First Strike combat assignment for (final Entry entry : defendingDamageMap.entrySet()) { GameEntity defender = getDefenderByAttacker(entry.getKey()); + if (dividedToPlayer) { + defender = getDefenderPlayerByAttacker(entry.getKey()); + } if (defender instanceof Player) { // player defender.addCombatDamage(entry.getValue(), entry.getKey(), dealtDamageTo, preventMap, counterTable); } @@ -819,6 +852,7 @@ public class Combat { combatants.addAll(getAttackers()); combatants.addAll(getAllBlockers()); combatants.addAll(getDefendingPlaneswalkers()); + combatants.addAll(getDefendersCreatures()); for (final Card c : combatants) { // if no assigned damage to resolve, move to next diff --git a/forge-game/src/main/java/forge/game/cost/Cost.java b/forge-game/src/main/java/forge/game/cost/Cost.java index 1c175b57428..bda38820815 100644 --- a/forge-game/src/main/java/forge/game/cost/Cost.java +++ b/forge-game/src/main/java/forge/game/cost/Cost.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Lists; @@ -42,7 +43,7 @@ import forge.util.TextUtil; *

* Cost class. *

- * + * * @author Forge * @version $Id$ */ @@ -105,7 +106,7 @@ public class Cost implements Serializable { /** * Gets the cost parts. - * + * * @return the cost parts */ public final List getCostParts() { @@ -127,11 +128,11 @@ public class Cost implements Serializable { } }); } - + /** * Get the cost parts, always including a mana cost part (which may be * zero). - * + * * @return the cost parts, possibly with an extra zero mana {@link * CostPartMana}. */ @@ -149,7 +150,7 @@ public class Cost implements Serializable { *

* isOnlyManaCost. *

- * + * * @return a boolean. */ public final boolean isOnlyManaCost() { @@ -167,7 +168,7 @@ public class Cost implements Serializable { *

* getTotalMana. *

- * + * * @return a {@link java.lang.String} object. */ public final ManaCost getTotalMana() { @@ -179,17 +180,17 @@ public class Cost implements Serializable { *

* isMandatory *

- * + * * @return boolean */ public final boolean isMandatory() { return this.isMandatory; } - + public final boolean isAbility() { return this.isAbility; } - + private Cost() { } @@ -400,9 +401,9 @@ public class Cost implements Serializable { } if (parse.startsWith("RemoveAnyCounter<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostRemoveAnyCounter(splitStr[0], splitStr[1], description); + final String[] splitStr = abCostParse(parse, 4); + final String description = splitStr.length > 3 ? splitStr[3] : null; + return new CostRemoveAnyCounter(splitStr[0], CounterType.getType(splitStr[1]), splitStr[2], description); } if (parse.startsWith("Exile<")) { @@ -515,7 +516,7 @@ public class Cost implements Serializable { *

* abCostParse. *

- * + * * @param parse * a {@link java.lang.String} object. * @param numParse @@ -539,7 +540,7 @@ public class Cost implements Serializable { toRet.cacheTapCost(); return toRet; } - + public final Cost copyWithNoMana() { Cost toRet = new Cost(0); toRet.isAbility = this.isAbility; @@ -580,7 +581,7 @@ public class Cost implements Serializable { *

* refundPaidCost. *

- * + * * @param source * a {@link forge.game.card.Card} object. */ @@ -595,7 +596,7 @@ public class Cost implements Serializable { *

* isUndoable. *

- * + * * @return a boolean. */ public final boolean isUndoable() { @@ -612,7 +613,7 @@ public class Cost implements Serializable { *

* isReusuableResource. *

- * + * * @return a boolean. */ public final boolean isReusuableResource() { @@ -629,7 +630,7 @@ public class Cost implements Serializable { *

* isRenewableResource. *

- * + * * @return a boolean. */ public final boolean isRenewableResource() { @@ -646,7 +647,7 @@ public class Cost implements Serializable { *

* toString. *

- * + * * @return a {@link java.lang.String} object. */ @Override @@ -665,7 +666,7 @@ public class Cost implements Serializable { *

* toStringAlt. *

- * + * * @return a {@link java.lang.String} object. */ public final String toStringAlt() { @@ -676,7 +677,7 @@ public class Cost implements Serializable { *

* toSimpleString. *

- * + * * @return a {@link java.lang.String} object. */ public final String toSimpleString() { @@ -696,7 +697,7 @@ public class Cost implements Serializable { *

* spellToString. *

- * + * * @param bFlag * a boolean. * @return a {@link java.lang.String} object. @@ -747,7 +748,7 @@ public class Cost implements Serializable { *

* abilityToString. *

- * + * * @return a {@link java.lang.String} object. */ private String abilityToString() { @@ -788,7 +789,7 @@ public class Cost implements Serializable { /** * Convert amount type to words. - * + * * @param i * the i * @param amount @@ -809,7 +810,7 @@ public class Cost implements Serializable { *

* convertIntAndTypeToWords. *

- * + * * @param i * a int. * @param type @@ -848,7 +849,7 @@ public class Cost implements Serializable { /** * Convert amount type to words. - * + * * @param amount * the amount * @param type @@ -887,12 +888,12 @@ public class Cost implements Serializable { } else { costParts.add(0, new CostPartMana(oldManaCost.toManaCost(), r)); } - } else if (part instanceof CostDiscard || part instanceof CostTapType || + } else if (part instanceof CostDiscard || part instanceof CostTapType || part instanceof CostAddMana || part instanceof CostPayLife) { boolean alreadyAdded = false; for (final CostPart other : costParts) { if (other.getClass().equals(part.getClass()) && - part.getType().equals(other.getType()) && + part.getType().equals(other.getType()) && StringUtils.isNumeric(part.getAmount()) && StringUtils.isNumeric(other.getAmount())) { final String amount = String.valueOf(Integer.parseInt(part.getAmount()) + Integer.parseInt(other.getAmount())); @@ -959,5 +960,20 @@ public class Cost implements Serializable { return xCost; } + public Integer getMaxForNonManaX(final SpellAbility ability, final Player payer) { + Integer val = null; + for (CostPart p : getCostParts()) { + if (!p.getAmount().equals("X")) { + continue; + } + + val = ObjectUtils.min(val, p.getMaxAmountX(ability, payer)); + } + // extra 0 check + if (val != null && val <= 0 && hasManaCost() && !getCostMana().canXbe0()) { + val = null; + } + return val; + } public static final Cost Zero = new Cost(0); } diff --git a/forge-game/src/main/java/forge/game/cost/CostDiscard.java b/forge-game/src/main/java/forge/game/cost/CostDiscard.java index 3edd069cfde..a51f5409a38 100644 --- a/forge-game/src/main/java/forge/game/cost/CostDiscard.java +++ b/forge-game/src/main/java/forge/game/cost/CostDiscard.java @@ -65,6 +65,16 @@ public class CostDiscard extends CostPartWithList { public int paymentOrder() { return 10; } + @Override + public Integer getMaxAmountX(SpellAbility ability, Player payer) { + final Card source = ability.getHostCard(); + String type = this.getType(); + CardCollectionView handList = payer.canDiscardBy(ability) ? payer.getCardsIn(ZoneType.Hand) : CardCollection.EMPTY; + + handList = CardLists.getValidCards(handList, type.split(";"), payer, source, ability); + return handList.size(); + } + /* * (non-Javadoc) * diff --git a/forge-game/src/main/java/forge/game/cost/CostPart.java b/forge-game/src/main/java/forge/game/cost/CostPart.java index cea0bfb9ee8..41434860833 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPart.java +++ b/forge-game/src/main/java/forge/game/cost/CostPart.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -49,7 +49,7 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Instantiates a new cost part. - * + * * @param amount * the amount * @param type @@ -67,16 +67,19 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Gets the amount. - * + * * @return the amount */ public final String getAmount() { return this.amount; } + public Integer getMaxAmountX(final SpellAbility ability, final Player payer) { + return null; + } /** * Gets the type. - * + * * @return the type */ public final String getType() { @@ -85,7 +88,7 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Gets the this. - * + * * @return the this */ public final boolean payCostFromSource() { @@ -94,7 +97,7 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Gets the type description. - * + * * @return the type description */ public final String getTypeDescription() { @@ -108,16 +111,16 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Checks if is reusable. - * + * * @return true, if is reusable */ public boolean isReusable() { return false; } - + /** * Checks if is renewable. - * + * * @return true, if is renewable */ public boolean isRenewable() { @@ -126,7 +129,7 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Checks if is undoable. - * + * * @return true, if is undoable */ public boolean isUndoable() { @@ -135,19 +138,19 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Convert amount. - * + * * @return the integer */ public final Integer convertAmount() { - return StringUtils.isNumeric(amount) ? Integer.parseInt(amount) : null; + return StringUtils.isNumeric(amount) ? Integer.parseInt(amount) : null; } /** * Can pay. - * + * * @param ability * the ability - * @param payer + * @param payer * @return true, if successful */ public abstract boolean canPay(SpellAbility ability, Player payer); @@ -156,7 +159,7 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /* * (non-Javadoc) - * + * * @see java.lang.Object#toString() */ @Override @@ -164,7 +167,7 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Refund. Overridden in classes which know how to refund. - * + * * @param source * the source */ @@ -173,7 +176,7 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Sets the amount. - * + * * @param amountIn * the amount to set */ diff --git a/forge-game/src/main/java/forge/game/cost/CostPayEnergy.java b/forge-game/src/main/java/forge/game/cost/CostPayEnergy.java index 5402e5e6749..d683ae8b618 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPayEnergy.java +++ b/forge-game/src/main/java/forge/game/cost/CostPayEnergy.java @@ -47,6 +47,10 @@ public class CostPayEnergy extends CostPart { @Override public int paymentOrder() { return 7; } + public Integer getMaxAmountX(final SpellAbility ability, final Player payer) { + return payer.getCounters(CounterEnumType.ENERGY); + } + /* * (non-Javadoc) * diff --git a/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java b/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java index 063a44ce224..7751acde500 100644 --- a/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java +++ b/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java @@ -6,30 +6,30 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package forge.game.cost; +import forge.game.GameEntity; import forge.game.ability.AbilityUtils; import forge.game.card.*; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; -import forge.util.TextUtil; -import java.util.Map; +import com.google.common.collect.Table; /** * The Class CostRemoveAnyCounter. */ -public class CostRemoveAnyCounter extends CostPartWithList { +public class CostRemoveAnyCounter extends CostPart { /** * Serializables need a version ID. */ @@ -37,13 +37,8 @@ public class CostRemoveAnyCounter extends CostPartWithList { // RemoveAnyCounter // Power Conduit and Chisei, Heart of Oceans // Both cards have "Remove a counter from a permanent you control" - private CounterType counterType; - /** - * @param counterType the counterType to set - */ - public void setCounterType(CounterType counterType) { - this.counterType = counterType; - } + + public final CounterType counter; /** * Instantiates a new cost CostRemoveAnyCounter. @@ -51,33 +46,34 @@ public class CostRemoveAnyCounter extends CostPartWithList { * @param amount * the amount */ - public CostRemoveAnyCounter(final String amount, final String type, final String description) { + public CostRemoveAnyCounter(final String amount, final CounterType counter, final String type, final String description) { super(amount, type, description); + this.counter = counter; } @Override public int paymentOrder() { return 8; } - /* (non-Javadoc) - * @see forge.card.cost.CostPartWithList#getHashForList() - */ @Override - public String getHashForLKIList() { - return "CounterRemove"; - } - @Override - public String getHashForCardList() { - return "CounterRemoveCards"; + public Integer getMaxAmountX(final SpellAbility ability, final Player payer) { + + final Card source = ability.getHostCard(); + + CardCollectionView validCards = CardLists.getValidCards(payer.getCardsIn(ZoneType.Battlefield), this.getType().split(";"), payer, source, ability); + int allCounters = 0; + for (Card c : validCards) { + if (this.counter != null) { + allCounters += c.getCounters(this.counter); + } else { + for (Integer value : c.getCounters().values()) { + allCounters += value; + } + } + } + return allCounters; } - /** - * Gets the counter. - * - * @return the counter - */ - public CounterType getCounter() { - return this.counterType; - } + /* * (non-Javadoc) @@ -88,27 +84,7 @@ public class CostRemoveAnyCounter extends CostPartWithList { */ @Override public final boolean canPay(final SpellAbility ability, final Player payer) { - final Card source = ability.getHostCard(); - CardCollectionView validCards = payer.getCardsIn(ZoneType.Battlefield); - validCards = CardLists.getValidCards(validCards, this.getType().split(";"), payer, source, ability); - validCards = CardLists.filter(validCards, CardPredicates.hasCounters()); - if (validCards.isEmpty()) { - return false; - } - Integer i = this.convertAmount(); - - if (i == null) { - i = AbilityUtils.calculateAmount(source, this.getAmount(), ability); - } - int allCounters = 0; - for (Card c : validCards) { - final Map tgtCounters = c.getCounters(); - for (Integer value : tgtCounters.values()) { - allCounters += value; - } - } - - return i <= allCounters; + return AbilityUtils.calculateAmount(ability.getHostCard(), this.getAmount(), ability) <= getMaxAmountX(ability, payer); } /* @@ -120,39 +96,29 @@ public class CostRemoveAnyCounter extends CostPartWithList { public final String toString() { final StringBuilder sb = new StringBuilder(); + String counters = this.counter == null ? "counter" : this.counter.getName() + " counter"; + sb.append("Remove "); - sb.append(Cost.convertIntAndTypeToWords(this.convertAmount(), "counter")); + sb.append(Cost.convertAmountTypeToWords(this.convertAmount(), this.getAmount(), counters)); final String desc = this.getTypeDescription() == null ? this.getType() : this.getTypeDescription(); sb.append(" from ").append(desc); return sb.toString(); } - @Override - public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) { - final String amount = this.getAmount(); - final Card source = ability.getHostCard(); - Integer c = this.convertAmount(); - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } - if (decision.cards.isEmpty()) { - System.err.println(TextUtil.concatWithSpace("Warning: payment decision array was empty when paying CostRemoveAnyCounter for" , ability.getDescription(), "from", ability.getHostCard().toString())); - return false; - } - Card valid = decision.cards.get(0); - counterType = decision.ct; - for (int i = 0; i < c; i++) { - executePayment(ability, valid); - } - source.setSVar("CostCountersRemoved", Integer.toString(c)); - return true; - } @Override - protected Card doPayment(SpellAbility ability, Card targetCard){ - targetCard.subtractCounter(this.getCounter(), 1); - return targetCard; + public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) { + final Card source = ability.getHostCard(); + + int removed = 0; + for (Table.Cell cell : decision.counterTable.cellSet()) { + removed += cell.getValue(); + cell.getRowKey().subtractCounter(cell.getColumnKey(), cell.getValue()); + } + + source.setSVar("CostCountersRemoved", Integer.toString(removed)); + return true; } public T accept(ICostVisitor visitor) { diff --git a/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java b/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java index d03d99cd45b..4788fdec31b 100644 --- a/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java +++ b/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -19,6 +19,7 @@ package forge.game.cost; import com.google.common.collect.Lists; +import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardLists; import forge.game.card.CounterEnumType; @@ -32,7 +33,7 @@ import java.util.List; /** * The Class CostRemoveCounter. */ -public class CostRemoveCounter extends CostPartWithList { +public class CostRemoveCounter extends CostPart { // SubCounter // Here are the cards that have RemoveCounter @@ -47,11 +48,10 @@ public class CostRemoveCounter extends CostPartWithList { private static final long serialVersionUID = 1L; public final CounterType counter; public final ZoneType zone; - private int cntRemoved; /** * Instantiates a new cost remove counter. - * + * * @param amount * the amount * @param counter @@ -73,11 +73,32 @@ public class CostRemoveCounter extends CostPartWithList { public int paymentOrder() { return 8; } @Override - public boolean isUndoable() { return true; } + public Integer getMaxAmountX(final SpellAbility ability, final Player payer) { + final CounterType cntrs = this.counter; + final Card source = ability.getHostCard(); + final String type = this.getType(); + if (this.payCostFromSource()) { + return source.getCounters(cntrs); + } else { + List typeList; + if (type.equals("OriginalHost")) { + typeList = Lists.newArrayList(ability.getOriginalHost()); + } else { + typeList = CardLists.getValidCards(payer.getCardsIn(this.zone), type.split(";"), payer, source, ability); + } + + // Single Target + int maxcount = 0; + for (Card c : typeList) { + maxcount = Math.max(maxcount, c.getCounters(cntrs)); + } + return maxcount; + } + } /* * (non-Javadoc) - * + * * @see forge.card.cost.CostPart#toString() */ @Override @@ -108,20 +129,7 @@ public class CostRemoveCounter extends CostPartWithList { /* * (non-Javadoc) - * - * @see forge.card.cost.CostPart#refund(forge.Card) - */ - @Override - public final void refund(final Card source) { - int refund = this.getCardList().size() == 1 ? this.cntRemoved : 1; // is wrong for Ooze Flux and Novijen Sages - for (final Card c : this.getCardList()) { - c.addCounter(this.counter, refund, source.getController(), false, false, null); - } - } - - /* - * (non-Javadoc) - * + * * @see * forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility, * forge.Card, forge.Player, forge.card.cost.Cost) @@ -144,21 +152,10 @@ public class CostRemoveCounter extends CostPartWithList { typeList = CardLists.getValidCards(payer.getCardsIn(this.zone), type.split(";"), payer, source, ability); } if (amount != null) { - // TODO find better way than TypeDescription - if (this.getTypeDescription().equals("among creatures you control")) { - // remove X counters from among creatures you control - int totalCounters = 0; - for (Card c : typeList) { - totalCounters += c.getCounters(cntrs); - } - return totalCounters >= amount; - - } else { - // (default logic) remove X counters from a single permanent - for (Card c : typeList) { - if (c.getCounters(cntrs) - amount >= 0) { - return true; - } + // (default logic) remove X counters from a single permanent + for (Card c : typeList) { + if (c.getCounters(cntrs) - amount >= 0) { + return true; } } return false; @@ -171,32 +168,19 @@ public class CostRemoveCounter extends CostPartWithList { @Override public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) { Card source = ability.getHostCard(); - cntRemoved = decision.c; - for (final Card card : decision.cards) { - executePayment(ability, card); + + int removed = 0; + int toRemove = AbilityUtils.calculateAmount(source, getAmount(), ability); + // for this cost, the list should be only one + for (Card c : decision.cards) { + removed += toRemove; + c.subtractCounter(counter, toRemove); } - source.setSVar("CostCountersRemoved", Integer.toString(cntRemoved)); + + source.setSVar("CostCountersRemoved", Integer.toString(removed)); return true; } - @Override - protected Card doPayment(SpellAbility ability, Card targetCard){ - targetCard.subtractCounter(this.counter, cntRemoved); - return targetCard; - } - - /* (non-Javadoc) - * @see forge.card.cost.CostPartWithList#getHashForList() - */ - @Override - public String getHashForLKIList() { - return "CounterRemove"; - } - @Override - public String getHashForCardList() { - return "CounterRemoveCards"; - } - public T accept(ICostVisitor visitor) { return visitor.visit(this); } diff --git a/forge-game/src/main/java/forge/game/cost/PaymentDecision.java b/forge-game/src/main/java/forge/game/cost/PaymentDecision.java index 1cbfd22c138..17c35140a44 100644 --- a/forge-game/src/main/java/forge/game/cost/PaymentDecision.java +++ b/forge-game/src/main/java/forge/game/cost/PaymentDecision.java @@ -1,8 +1,8 @@ package forge.game.cost; +import forge.game.GameEntityCounterTable; import forge.game.card.Card; import forge.game.card.CardCollection; -import forge.game.card.CounterType; import forge.game.mana.Mana; import forge.game.player.Player; import forge.game.spellability.SpellAbility; @@ -10,72 +10,74 @@ import forge.util.TextUtil; import java.util.List; - public class PaymentDecision { public int c = 0; public String type; - public CounterType ct; public final CardCollection cards = new CardCollection(); public final List mana; public final List players; public final List sp; + // used for CostRemoveAnyCounter + public final GameEntityCounterTable counterTable; + public PaymentDecision(int cnt) { - this(null, null, null, null); + this(null, null, null, null, null); c = cnt; } private PaymentDecision(Iterable chosen, List manaProduced, List players, - List sp) { + List sp, GameEntityCounterTable counterTable) { if (chosen != null) { cards.addAll(chosen); } mana = manaProduced; this.players = players; this.sp = sp; + this.counterTable = counterTable; } - + private PaymentDecision(Card chosen) { - this(null, null, null, null); + this(null, null, null, null, null); cards.add(chosen); } - + public PaymentDecision(String choice) { - this(null, null, null, null); + this(null, null, null, null, null); type = choice; } public static PaymentDecision card(Card chosen) { return new PaymentDecision(chosen); } - + public static PaymentDecision card(Card chosen, int n) { PaymentDecision res = new PaymentDecision(chosen); res.c = n; return res; } - - + + public static PaymentDecision number(int c) { return new PaymentDecision(c); } public static PaymentDecision card(Iterable chosen) { - return new PaymentDecision(chosen, null, null, null); + return new PaymentDecision(chosen, null, null, null, null); } - + public static PaymentDecision card(Iterable chosen, int n) { - PaymentDecision res = new PaymentDecision(chosen, null, null, null); + PaymentDecision res = new PaymentDecision(chosen, null, null, null, null); res.c = n; return res; } - + public static PaymentDecision mana(List manas) { - return new PaymentDecision(null, manas, null, null); + return new PaymentDecision(null, manas, null, null, null); } - - + + /* (non-Javadoc) * @see java.lang.Object#toString() */ @@ -90,16 +92,14 @@ public class PaymentDecision { public static PaymentDecision players(List players) { // TODO Auto-generated method stub - return new PaymentDecision(null, null, players, null); - } - - public static PaymentDecision spellabilities(List sp) { - return new PaymentDecision(null, null, null, sp); + return new PaymentDecision(null, null, players, null, null); } - public static PaymentDecision card(Card selected, CounterType counterType) { - PaymentDecision res = card(selected); - res.ct = counterType; - return res; + public static PaymentDecision spellabilities(List sp) { + return new PaymentDecision(null, null, null, sp, null); + } + + public static PaymentDecision counters(GameEntityCounterTable counterTable) { + return new PaymentDecision(null, null, null, null, counterTable); } } diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java index de9f14c2ecf..56a10e903c1 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java @@ -1,22 +1,22 @@ package forge.game.keyword; -import java.io.Serializable; - import java.util.Collection; import java.util.Iterator; +import java.util.List; +import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import forge.game.card.Card; -public class KeywordCollection implements Iterable, Serializable { - private static final long serialVersionUID = -2882986558147844702L; +public class KeywordCollection implements Iterable { private boolean hidden = false; private transient KeywordCollectionView view; - private final Multimap map = MultimapBuilder.enumKeys(Keyword.class) + // don't use enumKeys it causes a slow down + private final Multimap map = MultimapBuilder.hashKeys() .arrayListValues().build(); public KeywordCollection() { @@ -157,35 +157,20 @@ public class KeywordCollection implements Iterable, Serializable { return map.get(keyword); } + public List asStringList() { + List result = Lists.newArrayList(); + for (KeywordInterface kw : getValues()) { + result.add(kw.getOriginal()); + } + return result; + } + public void setHostCard(final Card host) { for (KeywordInterface k : map.values()) { k.setHostCard(host); } } - @Override - public Iterator iterator() { - return new Iterator() { - private final Iterator iterator = map.values().iterator(); - - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public String next() { - KeywordInterface entry = iterator.next(); - return entry.getOriginal(); - } - - @Override - public void remove() { - //Don't support this - } - }; - } - /* (non-Javadoc) * @see java.lang.Object#toString() */ @@ -204,8 +189,7 @@ public class KeywordCollection implements Iterable, Serializable { return view; } - public class KeywordCollectionView implements Iterable, Serializable { - private static final long serialVersionUID = 7536969077044188264L; + public class KeywordCollectionView implements Iterable { protected KeywordCollectionView() { } @@ -229,9 +213,18 @@ public class KeywordCollection implements Iterable, Serializable { return KeywordCollection.this.contains(keyword); } + public List asStringList() { + return KeywordCollection.this.asStringList(); + } + @Override - public Iterator iterator() { + public Iterator iterator() { return KeywordCollection.this.iterator(); } } + + @Override + public Iterator iterator() { + return this.map.values().iterator(); + } } diff --git a/forge-game/src/main/java/forge/game/phase/Untap.java b/forge-game/src/main/java/forge/game/phase/Untap.java index 6fe6668b151..94fb0f26773 100644 --- a/forge-game/src/main/java/forge/game/phase/Untap.java +++ b/forge-game/src/main/java/forge/game/phase/Untap.java @@ -29,6 +29,7 @@ import forge.game.card.CardCollection; import forge.game.card.CardLists; import forge.game.card.CardPredicates.Presets; import forge.game.keyword.Keyword; +import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.player.PlayerController.BinaryChoiceType; import forge.game.spellability.SpellAbility; @@ -125,7 +126,8 @@ public class Untap extends Phase { final Map restrictUntap = Maps.newHashMap(); boolean hasChosen = false; - for (String kw : player.getKeywords()) { + for (KeywordInterface ki : player.getKeywords()) { + String kw = ki.getOriginal(); if (kw.startsWith("UntapAdjust")) { String[] parse = kw.split(":"); if (!restrictUntap.containsKey(parse[1]) diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index e24173bef9e..c199f58a88e 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -1236,7 +1236,8 @@ public class Player extends GameEntity implements Comparable { public boolean hasProtectionFrom(final Card source, final boolean checkSBA, final boolean damageSource) { final boolean colorlessDamage = damageSource && source.hasKeyword("Colorless Damage Source"); - for (String kw : keywords) { + for (KeywordInterface ki : keywords) { + String kw = ki.getOriginal(); if (kw.startsWith("Protection")) { if (kw.startsWith("Protection:")) { // uses isValid final String characteristic = kw.split(":")[1]; @@ -2573,6 +2574,8 @@ public class Player extends GameEntity implements Comparable { final PlayerController oldController = getController(); controlledBy.remove(timestamp); + getView().updateMindSlaveMaster(this); + if (event) { game.fireEvent(new GameEventPlayerControl(this, oldLobbyPlayer, oldController, getLobbyPlayer(), getController())); } @@ -2580,9 +2583,9 @@ public class Player extends GameEntity implements Comparable { public void clearController() { controlledBy.clear(); + game.fireEvent(new GameEventPlayerControl(this, null, null, getLobbyPlayer(), getController())); } - public Map.Entry getControlledWhileSearching() { if (controlledWhileSearching.isEmpty()) { return null; @@ -3226,7 +3229,7 @@ public class Player extends GameEntity implements Comparable { keywordEffect.updateAbilityTextForView(); boolean headerAdded = false; StringBuilder kw = new StringBuilder(); - for(String k : keywords) { + for(KeywordInterface k : keywords) { if(!headerAdded) { headerAdded = true; kw.append(this.getName()).append(" has: \n"); diff --git a/forge-game/src/main/java/forge/game/player/PlayerView.java b/forge-game/src/main/java/forge/game/player/PlayerView.java index 311177cdc3e..19e6e1cb6fd 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerView.java +++ b/forge-game/src/main/java/forge/game/player/PlayerView.java @@ -311,7 +311,7 @@ public class PlayerView extends GameEntityView { return getKeywords().contains(keyword); } void updateKeywords(Player p) { - set(TrackableProperty.Keywords, ImmutableMultiset.copyOf(p.getKeywords())); + set(TrackableProperty.Keywords, ImmutableMultiset.copyOf(p.getKeywords().asStringList())); } public List getCommanders() { diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceDamage.java b/forge-game/src/main/java/forge/game/replacement/ReplaceDamage.java index a5ce0550ef2..c0d8e1ebcb0 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplaceDamage.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplaceDamage.java @@ -17,6 +17,7 @@ */ package forge.game.replacement; +import forge.game.Game; import forge.game.GameEntity; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; @@ -48,6 +49,7 @@ public class ReplaceDamage extends ReplacementEffect { */ @Override public boolean canReplace(Map runParams) { + final Game game = getHostCard().getGame(); if (!(runParams.containsKey(AbilityKey.Prevention) == (hasParam("PreventionEffect") || hasParam("Prevent")))) { return false; @@ -128,17 +130,33 @@ public class ReplaceDamage extends ReplacementEffect { } // check for DamageRedirection, the Thing where the damage is redirected to must be a creature or planeswalker or a player String def = getParam("DamageTarget"); - for (Player p : AbilityUtils.getDefinedPlayers(hostCard, def, null)) { - if (!p.getGame().getPlayers().contains(p)) { + if (def.startsWith("Replaced")) { + // this can't work with the Defined below because the replaced objects aren't set to a possible SA yet + if (def.equals("ReplacedSourceController")) { + Card source = (Card) runParams.get(AbilityKey.DamageSource); + if (!game.getPlayers().contains(source.getController())) { + return false; + } + } else if (def.equals("ReplacedTargetController")) { + if (!(affected instanceof Card) || !game.getPlayers().contains(((Card) affected).getController())) { + return false; + } + } else { return false; } - } - for (Card c : AbilityUtils.getDefinedCards(hostCard, def, null)) { - if (!c.isCreature() && !c.isPlaneswalker()) { - return false; + } else { + for (Player p : AbilityUtils.getDefinedPlayers(getHostCard(), def, null)) { + if (!game.getPlayers().contains(p)) { + return false; + } } - if (!c.isInPlay()) { - return false; + for (Card c : AbilityUtils.getDefinedCards(getHostCard(), def, null)) { + if (!c.isCreature() && !c.isPlaneswalker()) { + return false; + } + if (!c.isInPlay()) { + return false; + } } } } diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java index 92af2439f71..9842d8ca356 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java @@ -288,34 +288,25 @@ public class ReplacementHandler { host = game.getCardState(host); } - if (mapParams.containsKey("ReplaceWith")) { + if (replacementEffect.getOverridingAbility() == null && mapParams.containsKey("ReplaceWith")) { final String effectSVar = mapParams.get("ReplaceWith"); - final String effectAbString = host.getSVar(effectSVar); // TODO: the source of replacement effect should be the source of the original effect - effectSA = AbilityFactory.getAbility(effectAbString, host); + effectSA = AbilityFactory.getAbility(host, effectSVar, replacementEffect); + //replacementEffect.setOverridingAbility(effectSA); //effectSA.setTrigger(true); - - SpellAbility tailend = effectSA; - do { - replacementEffect.setReplacingObjects(runParams, tailend); - //set original Params to update them later - tailend.setReplacingObject(AbilityKey.OriginalParams, runParams); - tailend = tailend.getSubAbility(); - } while(tailend != null); - - } - else if (replacementEffect.getOverridingAbility() != null) { + } else if (replacementEffect.getOverridingAbility() != null) { effectSA = replacementEffect.getOverridingAbility(); - SpellAbility tailend = effectSA; - do { - replacementEffect.setReplacingObjects(runParams, tailend); - //set original Params to update them later - tailend.setReplacingObject(AbilityKey.OriginalParams, runParams); - tailend = tailend.getSubAbility(); - } while(tailend != null); } if (effectSA != null) { + SpellAbility tailend = effectSA; + do { + replacementEffect.setReplacingObjects(runParams, tailend); + //set original Params to update them later + tailend.setReplacingObject(AbilityKey.OriginalParams, runParams); + tailend = tailend.getSubAbility(); + } while(tailend != null); + effectSA.setLastStateBattlefield(game.getLastStateBattlefield()); effectSA.setLastStateGraveyard(game.getLastStateGraveyard()); if (replacementEffect.isIntrinsic()) { diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityActivated.java b/forge-game/src/main/java/forge/game/spellability/AbilityActivated.java index 050c551f62c..42d2e543a1b 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityActivated.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityActivated.java @@ -64,9 +64,7 @@ public abstract class AbilityActivated extends SpellAbility implements Cloneable */ public AbilityActivated(final Card sourceCard, final Cost abCost, final TargetRestrictions tgt) { super(sourceCard, abCost); - if ((tgt != null) && tgt.doesTarget()) { - this.setTargetRestrictions(tgt); - } + this.setTargetRestrictions(tgt); } public boolean isActivatedAbility() { return true; } diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java index 0b5ff4626ef..c867926a28b 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -348,7 +348,7 @@ public class AbilityManaPart implements java.io.Serializable { } if (restriction.startsWith("CostContainsX")) { - if (sa.isXCost()) { + if (sa.costHasManaX()) { return true; } continue; diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityStatic.java b/forge-game/src/main/java/forge/game/spellability/AbilityStatic.java index bd14a4c8834..9c769c5e982 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityStatic.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityStatic.java @@ -47,9 +47,7 @@ public abstract class AbilityStatic extends Ability implements Cloneable { public AbilityStatic(final Card sourceCard, final Cost abCost, final TargetRestrictions tgt) { super(sourceCard, abCost); - if ((tgt != null) && tgt.doesTarget()) { - this.setTargetRestrictions(tgt); - } + this.setTargetRestrictions(tgt); } @Override public boolean canPlay() { diff --git a/forge-game/src/main/java/forge/game/spellability/Spell.java b/forge-game/src/main/java/forge/game/spellability/Spell.java index 4bbe33935f2..baca8b77c1e 100644 --- a/forge-game/src/main/java/forge/game/spellability/Spell.java +++ b/forge-game/src/main/java/forge/game/spellability/Spell.java @@ -17,6 +17,8 @@ */ package forge.game.spellability; +import org.apache.commons.lang3.ObjectUtils; + import forge.card.CardStateName; import forge.card.mana.ManaCost; import forge.game.Game; @@ -100,7 +102,9 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable card.setController(activator, 0); } - if (!this.getRestrictions().canPlay(getHostCard(), this)) { + card = ObjectUtils.firstNonNull(getAlternateHost(card), card); + + if (!this.getRestrictions().canPlay(card, this)) { return false; } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 887fd94252e..d9e04293f5e 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -39,7 +39,6 @@ import forge.game.card.CardPredicates; import forge.game.card.CardZoneTable; import forge.game.cost.Cost; import forge.game.cost.CostPart; -import forge.game.cost.CostPartMana; import forge.game.cost.CostRemoveCounter; import forge.game.keyword.Keyword; import forge.game.mana.Mana; @@ -47,6 +46,7 @@ import forge.game.player.Player; import forge.game.player.PlayerCollection; import forge.game.replacement.ReplacementEffect; import forge.game.staticability.StaticAbility; +import forge.game.staticability.StaticAbilityCastWithFlash; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerType; import forge.game.trigger.WrappedAbility; @@ -91,7 +91,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private Player activatingPlayer = null; private Player targetingPlayer = null; - private Card grantorCard = null; // card which grants the ability (equipment or owner of static ability that gave this one) private SpellAbility grantorOriginal = null; private StaticAbility grantorStatic = null; @@ -114,8 +113,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private CardStateName stateName = null; - private int totalManaSpent = 0; - /** The pay costs. */ private Cost payCosts; private SpellAbilityRestriction restrictions = new SpellAbilityRestriction(); @@ -368,11 +365,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return this.isAlternativeCost(AlternativeCost.Cycling); } - public Card getOriginalHost() { - return grantorCard; - } public void setOriginalHost(final Card c) { - grantorCard = c; + super.setOriginalHost(c); if (subAbility != null) { subAbility.setOriginalHost(c); } @@ -623,7 +617,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public String getStackDescription() { String text = getHostCard().getView().getText(); - if (stackDescription.equals(text)) { + if (stackDescription.equals(text) && !text.isEmpty()) { return getHostCard().getName() + " - " + text; } return TextUtil.fastReplace(stackDescription, "CARDNAME", getHostCard().getName()); @@ -990,7 +984,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit final TargetRestrictions tr = getTargetRestrictions(); // Restriction related to this ability - if (tr != null) { + if (usesTargeting()) { if (tr.isUniqueTargets() && getUniqueTargets().contains(entity)) return false; @@ -1327,11 +1321,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit originalMapParams.put("Announce", announce + ";" + variable); } - public boolean isXCost() { - CostPartMana cm = payCosts != null ? getPayCosts().getCostMana() : null; - return cm != null && cm.getAmountOfX() > 0; - } - @Override public boolean canBeTargetedBy(SpellAbility sa) { return sa.canTargetSpellAbility(this); @@ -1402,21 +1391,37 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return false; } - return getTargets().size() < getTargetRestrictions().getMaxTargets(hostCard, this); + return getTargets().size() < getMaxTargets(); } public boolean isZeroTargets() { return getTargetRestrictions().getMinTargets(hostCard, this) == 0 && getTargets().size() == 0; } + public boolean isMinTargetChosen() { + return getTargetRestrictions().isMinTargetsChosen(hostCard, this); + } + public boolean isMaxTargetChosen() { + return getTargetRestrictions().isMaxTargetsChosen(hostCard, this); + } + + public int getMinTargets() { + return getTargetRestrictions().getMinTargets(getHostCard(), this); + } + + public int getMaxTargets() { + return getTargetRestrictions().getMaxTargets(getHostCard(), this); + } + public boolean isTargetNumberValid() { if (!this.usesTargeting()) { return getTargets().isEmpty(); } - int minTargets = getTargetRestrictions().getMinTargets(hostCard, this); - int maxTargets = getTargetRestrictions().getMaxTargets(hostCard, this); - int numTargets = getTargets().size(); + if (!isMinTargetChosen()) { + return false; + } + int maxTargets = getMaxTargets(); if (maxTargets == 0 && getPayCosts().hasSpecificCostType(CostRemoveCounter.class) && hasSVar(getParam("TargetMax")) @@ -1429,7 +1434,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit maxTargets = Integer.parseInt(getHostCard().getSVar("CostCountersRemoved")); } - return minTargets <= numTargets && maxTargets >= numTargets; + return maxTargets >= getTargets().size(); } /** *

@@ -1670,8 +1675,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit SpellAbility currentAbility = this; final Card source = getHostCard(); do { - final TargetRestrictions tgt = currentAbility.getTargetRestrictions(); - if (tgt != null && tgt.doesTarget()) { + if (currentAbility.usesTargeting()) { currentAbility.clearTargets(); Player targetingPlayer; if (currentAbility.hasParam("TargetingPlayer")) { @@ -1788,12 +1792,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } } - public void setTotalManaSpent(int totManaSpent) { - totalManaSpent = totManaSpent; - } - public int getTotalManaSpent() { - return totalManaSpent; + return this.getPayingMana().size(); } public List getChosenList() { @@ -2059,17 +2059,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return true; } } - final Game game = activator.getGame(); - final CardCollection allp = new CardCollection(game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)); - allp.add(host); - for (final Card ca : allp) { - for (final StaticAbility stAb : ca.getStaticAbilities()) { - if (stAb.applyAbility("CastWithFlash", host, this, activator)) { - return true; - } - } - } - return false; + + return StaticAbilityCastWithFlash.anyWithFlash(this, host, activator); } } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java index 887c845d163..687d58ffdad 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java @@ -24,7 +24,6 @@ import forge.game.GameType; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollectionView; -import forge.game.card.CardFactoryUtil; import forge.game.card.CardLists; import forge.game.card.CardUtil; import forge.game.phase.PhaseHandler; @@ -271,7 +270,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables { if (this.isAllTargetsLegal()) { for (Card c : sa.getTargets().getTargetCards()) { - if (!CardFactoryUtil.isTargetStillValid(sa, c)) { + if (!sa.canTarget(c)) { return false; } } 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 6c38eccddf2..069b3fed888 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -24,6 +24,7 @@ import forge.game.card.*; import forge.game.cost.IndividualCostPaymentInstance; import forge.game.phase.PhaseType; import forge.game.player.Player; +import forge.game.staticability.StaticAbilityCastWithFlash; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.util.Expressions; @@ -35,7 +36,7 @@ import java.util.Map; *

* SpellAbilityRestriction class. *

- * + * * @author Forge * @version $Id$ */ @@ -61,7 +62,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { *

* setRestrictions. *

- * + * * @param params * a {@link java.util.HashMap} object. * @since 1.0.15 @@ -321,7 +322,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { String validPlayer = this.getActivator(); return activator.isValid(validPlayer, c.getController(), c, sa); } - + public final boolean checkOtherRestrictions(final Card c, final SpellAbility sa, final Player activator) { final Game game = activator.getGame(); @@ -484,7 +485,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { *

* canPlay. *

- * + * * @param c * a {@link forge.game.card.Card} object. * @param sa @@ -503,6 +504,12 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { System.out.println(c.getName() + " Did not have activator set in SpellAbilityRestriction.canPlay()"); } + if (!StaticAbilityCastWithFlash.anyWithFlashNeedsTargeting(sa, c, activator)) { + if (!sa.canCastTiming(c, activator)) { + return false; + } + } + if (!sa.hasSVar("IsCastFromPlayEffect")) { if (!checkTimingRestrictions(c, sa)) { return false; @@ -516,7 +523,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { if (!checkZoneRestrictions(c, sa)) { return false; } - + if (!checkOtherRestrictions(c, sa, activator)) { return false; } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java index 747393bef06..3a5c171ca3d 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java @@ -39,6 +39,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; /** @@ -141,10 +142,10 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView { } // We probably should be storing SA svars too right? if (!sa.isWrapper()) { - for (final String store : sa.getSVars()) { - final String value = source.getSVar(store); + for (final Entry e : sa.getSVars().entrySet()) { + final String value = e.getValue(); if (!StringUtils.isEmpty(value)) { - storedSVars.put(store, value); + storedSVars.put(e.getKey(), value); } } } diff --git a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java index 03ed158bcb6..d917a4058f9 100644 --- a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java +++ b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java @@ -49,7 +49,6 @@ public class TargetRestrictions { // Target Choices (which is specific for the StackInstance) // What this Object is restricted to targeting - private boolean tgtValid = false; private String[] originalValidTgts, validTgts; private String uiPrompt = ""; @@ -93,7 +92,6 @@ public class TargetRestrictions { * a {@link forge.game.spellability.TargetRestrictions} object. */ public TargetRestrictions(final TargetRestrictions target) { - this.tgtValid = true; this.uiPrompt = target.getVTSelection(); this.originalValidTgts = target.getValidTgts(); this.validTgts = this.originalValidTgts.clone(); @@ -129,7 +127,6 @@ public class TargetRestrictions { * a {@link java.lang.String} object. */ public TargetRestrictions(final String prompt, final String[] valid, final String min, final String max) { - this.tgtValid = true; this.uiPrompt = prompt; this.originalValidTgts = valid; this.validTgts = this.originalValidTgts.clone(); @@ -172,17 +169,6 @@ public class TargetRestrictions { this.maxTotalCMC = cmc; } - /** - *

- * doesTarget. - *

- * - * @return a boolean. - */ - public final boolean doesTarget() { - return this.tgtValid; - } - /** *

* getValidTgts. @@ -210,7 +196,7 @@ public class TargetRestrictions { * * @return the min targets */ - private final String getMinTargets() { + public final String getMinTargets() { return this.minTargets; } @@ -219,7 +205,7 @@ public class TargetRestrictions { * * @return the max targets */ - private final String getMaxTargets() { + public final String getMaxTargets() { return this.maxTargets; } @@ -278,8 +264,7 @@ public class TargetRestrictions { * @return a boolean. */ public final boolean isMaxTargetsChosen(final Card c, final SpellAbility sa) { - TargetChoices choice = sa.getTargets(); - return this.getMaxTargets(c, sa) == choice.size(); + return this.getMaxTargets(c, sa) == sa.getTargets().size(); } /** @@ -294,11 +279,11 @@ public class TargetRestrictions { * @return a boolean. */ public final boolean isMinTargetsChosen(final Card c, final SpellAbility sa) { - if (this.getMinTargets(c, sa) == 0) { + int min = getMinTargets(c, sa); + if (min == 0) { return true; } - TargetChoices choice = sa.getTargets(); - return this.getMinTargets(c, sa) <= choice.size(); + return min <= sa.getTargets().size(); } /** diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java index bfb2ac6f552..8d2f5392fec 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -403,24 +403,6 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone return false; } - public final boolean applyAbility(final String mode, final Card card, final SpellAbility spellAbility, final Player player) { - - // don't apply the ability if it hasn't got the right mode - if (!getParam("Mode").equals(mode)) { - return false; - } - - if (this.isSuppressed() || !this.checkConditions()) { - return false; - } - - if (mode.equals("CastWithFlash")) { - return StaticAbilityCastWithFlash.applyWithFlashAbility(this, spellAbility, card, player); - } - - return false; - } - public final boolean applyAbility(String mode, Card card, CounterType type) { // don't apply the ability if it hasn't got the right mode @@ -582,6 +564,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone if (condition.equals("Ferocious") && !controller.hasFerocious()) return false; if (condition.equals("Desert") && !controller.hasDesert()) return false; if (condition.equals("Blessing") && !controller.hasBlessing()) return false; + if (condition.equals("Monarch") & !controller.isMonarch()) return false; if (condition.equals("PlayerTurn")) { if (!ph.isPlayerTurn(controller)) { diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java index 00243f7e4c9..656749c5285 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java @@ -18,6 +18,7 @@ package forge.game.staticability; import forge.game.card.Card; +import forge.game.keyword.KeywordInterface; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; @@ -74,7 +75,8 @@ public class StaticAbilityCantTarget { if (st.hasParam("Hexproof") && (activator != null)) { - for (String k : activator.getKeywords()) { + for (KeywordInterface kw : activator.getKeywords()) { + String k = kw.getOriginal(); if (k.startsWith("IgnoreHexproof")) { String[] m = k.split(":"); if (card.isValid(m[1].split(","), activator, source, spellAbility)) { @@ -84,7 +86,8 @@ public class StaticAbilityCantTarget { } } if (st.hasParam("Shroud") && (activator != null)) { - for (String k : activator.getKeywords()) { + for (KeywordInterface kw : activator.getKeywords()) { + String k = kw.getOriginal(); if (k.startsWith("IgnoreShroud")) { String[] m = k.split(":"); if (card.isValid(m[1].split(","), activator, source, spellAbility)) { @@ -114,7 +117,8 @@ public class StaticAbilityCantTarget { if (st.hasParam("Hexproof") && (activator != null)) { - for (String k : activator.getKeywords()) { + for (KeywordInterface kw : activator.getKeywords()) { + String k = kw.getOriginal(); if (k.startsWith("IgnoreHexproof")) { String[] m = k.split(":"); if (player.isValid(m[1].split(","), activator, source, spellAbility)) { diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCastWithFlash.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCastWithFlash.java index aa52bb57c9d..08e4a883ecc 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCastWithFlash.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCastWithFlash.java @@ -1,15 +1,57 @@ package forge.game.staticability; -import java.util.List; +import com.google.common.collect.Iterables; -import forge.game.GameObject; +import forge.game.Game; +import forge.game.GameObjectPredicates; import forge.game.card.Card; +import forge.game.card.CardCollection; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; public class StaticAbilityCastWithFlash { - public static boolean applyWithFlashAbility(final StaticAbility stAb, final SpellAbility sa, final Card card, final Player activator) { + + static String MODE = "CastWithFlash"; + + public static boolean anyWithFlashNeedsTargeting(final SpellAbility sa, final Card card, final Player activator) { + final Game game = activator.getGame(); + final CardCollection allp = new CardCollection(game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)); + allp.add(card); + for (final Card ca : allp) { + for (final StaticAbility stAb : ca.getStaticAbilities()) { + if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) { + continue; + } + if (applyWithFlashNeedsTargeting(stAb, sa, card, activator)) { + return true; + } + } + } + return false; + } + + public static boolean anyWithFlash(final SpellAbility sa, final Card card, final Player activator) { + final Game game = activator.getGame(); + final CardCollection allp = new CardCollection(game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)); + allp.add(card); + for (final Card ca : allp) { + for (final StaticAbility stAb : ca.getStaticAbilities()) { + if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) { + continue; + } + if (applyWithFlashAbility(stAb, sa, card, activator)) { + return true; + } + } + } + return false; + } + + + + + public static boolean commonParts(final StaticAbility stAb, final SpellAbility sa, final Card card, final Player activator) { final Card hostCard = stAb.getHostCard(); if (stAb.hasParam("ValidCard") @@ -21,32 +63,36 @@ public class StaticAbilityCastWithFlash { && !sa.isValid(stAb.getParam("ValidSA").split(","), hostCard.getController(), hostCard, null)) { return false; } - + if (stAb.hasParam("Caster") && (activator != null) && !activator.isValid(stAb.getParam("Caster"), hostCard.getController(), hostCard, null)) { return false; } + return true; + } + + public static boolean applyWithFlashNeedsTargeting(final StaticAbility stAb, final SpellAbility sa, final Card card, final Player activator) { + if (!commonParts(stAb, sa, card, activator)) { + return false; + } + + return stAb.hasParam("Targeting"); + } + + public static boolean applyWithFlashAbility(final StaticAbility stAb, final SpellAbility sa, final Card card, final Player activator) { + final Card hostCard = stAb.getHostCard(); + + if (!commonParts(stAb, sa, card, activator)) { + return false; + } if (stAb.hasParam("Targeting")) { if (!sa.usesTargeting()) { return false; } - boolean found = false; - String[] valids = stAb.getParam("Targeting").split(","); - for (GameObject ga : sa.getTargets()) { - if (ga.isValid(valids, hostCard.getController(), hostCard, null)) { - found = true; - break; - } - } - if (!found) { - return false; - } - } - if (stAb.hasParam("Origin")) { - List src = ZoneType.listValueOf(stAb.getParam("Origin")); - if (!src.contains(hostCard.getGame().getZoneOf(card).getZoneType())) { + String[] valids = stAb.getParam("Targeting").split(","); + if (!Iterables.any(sa.getTargets(), GameObjectPredicates.restriction(valids, hostCard.getController(), hostCard, null))) { 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 6d0c1a729f6..48676613425 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -728,7 +728,7 @@ public final class StaticAbilityContinuous { abilty = TextUtil.fastReplace(abilty, "ConvertedManaCost", costcmc); } if (abilty.startsWith("AB") || abilty.startsWith("ST")) { // grant the ability - final SpellAbility sa = AbilityFactory.getAbility(abilty, affectedCard); + final SpellAbility sa = AbilityFactory.getAbility(abilty, affectedCard, stAb); sa.setIntrinsic(false); sa.setOriginalHost(hostCard); addedAbilities.add(sa); @@ -790,15 +790,10 @@ public final class StaticAbilityContinuous { // with that the TargetedCard does not need the Svars added to them anymore // but only do it if the trigger doesn't already have a overriding ability if (actualTrigger.hasParam("Execute") && actualTrigger.getOverridingAbility() == null) { - String svar = AbilityUtils.getSVar(stAb, actualTrigger.getParam("Execute")); - SpellAbility sa = AbilityFactory.getAbility(svar, hostCard); - // set hostcard there so when the card is added to trigger, it doesn't make a copy of it - sa.setHostCard(affectedCard); - // set OriginalHost to get the owner of this static ability - sa.setOriginalHost(hostCard); // set overriding ability to the trigger - actualTrigger.setOverridingAbility(sa); + actualTrigger.setOverridingAbility(AbilityFactory.getAbility(affectedCard, actualTrigger.getParam("Execute"), stAb)); } + actualTrigger.setOriginalHost(hostCard); actualTrigger.setIntrinsic(false); addedTrigger.add(actualTrigger); } @@ -814,6 +809,7 @@ public final class StaticAbilityContinuous { StaticAbility stat = new StaticAbility(s, affectedCard); stat.setIntrinsic(false); + stat.setOriginalHost(hostCard); addedStaticAbility.add(stat); } } diff --git a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java index 9277b987af0..4cf6c739e8d 100644 --- a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java +++ b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java @@ -14,7 +14,6 @@ import forge.game.spellability.*; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; @@ -309,16 +308,6 @@ public class WrappedAbility extends Ability { return sa.isSpell(); } - @Override - public boolean isXCost() { - return sa.isXCost(); - } - - @Override - public String getSvarWithFallback(String name) { - return sa.getSvarWithFallback(name); - } - @Override public String getSVar(String name) { return sa.getSVar(name); @@ -330,7 +319,7 @@ public class WrappedAbility extends Ability { } @Override - public Set getSVars() { + public Map getSVars() { return sa.getSVars(); } diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index ba22a8349e7..6163a38b313 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -41,7 +41,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.CardUtil; import forge.game.event.EventValueChangeType; import forge.game.event.GameEventCardStatsChanged; @@ -273,11 +272,8 @@ public class MagicStack /* extends MyObservable */ implements Iterable runParams = AbilityKey.newMap(); if (!sp.isCopied()) { @@ -593,7 +587,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable com.badlogicgames.gdx gdx-backend-android - 1.9.11 + 1.9.10 diff --git a/forge-gui-desktop/pom.xml b/forge-gui-desktop/pom.xml index 94ec86fd6e7..b7847db08d4 100644 --- a/forge-gui-desktop/pom.xml +++ b/forge-gui-desktop/pom.xml @@ -132,7 +132,7 @@ com.miglayout miglayout-swing - 5.2 + 4.2 com.mortennobel diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java index cb49ced38f4..1c191c8b1d1 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java @@ -793,9 +793,11 @@ public final class CMatchUI initHandViews(); SLayoutIO.loadLayout(null); view.populate(); - for (final VHand h : getHandViews()) { - h.getLayoutControl().updateHand(); + final PlayerZoneUpdates zones = new PlayerZoneUpdates(); + for (final PlayerView p : sortedPlayers) { + zones.add(new PlayerZoneUpdate(p, ZoneType.Hand)); } + updateZones(zones); } @Override @@ -1025,6 +1027,7 @@ public final class CMatchUI @Override public void afterGameEnd() { + super.afterGameEnd(); Singletons.getView().getLpnDocument().remove(targetingOverlay.getPanel()); FThreads.invokeInEdtNowOrLater(new Runnable() { @Override public void run() { diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/VAssignCombatDamage.java b/forge-gui-desktop/src/main/java/forge/screens/match/VAssignCombatDamage.java index 673e4950c89..4cebdcc3491 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/VAssignCombatDamage.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/VAssignCombatDamage.java @@ -74,6 +74,7 @@ public class VAssignCombatDamage { private final int totalDamageToAssign; private boolean attackerHasDeathtouch = false; + private boolean attackerHasDivideDamage = false; private boolean attackerHasTrample = false; private boolean attackerHasInfect = false; private boolean overrideCombatantOrder = false; @@ -152,6 +153,7 @@ public class VAssignCombatDamage { attackerHasDeathtouch = attacker.getCurrentState().hasDeathtouch(); attackerHasInfect = attacker.getCurrentState().hasInfect(); attackerHasTrample = defender != null && attacker.getCurrentState().hasTrample(); + attackerHasDivideDamage = attacker.getCurrentState().hasDivideDamage(); overrideCombatantOrder = overrideOrder; // Top-level UI stuff @@ -173,7 +175,7 @@ public class VAssignCombatDamage { // Defenders area final JPanel pnlDefenders = new JPanel(); pnlDefenders.setOpaque(false); - int cols = attackerHasTrample ? blockers.size() + 1 : blockers.size(); + int cols = ((attackerHasTrample) || (attackerHasDivideDamage && overrideCombatantOrder)) ? blockers.size() + 1 : blockers.size(); final String wrap = "wrap " + cols; pnlDefenders.setLayout(new MigLayout("insets 0, gap 0, ax center, " + wrap)); @@ -187,7 +189,7 @@ public class VAssignCombatDamage { addPanelForDefender(pnlDefenders, c); } - if (attackerHasTrample) { + if ((attackerHasTrample) || (attackerHasDivideDamage && overrideCombatantOrder)) { final DamageTarget dt = new DamageTarget(null, new FLabel.Builder().text("0").fontSize(18).fontAlign(SwingConstants.CENTER).build()); damage.put(null, dt); defenders.add(dt); @@ -275,10 +277,12 @@ public class VAssignCombatDamage { source = null; // If trying to assign to the defender, follow the normal assignment rules - // No need to check for "active" creature assignee when overiding combatant order - if ((source == null || source == defender || !overrideCombatantOrder) && isAdding && - !VAssignCombatDamage.this.canAssignTo(source)) { - return; + // No need to check for "active" creature assignee when overriding combatant order + if (!attackerHasDivideDamage) { // Creatures with this can assign to defender + if ((source == null || source == defender || !overrideCombatantOrder) && isAdding && + !VAssignCombatDamage.this.canAssignTo(source)) { + return; + } } // If lethal damage has already been assigned just act like it's 0. @@ -316,6 +320,9 @@ public class VAssignCombatDamage { } private void checkDamageQueue() { + if (overrideCombatantOrder && attackerHasDivideDamage) { + return; + } // Clear out any Damage that shouldn't be assigned to other combatants boolean hasAliveEnemy = false; for(DamageTarget dt : defenders) { diff --git a/forge-gui-ios/pom.xml b/forge-gui-ios/pom.xml index 8b1a741a5ee..a34a6d9e8fd 100644 --- a/forge-gui-ios/pom.xml +++ b/forge-gui-ios/pom.xml @@ -73,7 +73,7 @@ com.badlogicgames.gdx gdx-backend-robovm - 1.9.11 + 1.9.10 diff --git a/forge-gui-mobile-dev/pom.xml b/forge-gui-mobile-dev/pom.xml index 4cd801f7408..14aacfe4a26 100644 --- a/forge-gui-mobile-dev/pom.xml +++ b/forge-gui-mobile-dev/pom.xml @@ -60,18 +60,18 @@ com.badlogicgames.gdx gdx-backend-lwjgl - 1.9.11 + 1.9.10 com.badlogicgames.gdx gdx-platform - 1.9.11 + 1.9.10 natives-desktop com.badlogicgames.gdx gdx-freetype-platform - 1.9.11 + 1.9.10 natives-desktop diff --git a/forge-gui-mobile/pom.xml b/forge-gui-mobile/pom.xml index b3eb00fd614..339886fa293 100644 --- a/forge-gui-mobile/pom.xml +++ b/forge-gui-mobile/pom.xml @@ -63,12 +63,12 @@ com.badlogicgames.gdx gdx - 1.9.11 + 1.9.10 com.badlogicgames.gdx gdx-freetype - 1.9.11 + 1.9.10 diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java index 7870c987f29..4dd2ed5771d 100644 --- a/forge-gui-mobile/src/forge/Forge.java +++ b/forge-gui-mobile/src/forge/Forge.java @@ -76,6 +76,7 @@ public class Forge implements ApplicationListener { public static int cacheSize = 400; public static int totalDeviceRAM = 0; public static int androidVersion = 0; + public static boolean autoCache = false; public static ApplicationListener getApp(Clipboard clipboard0, IDeviceAdapter deviceAdapter0, String assetDir0, boolean value, boolean androidOrientation, int totalRAM, boolean isTablet, int AndroidVersion) { if (GuiBase.getInterface() == null) { @@ -87,9 +88,6 @@ public class Forge implements ApplicationListener { totalDeviceRAM = totalRAM; isTabletDevice = isTablet; androidVersion = AndroidVersion; - //increase cacheSize for devices with RAM more than 5GB, default is 400. Some phones have more than 10GB RAM (Mi 10, OnePlus 8, S20, etc..) - if (totalDeviceRAM>5000) //devices with more than 10GB RAM will have 1000 Cache size, 700 Cache size for morethan 5GB RAM - cacheSize = totalDeviceRAM>10000 ? 1000: 700; } return app; } @@ -103,6 +101,7 @@ public class Forge implements ApplicationListener { ExceptionHandler.registerErrorHandling(); GuiBase.setIsAndroid(Gdx.app.getType() == Application.ApplicationType.Android); + graphics = new Graphics(); splashScreen = new SplashScreen(); frameRate = new FrameRate(); @@ -132,6 +131,13 @@ public class Forge implements ApplicationListener { enableUIMask = prefs.getPrefBoolean(FPref.UI_ENABLE_BORDER_MASKING); enablePreloadExtendedArt = prefs.getPrefBoolean(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART); locale = prefs.getPref(FPref.UI_LANGUAGE); + autoCache = prefs.getPrefBoolean(FPref.UI_AUTO_CACHE_SIZE); + + if (autoCache) { + //increase cacheSize for devices with RAM more than 5GB, default is 400. Some phones have more than 10GB RAM (Mi 10, OnePlus 8, S20, etc..) + if (totalDeviceRAM>5000) //devices with more than 10GB RAM will have 1000 Cache size, 700 Cache size for morethan 5GB RAM + cacheSize = totalDeviceRAM>10000 ? 1000: 700; + } final Localizer localizer = Localizer.getInstance(); @@ -155,12 +161,12 @@ public class Forge implements ApplicationListener { //add reminder to preload if (enablePreloadExtendedArt) { - if(totalDeviceRAM>0) + if(autoCache) splashScreen.getProgressBar().setDescription(localizer.getMessage("lblPreloadExtendedArt")+"\nDetected RAM: " +totalDeviceRAM+"MB. Cache size: "+cacheSize); else splashScreen.getProgressBar().setDescription(localizer.getMessage("lblPreloadExtendedArt")); } else { - if(totalDeviceRAM>0) + if(autoCache) splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup")+"\nDetected RAM: " +totalDeviceRAM+"MB. Cache size: "+cacheSize); else splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup")); diff --git a/forge-gui-mobile/src/forge/Graphics.java b/forge-gui-mobile/src/forge/Graphics.java index fb2cf05335e..d503653abaf 100644 --- a/forge-gui-mobile/src/forge/Graphics.java +++ b/forge-gui-mobile/src/forge/Graphics.java @@ -680,11 +680,12 @@ public class Graphics { batch.draw(image, adjustX(x), adjustY(y, h), w, h); } else { batch.end(); - shaderOutline.bind(); + shaderOutline.begin(); shaderOutline.setUniformf("u_viewportInverse", new Vector2(1f / w, 1f / h)); shaderOutline.setUniformf("u_offset", 3f); shaderOutline.setUniformf("u_step", Math.min(1f, w / 70f)); shaderOutline.setUniformf("u_color", new Vector3(glowColor.r, glowColor.g, glowColor.b)); + shaderOutline.end(); batch.setShader(shaderOutline); batch.begin(); //glow @@ -703,11 +704,12 @@ public class Graphics { batch.draw(image, adjustX(x), adjustY(yBox, h), w, h); } else { batch.end(); - shaderOutline.bind(); + shaderOutline.begin(); shaderOutline.setUniformf("u_viewportInverse", new Vector2(1f / w, 1f / h)); shaderOutline.setUniformf("u_offset", 3f); shaderOutline.setUniformf("u_step", Math.min(1f, w / 70f)); shaderOutline.setUniformf("u_color", new Vector3(glowColor.r, glowColor.g, glowColor.b)); + shaderOutline.end(); batch.setShader(shaderOutline); batch.begin(); //glow diff --git a/forge-gui-mobile/src/forge/assets/AssetsDownloader.java b/forge-gui-mobile/src/forge/assets/AssetsDownloader.java index fc4971ea829..5d60a50690f 100644 --- a/forge-gui-mobile/src/forge/assets/AssetsDownloader.java +++ b/forge-gui-mobile/src/forge/assets/AssetsDownloader.java @@ -155,5 +155,11 @@ public class AssetsDownloader { //save version string to file once assets finish downloading //so they don't need to be re-downloaded until you upgrade again FileUtil.writeFile(versionFile, Forge.CURRENT_VERSION); + + //add restart after assets update + switch (SOptionPane.showOptionDialog("Resource update finished. Please restart Forge.", "", null, ImmutableList.of("Ok"))) { + default: + Forge.restart(true); + } } } diff --git a/forge-gui-mobile/src/forge/assets/FSkinImage.java b/forge-gui-mobile/src/forge/assets/FSkinImage.java index 5926e5e05e8..927a97f766a 100644 --- a/forge-gui-mobile/src/forge/assets/FSkinImage.java +++ b/forge-gui-mobile/src/forge/assets/FSkinImage.java @@ -351,6 +351,8 @@ public enum FSkinImage implements FImage { FOIL_19 (FSkinProp.FOIL_19, SourceFile.OLD_FOILS), FOIL_20 (FSkinProp.FOIL_20, SourceFile.OLD_FOILS), + //COMMANDER + IMG_ABILITY_COMMANDER (FSkinProp.IMG_ABILITY_COMMANDER, SourceFile.ABILITIES), //ABILITY ICONS IMG_ABILITY_DEATHTOUCH (FSkinProp.IMG_ABILITY_DEATHTOUCH, SourceFile.ABILITIES), IMG_ABILITY_DEFENDER (FSkinProp.IMG_ABILITY_DEFENDER, SourceFile.ABILITIES), @@ -364,6 +366,7 @@ public enum FSkinImage implements FImage { IMG_ABILITY_HORSEMANSHIP (FSkinProp.IMG_ABILITY_HORSEMANSHIP, SourceFile.ABILITIES), IMG_ABILITY_INDESTRUCTIBLE (FSkinProp.IMG_ABILITY_INDESTRUCTIBLE, SourceFile.ABILITIES), IMG_ABILITY_INTIMIDATE (FSkinProp.IMG_ABILITY_INTIMIDATE, SourceFile.ABILITIES), + IMG_ABILITY_LANDWALK (FSkinProp.IMG_ABILITY_LANDWALK, SourceFile.ABILITIES), IMG_ABILITY_LIFELINK (FSkinProp.IMG_ABILITY_LIFELINK, SourceFile.ABILITIES), IMG_ABILITY_MENACE (FSkinProp.IMG_ABILITY_MENACE, SourceFile.ABILITIES), IMG_ABILITY_REACH (FSkinProp.IMG_ABILITY_REACH, SourceFile.ABILITIES), diff --git a/forge-gui-mobile/src/forge/card/CardFaceSymbols.java b/forge-gui-mobile/src/forge/card/CardFaceSymbols.java index 96f355dd730..a26bc910f45 100644 --- a/forge-gui-mobile/src/forge/card/CardFaceSymbols.java +++ b/forge-gui-mobile/src/forge/card/CardFaceSymbols.java @@ -102,6 +102,7 @@ public class CardFaceSymbols { MANA_IMAGES.put("foil19", FSkinImage.FOIL_19); MANA_IMAGES.put("foil20", FSkinImage.FOIL_20); + MANA_IMAGES.put("commander", FSkinImage.IMG_ABILITY_COMMANDER); MANA_IMAGES.put("deathtouch", FSkinImage.IMG_ABILITY_DEATHTOUCH); MANA_IMAGES.put("defender", FSkinImage.IMG_ABILITY_DEFENDER); @@ -115,6 +116,7 @@ public class CardFaceSymbols { MANA_IMAGES.put("horsemanship", FSkinImage.IMG_ABILITY_HORSEMANSHIP); MANA_IMAGES.put("indestructible", FSkinImage.IMG_ABILITY_INDESTRUCTIBLE); MANA_IMAGES.put("intimidate", FSkinImage.IMG_ABILITY_INTIMIDATE); + MANA_IMAGES.put("landwalk", FSkinImage.IMG_ABILITY_LANDWALK); MANA_IMAGES.put("lifelink", FSkinImage.IMG_ABILITY_LIFELINK); MANA_IMAGES.put("menace", FSkinImage.IMG_ABILITY_MENACE); MANA_IMAGES.put("reach", FSkinImage.IMG_ABILITY_REACH); diff --git a/forge-gui-mobile/src/forge/card/CardRenderer.java b/forge-gui-mobile/src/forge/card/CardRenderer.java index 21dca687712..504e5da3bc3 100644 --- a/forge-gui-mobile/src/forge/card/CardRenderer.java +++ b/forge-gui-mobile/src/forge/card/CardRenderer.java @@ -654,6 +654,11 @@ public class CardRenderer { abiY += abiSpace; abiCount += 1; } + if (card.isCommander()) { + CardFaceSymbols.drawSymbol("commander", g, abiX, abiY, abiScale, abiScale); + abiY += abiSpace; + abiCount += 1; + } if (card.getCurrentState().hasFlying()) { CardFaceSymbols.drawSymbol("flying", g, abiX, abiY, abiScale, abiScale); abiY += abiSpace; @@ -716,6 +721,11 @@ public class CardRenderer { abiY += abiSpace; abiCount += 1; } + if (card.getCurrentState().hasLandwalk()) { + CardFaceSymbols.drawSymbol("landwalk", g, abiX, abiY, abiScale, abiScale); + abiY += abiSpace; + abiCount += 1; + } if (card.getCurrentState().hasHexproof()) { if (abiCount > 5 ) { abiY = cy + (abiSpace * (abiCount - 6)); abiX = cx + ((cw*2)/1.92f); } if (!card.getCurrentState().getHexproofKey().isEmpty()){ diff --git a/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java b/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java index 8456a248ad4..46a5d810514 100644 --- a/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java +++ b/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java @@ -1023,7 +1023,9 @@ public class ImageView extends ItemView { g.drawText(item.getName(), GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + PADDING*2, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false); } else { if (!dp.isGeneratedDeck()){ - FImageComplex cardArt = CardRenderer.getCardArt(dp.getHighestCMCCard().getImageKey(false), false, false, false); + //If deck has Commander, use it as cardArt reference + String deckImageKey = dp.getDeck().getCommanders().isEmpty() ? dp.getHighestCMCCard().getImageKey(false) : dp.getDeck().getCommanders().get(0).getImageKey(false); + FImageComplex cardArt = CardRenderer.getCardArt(deckImageKey, false, false, false); //draw the deckbox if (cardArt == null){ //draw generic box if null or still loading diff --git a/forge-gui-mobile/src/forge/screens/home/HomeScreen.java b/forge-gui-mobile/src/forge/screens/home/HomeScreen.java index 6c124ff90a7..2c03efda874 100644 --- a/forge-gui-mobile/src/forge/screens/home/HomeScreen.java +++ b/forge-gui-mobile/src/forge/screens/home/HomeScreen.java @@ -37,6 +37,8 @@ public class HomeScreen extends FScreen { private final List buttons = new ArrayList<>(); private int activeButtonIndex, baseButtonCount; private FDeckChooser deckManager; + private boolean QuestCommander = false; + private String QuestWorld = ""; private HomeScreen() { super((Header)null); @@ -104,6 +106,22 @@ public class HomeScreen extends FScreen { buttons.add(buttonScroller.add(new MenuButton(caption, command))); } + public void updateQuestCommanderMode(boolean isCommander){ + QuestCommander = isCommander; + } + + public void updateQuestWorld(String questWorld){ + QuestWorld = questWorld; + } + + public boolean getQuestCommanderMode() { + return QuestCommander; + } + + public String getQuestWorld() { + return QuestWorld; + } + public void addButtonForMode(String caption, final FEventHandler command) { //ensure we don't add the same mode button more than once for (int i = baseButtonCount; i < buttons.size(); i++) { diff --git a/forge-gui-mobile/src/forge/screens/match/MatchController.java b/forge-gui-mobile/src/forge/screens/match/MatchController.java index 7471e651775..51c1ffafe75 100644 --- a/forge-gui-mobile/src/forge/screens/match/MatchController.java +++ b/forge-gui-mobile/src/forge/screens/match/MatchController.java @@ -445,6 +445,7 @@ public class MatchController extends AbstractGuiGame { @Override public void afterGameEnd() { + super.afterGameEnd(); Forge.back(); //view = null; } diff --git a/forge-gui-mobile/src/forge/screens/match/views/VAssignCombatDamage.java b/forge-gui-mobile/src/forge/screens/match/views/VAssignCombatDamage.java index 6b709930750..f85881795bd 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VAssignCombatDamage.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VAssignCombatDamage.java @@ -61,6 +61,7 @@ public class VAssignCombatDamage extends FDialog { private final int totalDamageToAssign; private boolean attackerHasDeathtouch = false; + private boolean attackerHasDivideDamage = false; private boolean attackerHasTrample = false; private boolean attackerHasInfect = false; private boolean overrideCombatantOrder = false; @@ -102,6 +103,7 @@ public class VAssignCombatDamage extends FDialog { totalDamageToAssign = damage0; defender = defender0; attackerHasDeathtouch = attacker.getCurrentState().hasDeathtouch(); + attackerHasDivideDamage = attacker.getCurrentState().hasDivideDamage(); attackerHasInfect = attacker.getCurrentState().hasInfect(); attackerHasTrample = defender != null && attacker.getCurrentState().hasTrample(); overrideCombatantOrder = overrideOrder; @@ -166,7 +168,7 @@ public class VAssignCombatDamage extends FDialog { addDamageTarget(c); } - if (attackerHasTrample) { + if (attackerHasTrample || (attackerHasDivideDamage && overrideCombatantOrder)) { //add damage target for target of attack that trample damage will go through to addDamageTarget(null); } @@ -298,8 +300,10 @@ public class VAssignCombatDamage extends FDialog { // If trying to assign to the defender, follow the normal assignment rules // No need to check for "active" creature assignee when overiding combatant order - if ((source == null || source == defender || !overrideCombatantOrder) && isAdding && !canAssignTo(source)) { - return; + if (!attackerHasDivideDamage) { // Creatures with this can assign to defender + if ((source == null || source == defender || !overrideCombatantOrder) && isAdding && !canAssignTo(source)) { + return; + } } // If lethal damage has already been assigned just act like it's 0. @@ -330,6 +334,9 @@ public class VAssignCombatDamage extends FDialog { } private void checkDamageQueue() { + if (overrideCombatantOrder && attackerHasDivideDamage) { + return; + } // Clear out any Damage that shouldn't be assigned to other combatants boolean hasAliveEnemy = false; for (DamageTarget dt : defenders) { diff --git a/forge-gui-mobile/src/forge/screens/match/winlose/QuestWinLose.java b/forge-gui-mobile/src/forge/screens/match/winlose/QuestWinLose.java index 21036a445a8..56faf35e470 100644 --- a/forge-gui-mobile/src/forge/screens/match/winlose/QuestWinLose.java +++ b/forge-gui-mobile/src/forge/screens/match/winlose/QuestWinLose.java @@ -18,6 +18,7 @@ package forge.screens.match.winlose; import forge.game.GameView; import forge.quest.QuestWinLoseController; +import forge.screens.home.HomeScreen; /** *

@@ -44,6 +45,10 @@ public class QuestWinLose extends ControlWinLose { @Override public final void showRewards() { + //set loading overlay again + if (HomeScreen.instance.getQuestWorld().contains("XandomX")) { + HomeScreen.instance.updateQuestWorld(HomeScreen.instance.getQuestWorld().replace("XandomX","Random")); + } controller.showRewards(); } @@ -60,4 +65,13 @@ public class QuestWinLose extends ControlWinLose { controller.actionOnQuit(); super.actionOnQuit(); } + + @Override + public void actionOnContinue() { + //prevent loading overlay to show on continuing match... TODO: refactor this to a better implementation + if (HomeScreen.instance.getQuestWorld().contains("Random")) { + HomeScreen.instance.updateQuestWorld(HomeScreen.instance.getQuestWorld().replace("Random","XandomX")); + } + super.actionOnContinue(); + } } diff --git a/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMenu.java b/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMenu.java index 80ee1a8ff28..d585bdad2c4 100644 --- a/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMenu.java +++ b/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMenu.java @@ -10,6 +10,7 @@ import forge.menu.FPopupMenu; import forge.model.FModel; import forge.screens.FScreen; import forge.screens.LoadingOverlay; +import forge.screens.home.HomeScreen; import forge.screens.home.LoadGameMenu.LoadGameScreen; import forge.toolbox.FEvent; import forge.toolbox.FEvent.FEventHandler; @@ -81,6 +82,18 @@ public class ConquestMenu extends FPopupMenu { Forge.openScreen(screen0, Forge.getCurrentScreen() != multiverseScreen); } + static { + //the first time planarconquest mode is launched, add button for it if in Landscape mode + if (Forge.isLandscapeMode()) { + HomeScreen.instance.addButtonForMode("-"+Localizer.getInstance().getMessage("lblPlanarConquest"), new FEventHandler() { + @Override + public void handleEvent(FEvent e) { + launchPlanarConquest(LaunchReason.StartPlanarConquest); + } + }); + } + } + public static ConquestMenu getMenu() { return conquestMenu; } diff --git a/forge-gui-mobile/src/forge/screens/quest/QuestDuelsScreen.java b/forge-gui-mobile/src/forge/screens/quest/QuestDuelsScreen.java index 98a138b6365..4987202c0de 100644 --- a/forge-gui-mobile/src/forge/screens/quest/QuestDuelsScreen.java +++ b/forge-gui-mobile/src/forge/screens/quest/QuestDuelsScreen.java @@ -2,10 +2,13 @@ package forge.screens.quest; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Align; +import forge.FThreads; import forge.assets.FSkinFont; import forge.interfaces.IButton; import forge.model.FModel; import forge.quest.QuestEventDuel; +import forge.screens.LoadingOverlay; +import forge.screens.home.HomeScreen; import forge.toolbox.FEvent; import forge.toolbox.FEvent.FEventHandler; import forge.toolbox.FLabel; @@ -67,18 +70,32 @@ public class QuestDuelsScreen extends QuestLaunchScreen { @Override public void onUpdate() { + //add loading overlay for generated decks... + if (HomeScreen.instance.getQuestWorld().contains("Random")) { + FThreads.invokeInEdtLater(new Runnable() { + @Override + public void run() { + LoadingOverlay.show(Localizer.getInstance().getMessage("lblLoadingCurrentQuest"), new Runnable() { + @Override + public void run() { + generateDuels(); + } + }); + } + }); + } else { + generateDuels(); + } + } + private void generateDuels() { pnlDuels.clear(); - List duels = FModel.getQuest().getDuelsManager().generateDuels(); - if (duels != null) { for (QuestEventDuel duel : duels) { pnlDuels.add(new QuestEventPanel(duel, pnlDuels)); } } - pnlDuels.revalidate(); - } } diff --git a/forge-gui-mobile/src/forge/screens/quest/QuestMenu.java b/forge-gui-mobile/src/forge/screens/quest/QuestMenu.java index 5723234b19c..4d183aa111f 100644 --- a/forge-gui-mobile/src/forge/screens/quest/QuestMenu.java +++ b/forge-gui-mobile/src/forge/screens/quest/QuestMenu.java @@ -160,10 +160,10 @@ public class QuestMenu extends FPopupMenu implements IVQuestStats { static { //the first time quest mode is launched, add button for it if in Landscape mode if (Forge.isLandscapeMode()) { - HomeScreen.instance.addButtonForMode(Localizer.getInstance().getMessage("lblQuestMode"), new FEventHandler() { + HomeScreen.instance.addButtonForMode("-"+Localizer.getInstance().getMessage("lblQuestMode"), new FEventHandler() { @Override public void handleEvent(FEvent e) { - launchQuestMode(LaunchReason.StartQuestMode); + launchQuestMode(LaunchReason.StartQuestMode, HomeScreen.instance.getQuestCommanderMode()); } }); } @@ -182,10 +182,8 @@ public class QuestMenu extends FPopupMenu implements IVQuestStats { NewQuest } - public static void launchQuestMode(final LaunchReason reason) { - launchQuestMode(reason, false); - } public static void launchQuestMode(final LaunchReason reason, boolean commanderMode) { + HomeScreen.instance.updateQuestCommanderMode(commanderMode); decksScreen.commanderMode = commanderMode; //attempt to load current quest final File dirQuests = new File(ForgeConstants.QUEST_SAVE_DIR); @@ -224,6 +222,7 @@ public class QuestMenu extends FPopupMenu implements IVQuestStats { LoadGameScreen.QuestMode.setAsBackScreen(true); } } + HomeScreen.instance.updateQuestWorld(FModel.getQuest().getWorld() == null ? "" : FModel.getQuest().getWorld().toString()); } }); return; @@ -248,7 +247,8 @@ public class QuestMenu extends FPopupMenu implements IVQuestStats { addItem(spellShopItem); spellShopItem.setSelected(currentScreen == spellShopScreen); addItem(bazaarItem); bazaarItem.setSelected(currentScreen == bazaarScreen); addItem(unlockSetsItem); - addItem(travelItem); + if(!HomeScreen.instance.getQuestCommanderMode()) + addItem(travelItem); addItem(statsItem); statsItem.setSelected(currentScreen == statsScreen); addItem(prefsItem); prefsItem.setSelected(currentScreen == prefsScreen); } diff --git a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java index c55b92edbba..f2f0f34708e 100644 --- a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java +++ b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java @@ -274,6 +274,29 @@ public class SettingsPage extends TabPage { } }, 3); + if (GuiBase.isAndroid()) { //this option does nothing except on Android + lstSettings.addItem(new BooleanSetting(FPref.UI_AUTO_CACHE_SIZE, + localizer.getMessage("lblAutoCacheSize"), + localizer.getMessage("nlAutoCacheSize")) { + @Override + public void select() { + super.select(); + FOptionPane.showConfirmDialog( + localizer.getMessage("lblRestartForgeDescription"), + localizer.getMessage("lblRestartForge"), + localizer.getMessage("lblRestart"), + localizer.getMessage("lblLater"), new Callback() { + @Override + public void run(Boolean result) { + if (result) { + Forge.restart(true); + } + } + }); + } + }, + 3); + } //Graphic Options lstSettings.addItem(new BooleanSetting(FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER, diff --git a/forge-gui/release-files/CONTRIBUTORS.txt b/forge-gui/release-files/CONTRIBUTORS.txt index 7609eb480d1..b88e961289a 100644 --- a/forge-gui/release-files/CONTRIBUTORS.txt +++ b/forge-gui/release-files/CONTRIBUTORS.txt @@ -28,6 +28,7 @@ nefigah Northmoc OgreBattlecruiser pfps +Ral Ryan1729 Seravy Sirspud diff --git a/forge-gui/res/cardsfolder/upcoming/abomination_of_llanowar.txt b/forge-gui/res/cardsfolder/a/abomination_of_llanowar.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/abomination_of_llanowar.txt rename to forge-gui/res/cardsfolder/a/abomination_of_llanowar.txt diff --git a/forge-gui/res/cardsfolder/upcoming/aesi_tyrant_of_gyre_strait.txt b/forge-gui/res/cardsfolder/a/aesi_tyrant_of_gyre_strait.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/aesi_tyrant_of_gyre_strait.txt rename to forge-gui/res/cardsfolder/a/aesi_tyrant_of_gyre_strait.txt diff --git a/forge-gui/res/cardsfolder/upcoming/akroma_vision_of_ixidor.txt b/forge-gui/res/cardsfolder/a/akroma_vision_of_ixidor.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/akroma_vision_of_ixidor.txt rename to forge-gui/res/cardsfolder/a/akroma_vision_of_ixidor.txt diff --git a/forge-gui/res/cardsfolder/upcoming/akromas_will.txt b/forge-gui/res/cardsfolder/a/akromas_will.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/akromas_will.txt rename to forge-gui/res/cardsfolder/a/akromas_will.txt diff --git a/forge-gui/res/cardsfolder/upcoming/alena_kessig_trapper.txt b/forge-gui/res/cardsfolder/a/alena_kessig_trapper.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/alena_kessig_trapper.txt rename to forge-gui/res/cardsfolder/a/alena_kessig_trapper.txt diff --git a/forge-gui/res/cardsfolder/upcoming/alharu_solemn_ritualist.txt b/forge-gui/res/cardsfolder/a/alharu_solemn_ritualist.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/alharu_solemn_ritualist.txt rename to forge-gui/res/cardsfolder/a/alharu_solemn_ritualist.txt diff --git a/forge-gui/res/cardsfolder/upcoming/amareth_the_lustrous.txt b/forge-gui/res/cardsfolder/a/amareth_the_lustrous.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/amareth_the_lustrous.txt rename to forge-gui/res/cardsfolder/a/amareth_the_lustrous.txt diff --git a/forge-gui/res/cardsfolder/upcoming/amateur_auteur.txt b/forge-gui/res/cardsfolder/a/amateur_auteur.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/amateur_auteur.txt rename to forge-gui/res/cardsfolder/a/amateur_auteur.txt diff --git a/forge-gui/res/cardsfolder/upcoming/amphin_mutineer.txt b/forge-gui/res/cardsfolder/a/amphin_mutineer.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/amphin_mutineer.txt rename to forge-gui/res/cardsfolder/a/amphin_mutineer.txt diff --git a/forge-gui/res/cardsfolder/upcoming/anara_wolvid_familiar.txt b/forge-gui/res/cardsfolder/a/anara_wolvid_familiar.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/anara_wolvid_familiar.txt rename to forge-gui/res/cardsfolder/a/anara_wolvid_familiar.txt diff --git a/forge-gui/res/cardsfolder/upcoming/annoyed_altisaur.txt b/forge-gui/res/cardsfolder/a/annoyed_altisaur.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/annoyed_altisaur.txt rename to forge-gui/res/cardsfolder/a/annoyed_altisaur.txt diff --git a/forge-gui/res/cardsfolder/upcoming/anointer_of_valor.txt b/forge-gui/res/cardsfolder/a/anointer_of_valor.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/anointer_of_valor.txt rename to forge-gui/res/cardsfolder/a/anointer_of_valor.txt diff --git a/forge-gui/res/cardsfolder/upcoming/apex_devastator.txt b/forge-gui/res/cardsfolder/a/apex_devastator.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/apex_devastator.txt rename to forge-gui/res/cardsfolder/a/apex_devastator.txt diff --git a/forge-gui/res/cardsfolder/upcoming/araumi_of_the_dead_tide.txt b/forge-gui/res/cardsfolder/a/araumi_of_the_dead_tide.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/araumi_of_the_dead_tide.txt rename to forge-gui/res/cardsfolder/a/araumi_of_the_dead_tide.txt diff --git a/forge-gui/res/cardsfolder/upcoming/archelos_lagoon_mystic.txt b/forge-gui/res/cardsfolder/a/archelos_lagoon_mystic.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/archelos_lagoon_mystic.txt rename to forge-gui/res/cardsfolder/a/archelos_lagoon_mystic.txt diff --git a/forge-gui/res/cardsfolder/upcoming/archon_of_coronation.txt b/forge-gui/res/cardsfolder/a/archon_of_coronation.txt similarity index 70% rename from forge-gui/res/cardsfolder/upcoming/archon_of_coronation.txt rename to forge-gui/res/cardsfolder/a/archon_of_coronation.txt index f68662bebe9..6abb6d4b093 100755 --- a/forge-gui/res/cardsfolder/upcoming/archon_of_coronation.txt +++ b/forge-gui/res/cardsfolder/a/archon_of_coronation.txt @@ -5,5 +5,5 @@ PT:5/5 K:Flying T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMonarch | TriggerDescription$ When CARDNAME enters the battlefield, you become the monarch. SVar:TrigMonarch:DB$ BecomeMonarch | Defined$ You -S:Mode$ Continuous | Affected$ You | Condition$ You.isMonarch | AddKeyword$ Damage doesn't cause you to lose life. | Description$ As long as you're the monarch, damage doesn't cause you to lose life. +S:Mode$ Continuous | Affected$ You | Condition$ Monarch | AddKeyword$ Damage doesn't cause you to lose life. | Description$ As long as you're the monarch, damage doesn't cause you to lose life. Oracle:Flying\nWhen Archon of Coronation enters the battlefield, you become the monarch.\nAs long as you're the monarch, damage doesn't cause you to lose life. diff --git a/forge-gui/res/cardsfolder/upcoming/ardenn_intrepid_archaeologist.txt b/forge-gui/res/cardsfolder/a/ardenn_intrepid_archaeologist.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/ardenn_intrepid_archaeologist.txt rename to forge-gui/res/cardsfolder/a/ardenn_intrepid_archaeologist.txt diff --git a/forge-gui/res/cardsfolder/upcoming/armix_filigree_thrasher.txt b/forge-gui/res/cardsfolder/a/armix_filigree_thrasher.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/armix_filigree_thrasher.txt rename to forge-gui/res/cardsfolder/a/armix_filigree_thrasher.txt diff --git a/forge-gui/res/cardsfolder/upcoming/armored_skyhunter.txt b/forge-gui/res/cardsfolder/a/armored_skyhunter.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/armored_skyhunter.txt rename to forge-gui/res/cardsfolder/a/armored_skyhunter.txt diff --git a/forge-gui/res/cardsfolder/upcoming/aurora_phoenix.txt b/forge-gui/res/cardsfolder/a/aurora_phoenix.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/aurora_phoenix.txt rename to forge-gui/res/cardsfolder/a/aurora_phoenix.txt diff --git a/forge-gui/res/cardsfolder/upcoming/averna_the_chaos_bloom.txt b/forge-gui/res/cardsfolder/a/averna_the_chaos_bloom.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/averna_the_chaos_bloom.txt rename to forge-gui/res/cardsfolder/a/averna_the_chaos_bloom.txt diff --git a/forge-gui/res/cardsfolder/upcoming/azure_fleet_admiral.txt b/forge-gui/res/cardsfolder/a/azure_fleet_admiral.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/azure_fleet_admiral.txt rename to forge-gui/res/cardsfolder/a/azure_fleet_admiral.txt diff --git a/forge-gui/res/cardsfolder/upcoming/beast_in_show.txt b/forge-gui/res/cardsfolder/b/beast_in_show.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/beast_in_show.txt rename to forge-gui/res/cardsfolder/b/beast_in_show.txt diff --git a/forge-gui/res/cardsfolder/upcoming/belbe_corrupted_observer.txt b/forge-gui/res/cardsfolder/b/belbe_corrupted_observer.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/belbe_corrupted_observer.txt rename to forge-gui/res/cardsfolder/b/belbe_corrupted_observer.txt diff --git a/forge-gui/res/cardsfolder/upcoming/bell_borca_spectral_sergeant.txt b/forge-gui/res/cardsfolder/b/bell_borca_spectral_sergeant.txt similarity index 99% rename from forge-gui/res/cardsfolder/upcoming/bell_borca_spectral_sergeant.txt rename to forge-gui/res/cardsfolder/b/bell_borca_spectral_sergeant.txt index 0ef1f7333e4..40d534cdec2 100644 --- a/forge-gui/res/cardsfolder/upcoming/bell_borca_spectral_sergeant.txt +++ b/forge-gui/res/cardsfolder/b/bell_borca_spectral_sergeant.txt @@ -1,5 +1,5 @@ Name:Bell Borca, Spectral Sergeant -ManaCost:2 B R +ManaCost:2 R W Types:Legendary Creature Spirit Soldier PT:*/5 Text:Note the converted mana cost of each card as it's put into exile. diff --git a/forge-gui/res/cardsfolder/upcoming/benevolent_blessing.txt b/forge-gui/res/cardsfolder/b/benevolent_blessing.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/benevolent_blessing.txt rename to forge-gui/res/cardsfolder/b/benevolent_blessing.txt diff --git a/forge-gui/res/cardsfolder/upcoming/biowaste_blob.txt b/forge-gui/res/cardsfolder/b/biowaste_blob.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/biowaste_blob.txt rename to forge-gui/res/cardsfolder/b/biowaste_blob.txt diff --git a/forge-gui/res/cardsfolder/upcoming/bladegriff_prototype.txt b/forge-gui/res/cardsfolder/b/bladegriff_prototype.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/bladegriff_prototype.txt rename to forge-gui/res/cardsfolder/b/bladegriff_prototype.txt diff --git a/forge-gui/res/cardsfolder/b/blademane_baku.txt b/forge-gui/res/cardsfolder/b/blademane_baku.txt index 40355bb533f..6a76267a5de 100644 --- a/forge-gui/res/cardsfolder/b/blademane_baku.txt +++ b/forge-gui/res/cardsfolder/b/blademane_baku.txt @@ -3,12 +3,10 @@ ManaCost:1 R Types:Creature Spirit PT:1/1 T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, you may put a ki counter on CARDNAME. -SVar:TrigPutCounter:DB$PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 A:AB$ Pump | Cost$ 1 SubCounter | Defined$ Self | NumAtt$ +Y | References$ X,Y | SpellDescription$ For each counter removed, CARDNAME gets +2/+0 until end of turn. -SVar:X:XChoice -#ChosenX SVar created by Cost payment -SVar:Y:SVar$ChosenX/Times.2 +SVar:X:Count$xPaid +SVar:Y:SVar$X/Times.2 AI:RemoveDeck:Random DeckHints:Type$Spirit|Arcane -SVar:Picture:http://www.wizards.com/global/images/magic/general/blademane_baku.jpg Oracle:Whenever you cast a Spirit or Arcane spell, you may put a ki counter on Blademane Baku.\n{1}, Remove X ki counters from Blademane Baku: For each counter removed, Blademane Baku gets +2/+0 until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/blast_from_the_past.txt b/forge-gui/res/cardsfolder/b/blast_from_the_past.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/blast_from_the_past.txt rename to forge-gui/res/cardsfolder/b/blast_from_the_past.txt diff --git a/forge-gui/res/cardsfolder/upcoming/blazing_sunsteel.txt b/forge-gui/res/cardsfolder/b/blazing_sunsteel.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/blazing_sunsteel.txt rename to forge-gui/res/cardsfolder/b/blazing_sunsteel.txt diff --git a/forge-gui/res/cardsfolder/upcoming/blim_comedic_genius.txt b/forge-gui/res/cardsfolder/b/blim_comedic_genius.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/blim_comedic_genius.txt rename to forge-gui/res/cardsfolder/b/blim_comedic_genius.txt diff --git a/forge-gui/res/cardsfolder/upcoming/boarding_party.txt b/forge-gui/res/cardsfolder/b/boarding_party.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/boarding_party.txt rename to forge-gui/res/cardsfolder/b/boarding_party.txt diff --git a/forge-gui/res/cardsfolder/upcoming/body_of_knowledge.txt b/forge-gui/res/cardsfolder/b/body_of_knowledge.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/body_of_knowledge.txt rename to forge-gui/res/cardsfolder/b/body_of_knowledge.txt diff --git a/forge-gui/res/cardsfolder/upcoming/breeches_brazen_plunderer.txt b/forge-gui/res/cardsfolder/b/breeches_brazen_plunderer.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/breeches_brazen_plunderer.txt rename to forge-gui/res/cardsfolder/b/breeches_brazen_plunderer.txt diff --git a/forge-gui/res/cardsfolder/upcoming/briarblade_adept.txt b/forge-gui/res/cardsfolder/b/briarblade_adept.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/briarblade_adept.txt rename to forge-gui/res/cardsfolder/b/briarblade_adept.txt diff --git a/forge-gui/res/cardsfolder/upcoming/brinelin_the_moon_kraken.txt b/forge-gui/res/cardsfolder/b/brinelin_the_moon_kraken.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/brinelin_the_moon_kraken.txt rename to forge-gui/res/cardsfolder/b/brinelin_the_moon_kraken.txt diff --git a/forge-gui/res/cardsfolder/b/butcher_orgg.txt b/forge-gui/res/cardsfolder/b/butcher_orgg.txt new file mode 100644 index 00000000000..120e3ed6c58 --- /dev/null +++ b/forge-gui/res/cardsfolder/b/butcher_orgg.txt @@ -0,0 +1,7 @@ +Name:Butcher Orgg +ManaCost:4 R R R +Types:Creature Orgg +PT:6/6 +K:You may assign CARDNAME's combat damage divided as you choose among defending player and/or any number of creatures they control. +AI:RemoveDeck:All +Oracle:You may assign Butcher Orgg’s combat damage divided as you choose among defending player and/or any number of creatures they control. diff --git a/forge-gui/res/cardsfolder/upcoming/captain_vargus_wrath.txt b/forge-gui/res/cardsfolder/c/captain_vargus_wrath.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/captain_vargus_wrath.txt rename to forge-gui/res/cardsfolder/c/captain_vargus_wrath.txt diff --git a/forge-gui/res/cardsfolder/c/chamber_sentry.txt b/forge-gui/res/cardsfolder/c/chamber_sentry.txt index 476a1d5715a..877e5f46cf3 100644 --- a/forge-gui/res/cardsfolder/c/chamber_sentry.txt +++ b/forge-gui/res/cardsfolder/c/chamber_sentry.txt @@ -4,7 +4,7 @@ Types:Artifact Creature Construct PT:0/0 K:etbCounter:P1P1:Y:no Condition:CARDNAME enters the battlefield with a +1/+1 counter on it for each color of mana spent to cast it. SVar:Y:Count$Converge -A:AB$ DealDamage | Announce$ X | Cost$ X T SubCounter | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | References$ X | AILogic$ XCountersDamage | SpellDescription$ CARDNAME deals X damage to any target. +A:AB$ DealDamage | Cost$ X T SubCounter | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | References$ X | SpellDescription$ CARDNAME deals X damage to any target. SVar:X:Count$xPaid A:AB$ ChangeZone | Cost$ W U B R G | Origin$ Graveyard | Destination$ Hand | ActivationZone$ Graveyard | SpellDescription$ Return CARDNAME from your graveyard to your hand. SVar:DiscardMe:1 diff --git a/forge-gui/res/cardsfolder/c/chisei_heart_of_oceans.txt b/forge-gui/res/cardsfolder/c/chisei_heart_of_oceans.txt index 3abbb3ec535..5e9cca07dfe 100644 --- a/forge-gui/res/cardsfolder/c/chisei_heart_of_oceans.txt +++ b/forge-gui/res/cardsfolder/c/chisei_heart_of_oceans.txt @@ -4,8 +4,8 @@ Types:Legendary Creature Spirit PT:4/4 K:Flying T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigSac | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you remove a counter from a permanent you control. -SVar:TrigSac:DB$ Sacrifice | Defined$ Self | UnlessPayer$ You | UnlessCost$ RemoveAnyCounter<1/Permanent.YouCtrl/a permanent you control> +SVar:TrigSac:DB$ Sacrifice | Defined$ Self | UnlessPayer$ You | UnlessCost$ RemoveAnyCounter<1/Any/Permanent.YouCtrl/a permanent you control> DeckNeeds:Ability$Counters SVar:NeedsToPlay:Creature.YouCtrl+HasCounters -SVar:Picture:http://www.wizards.com/global/images/magic/general/chisei_heart_of_oceans.jpg +SVar:AIRemoveCounterCostPriority:ANY Oracle:Flying\nAt the beginning of your upkeep, sacrifice Chisei, Heart of Oceans unless you remove a counter from a permanent you control. diff --git a/forge-gui/res/cardsfolder/c/chromatic_orrery.txt b/forge-gui/res/cardsfolder/c/chromatic_orrery.txt index 68f184423fb..d364c3cb044 100755 --- a/forge-gui/res/cardsfolder/c/chromatic_orrery.txt +++ b/forge-gui/res/cardsfolder/c/chromatic_orrery.txt @@ -5,5 +5,5 @@ S:Mode$ Continuous | Affected$ You | ManaColorConversion$ Additive | WhiteConver SVar:NonStackingEffect:True A:AB$ Mana | Cost$ T | Produced$ C | Amount$ 5 | SpellDescription$ Add {C}{C}{C}{C}{C}. A:AB$ Draw | Cost$ 5 T | NumCards$ X | References$ X | SpellDescription$ Draw a card for each color among permanents you control. -SVar:X:Count$ColorsCtrl Permanent.YouCtrl+inZoneBattlefield +SVar:X:Count$ColorsCtrl Permanent Oracle:You may spend mana as though it were mana of any color.\n{T}: Add {C}{C}{C}{C}{C}.\n{5}, {T}: Draw a card for each color among permanents you control. diff --git a/forge-gui/res/cardsfolder/upcoming/clambassadors.txt b/forge-gui/res/cardsfolder/c/clambassadors.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/clambassadors.txt rename to forge-gui/res/cardsfolder/c/clambassadors.txt diff --git a/forge-gui/res/cardsfolder/c/coalition_victory.txt b/forge-gui/res/cardsfolder/c/coalition_victory.txt index 2e9d71d3316..8b038c972bf 100644 --- a/forge-gui/res/cardsfolder/c/coalition_victory.txt +++ b/forge-gui/res/cardsfolder/c/coalition_victory.txt @@ -2,9 +2,8 @@ Name:Coalition Victory ManaCost:3 W U B R G Types:Sorcery A:SP$ WinsGame | Cost$ 3 W U B R G | Defined$ You | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ10 | References$ X,Y,Z | SpellDescription$ You win the game if you control a land of each basic land type and a creature of each color. -SVar:Z:Count$ColoredCreatures +SVar:Z:Count$ColorsCtrl Creature SVar:Y:Count$Domain SVar:X:SVar$Y/Plus.Z AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/coalition_victory.jpg Oracle:You win the game if you control a land of each basic land type and a creature of each color. diff --git a/forge-gui/res/cardsfolder/upcoming/coastline_marauders.txt b/forge-gui/res/cardsfolder/c/coastline_marauders.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/coastline_marauders.txt rename to forge-gui/res/cardsfolder/c/coastline_marauders.txt diff --git a/forge-gui/res/cardsfolder/upcoming/coercive_recruiter.txt b/forge-gui/res/cardsfolder/c/coercive_recruiter.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/coercive_recruiter.txt rename to forge-gui/res/cardsfolder/c/coercive_recruiter.txt diff --git a/forge-gui/res/cardsfolder/upcoming/colfenor_the_last_yew.txt b/forge-gui/res/cardsfolder/c/colfenor_the_last_yew.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/colfenor_the_last_yew.txt rename to forge-gui/res/cardsfolder/c/colfenor_the_last_yew.txt diff --git a/forge-gui/res/cardsfolder/upcoming/commanders_plate.txt b/forge-gui/res/cardsfolder/c/commanders_plate.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/commanders_plate.txt rename to forge-gui/res/cardsfolder/c/commanders_plate.txt diff --git a/forge-gui/res/cardsfolder/c/commune_with_lava.txt b/forge-gui/res/cardsfolder/c/commune_with_lava.txt index 09be87b5a45..256327f9ebf 100644 --- a/forge-gui/res/cardsfolder/c/commune_with_lava.txt +++ b/forge-gui/res/cardsfolder/c/commune_with_lava.txt @@ -2,7 +2,7 @@ Name:Commune with Lava ManaCost:X R R Types:Instant A:SP$ Dig | Cost$ X R R | Defined$ You | DigNum$ X | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | References$ X | SubAbility$ DBMayPlay | SpellDescription$ Exile the top X cards of your library. Until the end of your next turn, you may play those cards. -SVar:DBMayPlay:DB$ Effect | StaticAbilities$ STCommuned | Duration$ UntilTheEndOfYourNextTurn | RememberObjects$ Remembered | ForgetOnMoved$ Exile +SVar:DBMayPlay:DB$ Effect | StaticAbilities$ STCommuned | Duration$ UntilTheEndOfYourNextTurn | RememberObjects$ Remembered | ForgetOnMoved$ Exile | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:STCommuned:Mode$ Continuous | Affected$ Card.IsRemembered | EffectZone$ Command | AffectedZone$ Exile | MayPlay$ True | Description$ Until the end of your next turn, you may play those cards. SVar:X:Count$xPaid diff --git a/forge-gui/res/cardsfolder/c/congregation_at_dawn.txt b/forge-gui/res/cardsfolder/c/congregation_at_dawn.txt index a985ecc886b..69971054006 100644 --- a/forge-gui/res/cardsfolder/c/congregation_at_dawn.txt +++ b/forge-gui/res/cardsfolder/c/congregation_at_dawn.txt @@ -1,6 +1,5 @@ Name:Congregation at Dawn ManaCost:G G W Types:Instant -A:SP$ ChangeZone | Cost$ G G W | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Creature | ChangeNum$ 3 | SpellDescription$ Search your library for up to three creature cards and reveal them. Shuffle you library, then put those cards on top of it in any order. -SVar:Picture:http://www.wizards.com/global/images/magic/general/congregation_at_dawn.jpg +A:SP$ ChangeZone | Cost$ G G W | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Creature | ChangeNum$ 3 | SpellDescription$ Search your library for up to three creature cards and reveal them. Shuffle your library, then put those cards on top of it in any order. Oracle:Search your library for up to three creature cards and reveal them. Shuffle your library, then put those cards on top of it in any order. diff --git a/forge-gui/res/cardsfolder/c/conquerors_flail.txt b/forge-gui/res/cardsfolder/c/conquerors_flail.txt index e1aad141b8c..a7936dda2c6 100644 --- a/forge-gui/res/cardsfolder/c/conquerors_flail.txt +++ b/forge-gui/res/cardsfolder/c/conquerors_flail.txt @@ -3,7 +3,6 @@ ManaCost:2 Types:Artifact Equipment S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ X | AddToughness$ X | References$ X | Description$ Equipped creature gets +1/+1 for each color among permanents you control. S:Mode$ CantBeCast | ValidCard$ Card | Condition$ PlayerTurn | Caster$ Opponent | IsPresent$ Card.Self+AttachedTo Creature | Description$ As long as Conqueror's Flail is attached to a creature, your opponents can't cast spells during your turn. -SVar:X:Count$ColorsCtrl Permanent.YouCtrl+inZoneBattlefield +SVar:X:Count$ColorsCtrl Permanent K:Equip:2 -SVar:Picture:http://www.wizards.com/global/images/magic/general/conquerors_flail.jpg Oracle:Equipped creature gets +1/+1 for each color among permanents you control.\nAs long as Conqueror's Flail is attached to a creature, your opponents can't cast spells during your turn.\nEquip {2} diff --git a/forge-gui/res/cardsfolder/upcoming/court_of_ambition.txt b/forge-gui/res/cardsfolder/c/court_of_ambition.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/court_of_ambition.txt rename to forge-gui/res/cardsfolder/c/court_of_ambition.txt diff --git a/forge-gui/res/cardsfolder/upcoming/court_of_bounty.txt b/forge-gui/res/cardsfolder/c/court_of_bounty.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/court_of_bounty.txt rename to forge-gui/res/cardsfolder/c/court_of_bounty.txt diff --git a/forge-gui/res/cardsfolder/upcoming/court_of_cunning.txt b/forge-gui/res/cardsfolder/c/court_of_cunning.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/court_of_cunning.txt rename to forge-gui/res/cardsfolder/c/court_of_cunning.txt diff --git a/forge-gui/res/cardsfolder/upcoming/court_of_grace.txt b/forge-gui/res/cardsfolder/c/court_of_grace.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/court_of_grace.txt rename to forge-gui/res/cardsfolder/c/court_of_grace.txt diff --git a/forge-gui/res/cardsfolder/upcoming/court_of_ire.txt b/forge-gui/res/cardsfolder/c/court_of_ire.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/court_of_ire.txt rename to forge-gui/res/cardsfolder/c/court_of_ire.txt diff --git a/forge-gui/res/cardsfolder/upcoming/crimson_fleet_commodore.txt b/forge-gui/res/cardsfolder/c/crimson_fleet_commodore.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/crimson_fleet_commodore.txt rename to forge-gui/res/cardsfolder/c/crimson_fleet_commodore.txt diff --git a/forge-gui/res/cardsfolder/upcoming/crow_storm.txt b/forge-gui/res/cardsfolder/c/crow_storm.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/crow_storm.txt rename to forge-gui/res/cardsfolder/c/crow_storm.txt diff --git a/forge-gui/res/cardsfolder/upcoming/curious_killbot.txt b/forge-gui/res/cardsfolder/c/curious_killbot.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/curious_killbot.txt rename to forge-gui/res/cardsfolder/c/curious_killbot.txt diff --git a/forge-gui/res/cardsfolder/upcoming/dargo_the_shipwrecker.txt b/forge-gui/res/cardsfolder/d/dargo_the_shipwrecker.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/dargo_the_shipwrecker.txt rename to forge-gui/res/cardsfolder/d/dargo_the_shipwrecker.txt diff --git a/forge-gui/res/cardsfolder/upcoming/dawnblade_regent.txt b/forge-gui/res/cardsfolder/d/dawnblade_regent.txt similarity index 81% rename from forge-gui/res/cardsfolder/upcoming/dawnblade_regent.txt rename to forge-gui/res/cardsfolder/d/dawnblade_regent.txt index 71f5f107711..f3ea5998b43 100644 --- a/forge-gui/res/cardsfolder/upcoming/dawnblade_regent.txt +++ b/forge-gui/res/cardsfolder/d/dawnblade_regent.txt @@ -4,5 +4,5 @@ Types:Creature Elk PT:8/8 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMonarch | TriggerDescription$ When CARDNAME enters the battlefield, you become the monarch. SVar:TrigMonarch:DB$ BecomeMonarch | Defined$ You -S:Mode$ Continuous | Affected$ Permanent.YouCtrl | AddKeyword$ Hexproof | Condition$ You.isMonarch | Description$ As long as you're the monarch, permanents you control have hexproof. +S:Mode$ Continuous | Affected$ Permanent.YouCtrl | AddKeyword$ Hexproof | Condition$ Monarch | Description$ As long as you're the monarch, permanents you control have hexproof. Oracle:When Dawnglade Regent enters the battlefield, you become the monarch.\nAs long as you're the monarch, permanents you control have hexproof. diff --git a/forge-gui/res/cardsfolder/upcoming/delighted_killbot.txt b/forge-gui/res/cardsfolder/d/delighted_killbot.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/delighted_killbot.txt rename to forge-gui/res/cardsfolder/d/delighted_killbot.txt diff --git a/forge-gui/res/cardsfolder/upcoming/demonic_lore.txt b/forge-gui/res/cardsfolder/d/demonic_lore.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/demonic_lore.txt rename to forge-gui/res/cardsfolder/d/demonic_lore.txt diff --git a/forge-gui/res/cardsfolder/upcoming/despondent_killbot.txt b/forge-gui/res/cardsfolder/d/despondent_killbot.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/despondent_killbot.txt rename to forge-gui/res/cardsfolder/d/despondent_killbot.txt diff --git a/forge-gui/res/cardsfolder/upcoming/distract.txt b/forge-gui/res/cardsfolder/d/distract.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/distract.txt rename to forge-gui/res/cardsfolder/d/distract.txt diff --git a/forge-gui/res/cardsfolder/upcoming/drake_stone.txt b/forge-gui/res/cardsfolder/d/drake_stone.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/drake_stone.txt rename to forge-gui/res/cardsfolder/d/drake_stone.txt diff --git a/forge-gui/res/cardsfolder/upcoming/earl_of_squirrel.txt b/forge-gui/res/cardsfolder/e/earl_of_squirrel.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/earl_of_squirrel.txt rename to forge-gui/res/cardsfolder/e/earl_of_squirrel.txt diff --git a/forge-gui/res/cardsfolder/upcoming/eligeth_crossroads_augur.txt b/forge-gui/res/cardsfolder/e/eligeth_crossroads_augur.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/eligeth_crossroads_augur.txt rename to forge-gui/res/cardsfolder/e/eligeth_crossroads_augur.txt diff --git a/forge-gui/res/cardsfolder/upcoming/elvish_doomsayer.txt b/forge-gui/res/cardsfolder/e/elvish_doomsayer.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/elvish_doomsayer.txt rename to forge-gui/res/cardsfolder/e/elvish_doomsayer.txt diff --git a/forge-gui/res/cardsfolder/upcoming/elvish_dreadlord.txt b/forge-gui/res/cardsfolder/e/elvish_dreadlord.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/elvish_dreadlord.txt rename to forge-gui/res/cardsfolder/e/elvish_dreadlord.txt diff --git a/forge-gui/res/cardsfolder/upcoming/emberwilde_captain.txt b/forge-gui/res/cardsfolder/e/emberwilde_captain.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/emberwilde_captain.txt rename to forge-gui/res/cardsfolder/e/emberwilde_captain.txt diff --git a/forge-gui/res/cardsfolder/upcoming/enraged_killbot.txt b/forge-gui/res/cardsfolder/e/enraged_killbot.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/enraged_killbot.txt rename to forge-gui/res/cardsfolder/e/enraged_killbot.txt diff --git a/forge-gui/res/cardsfolder/upcoming/enthrall.txt b/forge-gui/res/cardsfolder/e/enthrall.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/enthrall.txt rename to forge-gui/res/cardsfolder/e/enthrall.txt diff --git a/forge-gui/res/cardsfolder/e/entourage_of_trest.txt b/forge-gui/res/cardsfolder/e/entourage_of_trest.txt index 8b0c23ee88b..7f8ea4dca78 100644 --- a/forge-gui/res/cardsfolder/e/entourage_of_trest.txt +++ b/forge-gui/res/cardsfolder/e/entourage_of_trest.txt @@ -4,6 +4,6 @@ Types:Creature Elf Soldier PT:4/4 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMonarch | TriggerDescription$ When CARDNAME enters the battlefield, you become the monarch. SVar:TrigMonarch:DB$ BecomeMonarch | Defined$ You -S:Mode$ Continuous | Affected$ Card.Self | Condition$ You.isMonarch | CanBlockAmount$ 1 | Description$ CARDNAME can block an additional creature each combat as long as you're the monarch. +S:Mode$ Continuous | Affected$ Card.Self | Condition$ Monarch | CanBlockAmount$ 1 | Description$ CARDNAME can block an additional creature each combat as long as you're the monarch. SVar:Picture:http://www.wizards.com/global/images/magic/general/entourage_of_trest.jpg Oracle:When Entourage of Trest enters the battlefield, you become the monarch.\nEntourage of Trest can block an additional creature each combat as long as you're the monarch. diff --git a/forge-gui/res/cardsfolder/upcoming/esior_wardwing_familiar.txt b/forge-gui/res/cardsfolder/e/esior_wardwing_familiar.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/esior_wardwing_familiar.txt rename to forge-gui/res/cardsfolder/e/esior_wardwing_familiar.txt diff --git a/forge-gui/res/cardsfolder/upcoming/explosion_of_riches.txt b/forge-gui/res/cardsfolder/e/explosion_of_riches.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/explosion_of_riches.txt rename to forge-gui/res/cardsfolder/e/explosion_of_riches.txt diff --git a/forge-gui/res/cardsfolder/upcoming/exquisite_huntmaster.txt b/forge-gui/res/cardsfolder/e/exquisite_huntmaster.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/exquisite_huntmaster.txt rename to forge-gui/res/cardsfolder/e/exquisite_huntmaster.txt diff --git a/forge-gui/res/cardsfolder/upcoming/eyeblight_cullers.txt b/forge-gui/res/cardsfolder/e/eyeblight_cullers.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/eyeblight_cullers.txt rename to forge-gui/res/cardsfolder/e/eyeblight_cullers.txt diff --git a/forge-gui/res/cardsfolder/f/faeburrow_elder.txt b/forge-gui/res/cardsfolder/f/faeburrow_elder.txt index 46ee607e76c..3b65c041662 100644 --- a/forge-gui/res/cardsfolder/f/faeburrow_elder.txt +++ b/forge-gui/res/cardsfolder/f/faeburrow_elder.txt @@ -4,7 +4,7 @@ Types:Creature Treefolk Druid PT:0/0 K:Vigilance S:Mode$ Continuous | Affected$ Card.Self | AddPower$ X | AddToughness$ X | Description$ CARDNAME gets +1/+1 for each color among permanents you control. -SVar:X:Count$ColorsCtrl Permanent.YouCtrl+inZoneBattlefield +SVar:X:Count$ColorsCtrl Permanent A:AB$ Mana | Cost$ T | Produced$ Special EachColorAmong_Permanent.YouCtrl | SpellDescription$ For each color among permanents you control, add one mana of that color. AI:RemoveDeck:All Oracle:Vigilance\nFaeburrow Elder gets +1/+1 for each color among permanents you control.\n{T}: For each color among permanents you control, add one mana of that color. diff --git a/forge-gui/res/cardsfolder/upcoming/fall_from_favor.txt b/forge-gui/res/cardsfolder/f/fall_from_favor.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/fall_from_favor.txt rename to forge-gui/res/cardsfolder/f/fall_from_favor.txt diff --git a/forge-gui/res/cardsfolder/upcoming/falthis_shadowcat_familiar.txt b/forge-gui/res/cardsfolder/f/falthis_shadowcat_familiar.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/falthis_shadowcat_familiar.txt rename to forge-gui/res/cardsfolder/f/falthis_shadowcat_familiar.txt diff --git a/forge-gui/res/cardsfolder/upcoming/fathom_fleet_swordjack.txt b/forge-gui/res/cardsfolder/f/fathom_fleet_swordjack.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/fathom_fleet_swordjack.txt rename to forge-gui/res/cardsfolder/f/fathom_fleet_swordjack.txt diff --git a/forge-gui/res/cardsfolder/upcoming/feast_of_succession.txt b/forge-gui/res/cardsfolder/f/feast_of_succession.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/feast_of_succession.txt rename to forge-gui/res/cardsfolder/f/feast_of_succession.txt diff --git a/forge-gui/res/cardsfolder/upcoming/fin_clade_fugitives.txt b/forge-gui/res/cardsfolder/f/fin_clade_fugitives.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/fin_clade_fugitives.txt rename to forge-gui/res/cardsfolder/f/fin_clade_fugitives.txt diff --git a/forge-gui/res/cardsfolder/upcoming/flamekin_herald.txt b/forge-gui/res/cardsfolder/f/flamekin_herald.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/flamekin_herald.txt rename to forge-gui/res/cardsfolder/f/flamekin_herald.txt diff --git a/forge-gui/res/cardsfolder/upcoming/forceful_denial.txt b/forge-gui/res/cardsfolder/f/forceful_denial.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/forceful_denial.txt rename to forge-gui/res/cardsfolder/f/forceful_denial.txt diff --git a/forge-gui/res/cardsfolder/upcoming/form_of_the_squirrel.txt b/forge-gui/res/cardsfolder/f/form_of_the_squirrel.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/form_of_the_squirrel.txt rename to forge-gui/res/cardsfolder/f/form_of_the_squirrel.txt diff --git a/forge-gui/res/cardsfolder/upcoming/frenzied_saddlebrute.txt b/forge-gui/res/cardsfolder/f/frenzied_saddlebrute.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/frenzied_saddlebrute.txt rename to forge-gui/res/cardsfolder/f/frenzied_saddlebrute.txt diff --git a/forge-gui/res/cardsfolder/g/garruk_primal_hunter.txt b/forge-gui/res/cardsfolder/g/garruk_primal_hunter.txt index ec658e672f6..c6c8ab5f467 100644 --- a/forge-gui/res/cardsfolder/g/garruk_primal_hunter.txt +++ b/forge-gui/res/cardsfolder/g/garruk_primal_hunter.txt @@ -7,5 +7,4 @@ A:AB$ Draw | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | Defined$ You | N SVar:X:Count$GreatestPower_Creature.YouCtrl A:AB$ Token | Cost$ SubCounter<6/LOYALTY> | Planeswalker$ True | Ultimate$ True | TokenAmount$ Y | TokenScript$ g_6_6_wurm | TokenOwner$ You | LegacyImage$ g 6 6 wurm m12 | SpellDescription$ Create a 6/6 green Wurm creature for each land you control. SVar:Y:Count$Valid Land.YouCtrl -SVar:Picture:http://www.wizards.com/global/images/magic/general/garruk_primal_hunter.jpg Oracle:[+1]: Create a 3/3 green Beast creature token.\n[-3]: Draw cards equal to the greatest power among creatures you control.\n[-6]: Create a 6/6 green Wurm creature for each land you control. diff --git a/forge-gui/res/cardsfolder/upcoming/ghen_arcanum_weaver.txt b/forge-gui/res/cardsfolder/g/ghen_arcanum_weaver.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/ghen_arcanum_weaver.txt rename to forge-gui/res/cardsfolder/g/ghen_arcanum_weaver.txt diff --git a/forge-gui/res/cardsfolder/upcoming/ghost_of_ramirez_depietro.txt b/forge-gui/res/cardsfolder/g/ghost_of_ramirez_depietro.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/ghost_of_ramirez_depietro.txt rename to forge-gui/res/cardsfolder/g/ghost_of_ramirez_depietro.txt diff --git a/forge-gui/res/cardsfolder/upcoming/gilanra_caller_of_wirewood.txt b/forge-gui/res/cardsfolder/g/gilanra_caller_of_wirewood.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/gilanra_caller_of_wirewood.txt rename to forge-gui/res/cardsfolder/g/gilanra_caller_of_wirewood.txt diff --git a/forge-gui/res/cardsfolder/upcoming/glacian_powerstone_engineer.txt b/forge-gui/res/cardsfolder/g/glacian_powerstone_engineer.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/glacian_powerstone_engineer.txt rename to forge-gui/res/cardsfolder/g/glacian_powerstone_engineer.txt diff --git a/forge-gui/res/cardsfolder/upcoming/gnostro_voice_of_the_crags.txt b/forge-gui/res/cardsfolder/g/gnostro_voice_of_the_crags.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/gnostro_voice_of_the_crags.txt rename to forge-gui/res/cardsfolder/g/gnostro_voice_of_the_crags.txt diff --git a/forge-gui/res/cardsfolder/upcoming/gor_muldrak_amphinologist.txt b/forge-gui/res/cardsfolder/g/gor_muldrak_amphinologist.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/gor_muldrak_amphinologist.txt rename to forge-gui/res/cardsfolder/g/gor_muldrak_amphinologist.txt diff --git a/forge-gui/res/cardsfolder/upcoming/guildless_commons.txt b/forge-gui/res/cardsfolder/g/guildless_commons.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/guildless_commons.txt rename to forge-gui/res/cardsfolder/g/guildless_commons.txt diff --git a/forge-gui/res/cardsfolder/upcoming/halana_kessig_ranger.txt b/forge-gui/res/cardsfolder/h/halana_kessig_ranger.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/halana_kessig_ranger.txt rename to forge-gui/res/cardsfolder/h/halana_kessig_ranger.txt diff --git a/forge-gui/res/cardsfolder/upcoming/hamza_guardian_of_arashin.txt b/forge-gui/res/cardsfolder/h/hamza_guardian_of_arashin.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/hamza_guardian_of_arashin.txt rename to forge-gui/res/cardsfolder/h/hamza_guardian_of_arashin.txt diff --git a/forge-gui/res/cardsfolder/upcoming/hans_eriksson.txt b/forge-gui/res/cardsfolder/h/hans_eriksson.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/hans_eriksson.txt rename to forge-gui/res/cardsfolder/h/hans_eriksson.txt diff --git a/forge-gui/res/cardsfolder/h/happily_ever_after.txt b/forge-gui/res/cardsfolder/h/happily_ever_after.txt index 30b5e69da1b..683fd942c34 100644 --- a/forge-gui/res/cardsfolder/h/happily_ever_after.txt +++ b/forge-gui/res/cardsfolder/h/happily_ever_after.txt @@ -6,7 +6,7 @@ SVar:TrigGainLife:DB$ GainLife | Defined$ Player | LifeAmount$ 5 | SubAbility$ D SVar:DBDraw:DB$ Draw | Defined$ Player | NumCards$ 1 T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ Z | SVarCompare$ EQ11 | LifeTotal$ You | LifeAmount$ GEW | References$ X,Y,Z,W | Execute$ TrigWin | TriggerDescription$ At the beginning of your upkeep, if there are five colors among permanents you control, there are six or more card types among permanents you control and/or cards in your graveyard, and your life total is greater than or equal to your starting life total, you win the game. SVar:TrigWin:DB$ WinsGame | Defined$ You -SVar:X:Count$ColorsCtrl Permanent.YouCtrl+inZoneBattlefield/LimitMax.5 +SVar:X:Count$ColorsCtrl Permanent/LimitMax.5 SVar:Y:Count$CardControllerTypes.Battlefield,Graveyard/LimitMax.6 SVar:Z:SVar$X/Plus.Y SVar:W:Count$YourStartingLife diff --git a/forge-gui/res/cardsfolder/h/hazezon_tamar.txt b/forge-gui/res/cardsfolder/h/hazezon_tamar.txt index f031278ce43..ba10de6003e 100644 --- a/forge-gui/res/cardsfolder/h/hazezon_tamar.txt +++ b/forge-gui/res/cardsfolder/h/hazezon_tamar.txt @@ -3,10 +3,11 @@ ManaCost:4 R G W Types:Legendary Creature Human Warrior PT:2/4 T:Mode$ ChangesZone | ValidCard$ Creature.Self | Origin$ Any | Destination$ Battlefield | Execute$ DelTrig | TriggerDescription$ When CARDNAME enters the battlefield, create X 1/1 Sand Warrior creature tokens that are red, green, and white at the beginning of your next upkeep, where X is the number of lands you control at that time. -SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigTokens | TriggerController$ TriggeredCardController | RememberObjects$ TriggeredCardController | TriggerDescription$ Create a 1/1 Sand Warrior creature token that are red, green, and white for each land you control. -SVar:TrigTokens:DB$ Token | TokenAmount$ X | TokenScript$ rgw_1_1_sand_warrior | TokenOwner$ DelayTriggerRemembered | LegacyImage$ rgw 1 1 sand warrior leg | References$ X -T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Any | Execute$ TrigExile | TriggerDescription$ When Hazezon leaves the battlefield, exile all Sand Warriors. +SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigTokens | TriggerDescription$ Create X 1/1 Sand Warrior creature tokens that are red, green, and white, where X is the number of lands you control. +SVar:TrigTokens:DB$ Token | TokenAmount$ X | TokenScript$ rgw_1_1_sand_warrior | TokenOwner$ You | References$ X +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Any | Execute$ TrigExile | TriggerDescription$ When CARDNAME leaves the battlefield, exile all Sand Warriors. +#NICKNAME upgrade SVar:TrigExile:DB$ ChangeZoneAll | Origin$ Battlefield | Destination$ Exile | ChangeType$ Sand.Warrior SVar:X:Count$Valid Land.YouCtrl -SVar:Picture:http://www.wizards.com/global/images/magic/general/hazezon_tamar.jpg +DeckHas:Ability$Token Oracle:When Hazezon Tamar enters the battlefield, create X 1/1 Sand Warrior creature tokens that are red, green, and white at the beginning of your next upkeep, where X is the number of lands you control at that time.\nWhen Hazezon leaves the battlefield, exile all Sand Warriors. diff --git a/forge-gui/res/cardsfolder/upcoming/hellkite_courser.txt b/forge-gui/res/cardsfolder/h/hellkite_courser.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/hellkite_courser.txt rename to forge-gui/res/cardsfolder/h/hellkite_courser.txt diff --git a/forge-gui/res/cardsfolder/upcoming/horizon_stone.txt b/forge-gui/res/cardsfolder/h/horizon_stone.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/horizon_stone.txt rename to forge-gui/res/cardsfolder/h/horizon_stone.txt diff --git a/forge-gui/res/cardsfolder/h/huatli_warrior_poet.txt b/forge-gui/res/cardsfolder/h/huatli_warrior_poet.txt index 9357d49fdb3..7977fee52a6 100644 --- a/forge-gui/res/cardsfolder/h/huatli_warrior_poet.txt +++ b/forge-gui/res/cardsfolder/h/huatli_warrior_poet.txt @@ -6,10 +6,9 @@ A:AB$ GainLife | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | LifeAmount$ SVar:GreatestPow:Count$GreatestPower_Creature.YouCtrl A:AB$ Token | Cost$ AddCounter<0/LOYALTY> | Planeswalker$ True | TokenAmount$ 1 | TokenScript$ g_3_3_dinosaur_trample | TokenOwner$ You | SpellDescription$ Create a 3/3 green Dinosaur creature token with trample. # TODO: The AI never uses the Ultimate ability (most likely doesn't have the required logic for it) -A:AB$ DealDamage | Cost$ SubCounter | Announce$ X | XMaxLimit$ L | NumDmg$ X | References$ X,L | Planeswalker$ True | Ultimate$ True | ValidTgts$ Creature | TgtPrompt$ Select any number of target creatures | TargetMin$ 1 | TargetMax$ X | DividedAsYouChoose$ X | RememberDamaged$ True | SubAbility$ DBNoBlock | SpellDescription$ CARDNAME deals X damage divided as you choose among any number of target creatures. Creatures dealt damage this way can't block this turn. +A:AB$ DealDamage | Cost$ SubCounter | Announce$ X | NumDmg$ X | References$ X | Planeswalker$ True | Ultimate$ True | ValidTgts$ Creature | TgtPrompt$ Select any number of target creatures | TargetMin$ 1 | TargetMax$ X | DividedAsYouChoose$ X | RememberDamaged$ True | SubAbility$ DBNoBlock | SpellDescription$ CARDNAME deals X damage divided as you choose among any number of target creatures. Creatures dealt damage this way can't block this turn. SVar:DBNoBlock:DB$ Pump | KW$ HIDDEN CARDNAME can't block. | Defined$ Remembered | SubAbility$ DBCleanup | StackDescription$ Creatures dealt damage this way can't block this turn. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$XChoice -SVar:L:Count$CardCounters.LOYALTY DeckHas:Ability$LifeGain & Ability$Token Oracle:[+2]: You gain life equal to the greatest power among creatures you control.\n[0]: Create a 3/3 green Dinosaur creature token with trample.\n[-X]: Huatli, Warrior Poet deals X damage divided as you choose among any number of target creatures. Creatures dealt damage this way can't block this turn. diff --git a/forge-gui/res/cardsfolder/upcoming/hullbreacher.txt b/forge-gui/res/cardsfolder/h/hullbreacher.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/hullbreacher.txt rename to forge-gui/res/cardsfolder/h/hullbreacher.txt diff --git a/forge-gui/res/cardsfolder/upcoming/ich_tekik_salvage_splicer.txt b/forge-gui/res/cardsfolder/i/ich_tekik_salvage_splicer.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/ich_tekik_salvage_splicer.txt rename to forge-gui/res/cardsfolder/i/ich_tekik_salvage_splicer.txt diff --git a/forge-gui/res/cardsfolder/upcoming/imoti_celebrant_of_bounty.txt b/forge-gui/res/cardsfolder/i/imoti_celebrant_of_bounty.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/imoti_celebrant_of_bounty.txt rename to forge-gui/res/cardsfolder/i/imoti_celebrant_of_bounty.txt diff --git a/forge-gui/res/cardsfolder/upcoming/impulsive_pilferer.txt b/forge-gui/res/cardsfolder/i/impulsive_pilferer.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/impulsive_pilferer.txt rename to forge-gui/res/cardsfolder/i/impulsive_pilferer.txt diff --git a/forge-gui/res/cardsfolder/upcoming/ingenuity_engine.txt b/forge-gui/res/cardsfolder/i/ingenuity_engine.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/ingenuity_engine.txt rename to forge-gui/res/cardsfolder/i/ingenuity_engine.txt diff --git a/forge-gui/res/cardsfolder/upcoming/jackknight.txt b/forge-gui/res/cardsfolder/j/jackknight.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/jackknight.txt rename to forge-gui/res/cardsfolder/j/jackknight.txt diff --git a/forge-gui/res/cardsfolder/upcoming/jared_carthalion_true_heir.txt b/forge-gui/res/cardsfolder/j/jared_carthalion_true_heir.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/jared_carthalion_true_heir.txt rename to forge-gui/res/cardsfolder/j/jared_carthalion_true_heir.txt diff --git a/forge-gui/res/cardsfolder/upcoming/jeska_thrice_reborn.txt b/forge-gui/res/cardsfolder/j/jeska_thrice_reborn.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/jeska_thrice_reborn.txt rename to forge-gui/res/cardsfolder/j/jeska_thrice_reborn.txt diff --git a/forge-gui/res/cardsfolder/upcoming/jeskas_will.txt b/forge-gui/res/cardsfolder/j/jeskas_will.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/jeskas_will.txt rename to forge-gui/res/cardsfolder/j/jeskas_will.txt diff --git a/forge-gui/res/cardsfolder/upcoming/jeweled_lotus.txt b/forge-gui/res/cardsfolder/j/jeweled_lotus.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/jeweled_lotus.txt rename to forge-gui/res/cardsfolder/j/jeweled_lotus.txt diff --git a/forge-gui/res/cardsfolder/upcoming/juri_master_of_the_revue.txt b/forge-gui/res/cardsfolder/j/juri_master_of_the_revue.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/juri_master_of_the_revue.txt rename to forge-gui/res/cardsfolder/j/juri_master_of_the_revue.txt diff --git a/forge-gui/res/cardsfolder/upcoming/kamahl_heart_of_krosa.txt b/forge-gui/res/cardsfolder/k/kamahl_heart_of_krosa.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/kamahl_heart_of_krosa.txt rename to forge-gui/res/cardsfolder/k/kamahl_heart_of_krosa.txt diff --git a/forge-gui/res/cardsfolder/upcoming/kamahls_will.txt b/forge-gui/res/cardsfolder/k/kamahls_will.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/kamahls_will.txt rename to forge-gui/res/cardsfolder/k/kamahls_will.txt diff --git a/forge-gui/res/cardsfolder/upcoming/kangee_sky_warden.txt b/forge-gui/res/cardsfolder/k/kangee_sky_warden.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/kangee_sky_warden.txt rename to forge-gui/res/cardsfolder/k/kangee_sky_warden.txt diff --git a/forge-gui/res/cardsfolder/upcoming/kangees_lieutenant.txt b/forge-gui/res/cardsfolder/k/kangees_lieutenant.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/kangees_lieutenant.txt rename to forge-gui/res/cardsfolder/k/kangees_lieutenant.txt diff --git a/forge-gui/res/cardsfolder/upcoming/kediss_emberclaw_familiar.txt b/forge-gui/res/cardsfolder/k/kediss_emberclaw_familiar.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/kediss_emberclaw_familiar.txt rename to forge-gui/res/cardsfolder/k/kediss_emberclaw_familiar.txt diff --git a/forge-gui/res/cardsfolder/upcoming/keeper_of_the_accord.txt b/forge-gui/res/cardsfolder/k/keeper_of_the_accord.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/keeper_of_the_accord.txt rename to forge-gui/res/cardsfolder/k/keeper_of_the_accord.txt diff --git a/forge-gui/res/cardsfolder/upcoming/keleth_sunmane_familiar.txt b/forge-gui/res/cardsfolder/k/keleth_sunmane_familiar.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/keleth_sunmane_familiar.txt rename to forge-gui/res/cardsfolder/k/keleth_sunmane_familiar.txt diff --git a/forge-gui/res/cardsfolder/upcoming/keskit_the_flesh_sculptor.txt b/forge-gui/res/cardsfolder/k/keskit_the_flesh_sculptor.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/keskit_the_flesh_sculptor.txt rename to forge-gui/res/cardsfolder/k/keskit_the_flesh_sculptor.txt diff --git a/forge-gui/res/cardsfolder/upcoming/kinsbaile_courier.txt b/forge-gui/res/cardsfolder/k/kinsbaile_courier.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/kinsbaile_courier.txt rename to forge-gui/res/cardsfolder/k/kinsbaile_courier.txt diff --git a/forge-gui/res/cardsfolder/upcoming/kitesail_skirmisher.txt b/forge-gui/res/cardsfolder/k/kitesail_skirmisher.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/kitesail_skirmisher.txt rename to forge-gui/res/cardsfolder/k/kitesail_skirmisher.txt diff --git a/forge-gui/res/cardsfolder/upcoming/kodama_of_the_east_tree.txt b/forge-gui/res/cardsfolder/k/kodama_of_the_east_tree.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/kodama_of_the_east_tree.txt rename to forge-gui/res/cardsfolder/k/kodama_of_the_east_tree.txt diff --git a/forge-gui/res/cardsfolder/upcoming/krark_the_thumbless.txt b/forge-gui/res/cardsfolder/k/krark_the_thumbless.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/krark_the_thumbless.txt rename to forge-gui/res/cardsfolder/k/krark_the_thumbless.txt diff --git a/forge-gui/res/cardsfolder/upcoming/kwain_itinerant_meddler.txt b/forge-gui/res/cardsfolder/k/kwain_itinerant_meddler.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/kwain_itinerant_meddler.txt rename to forge-gui/res/cardsfolder/k/kwain_itinerant_meddler.txt diff --git a/forge-gui/res/cardsfolder/upcoming/laboratory_drudge.txt b/forge-gui/res/cardsfolder/l/laboratory_drudge.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/laboratory_drudge.txt rename to forge-gui/res/cardsfolder/l/laboratory_drudge.txt diff --git a/forge-gui/res/cardsfolder/upcoming/lathiel_the_bounteous_dawn.txt b/forge-gui/res/cardsfolder/l/lathiel_the_bounteous_dawn.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/lathiel_the_bounteous_dawn.txt rename to forge-gui/res/cardsfolder/l/lathiel_the_bounteous_dawn.txt diff --git a/forge-gui/res/cardsfolder/upcoming/liesa_shroud_of_dusk.txt b/forge-gui/res/cardsfolder/l/liesa_shroud_of_dusk.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/liesa_shroud_of_dusk.txt rename to forge-gui/res/cardsfolder/l/liesa_shroud_of_dusk.txt diff --git a/forge-gui/res/cardsfolder/l/living_hive.txt b/forge-gui/res/cardsfolder/l/living_hive.txt index 01a978ce006..dbbb555353b 100644 --- a/forge-gui/res/cardsfolder/l/living_hive.txt +++ b/forge-gui/res/cardsfolder/l/living_hive.txt @@ -3,8 +3,7 @@ ManaCost:6 G G Types:Creature Elemental Insect PT:6/6 K:Trample -T:Mode$ DamageDone | ValidSource$ Card.Self | Execute$ TrigToken | ValidTarget$ Player | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, create that many 1/1 green Insect creature tokens. -SVar:TrigToken:DB$ Token | TokenAmount$ X | TokenScript$ g_1_1_insect | TokenOwner$ You | LegacyImage$ g 1 1 insect mrd | References$ X +T:Mode$ DamageDone | ValidSource$ Card.Self | Execute$ TrigToken | CombatDamage$ True | ValidTarget$ Player | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, create that many 1/1 green Insect creature tokens. +SVar:TrigToken:DB$ Token | TokenAmount$ X | TokenScript$ g_1_1_insect | TokenOwner$ You | References$ X SVar:X:TriggerCount$DamageAmount -SVar:Picture:http://www.wizards.com/global/images/magic/general/living_hive.jpg Oracle:Trample\nWhenever Living Hive deals combat damage to a player, create that many 1/1 green Insect creature tokens. diff --git a/forge-gui/res/cardsfolder/upcoming/livio_oathsworn_sentinel.txt b/forge-gui/res/cardsfolder/l/livio_oathsworn_sentinel.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/livio_oathsworn_sentinel.txt rename to forge-gui/res/cardsfolder/l/livio_oathsworn_sentinel.txt diff --git a/forge-gui/res/cardsfolder/l/ludevic_necro_alchemist.txt b/forge-gui/res/cardsfolder/l/ludevic_necro_alchemist.txt index 0c9ef6c8076..2da49610f6d 100644 --- a/forge-gui/res/cardsfolder/l/ludevic_necro_alchemist.txt +++ b/forge-gui/res/cardsfolder/l/ludevic_necro_alchemist.txt @@ -3,7 +3,6 @@ ManaCost:1 U R Types:Legendary Creature Human Wizard PT:1/4 T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ Player | Execute$ DrawDamageOther | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of each player's end step, that player may draw a card if a player other than you lost life this turn. -SVar:DrawDamageOther:DB$ Draw | Defined$ TriggeredPlayer | NumCards$ 1 | ConditionPlayerContains$ Player.Other+wasDealtDamageThisTurn | ConditionPlayerDefined$ Player | OptionalDecider$ True +SVar:DrawDamageOther:DB$ Draw | Defined$ TriggeredPlayer | NumCards$ 1 | ConditionPlayerDefined$ Player.Other | ConditionPlayerContains$ Player.wasDealtDamageThisTurn | OptionalDecider$ True K:Partner -SVar:Picture:http://www.wizards.com/global/images/magic/general/ludevic_necro-alchemist.jpg -Oracle:At the beginning of each player's end step, that player may draw a card if a player other than you lost life this turn.\nPartner (You can have two commanders if both have partner.) \ No newline at end of file +Oracle:At the beginning of each player's end step, that player may draw a card if a player other than you lost life this turn.\nPartner (You can have two commanders if both have partner.) diff --git a/forge-gui/res/cardsfolder/upcoming/maelstrom_colossus.txt b/forge-gui/res/cardsfolder/m/maelstrom_colossus.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/maelstrom_colossus.txt rename to forge-gui/res/cardsfolder/m/maelstrom_colossus.txt diff --git a/forge-gui/res/cardsfolder/m/magma_phoenix.txt b/forge-gui/res/cardsfolder/m/magma_phoenix.txt index 8eb4e3da240..fc92728e375 100644 --- a/forge-gui/res/cardsfolder/m/magma_phoenix.txt +++ b/forge-gui/res/cardsfolder/m/magma_phoenix.txt @@ -3,9 +3,8 @@ ManaCost:3 R R Types:Creature Phoenix PT:3/3 K:Flying -A:AB$ ChangeZone | Cost$ 3 R R | Origin$ Graveyard | Destination$ Hand | ActivationZone$ Graveyard | SpellDescription$ Return CARDNAME from your graveyard to the hand. +A:AB$ ChangeZone | Cost$ 3 R R | Origin$ Graveyard | Destination$ Hand | ActivationZone$ Graveyard | SpellDescription$ Return CARDNAME from your graveyard to your hand. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigDamage | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, it deals 3 damage to each creature and each player. SVar:TrigDamage:DB$ DamageAll | ValidCards$ Creature | ValidPlayers$ Player | NumDmg$ 3 | ValidDescription$ each creature and each player. SVar:DiscardMe:2 -SVar:Picture:http://www.wizards.com/global/images/magic/general/magma_phoenix.jpg Oracle:Flying\nWhen Magma Phoenix dies, it deals 3 damage to each creature and each player.\n{3}{R}{R}: Return Magma Phoenix from your graveyard to your hand. diff --git a/forge-gui/res/cardsfolder/upcoming/magus_of_the_order.txt b/forge-gui/res/cardsfolder/m/magus_of_the_order.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/magus_of_the_order.txt rename to forge-gui/res/cardsfolder/m/magus_of_the_order.txt diff --git a/forge-gui/res/cardsfolder/upcoming/malcolm_keen-eyed_navigator.txt b/forge-gui/res/cardsfolder/m/malcolm_keen-eyed_navigator.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/malcolm_keen-eyed_navigator.txt rename to forge-gui/res/cardsfolder/m/malcolm_keen-eyed_navigator.txt diff --git a/forge-gui/res/cardsfolder/m/maze_of_ith.txt b/forge-gui/res/cardsfolder/m/maze_of_ith.txt index 0dbf163a7a8..2eaa3a91201 100644 --- a/forge-gui/res/cardsfolder/m/maze_of_ith.txt +++ b/forge-gui/res/cardsfolder/m/maze_of_ith.txt @@ -1,7 +1,6 @@ Name:Maze of Ith ManaCost:no cost Types:Land -A:AB$ Pump | Cost$ T | ValidTgts$ Creature.attacking | TgtPrompt$ Select target attacking creature | KW$ Prevent all combat damage that would be dealt to and dealt by CARDNAME. | SubAbility$ DBUntap | IsCurse$ True | SpellDescription$ Untap target attacking creature. Prevent all combat damage that would be dealt to and dealt by that creature this turn. -SVar:DBUntap:DB$Untap | Defined$ Targeted -SVar:Picture:http://www.wizards.com/global/images/magic/general/maze_of_ith.jpg +A:AB$ Untap | Cost$ T | ValidTgts$ Creature.attacking | TgtPrompt$ Select target attacking creature | SubAbility$ DBPump | IsCurse$ True | StackDescription$ Untap {c:Targeted}. | SpellDescription$ Untap target attacking creature. Prevent all combat damage that would be dealt to and dealt by that creature this turn. +SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Prevent all combat damage that would be dealt to and dealt by CARDNAME. | StackDescription$ Prevent all combat damage that would be dealt to and dealt by that creature this turn. Oracle:{T}: Untap target attacking creature. Prevent all combat damage that would be dealt to and dealt by that creature this turn. diff --git a/forge-gui/res/cardsfolder/upcoming/merchant_raiders.txt b/forge-gui/res/cardsfolder/m/merchant_raiders.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/merchant_raiders.txt rename to forge-gui/res/cardsfolder/m/merchant_raiders.txt diff --git a/forge-gui/res/cardsfolder/upcoming/meteoric_mace.txt b/forge-gui/res/cardsfolder/m/meteoric_mace.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/meteoric_mace.txt rename to forge-gui/res/cardsfolder/m/meteoric_mace.txt diff --git a/forge-gui/res/cardsfolder/upcoming/miara_thorn_of_the_glade.txt b/forge-gui/res/cardsfolder/m/miara_thorn_of_the_glade.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/miara_thorn_of_the_glade.txt rename to forge-gui/res/cardsfolder/m/miara_thorn_of_the_glade.txt diff --git a/forge-gui/res/cardsfolder/upcoming/mnemonic_deluge.txt b/forge-gui/res/cardsfolder/m/mnemonic_deluge.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/mnemonic_deluge.txt rename to forge-gui/res/cardsfolder/m/mnemonic_deluge.txt diff --git a/forge-gui/res/cardsfolder/upcoming/nadier_agent_of_the_duskenel.txt b/forge-gui/res/cardsfolder/n/nadier_agent_of_the_duskenel.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/nadier_agent_of_the_duskenel.txt rename to forge-gui/res/cardsfolder/n/nadier_agent_of_the_duskenel.txt diff --git a/forge-gui/res/cardsfolder/upcoming/nadiers_nightblade.txt b/forge-gui/res/cardsfolder/n/nadiers_nightblade.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/nadiers_nightblade.txt rename to forge-gui/res/cardsfolder/n/nadiers_nightblade.txt diff --git a/forge-gui/res/cardsfolder/upcoming/natural_reclamation.txt b/forge-gui/res/cardsfolder/n/natural_reclamation.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/natural_reclamation.txt rename to forge-gui/res/cardsfolder/n/natural_reclamation.txt diff --git a/forge-gui/res/cardsfolder/n/necropotence.txt b/forge-gui/res/cardsfolder/n/necropotence.txt index faed1082aa5..e22a9175061 100644 --- a/forge-gui/res/cardsfolder/n/necropotence.txt +++ b/forge-gui/res/cardsfolder/n/necropotence.txt @@ -3,7 +3,7 @@ ManaCost:B B B Types:Enchantment S:Mode$ Continuous | Affected$ You | AddKeyword$ Skip your draw step. | Description$ Skip your draw step. T:Mode$ Discarded | ValidCard$ Card.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigChange | TriggerDescription$ Whenever you discard a card, exile that card from your graveyard. -SVar:TrigChange:DB$ ChangeZone | Defined$ TriggeredCardLKICopy | Origin$ Graveyard | Destination$ Exile +SVar:TrigChange:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Exile A:AB$ ChangeZone | Cost$ PayLife<1> | Defined$ TopOfLibrary | Origin$ Library | Destination$ Exile | ExileFaceDown$ True | RememberChanged$ True | SubAbility$ DelayedReturn | AILogic$ Necropotence | AILifeThreshold$ 1 | SpellDescription$ Exile the top card of your library face down. Put that card into your hand at the beginning of your next end step. SVar:DelayedReturn:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | Execute$ TrigReturn | RememberObjects$ RememberedLKI | TriggerDescription$ Put the exiled card into your hand. | SubAbility$ DBCleanup SVar:TrigReturn:DB$ ChangeZone | Origin$ Exile | Destination$ Hand | Defined$ DelayTriggerRememberedLKI @@ -11,4 +11,5 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:AICastPreference:NeverCastIfLifeBelow$ 7 AI:RemoveDeck:Random SVar:NonStackingEffect:True +DeckHints:Ability$LifeGain Oracle:Skip your draw step.\nWhenever you discard a card, exile that card from your graveyard.\nPay 1 life: Exile the top card of your library face down. Put that card into your hand at the beginning of your next end step. diff --git a/forge-gui/res/cardsfolder/upcoming/necrotic_hex.txt b/forge-gui/res/cardsfolder/n/necrotic_hex.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/necrotic_hex.txt rename to forge-gui/res/cardsfolder/n/necrotic_hex.txt diff --git a/forge-gui/res/cardsfolder/upcoming/nevinyrral_urborg_tyrant.txt b/forge-gui/res/cardsfolder/n/nevinyrral_urborg_tyrant.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/nevinyrral_urborg_tyrant.txt rename to forge-gui/res/cardsfolder/n/nevinyrral_urborg_tyrant.txt diff --git a/forge-gui/res/cardsfolder/upcoming/nightshade_harvester.txt b/forge-gui/res/cardsfolder/n/nightshade_harvester.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/nightshade_harvester.txt rename to forge-gui/res/cardsfolder/n/nightshade_harvester.txt diff --git a/forge-gui/res/cardsfolder/upcoming/novellamental.txt b/forge-gui/res/cardsfolder/n/novellamental.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/novellamental.txt rename to forge-gui/res/cardsfolder/n/novellamental.txt diff --git a/forge-gui/res/cardsfolder/n/novijen_sages.txt b/forge-gui/res/cardsfolder/n/novijen_sages.txt index 1475498019e..2fa0c728d6e 100644 --- a/forge-gui/res/cardsfolder/n/novijen_sages.txt +++ b/forge-gui/res/cardsfolder/n/novijen_sages.txt @@ -3,8 +3,7 @@ ManaCost:4 U U Types:Creature Human Advisor Mutant PT:0/0 K:Graft:4 -A:AB$ Draw | Cost$ 1 SubCounter<2/P1P1/Creature/among creatures you control> | NumCards$ 1 | SpellDescription$ Draw a card. +A:AB$ Draw | Cost$ 1 RemoveAnyCounter<2/P1P1/Creature.YouCtrl/among creatures you control> | NumCards$ 1 | SpellDescription$ Draw a card. DeckNeeds:Ability$Counters DeckHas:Ability$Counters -SVar:Picture:http://www.wizards.com/global/images/magic/general/novijen_sages.jpg Oracle:Graft 4 (This creature enters the battlefield with four +1/+1 counters on it. Whenever another creature enters the battlefield, you may move a +1/+1 counter from this creature onto it.)\n{1}, Remove two +1/+1 counters from among creatures you control: Draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/numa_joraga_chieftain.txt b/forge-gui/res/cardsfolder/n/numa_joraga_chieftain.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/numa_joraga_chieftain.txt rename to forge-gui/res/cardsfolder/n/numa_joraga_chieftain.txt diff --git a/forge-gui/res/cardsfolder/upcoming/nymris_oonas_trickster.txt b/forge-gui/res/cardsfolder/n/nymris_oonas_trickster.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/nymris_oonas_trickster.txt rename to forge-gui/res/cardsfolder/n/nymris_oonas_trickster.txt diff --git a/forge-gui/res/cardsfolder/upcoming/obeka_brute_chronologist.txt b/forge-gui/res/cardsfolder/o/obeka_brute_chronologist.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/obeka_brute_chronologist.txt rename to forge-gui/res/cardsfolder/o/obeka_brute_chronologist.txt diff --git a/forge-gui/res/cardsfolder/upcoming/old_fogey.txt b/forge-gui/res/cardsfolder/o/old_fogey.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/old_fogey.txt rename to forge-gui/res/cardsfolder/o/old_fogey.txt diff --git a/forge-gui/res/cardsfolder/o/ooze_flux.txt b/forge-gui/res/cardsfolder/o/ooze_flux.txt index 0bfc0039635..3e1161d798f 100644 --- a/forge-gui/res/cardsfolder/o/ooze_flux.txt +++ b/forge-gui/res/cardsfolder/o/ooze_flux.txt @@ -1,9 +1,8 @@ Name:Ooze Flux ManaCost:3 G Types:Enchantment -A:AB$ Token | Announce$ X | Cost$ XCantBe0 1 G SubCounter | TokenAmount$ 1 | TokenScript$ g_x_x_ooze | TokenOwner$ You | LegacyImage$ g x x ooze gtc | TokenPower$ X | TokenToughness$ X | SpellDescription$ Create an X/X green Ooze creature token, where X is the number of +1/+1 counters removed this way. +A:AB$ Token | Cost$ XCantBe0 1 G RemoveAnyCounter | TokenAmount$ 1 | TokenScript$ g_x_x_ooze | TokenOwner$ You | LegacyImage$ g x x ooze gtc | TokenPower$ X | TokenToughness$ X | SpellDescription$ Create an X/X green Ooze creature token, where X is the number of +1/+1 counters removed this way. SVar:X:Count$xPaid AI:RemoveDeck:All DeckHints:Ability$Counters -SVar:Picture:http://www.wizards.com/global/images/magic/general/ooze_flux.jpg Oracle:{1}{G}, Remove one or more +1/+1 counters from among creatures you control: Create an X/X green Ooze creature token, where X is the number of +1/+1 counters removed this way. diff --git a/forge-gui/res/cardsfolder/upcoming/opposition_agent.txt b/forge-gui/res/cardsfolder/o/opposition_agent.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/opposition_agent.txt rename to forge-gui/res/cardsfolder/o/opposition_agent.txt diff --git a/forge-gui/res/cardsfolder/p/parallax_wave.txt b/forge-gui/res/cardsfolder/p/parallax_wave.txt index eb087bdc4b1..afe19bab11e 100644 --- a/forge-gui/res/cardsfolder/p/parallax_wave.txt +++ b/forge-gui/res/cardsfolder/p/parallax_wave.txt @@ -2,10 +2,10 @@ Name:Parallax Wave ManaCost:2 W W Types:Enchantment K:Fading:5 -A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | Imprint$ True | SpellDescription$ Exile target creature. +A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | RememberTargets$ True | SpellDescription$ Exile target creature. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME leaves the battlefield, each player returns to the battlefield all cards they own exiled with CARDNAME. -SVar:TrigReturn:DB$ ChangeZone | Defined$ Imprinted | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True +SVar:TrigReturn:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered+ExiledWithSource | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup +SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True SVar:PlayMain1:TRUE AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/parallax_wave.jpg diff --git a/forge-gui/res/cardsfolder/p/petalmane_baku.txt b/forge-gui/res/cardsfolder/p/petalmane_baku.txt index 07bbc0fb932..78deace7265 100644 --- a/forge-gui/res/cardsfolder/p/petalmane_baku.txt +++ b/forge-gui/res/cardsfolder/p/petalmane_baku.txt @@ -3,11 +3,9 @@ ManaCost:1 G Types:Creature Spirit PT:1/2 T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, you may put a ki counter on CARDNAME. -SVar:TrigPutCounter:DB$PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 -#ChosenX SVar created by Cost payment -A:AB$ Mana | Cost$ 1 SubCounter | Produced$ Any | Amount$ ChosenX | References$ X | AILogic$ ManaRitual | AINoRecursiveCheck$ True | SpellDescription$ Add X mana of any one color. -SVar:X:XChoice +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 +A:AB$ Mana | Cost$ 1 SubCounter | Produced$ Any | Amount$ X | References$ X | AILogic$ ManaRitual | AINoRecursiveCheck$ True | SpellDescription$ Add X mana of any one color. +SVar:X:Count$xPaid AI:RemoveDeck:Random DeckHints:Type$Spirit|Arcane -SVar:Picture:http://www.wizards.com/global/images/magic/general/petalmane_baku.jpg Oracle:Whenever you cast a Spirit or Arcane spell, you may put a ki counter on Petalmane Baku.\n{1}, Remove X ki counters from Petalmane Baku: Add X mana of any one color. diff --git a/forge-gui/res/cardsfolder/upcoming/phyrexian_triniform.txt b/forge-gui/res/cardsfolder/p/phyrexian_triniform.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/phyrexian_triniform.txt rename to forge-gui/res/cardsfolder/p/phyrexian_triniform.txt diff --git a/forge-gui/res/cardsfolder/upcoming/plague_reaver.txt b/forge-gui/res/cardsfolder/p/plague_reaver.txt similarity index 82% rename from forge-gui/res/cardsfolder/upcoming/plague_reaver.txt rename to forge-gui/res/cardsfolder/p/plague_reaver.txt index 95414bb8e13..5a0dd84b79d 100644 --- a/forge-gui/res/cardsfolder/upcoming/plague_reaver.txt +++ b/forge-gui/res/cardsfolder/p/plague_reaver.txt @@ -5,7 +5,7 @@ PT:6/5 T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ SacAllOthers | TriggerDescription$ At the beginning of your end step, sacrifice each other creature you control. SVar:SacAllOthers:DB$ SacrificeAll | ValidCards$ Creature.Other+YouCtrl A:AB$ Pump | Cost$ Discard<2/Card> Sac<1/CARDNAME> | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | RememberTargets$ True | SubAbility$ DBDelayTrig | StackDescription$ Return CARDNAME to the battlefield under {p:Targeted}'s control at the beginning of their next upkeep. | SpellDescription$ Choose target opponent. Return CARDNAME to the battlefield under that player's control at the beginning of their next upkeep. -SVar:DBDelayTrig:DB$ DelayedTrigger | TriggerZones$ Graveyard | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player.IsRemembered | RememberObjects$ Remembered | Execute$ DBChange | StackDescription$ None | TriggerDescription$ Return CARDNAME to the battlefield under that player’s control at the beginning of their next upkeep. +SVar:DBDelayTrig:DB$ DelayedTrigger | TriggerZones$ Graveyard | Mode$ Phase | Phase$ Upkeep | DelayedTriggerDefinedPlayer$ Remembered | RememberObjects$ Remembered | Execute$ DBChange | StackDescription$ None | TriggerDescription$ Return CARDNAME to the battlefield under that player’s control at the beginning of their next upkeep. SVar:DBChange:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | NewController$ DelayTriggerRemembered AI:RemoveDeck:All DeckHas:Ability$Discard & Ability$Sacrifice diff --git a/forge-gui/res/cardsfolder/upcoming/port_razer.txt b/forge-gui/res/cardsfolder/p/port_razer.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/port_razer.txt rename to forge-gui/res/cardsfolder/p/port_razer.txt diff --git a/forge-gui/res/cardsfolder/p/portcullis.txt b/forge-gui/res/cardsfolder/p/portcullis.txt index 51730619950..82ff88ef890 100644 --- a/forge-gui/res/cardsfolder/p/portcullis.txt +++ b/forge-gui/res/cardsfolder/p/portcullis.txt @@ -2,8 +2,8 @@ Name:Portcullis ManaCost:4 Types:Artifact T:Mode$ ChangesZone | ValidCard$ Creature | Origin$ Any | Destination$ Battlefield | Execute$ TrigExile | TriggerZones$ Battlefield | TriggerDescription$ Whenever a creature enters the battlefield, if there are two or more other creatures on the battlefield, exile that creature. -T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Any | Execute$ TrigReturn | TriggerController$ TriggeredCardController | TriggerDescription$ Return that card to the battlefield under its owner's control when CARDNAME leaves the battlefield. -SVar:TrigExile:DB$ ChangeZone | ConditionPresent$ Creature | ConditionCompare$ GE3 | Defined$ TriggeredNewCardLKICopy | RememberChanged$ True | Origin$ Battlefield | Destination$ Exile +SVar:TrigExile:DB$ ChangeZone | ConditionPresent$ Creature | ConditionCompare$ GE3 | Defined$ TriggeredCard | RememberChanged$ True | Origin$ Battlefield | Destination$ Exile +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Any | Execute$ TrigReturn | TriggerDescription$ Return that card to the battlefield under its owner's control when CARDNAME leaves the battlefield. SVar:TrigReturn:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/p/power_conduit.txt b/forge-gui/res/cardsfolder/p/power_conduit.txt index 60fdd24c135..cc83bc5695e 100644 --- a/forge-gui/res/cardsfolder/p/power_conduit.txt +++ b/forge-gui/res/cardsfolder/p/power_conduit.txt @@ -1,9 +1,8 @@ Name:Power Conduit ManaCost:2 Types:Artifact -A:AB$ Charm | Cost$ T RemoveAnyCounter<1/Permanent.YouCtrl/a permanent you control> | Choices$ ConduitCharge,ConduitP1P1 | Defined$ You +A:AB$ Charm | Cost$ T RemoveAnyCounter<1/Any/Permanent.YouCtrl/a permanent you control> | Choices$ ConduitCharge,ConduitP1P1 | Defined$ You SVar:ConduitCharge:DB$ PutCounter | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | CounterType$ CHARGE | CounterNum$ 1 | SpellDescription$ Put a charge counter on target artifact. SVar:ConduitP1P1:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on target creature. SVar:AIRemoveCounterCostPriority:ANY -SVar:Picture:http://www.wizards.com/global/images/magic/general/power_conduit.jpg Oracle:{T}, Remove a counter from a permanent you control: Choose one —\n• Put a charge counter on target artifact.\n• Put a +1/+1 counter on target creature. diff --git a/forge-gui/res/cardsfolder/upcoming/prava_of_the_steel_legion.txt b/forge-gui/res/cardsfolder/p/prava_of_the_steel_legion.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/prava_of_the_steel_legion.txt rename to forge-gui/res/cardsfolder/p/prava_of_the_steel_legion.txt diff --git a/forge-gui/res/cardsfolder/p/preferred_selection.txt b/forge-gui/res/cardsfolder/p/preferred_selection.txt new file mode 100644 index 00000000000..e956cac9394 --- /dev/null +++ b/forge-gui/res/cardsfolder/p/preferred_selection.txt @@ -0,0 +1,9 @@ +Name:Preferred Selection +ManaCost:2 G G +Types:Enchantment +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigLook | TriggerDescription$ At the beginning of your upkeep, look at the top two cards of your library. You may sacrifice CARDNAME and pay {2}{G}{G}. If you do, put one of those cards into your hand. If you don’t, put one of those cards on the bottom of your library. +SVar:TrigLook:DB$ PeekAndReveal | Defined$ You | PeekAmount$ 2 | NoReveal$ True | SubAbility$ DBBottom +SVar:DBBottom:DB$ Dig | UnlessCost$ Sac<1/CARDNAME> 2 G G | UnlessPayer$ You | UnlessResolveSubs$ WhenPaid | UnlessAI$ Never | SubAbility$ DBPutHand | DigNum$ 2 | ChangeNum$ 1 | AILogic$ WorstCard | DestinationZone$ Library | DestinationZone2$ Library | LibraryPosition$ -1 | LibraryPosition2$ 0 | ChangeValid$ Card +SVar:DBPutHand:DB$ Dig | DigNum$ 2 | ChangeNum$ 1 | DestinationZone$ Hand | DestinationZone2$ Library | LibraryPosition2$ 0 | ChangeValid$ Card +DeckHas:Ability$Sacrifice +Oracle:At the beginning of your upkeep, look at the top two cards of your library. You may sacrifice Preferred Selection and pay {2}{G}{G}. If you do, put one of those cards into your hand. If you don’t, put one of those cards on the bottom of your library. diff --git a/forge-gui/res/cardsfolder/upcoming/pride_of_the_perfect.txt b/forge-gui/res/cardsfolder/p/pride_of_the_perfect.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/pride_of_the_perfect.txt rename to forge-gui/res/cardsfolder/p/pride_of_the_perfect.txt diff --git a/forge-gui/res/cardsfolder/upcoming/profane_transfusion.txt b/forge-gui/res/cardsfolder/p/profane_transfusion.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/profane_transfusion.txt rename to forge-gui/res/cardsfolder/p/profane_transfusion.txt diff --git a/forge-gui/res/cardsfolder/upcoming/promise_of_tomorrow.txt b/forge-gui/res/cardsfolder/p/promise_of_tomorrow.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/promise_of_tomorrow.txt rename to forge-gui/res/cardsfolder/p/promise_of_tomorrow.txt diff --git a/forge-gui/res/cardsfolder/q/quillmane_baku.txt b/forge-gui/res/cardsfolder/q/quillmane_baku.txt index ddf79d3bf5e..3f9a9f7e786 100644 --- a/forge-gui/res/cardsfolder/q/quillmane_baku.txt +++ b/forge-gui/res/cardsfolder/q/quillmane_baku.txt @@ -3,10 +3,10 @@ ManaCost:4 U Types:Creature Spirit PT:3/3 T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, you may put a ki counter on CARDNAME. -SVar:TrigPutCounter:DB$PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 -A:AB$ ChangeZone | Cost$ 1 T SubCounter | Origin$ Battlefield | Destination$ Hand | ValidTgts$ Creature | ChangeNum$ 1 | References$ X | AITgtBeforeCostEval$ True | SpellDescription$ Return target creature with converted mana cost X or less to its owner's hand. -SVar:X:Targeted$CardManaCost +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 +A:AB$ ChangeZone | Cost$ 1 T SubCounter | Origin$ Battlefield | Destination$ Hand | ValidTgts$ Creature.cmcLEX | ChangeNum$ 1 | References$ X | SpellDescription$ Return target creature with converted mana cost X or less to its owner's hand. +SVar:X:Count$xPaid AI:RemoveDeck:Random +DeckHints:Type$Spirit|Arcane # We'll need to improve the script at some stage, especially if we add Hunter of Eyeblights or Razorfin Abolisher. -SVar:Picture:http://www.wizards.com/global/images/magic/general/quillmane_baku.jpg Oracle:Whenever you cast a Spirit or Arcane spell, you may put a ki counter on Quillmane Baku.\n{1}, {T}, Remove X ki counters from Quillmane Baku: Return target creature with converted mana cost X or less to its owner's hand. diff --git a/forge-gui/res/cardsfolder/upcoming/radiant_serra_archangel.txt b/forge-gui/res/cardsfolder/r/radiant_serra_archangel.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/radiant_serra_archangel.txt rename to forge-gui/res/cardsfolder/r/radiant_serra_archangel.txt diff --git a/forge-gui/res/cardsfolder/r/raiding_party.txt b/forge-gui/res/cardsfolder/r/raiding_party.txt new file mode 100644 index 00000000000..66bf60c1755 --- /dev/null +++ b/forge-gui/res/cardsfolder/r/raiding_party.txt @@ -0,0 +1,18 @@ +Name:Raiding Party +ManaCost:2 R +Types:Enchantment +S:Mode$ CantTarget | ValidCard$ Card.Self | ValidSource$ Card.White | Description$ CARDNAME can't be the target of white spells or abilities from white sources. +A:AB$ RepeatEach | Cost$ Sac<1/Orc/Orc> | CostDesc$ Sacrifice an Orc: | RepeatPlayers$ Player | RepeatSubAbility$ ChooseCardsToTap | SubAbility$ DBDestroy | SpellDescription$ Each player may tap any number of untapped white creatures they control. For each creature tapped this way, that player chooses up to two Plains. Then destroy all Plains that weren’t chosen this way by any player. +SVar:ChooseCardsToTap:DB$ ChooseCard | Defined$ Remembered | MinAmount$ 0 | Amount$ NumCreatures | References$ NumCreatures,NumPlainsDiv2 | Choices$ Creature.untapped+White+RememberedPlayerCtrl | ChoiceTitle$ Choose any number of untapped white creatures you control | ChoiceZone$ Battlefield | RememberChosen$ True | AIMaxAmount$ NumPlainsDiv2 | SubAbility$ DBTap +SVar:DBTap:DB$ Tap | Defined$ Remembered | SubAbility$ ChoosePlainsToSave +SVar:ChoosePlainsToSave:DB$ ChooseCard | Defined$ Remembered | MinAmount$ 0 | Amount$ TappedXTwo | References$ TappedXTwo | Choices$ Plains | ChoiceTitle$ Choose up to two Plains for each creature tapped | ChoiceZone$ Battlefield | ImprintChosen$ True | AILogic$ OwnCard | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearChosenCard$ True +SVar:DBDestroy:DB$ DestroyAll | ValidCards$ Plains.IsNotImprinted | SubAbility$ DBCleanImp | AILogic$ RaidingParty | StackDescription$ None +SVar:DBCleanImp:DB$ Cleanup | ClearImprinted$ True +SVar:NumCreatures:Count$Valid Creature.untapped+White+RememberedPlayerCtrl +SVar:TappedXTwo:Count$Valid Creature.IsRemembered/Times.2 +SVar:NumPlainsDiv2:Count$Valid Plains.YouCtrl/HalfUp +AI:RemoveDeck:Random +SVar:NeedsToPlay:Plains.OppCtrl +DeckNeeds:Type$Orc +Oracle:Raiding Party can’t be the target of white spells or abilities from white sources.\nSacrifice an Orc: Each player may tap any number of untapped white creatures they control. For each creature tapped this way, that player chooses up to two Plains. Then destroy all Plains that weren’t chosen this way by any player. diff --git a/forge-gui/res/cardsfolder/upcoming/rakshasa_debaser.txt b/forge-gui/res/cardsfolder/r/rakshasa_debaser.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/rakshasa_debaser.txt rename to forge-gui/res/cardsfolder/r/rakshasa_debaser.txt diff --git a/forge-gui/res/cardsfolder/r/ramos_dragon_engine.txt b/forge-gui/res/cardsfolder/r/ramos_dragon_engine.txt index 07b0157aee1..4c8af5bad34 100644 --- a/forge-gui/res/cardsfolder/r/ramos_dragon_engine.txt +++ b/forge-gui/res/cardsfolder/r/ramos_dragon_engine.txt @@ -5,7 +5,6 @@ PT:4/4 K:Flying T:Mode$ SpellCast | ValidActivatingPlayer$ You | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a spell, put a +1/+1 counter on CARDNAME for each of that spell's colors. SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ X | References$ X -SVar:X:TriggeredCard$ColorsCtrl Card.Self +SVar:X:TriggeredCard$CardNumColors A:AB$ Mana | Cost$ SubCounter<5/P1P1> | Produced$ W W U U B B R R G G | ActivationLimit$ 1 | SpellDescription$ Add {W}{W}{U}{U}{B}{B}{R}{R}{G}{G}. Activate this ability only once each turn. -SVar:Picture:http://www.wizards.com/global/images/magic/general/ramos_dragon_engine.jpg Oracle:Flying\nWhenever you cast a spell, put a +1/+1 counter on Ramos, Dragon Engine for each of that spell's colors.\nRemove five +1/+1 counters from Ramos: Add {W}{W}{U}{U}{B}{B}{R}{R}{G}{G}. Activate this ability only once each turn. diff --git a/forge-gui/res/cardsfolder/upcoming/rebbec_architect_of_ascension.txt b/forge-gui/res/cardsfolder/r/rebbec_architect_of_ascension.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/rebbec_architect_of_ascension.txt rename to forge-gui/res/cardsfolder/r/rebbec_architect_of_ascension.txt diff --git a/forge-gui/res/cardsfolder/upcoming/rejuvenating_springs.txt b/forge-gui/res/cardsfolder/r/rejuvenating_springs.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/rejuvenating_springs.txt rename to forge-gui/res/cardsfolder/r/rejuvenating_springs.txt diff --git a/forge-gui/res/cardsfolder/upcoming/reshape_the_earth.txt b/forge-gui/res/cardsfolder/r/reshape_the_earth.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/reshape_the_earth.txt rename to forge-gui/res/cardsfolder/r/reshape_the_earth.txt diff --git a/forge-gui/res/cardsfolder/r/retribution_of_the_ancients.txt b/forge-gui/res/cardsfolder/r/retribution_of_the_ancients.txt index 806ab4283aa..a78a1318b96 100644 --- a/forge-gui/res/cardsfolder/r/retribution_of_the_ancients.txt +++ b/forge-gui/res/cardsfolder/r/retribution_of_the_ancients.txt @@ -1,9 +1,8 @@ Name:Retribution of the Ancients ManaCost:B Types:Enchantment -A:AB$ Pump | Announce$ X | Cost$ B SubCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -X | NumDef$ -X | References$ X | IsCurse$ True | SpellDescription$ Target creature gets -X/-X until end of turn. +A:AB$ Pump | Cost$ B RemoveAnyCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -X | NumDef$ -X | References$ X | IsCurse$ True | SpellDescription$ Target creature gets -X/-X until end of turn. SVar:X:Count$xPaid AI:RemoveDeck:All DeckNeeds:Ability$Counters -SVar:Picture:http://www.wizards.com/global/images/magic/general/retribution_of_the_ancients.jpg Oracle:{B}, Remove X +1/+1 counters from among creatures you control: Target creature gets -X/-X until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/reyav_master_smith.txt b/forge-gui/res/cardsfolder/r/reyav_master_smith.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/reyav_master_smith.txt rename to forge-gui/res/cardsfolder/r/reyav_master_smith.txt diff --git a/forge-gui/res/cardsfolder/r/rockslide_sorcerer.txt b/forge-gui/res/cardsfolder/r/rockslide_sorcerer.txt index 1aa627b48fc..a4893deb9f7 100755 --- a/forge-gui/res/cardsfolder/r/rockslide_sorcerer.txt +++ b/forge-gui/res/cardsfolder/r/rockslide_sorcerer.txt @@ -1,6 +1,6 @@ Name:Rockslide Sorcerer ManaCost:3 R -Types:Creature Human Warrior +Types:Creature Human Wizard PT:3/3 T:Mode$ SpellCast | ValidCard$ Instant,Sorcery,Wizard | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDealDamage | TriggerDescription$ Whenever you cast an instant, sorcery, or Wizard spell, CARDNAME deals 1 damage to any target. SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 diff --git a/forge-gui/res/cardsfolder/upcoming/rograkh_son_of_rohgahh.txt b/forge-gui/res/cardsfolder/r/rograkh_son_of_rohgahh.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/rograkh_son_of_rohgahh.txt rename to forge-gui/res/cardsfolder/r/rograkh_son_of_rohgahh.txt diff --git a/forge-gui/res/cardsfolder/upcoming/rootweaver_druid.txt b/forge-gui/res/cardsfolder/r/rootweaver_druid.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/rootweaver_druid.txt rename to forge-gui/res/cardsfolder/r/rootweaver_druid.txt diff --git a/forge-gui/res/cardsfolder/r/rukh_egg.txt b/forge-gui/res/cardsfolder/r/rukh_egg.txt index fc091aea87e..5baebd36c41 100644 --- a/forge-gui/res/cardsfolder/r/rukh_egg.txt +++ b/forge-gui/res/cardsfolder/r/rukh_egg.txt @@ -2,10 +2,10 @@ Name:Rukh Egg ManaCost:3 R Types:Creature Bird Egg PT:0/3 -T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ DelTrigLeaves | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, create a 4/4 red Bird creature token with flying at the beginning of the next end step. -SVar:DelTrigLeaves:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ TrigToken | TriggerController$ TriggeredCardController | TriggerDescription$ Create a 4/4 red Bird creature token with flying at the beginning of the next end step. -SVar:TrigToken:DB$ Token | TokenOwner$ You | TokenScript$ r_4_4_bird_flying | LegacyImage$ r 4 4 bird flying arn | TokenAmount$ 1 +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ DelTrigLeaves | TriggerDescription$ When CARDNAME dies, create a 4/4 red Bird creature token with flying at the beginning of the next end step. +SVar:DelTrigLeaves:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ TrigToken | TriggerDescription$ Create a 4/4 red Bird creature token with flying at the beginning of the next end step. +SVar:TrigToken:DB$ Token | TokenOwner$ You | TokenScript$ r_4_4_bird_flying | TokenAmount$ 1 SVar:SacMe:4 DeckHas:Ability$Token -SVar:Picture:http://www.wizards.com/global/images/magic/general/rukh_egg.jpg +DeckHints:Ability$Sacrifice Oracle:When Rukh Egg dies, create a 4/4 red Bird creature token with flying at the beginning of the next end step. diff --git a/forge-gui/res/cardsfolder/upcoming/sakashima_of_a_thousand_faces.txt b/forge-gui/res/cardsfolder/s/sakashima_of_a_thousand_faces.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/sakashima_of_a_thousand_faces.txt rename to forge-gui/res/cardsfolder/s/sakashima_of_a_thousand_faces.txt diff --git a/forge-gui/res/cardsfolder/upcoming/sakashimas_protege.txt b/forge-gui/res/cardsfolder/s/sakashimas_protege.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/sakashimas_protege.txt rename to forge-gui/res/cardsfolder/s/sakashimas_protege.txt diff --git a/forge-gui/res/cardsfolder/upcoming/sakashimas_will.txt b/forge-gui/res/cardsfolder/s/sakashimas_will.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/sakashimas_will.txt rename to forge-gui/res/cardsfolder/s/sakashimas_will.txt diff --git a/forge-gui/res/cardsfolder/s/scorched_earth.txt b/forge-gui/res/cardsfolder/s/scorched_earth.txt index 291ca501892..60bb6747e7f 100644 --- a/forge-gui/res/cardsfolder/s/scorched_earth.txt +++ b/forge-gui/res/cardsfolder/s/scorched_earth.txt @@ -1,7 +1,7 @@ Name:Scorched Earth ManaCost:X R Types:Sorcery -A:SP$ Destroy | Cost$ X R Discard | CostDesc$ As an additional cost to cast this spell, discard X land cards. | TargetMin$ X | TargetMax$ X | ValidTgts$ Land | TgtPrompt$ Select X target lands | References$ X | SpellDescription$ Destroy X target lands. | AILogic$ ScorchedEarth +A:SP$ Destroy | Cost$ X R Discard | CostDesc$ As an additional cost to cast this spell, discard X land cards. | TargetMin$ X | TargetMax$ X | ValidTgts$ Land | TgtPrompt$ Select X target lands | References$ X | SpellDescription$ Destroy X target lands. SVar:X:Count$xPaid AI:RemoveDeck:Random SVar:PlayBeforeLandDrop:true diff --git a/forge-gui/res/cardsfolder/upcoming/sengir_the_dark_baron.txt b/forge-gui/res/cardsfolder/s/sengir_the_dark_baron.txt similarity index 75% rename from forge-gui/res/cardsfolder/upcoming/sengir_the_dark_baron.txt rename to forge-gui/res/cardsfolder/s/sengir_the_dark_baron.txt index 6487b7c6666..abc7e5fb65e 100644 --- a/forge-gui/res/cardsfolder/upcoming/sengir_the_dark_baron.txt +++ b/forge-gui/res/cardsfolder/s/sengir_the_dark_baron.txt @@ -6,8 +6,8 @@ K:Flying K:Partner T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.Other | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever another creature dies, put two +1/+1 counters on CARDNAME. SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 2 -T:Mode$ LosesGame | ValidPlayer$ Player.Other | Execute$ TrigGainLife | TriggerDescription$ Whenever another player loses the game, you gain life equal to that player's life total as the turn began. +T:Mode$ LosesGame | ValidPlayer$ Player.Other | TriggerZones$ Battlefield | Execute$ TrigGainLife | TriggerDescription$ Whenever another player loses the game, you gain life equal to that player's life total as the turn began. SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X | References$ X SVar:X:TriggeredPlayer$LifeStartedThisTurnWith -DeckHas:Ability$Counters +DeckHas:Ability$Counters & Ability$LifeGain Oracle:Whenever another creature dies, put two +1/+1 counters on Sengir, the Dark Baron.\nWhenever another player loses the game, you gain life equal to that player's life total as the turn began.\nPartner (You can have two commanders if both have partner.) diff --git a/forge-gui/res/cardsfolder/upcoming/seraphic_greatsword.txt b/forge-gui/res/cardsfolder/s/seraphic_greatsword.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/seraphic_greatsword.txt rename to forge-gui/res/cardsfolder/s/seraphic_greatsword.txt diff --git a/forge-gui/res/cardsfolder/upcoming/siani_eye_of_the_storm.txt b/forge-gui/res/cardsfolder/s/siani_eye_of_the_storm.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/siani_eye_of_the_storm.txt rename to forge-gui/res/cardsfolder/s/siani_eye_of_the_storm.txt diff --git a/forge-gui/res/cardsfolder/s/sisay_weatherlight_captain.txt b/forge-gui/res/cardsfolder/s/sisay_weatherlight_captain.txt index d92106ac331..fb6bff9ed7d 100644 --- a/forge-gui/res/cardsfolder/s/sisay_weatherlight_captain.txt +++ b/forge-gui/res/cardsfolder/s/sisay_weatherlight_captain.txt @@ -3,7 +3,7 @@ ManaCost:2 W Types:Legendary Creature Human Soldier PT:2/2 S:Mode$ Continuous | Affected$ Card.Self | AddPower$ X | AddToughness$ X | References$ X | Description$ CARDNAME gets +1/+1 for each color among other legendary permanents you control. -SVar:X:Count$ColorsCtrl Permanent.YouCtrl+Other+Legendary+inZoneBattlefield +SVar:X:Count$ColorsCtrl Permanent.Other+Legendary A:AB$ ChangeZone | Cost$ W U B R G | Origin$ Library | Destination$ Battlefield | ChangeType$ Permanent.Legendary+cmcLTY | ChangeNum$ 1 | References$ Y | SpellDescription$ Search your library for a legendary permanent card with converted mana cost less than NICKNAME's power, put that card onto the battlefield, then shuffle your library. SVar:Y:Count$CardPower DeckHints:Type$Legendary diff --git a/forge-gui/res/cardsfolder/s/skullmane_baku.txt b/forge-gui/res/cardsfolder/s/skullmane_baku.txt index 7763a6af192..1dfa5d8e7de 100644 --- a/forge-gui/res/cardsfolder/s/skullmane_baku.txt +++ b/forge-gui/res/cardsfolder/s/skullmane_baku.txt @@ -3,11 +3,9 @@ ManaCost:3 B B Types:Creature Spirit PT:2/1 T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, you may put a ki counter on CARDNAME. -SVar:TrigPutCounter:DB$PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 -#ChosenX SVar created by Cost payment -A:AB$ Pump | Cost$ 1 T SubCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -ChosenX | NumDef$ -ChosenX | IsCurse$ True | AILogic$ DebuffForXCounters | References$ X | SpellDescription$ Target creature gets -X/-X until end of turn. -SVar:X:XChoice +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 +A:AB$ Pump | Cost$ 1 T SubCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -X | NumDef$ -X | IsCurse$ True | References$ X | SpellDescription$ Target creature gets -X/-X until end of turn. +SVar:X:Count$xPaid AI:RemoveDeck:Random DeckHints:Type$Spirit|Arcane -SVar:Picture:http://www.wizards.com/global/images/magic/general/skullmane_baku.jpg Oracle:Whenever you cast a Spirit or Arcane spell, you may put a ki counter on Skullmane Baku.\n{1}, {T}, Remove X ki counters from Skullmane Baku: Target creature gets -X/-X until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/slash_the_ranks.txt b/forge-gui/res/cardsfolder/s/slash_the_ranks.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/slash_the_ranks.txt rename to forge-gui/res/cardsfolder/s/slash_the_ranks.txt diff --git a/forge-gui/res/cardsfolder/upcoming/slurrk_all_ingesting.txt b/forge-gui/res/cardsfolder/s/slurrk_all_ingesting.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/slurrk_all_ingesting.txt rename to forge-gui/res/cardsfolder/s/slurrk_all_ingesting.txt diff --git a/forge-gui/res/cardsfolder/s/soul_diviner.txt b/forge-gui/res/cardsfolder/s/soul_diviner.txt index ee5b4f516fa..d86e1c17296 100644 --- a/forge-gui/res/cardsfolder/s/soul_diviner.txt +++ b/forge-gui/res/cardsfolder/s/soul_diviner.txt @@ -2,6 +2,6 @@ Name:Soul Diviner ManaCost:U B Types:Creature Zombie Wizard PT:2/3 -A:AB$ Draw | Cost$ T RemoveAnyCounter<1/Card.Artifact;Card.Creature;Card.Land;Card.Planeswalker/artifact, creature, land or planeswalker> | NumCards$ 1 | SpellDescription$ Draw a card. +A:AB$ Draw | Cost$ T RemoveAnyCounter<1/Any/Card.Artifact;Card.Creature;Card.Land;Card.Planeswalker/artifact, creature, land or planeswalker> | NumCards$ 1 | SpellDescription$ Draw a card. AI:RemoveDeck:Random Oracle:{T}, Remove a counter from an artifact, creature, land, or planeswalker you control: Draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/soul_of_eternity.txt b/forge-gui/res/cardsfolder/s/soul_of_eternity.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/soul_of_eternity.txt rename to forge-gui/res/cardsfolder/s/soul_of_eternity.txt diff --git a/forge-gui/res/cardsfolder/s/soul_of_ravnica.txt b/forge-gui/res/cardsfolder/s/soul_of_ravnica.txt index 8175bcba7fe..5e73aa3d942 100644 --- a/forge-gui/res/cardsfolder/s/soul_of_ravnica.txt +++ b/forge-gui/res/cardsfolder/s/soul_of_ravnica.txt @@ -5,7 +5,6 @@ PT:6/6 K:Flying A:AB$ Draw | Cost$ 5 U U | NumCards$ X | References$ X | SpellDescription$ Draw a card for each color among permanents you control. A:AB$ Draw | Cost$ 5 U U ExileFromGrave<1/CARDNAME> | ActivationZone$ Graveyard | NumCards$ X | References$ X | SpellDescription$ Draw a card for each color among permanents you control. -SVar:X:Count$ColorsCtrl Permanent.YouCtrl+inZoneBattlefield +SVar:X:Count$ColorsCtrl Permanent DeckNeeds:Color$White|Red|Green|Black -SVar:Picture:http://www.wizards.com/global/images/magic/general/soul_of_ravnica.jpg Oracle:Flying\n{5}{U}{U}: Draw a card for each color among permanents you control.\n{5}{U}{U}, Exile Soul of Ravnica from your graveyard: Draw a card for each color among permanents you control. diff --git a/forge-gui/res/cardsfolder/upcoming/soulfire_eruption.txt b/forge-gui/res/cardsfolder/s/soulfire_eruption.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/soulfire_eruption.txt rename to forge-gui/res/cardsfolder/s/soulfire_eruption.txt diff --git a/forge-gui/res/cardsfolder/upcoming/spectator_seating.txt b/forge-gui/res/cardsfolder/s/spectator_seating.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/spectator_seating.txt rename to forge-gui/res/cardsfolder/s/spectator_seating.txt diff --git a/forge-gui/res/cardsfolder/upcoming/sphinx_of_the_second_sun.txt b/forge-gui/res/cardsfolder/s/sphinx_of_the_second_sun.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/sphinx_of_the_second_sun.txt rename to forge-gui/res/cardsfolder/s/sphinx_of_the_second_sun.txt diff --git a/forge-gui/res/cardsfolder/upcoming/staunch_throneguard.txt b/forge-gui/res/cardsfolder/s/staunch_throneguard.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/staunch_throneguard.txt rename to forge-gui/res/cardsfolder/s/staunch_throneguard.txt diff --git a/forge-gui/res/cardsfolder/upcoming/stumpsquall_hydra.txt b/forge-gui/res/cardsfolder/s/stumpsquall_hydra.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/stumpsquall_hydra.txt rename to forge-gui/res/cardsfolder/s/stumpsquall_hydra.txt diff --git a/forge-gui/res/cardsfolder/upcoming/sweet_gum_recluse.txt b/forge-gui/res/cardsfolder/s/sweet_gum_recluse.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/sweet_gum_recluse.txt rename to forge-gui/res/cardsfolder/s/sweet_gum_recluse.txt diff --git a/forge-gui/res/cardsfolder/upcoming/szats_will.txt b/forge-gui/res/cardsfolder/s/szats_will.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/szats_will.txt rename to forge-gui/res/cardsfolder/s/szats_will.txt diff --git a/forge-gui/res/cardsfolder/upcoming/target_minotaur.txt b/forge-gui/res/cardsfolder/t/target_minotaur.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/target_minotaur.txt rename to forge-gui/res/cardsfolder/t/target_minotaur.txt diff --git a/forge-gui/res/cardsfolder/upcoming/tevesh_szat_doom_of_fools.txt b/forge-gui/res/cardsfolder/t/tevesh_szat_doom_of_fools.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/tevesh_szat_doom_of_fools.txt rename to forge-gui/res/cardsfolder/t/tevesh_szat_doom_of_fools.txt diff --git a/forge-gui/res/cardsfolder/upcoming/thalisse_reverent_medium.txt b/forge-gui/res/cardsfolder/t/thalisse_reverent_medium.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/thalisse_reverent_medium.txt rename to forge-gui/res/cardsfolder/t/thalisse_reverent_medium.txt diff --git a/forge-gui/res/cardsfolder/upcoming/the_prismatic_piper.txt b/forge-gui/res/cardsfolder/t/the_prismatic_piper.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/the_prismatic_piper.txt rename to forge-gui/res/cardsfolder/t/the_prismatic_piper.txt diff --git a/forge-gui/res/cardsfolder/t/thief_of_blood.txt b/forge-gui/res/cardsfolder/t/thief_of_blood.txt index d561db94bdf..dad73b276bd 100644 --- a/forge-gui/res/cardsfolder/t/thief_of_blood.txt +++ b/forge-gui/res/cardsfolder/t/thief_of_blood.txt @@ -4,10 +4,10 @@ Types:Creature Vampire PT:1/1 K:Flying K:ETBReplacement:Other:DBRemoveCounterAll -SVar:DBRemoveCounterAll:DB$ RemoveCounterAll | ValidCards$ Permanent | AllCounterTypes$ True | StackDescription$ SpellDescription | SubAbility$ DBPutCounters| RememberAmount$ True | SpellDescription$ As CARDNAME enters the battlefield, remove all counters from all permanents. CARDNAME enters the battlefield with a +1/+1 counter on it for each counter removed this way. -SVar:DBPutCounters:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBCleanup +SVar:DBRemoveCounterAll:DB$ RemoveCounterAll | ValidCards$ Permanent | AllCounterTypes$ True | StackDescription$ SpellDescription | SubAbility$ DBPutCounters | RememberAmount$ True | SpellDescription$ As CARDNAME enters the battlefield, remove all counters from all permanents. CARDNAME enters the battlefield with a +1/+1 counter on it for each counter removed this way. +SVar:DBPutCounters:DB$ PutCounter | ETB$ True | Defined$ Self | CounterType$ P1P1 | CounterNum$ X | References$ X | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$ChosenNumber AI:RemoveDeck:Random -SVar:Picture:http://www.wizards.com/global/images/magic/general/thief_of_blood.jpg +DeckHas:Ability$Counters Oracle:Flying\nAs Thief of Blood enters the battlefield, remove all counters from all permanents. Thief of Blood enters the battlefield with a +1/+1 counter on it for each counter removed this way. diff --git a/forge-gui/res/cardsfolder/upcoming/timely_ward.txt b/forge-gui/res/cardsfolder/t/timely_ward.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/timely_ward.txt rename to forge-gui/res/cardsfolder/t/timely_ward.txt diff --git a/forge-gui/res/cardsfolder/upcoming/toggo_goblin_weaponsmith.txt b/forge-gui/res/cardsfolder/t/toggo_goblin_weaponsmith.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/toggo_goblin_weaponsmith.txt rename to forge-gui/res/cardsfolder/t/toggo_goblin_weaponsmith.txt diff --git a/forge-gui/res/cardsfolder/upcoming/tormod_the_desecrator.txt b/forge-gui/res/cardsfolder/t/tormod_the_desecrator.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/tormod_the_desecrator.txt rename to forge-gui/res/cardsfolder/t/tormod_the_desecrator.txt diff --git a/forge-gui/res/cardsfolder/upcoming/training_center.txt b/forge-gui/res/cardsfolder/t/training_center.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/training_center.txt rename to forge-gui/res/cardsfolder/t/training_center.txt diff --git a/forge-gui/res/cardsfolder/upcoming/trench_behemoth.txt b/forge-gui/res/cardsfolder/t/trench_behemoth.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/trench_behemoth.txt rename to forge-gui/res/cardsfolder/t/trench_behemoth.txt diff --git a/forge-gui/res/cardsfolder/upcoming/triumphant_reckoning.txt b/forge-gui/res/cardsfolder/t/triumphant_reckoning.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/triumphant_reckoning.txt rename to forge-gui/res/cardsfolder/t/triumphant_reckoning.txt diff --git a/forge-gui/res/cardsfolder/upcoming/trove_tracker.txt b/forge-gui/res/cardsfolder/t/trove_tracker.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/trove_tracker.txt rename to forge-gui/res/cardsfolder/t/trove_tracker.txt diff --git a/forge-gui/res/cardsfolder/upcoming/tuya_bearclaw.txt b/forge-gui/res/cardsfolder/t/tuya_bearclaw.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/tuya_bearclaw.txt rename to forge-gui/res/cardsfolder/t/tuya_bearclaw.txt diff --git a/forge-gui/res/cardsfolder/upcoming/undergrowth_stadium.txt b/forge-gui/res/cardsfolder/u/undergrowth_stadium.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/undergrowth_stadium.txt rename to forge-gui/res/cardsfolder/u/undergrowth_stadium.txt diff --git a/forge-gui/res/cardsfolder/upcoming/absorb_identity.txt b/forge-gui/res/cardsfolder/upcoming/absorb_identity.txt new file mode 100644 index 00000000000..617f3b81f6a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/absorb_identity.txt @@ -0,0 +1,12 @@ +Name:Absorb Identity +ManaCost:1 U +Types:Instant +A:SP$ ChangeZone | Cost$ 1 U | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Hand | RememberChanged$ True | SubAbility$ DBChoice | SpellDescription$ Return target creature to its owner's hand. You may have Shapeshifters you control become copies of that creature until end of turn. +SVar:DBChoice:DB$ GenericChoice | Choices$ CloneArmy,DBNoop | Defined$ You | SubAbility$ DBCleanup | StackDesription$ You may have Shapeshifters you control become copies of that creature until end of turn. +SVar:CloneArmy:DB$ RepeatEach | UseImprinted$ True | RepeatCards$ Shapeshifter.YouCtrl | RepeatSubAbility$ DBCopy | SpellDescription$ Shapeshifters you control become copies of that creature until end of turn. +SVar:DBCopy:DB$ Clone | Defined$ Remembered | CloneTarget$ Imprinted | Duration$ UntilEndOfTurn +SVar:DBNoop:DB$ Cleanup | SpellDescription$ Do nothing. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True +DeckHints:Type$Shapeshifter +AI:RemoveDeck:All +Oracle:Return target creature to its owner's hand. You may have Shapeshifters you control become copies of that creature until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/armed_and_armored.txt b/forge-gui/res/cardsfolder/upcoming/armed_and_armored.txt new file mode 100644 index 00000000000..be255a89fd2 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/armed_and_armored.txt @@ -0,0 +1,14 @@ +Name:Armed and Armored +ManaCost:1 W +Types:Instant +A:SP$ AnimateAll | Cost$ 1 W | Types$ Creature,Artifact | ValidCards$ Vehicle.YouCtrl | SubAbility$ ChooseDwarf | StackDescription$ Vehicles {p:You} controls become artifact creatures until end of turn. | SpellDescription$ Vehicles you control become artifact creatures until end of turn. Choose a Dwarf you control. Attach any number of Equipment you control to it. +SVar:ChooseDwarf:DB$ ChooseCard | Defined$ You | Amount$ 1 | Choices$ Dwarf.YouCtrl | ChoiceTitle$ Choose a Dwarf you control | StackDescription$ {p:You} chooses a Dwarf they control and attaches any number of Equipment they control to it. | ImprintChosen$ True | SubAbility$ ChooseEquipment +SVar:ChooseEquipment:DB$ ChooseCard | Defined$ You | MinAmount$ 0 | Amount$ X | References$ X | Choices$ Equipment.YouCtrl | StackDescription$ None | ChoiceTitle$ Choose any number of Equipment you control | ForgetChosen$ True | SubAbility$ DeployDwarf +SVar:DeployDwarf:DB$ RepeatEach | RepeatSubAbility$ ArmDwarf | RepeatCards$ Card.ChosenCard | SubAbility$ DBCleanup +SVar:ArmDwarf:DB$ Attach | Object$ Remembered | Defined$ Imprinted | StackDescription$ None +SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True | ClearRemembered$ True | ClearImprinted$ True +SVar:X:Count$Valid Equipment.YouCtrl +DeckNeeds:Type$Vehicle|Dwarf +DeckHints:Type$Equipment +AI:RemoveDeck:All +Oracle:Vehicles you control become artifact creatures until end of turn. Choose a Dwarf you control. Attach any number of Equipment you control to it. diff --git a/forge-gui/res/cardsfolder/upcoming/barkchannel_pathway_tidechannel_pathway.txt b/forge-gui/res/cardsfolder/upcoming/barkchannel_pathway_tidechannel_pathway.txt new file mode 100644 index 00000000000..cc88f72b313 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/barkchannel_pathway_tidechannel_pathway.txt @@ -0,0 +1,14 @@ +Name:Barkchannel Pathway +ManaCost:no cost +Types:Land +A:AB$ Mana | Cost$ T | Produced$ G | SpellDescription$ Add {G}. +AlternateMode:Modal +Oracle:Add {G}. + +ALTERNATE + +Name:Tidechannel Pathway +ManaCost:no cost +Types:Land +A:AB$ Mana | Cost$ T | Produced$ U | SpellDescription$ Add {U}. +Oracle:Add {U}. diff --git a/forge-gui/res/cardsfolder/upcoming/bearded_axe.txt b/forge-gui/res/cardsfolder/upcoming/bearded_axe.txt new file mode 100644 index 00000000000..3f53e700690 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bearded_axe.txt @@ -0,0 +1,8 @@ +Name:Bearded Axe +ManaCost:2 R +Types:Artifact Equipment +S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ X | AddToughness$ X | References$ X | Description$ Equipped creature gets +1/+1 for each Dwarf, Equipment, and/or Vehicle you control. +K:Equip:2 +SVar:X:Count$Valid Dwarf.YouCtrl,Equipment.YouCtrl,Vehicle.YouCtrl +DeckHints:Type$Dwarf|Equipment|Vehicle +Oracle:Equipped creature gets +1/+1 for each Dwarf, Equipment, and/or Vehicle you control.\nEquip {2} diff --git a/forge-gui/res/cardsfolder/upcoming/blightstep_pathway_searstep_pathway.txt b/forge-gui/res/cardsfolder/upcoming/blightstep_pathway_searstep_pathway.txt new file mode 100644 index 00000000000..912e253dacf --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/blightstep_pathway_searstep_pathway.txt @@ -0,0 +1,14 @@ +Name:Blightstep Pathway +ManaCost:no cost +Types:Land +A:AB$ Mana | Cost$ T | Produced$ B | SpellDescription$ Add {B}. +AlternateMode:Modal +Oracle:Add {B}. + +ALTERNATE + +Name:Searstep Pathway +ManaCost:no cost +Types:Land +A:AB$ Mana | Cost$ T | Produced$ R | SpellDescription$ Add {R}. +Oracle:Add {R}. diff --git a/forge-gui/res/cardsfolder/upcoming/canopy_tactician.txt b/forge-gui/res/cardsfolder/upcoming/canopy_tactician.txt new file mode 100644 index 00000000000..d5be5d33df0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/canopy_tactician.txt @@ -0,0 +1,8 @@ +Name:Canopy Tactician +ManaCost:3 G +Types:Creature Elf Warrior +PT:3/3 +S:Mode$ Continuous | Affected$ Elf.Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other Elves you control get +1/+1. +A:AB$ Mana | Cost$ T | Produced$ G | Amount$ 3 | SpellDescription$ Add {G}{G}{G}. +DeckHints:Type$Elf +Oracle:Other Elves you control get +1/+1.\n{T}: Add {G}{G}{G}. diff --git a/forge-gui/res/cardsfolder/upcoming/cleaving_reaper.txt b/forge-gui/res/cardsfolder/upcoming/cleaving_reaper.txt new file mode 100644 index 00000000000..bebe4535923 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/cleaving_reaper.txt @@ -0,0 +1,11 @@ +Name:Cleaving Reaper +ManaCost:3 B B +Types:Creature Angel Berserker +PT:5/3 +K:Flying +K:Trample +A:AB$ ChangeZone | Cost$ PayLife<3> | Origin$ Graveyard | Destination$ Hand | ActivationZone$ Graveyard | CheckSVar$ X | References$ X | SpellDescription$ Return CARDNAME from your graveyard to your hand. Activate this ability only if you had an Angel or Berserker enter the battlefield under your control this turn. +SVar:X:Count$ThisTurnEntered_Battlefield_Angel.YouCtrl,Berserker.YouCtrl +DeckHints:Type$Angel|Berserker +DeckHas:Ability$Graveyard +Oracle:Flying, trample\nPay 3 life: Return Cleaving Reaper from your graveyard to your hand. Activate this ability only if you had an Angel or Berserker enter the battlefield under your control this turn. diff --git a/forge-gui/res/cardsfolder/upcoming/darkbore_pathway_slitherbore_pathway.txt b/forge-gui/res/cardsfolder/upcoming/darkbore_pathway_slitherbore_pathway.txt new file mode 100644 index 00000000000..e40562d985a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/darkbore_pathway_slitherbore_pathway.txt @@ -0,0 +1,14 @@ +Name:Darkbore Pathway +ManaCost:no cost +Types:Land +A:AB$ Mana | Cost$ T | Produced$ B | SpellDescription$ Add {B}. +AlternateMode:Modal +Oracle:Add {B}. + +ALTERNATE + +Name:Slitherbore Pathway +ManaCost:no cost +Types:Land +A:AB$ Mana | Cost$ T | Produced$ G | SpellDescription$ Add {G}. +Oracle:Add {G}. diff --git a/forge-gui/res/cardsfolder/upcoming/elderfang_ritualist.txt b/forge-gui/res/cardsfolder/upcoming/elderfang_ritualist.txt new file mode 100644 index 00000000000..752f485603d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/elderfang_ritualist.txt @@ -0,0 +1,10 @@ +Name:Elderfang Ritualist +ManaCost:2 B +Types:Creature Elf Cleric +PT:3/1 +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME dies, return another target Elf card from your graveyard to your hand. +SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Elf.Other+YouOwn | TgtPrompt$ Select another target Elf card from your graveyard +SVar:SacMe:2 +DeckNeeds:Type$Elf +DeckHas:Ability$Graveyard +Oracle:When Elderfang Ritualist dies, return another target Elf card from your graveyard to your hand. diff --git a/forge-gui/res/cardsfolder/upcoming/elven_ambush.txt b/forge-gui/res/cardsfolder/upcoming/elven_ambush.txt new file mode 100644 index 00000000000..8c2c5a8b62a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/elven_ambush.txt @@ -0,0 +1,9 @@ +Name:Elven Ambush +ManaCost:3 G +Types:Instant +A:SP$ Token | Cost$ 3 G | TokenAmount$ X | References$ X | TokenScript$ g_1_1_elf_warrior | TokenOwner$ You | SpellDescription$ Create a 1/1 green Elf Warrior creature token for each Elf you control. +SVar:X:Count$Valid Elf.YouCtrl +DeckHas:Ability$Token +DeckNeeds:Type$Elf +AI:RemoveDeck:Random +Oracle:Create a 1/1 green Elf Warrior creature token for each Elf you control. diff --git a/forge-gui/res/cardsfolder/upcoming/fire_giants_fury.txt b/forge-gui/res/cardsfolder/upcoming/fire_giants_fury.txt new file mode 100644 index 00000000000..ae2301eb86c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fire_giants_fury.txt @@ -0,0 +1,14 @@ +Name:Fire Giant's Fury +ManaCost:1 R +Types:Sorcery +A:SP$ Pump | Cost$ 1 R | ValidTgts$ Giant.YouCtrl | TgtPrompt$ Select target Giant you control | NumAtt$ +2 | NumDef$ +2 | KW$ Trample | SubAbility$ DBCombatDamageEffect | StackDescription$ {c:Targeted} gets +2/+2 and gains trample until end of turn. Whenever it deals combat damage to a player this turn, exile that many cards from the top of your library. Until the end of your next turn, you may play those cards. | SpellDescription$ Target Giant you control gets +2/+2 and gains trample until end of turn. Whenever it deals combat damage to a player this turn, exile that many cards from the top of your library. Until the end of your next turn, you may play those cards. +SVar:DBCombatDamageEffect:DB$ Effect | ImprintCards$ Targeted | Triggers$ DealCombatDamagePlayer | SVars$ TrigExile,DBEffect,X,STPlay,DBCleanup | SubAbility$ DBCleanup +SVar:DealCombatDamagePlayer:Mode$ DamageDone | ValidSource$ Card.IsImprinted | ValidTarget$ Player | Execute$ TrigExile | CombatDamage$ True | TriggerDescription$ Whenever this Giant deals combat damage to a player this turn, exile that many cards from the top of your library. Until the end of your next turn, you may play those cards. +SVar:TrigExile:DB$ Dig | Defined$ You | DigNum$ X | References$ X | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect +SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay | ForgetOnMoved$ Exile | RememberObjects$ RememberedCard | Duration$ UntilTheEndOfYourNextTurn | SubAbility$ DBCleanup +SVar:STPlay:Mode$ Continuous | EffectZone$ Command | Affected$ Card.IsRemembered | MayPlay$ True | AffectedZone$ Exile | Description$ Until the end of your next turn, you may play those cards. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True +SVar:X:TriggerCount$DamageAmount +DeckNeeds:Type$Giant +AI:RemoveDeck:Random +Oracle:Target Giant you control gets +2/+2 and gains trample until end of turn. Whenever it deals combat damage to a player this turn, exile that many cards from the top of your library. Until the end of your next turn, you may play those cards. diff --git a/forge-gui/res/cardsfolder/upcoming/giants_grasp.txt b/forge-gui/res/cardsfolder/upcoming/giants_grasp.txt new file mode 100644 index 00000000000..6ff381c90fb --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/giants_grasp.txt @@ -0,0 +1,9 @@ +Name:Giant's Grasp +ManaCost:2 U U +Types:Enchantment Aura +K:Enchant Giant you control +A:SP$ Attach | Cost$ 2 U U | ValidTgts$ Giant.YouCtrl | TgtPrompt$ Select Giant you control +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters the battlefield, gain control of target nonland permanent for as long as CARDNAME remains on the battlefield. +SVar:TrigChange:DB$ GainControl | TgtPrompt$ Choose target nonland permanent | ValidTgts$ Permanent.nonLand | LoseControl$ LeavesPlay +DeckNeeds:Type$Giant +Oracle:Enchant Giant you control\nWhen Giant's Grasp enters the battlefield, gain control of target nonland permanent for as long as Giant's Grasp remains on the battlefield. diff --git a/forge-gui/res/cardsfolder/upcoming/gilded_assault_cart.txt b/forge-gui/res/cardsfolder/upcoming/gilded_assault_cart.txt new file mode 100644 index 00000000000..295bebf28e7 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/gilded_assault_cart.txt @@ -0,0 +1,10 @@ +Name:Gilded Assault Cart +ManaCost:1 R R +Types:Artifact Vehicle +PT:5/1 +K:Trample +K:Crew:2 +A:AB$ ChangeZone | Cost$ Sac<2/Treasure> | CostDesc$ Sacrifice two Treasures: | Origin$ Graveyard | Destination$ Hand | ActivationZone$ Graveyard | SpellDescription$ Return CARDNAME from your graveyard to your hand. +DeckHas:Ability$Sacrifice +AI:RemoveDeck:Random +Oracle:Trample\nCrew 2 (Tap any number of creatures you control with total power 2 or more: This Vehicle becomes an artifact creature until end of turn.)\nSacrifice two Treasures: Return Gilded Assault Cart from your graveyard to your hand. diff --git a/forge-gui/res/cardsfolder/upcoming/gladewalker_ritualist.txt b/forge-gui/res/cardsfolder/upcoming/gladewalker_ritualist.txt new file mode 100644 index 00000000000..138512afaa1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/gladewalker_ritualist.txt @@ -0,0 +1,9 @@ +Name:Gladewalker Ritualist +ManaCost:2 G +Types:Creature Shapeshifter +PT:3/3 +K:Changeling +T:Mode$ ChangesZone | ValidCard$ Creature.YouCtrl+Other+namedGladewalker Ritualist | Origin$ Any | Destination$ Battlefield | Execute$ TrigDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever another creature named Gladewalker Ritualist enters the battlefield under your control, draw a card. +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 +DeckHints:Name$Gladewalker Ritualist +Oracle:Changeling (This card is every creature type.)\nWhenever another creature named Gladewalker Ritualist enters the battlefield under your control, draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/halvar_god_of_battle_sword_of_the_realms.txt b/forge-gui/res/cardsfolder/upcoming/halvar_god_of_battle_sword_of_the_realms.txt new file mode 100644 index 00000000000..d0c52a48069 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/halvar_god_of_battle_sword_of_the_realms.txt @@ -0,0 +1,23 @@ +Name:Halvar, God of Battle +ManaCost:2 W W +Types:Legendary Creature God +PT:4/4 +S:Mode$ Continuous | Affected$ Creature.YouCtrl+enchanted,Creature.YouCtrl+equipped | AddKeyword$ Double Strike | Description$ Creatures you control that are enchanted or equipped gain double strike. +T:Mode$ Phase | Phase$ BeginCombat | TriggerZones$ Battlefield | Execute$ TrigTargetAuraEquip | OptionalDecider$ You | TriggerDescription$ At the beginning of each combat, you may attach target Aura or Equipment attached to a creature you control to target creature you control. +SVar:TrigTargetAuraEquip:DB$ Pump | ValidTgts$ Equipment.AttachedTo Creature.YouCtrl,Aura.AttachedTo Creature.YouCtrl | TgtPrompt$ Select target Aura or Equipment attached to a creature you control | SubAbility$ DBAttach | StackDescription$ None +SVar:DBAttach:DB$ Attach | Object$ ParentTarget | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control +AlternateMode:Modal +DeckHints:Type$Equipment|Aura +Oracle:Creatures you control that are enchanted or equipped have double strike.\nAt the beginning of each combat, you may attach target Aura or Equipment attached to a creature you control to target creature you control. + +ALTERNATE + +Name:Sword of the Realms +ManaCost:1 W +Types:Legendary Artifact Equipment +K:Equip:1 W +S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 2 | AddKeyword$ Vigilance | Description$ Equipped creature gets +2/+0 and has vigilance. +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.AttachedBy | Execute$ TrigReturn | TriggerDescription$ Whenever equipped creature dies, return it to its owner's hand. +SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Hand +Oracle:Equipped creature gets +2/+0 and has vigilance.\nWhenever equipped creature dies, return it to its owner's hand.\nEquip {1} {W} + diff --git a/forge-gui/res/cardsfolder/upcoming/hengegate_pathway_mistgate_pathway.txt b/forge-gui/res/cardsfolder/upcoming/hengegate_pathway_mistgate_pathway.txt new file mode 100644 index 00000000000..27770d4e472 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/hengegate_pathway_mistgate_pathway.txt @@ -0,0 +1,14 @@ +Name:Hengegate Pathway +ManaCost:no cost +Types:Land +A:AB$ Mana | Cost$ T | Produced$ W | SpellDescription$ Add {W}. +AlternateMode:Modal +Oracle:Add {W}. + +ALTERNATE + +Name:Mistgate Pathway +ManaCost:no cost +Types:Land +A:AB$ Mana | Cost$ T | Produced$ U | SpellDescription$ Add {U}. +Oracle:Add {U}. diff --git a/forge-gui/res/cardsfolder/upcoming/kaya_the_inexorable.txt b/forge-gui/res/cardsfolder/upcoming/kaya_the_inexorable.txt new file mode 100644 index 00000000000..2691465d73b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/kaya_the_inexorable.txt @@ -0,0 +1,17 @@ +Name:Kaya the Inexorable +ManaCost:3 W B +Types:Legendary Planeswalker Kaya +Loyalty:5 +A:AB$ PutCounter | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature.nonToken | AITgts$ Creature.nonToken+YouCtrl | TgtPrompt$ Select target nontoken creature | TargetMin$ 0 | TargetMax$ 1 | CounterType$ GHOSTFORM | CounterNum$ 1 | SubAbility$ DBAnimate | SpellDescription$ Put a ghostform counter on up to one target nontoken creature. It gains "When this creature dies or is put into exile, return it to its owner's hand and create a 1/1 white Spirit creature token with flying." +SVar:DBAnimate:DB$ Animate | Defined$ Targeted | Permanent$ True | Triggers$ TrigDieExile | StackDescription$ {c:Targeted} gains "When this creature dies or is put into exile, return it to its owner's hand and create a 1/1 white Spirit creature token with flying." +SVar:TrigDieExile:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard,Exile | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When this creature dies or is put into exile, return it to its owner's hand and create a 1/1 white Spirit creature token with flying. +SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ All | Destination$ Hand | SubAbility$ DBToken +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_spirit_flying | TokenOwner$ TriggeredCardController +A:AB$ ChangeZone | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | SpellDescription$ Exile target nonland permanent. +A:AB$ Effect | Cost$ SubCounter<7/LOYALTY> | Planeswalker$ True | Ultimate$ True | Name$ Emblem - Kaya the Inexorable | Image$ emblem_kaya_the_inexorable | Duration$ Permanent | Triggers$ Upkeep | SpellDescription$ You get an emblem with "At the beginning of your upkeep, you may cast a legendary spell from your hand, from your graveyard, or from among cards you own in exile without paying its mana cost." +SVar:Upkeep:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | OptionalDecider$ You | Execute$ TrigCast | TriggerDescription$ At the beginning of your upkeep, you may cast a legendary spell from your hand, from your graveyard, or from among cards you own in exile without paying its mana cost. +SVar:TrigCast:DB$ Play | Valid$ Card.Legendary+YouOwn | ValidZone$ Hand,Graveyard,Exile | WithoutManaCost$ True +DeckHas:Ability$Token & Ability$Counters +DeckHints:Type$Legendary +Oracle:[+1]: Put a ghostform counter on up to one target nontoken creature. It gains "When this creature dies or is put into exile, return it to its owner's hand and create a 1/1 white Spirit creature token with flying."\n[−3]: Exile target nonland permanent.\n[−7]: You get an emblem with "At the beginning of your upkeep, you may cast a legendary spell from your hand, from your graveyard, or from among cards you own in exile without paying its mana cost." + diff --git a/forge-gui/res/cardsfolder/upcoming/magda_brazen_outlaw.txt b/forge-gui/res/cardsfolder/upcoming/magda_brazen_outlaw.txt new file mode 100644 index 00000000000..d5dab6f7945 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/magda_brazen_outlaw.txt @@ -0,0 +1,14 @@ +Name:Magda, Brazen Outlaw +ManaCost:1 R +Types:Legendary Creature Dwarf Berserker +PT:2/1 +S:Mode$ Continuous | Affected$ Dwarf.Other+YouCtrl | AddPower$ 1 | Description$ Other Dwarves you control get +1/+0. +T:Mode$ Taps | ValidCard$ Dwarf.YouCtrl | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever a Dwarf you control becomes tapped, create a Treasure token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_treasure_sac | TokenOwner$ You +A:AB$ ChangeZone | Cost$ Sac<5/Treasure> | CostDesc$ Sacrifice five Treasures: | Origin$ Library | Destination$ Battlefield | ChangeType$ Card.Artifact,Card.Dragon | ChangeNum$ 1 | Mandatory$ True | StackDescription$ {p:You} searches their library for an Artifact or Dragon card, puts that card onto the battlefield, then shuffles their library. | SpellDescription$ Search your library for an Artifact or Dragon card, put that card onto the battlefield, then shuffle your library. +SVar:BuffedBy:Dwarf +SVar:PlayMain1:TRUE +DeckNeeds:Type$Dwarf +DeckHints:Type$Dragon|Artifact +DeckHas:Ability$Token & Ability$Sacrifice +Oracle:Other Dwarves you control get +1/+0. \nWhenever a Dwarf you control becomes tapped, create a treasure token. \nSacrifice five Treasures: Search your library for an Artifact or Dragon card, put that card onto the battlefield, then shuffle your library. diff --git a/forge-gui/res/cardsfolder/upcoming/pyre_of_heroes.txt b/forge-gui/res/cardsfolder/upcoming/pyre_of_heroes.txt new file mode 100644 index 00000000000..c8f8f640ada --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/pyre_of_heroes.txt @@ -0,0 +1,10 @@ +Name:Pyre of Heroes +ManaCost:2 +Types:Artifact +A:AB$ ChangeZone | Cost$ 2 T Sac<1/Creature> | RememberCostCards$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Creature.cmcEQX+sharesCreatureTypeWith Remembered | References$ X | ChangeNum$ 1 | SorcerySpeed$ True | AILogic$ SacAndUpgrade | StackDescription$ Search your library for a creature card that shares a creature type with the sacrificed creature and has converted mana cost equal to 1 plus that creature's converted mana cost. Put that card onto the battlefield, then shuffle your library. | SubAbility$ DBCleanup | SpellDescription$ Search your library for a creature card that shares a creature type with the sacrificed creature and has converted mana cost equal to 1 plus that creature's converted mana cost. Put that card onto the battlefield, then shuffle your library. Activate this ability only any time you could cast a sorcery. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:Remembered$CardManaCost/Plus.1 +SVar:AIPreference:SacCost$Creature +DeckHas:Ability$Sacrifice +DeckNeeds:Type$Creature +Oracle:{2}, {T}, Sacrifice a creature: Search your library for a creature card that shares a creature type with the sacrificed creature and has converted mana cost equal to 1 plus that creature's converted mana cost. Put that card onto the battlefield, then shuffle your library. Activate this ability only any time you could cast a sorcery. diff --git a/forge-gui/res/cardsfolder/upcoming/rampage_of_the_valkyries.txt b/forge-gui/res/cardsfolder/upcoming/rampage_of_the_valkyries.txt new file mode 100644 index 00000000000..82c4de4f02f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/rampage_of_the_valkyries.txt @@ -0,0 +1,10 @@ +Name:Rampage of the Valkyries +ManaCost:3 W B +Types:Enchantment +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a 4/4 white Angel creature token with flying and vigilance. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_4_4_angel_flying_vigilance | TokenOwner$ You +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Angel.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigSac | TriggerDescription$ Whenever an Angel you control dies, each opponent sacrifices a creature. +SVar:TrigSac:DB$ Sacrifice | Defined$ Opponent | SacValid$ Creature +DeckHints:Type$Angel +DeckHas:Ability$Token +Oracle:When Rampage of the Valkyries enters the battlefield, create a 4/4 white Angel token with flying and vigilance.\nWhenever a creature you control dies, each opponent sacrifices a creature. diff --git a/forge-gui/res/cardsfolder/upcoming/realmwalker.txt b/forge-gui/res/cardsfolder/upcoming/realmwalker.txt new file mode 100644 index 00000000000..49fa31818ce --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/realmwalker.txt @@ -0,0 +1,10 @@ +Name:Realmwalker +ManaCost:2 G +Types:Creature Shapeshifter +PT:2/3 +K:Changeling +K:ETBReplacement:Other:ChooseCT +SVar:ChooseCT:DB$ ChooseType | Defined$ You | Type$ Creature | AILogic$ MostProminentInComputerDeck | SpellDescription$ As CARDNAME enters the battlefield, choose a creature type. +S:Mode$ Continuous | Affected$ Card.TopLibrary+YouCtrl | AffectedZone$ Library | MayLookAt$ You | Description$ You may look at the top card of your library any time. +S:Mode$ Continuous | Affected$ Creature.ChosenType+TopLibrary+YouCtrl+nonLand | AffectedZone$ Library | MayPlay$ True | Description$ You may cast creature spells of the chosen type from the top of your library. +Oracle:Changeling (This card is every creature type.)\nAs Realmwalker enters the battlefield, choose a creature type.\nYou may look at the top card of your library any time.\nYou may cast creature spells of the chosen type from the top of your library. diff --git a/forge-gui/res/cardsfolder/upcoming/renegade_reaper.txt b/forge-gui/res/cardsfolder/upcoming/renegade_reaper.txt new file mode 100644 index 00000000000..f3545f092d8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/renegade_reaper.txt @@ -0,0 +1,12 @@ +Name:Renegade Reaper +ManaCost:2 B +Types:Creature Angel Berserker +PT:2/3 +K:Flying +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMill | TriggerDescription$ When CARDNAME enters the battlefield, mill four cards. If at least one Angel card was milled this way, you gain 4 life. +SVar:TrigMill:DB$ Mill | NumCards$ 4 | Defined$ You | RememberMilled$ True | SubAbility$ DBLifeGain +SVar:DBLifeGain:DB$ GainLife | Defined$ You | LifeAmount$ 4 | ConditionDefined$ Remembered | ConditionPresent$ Angel | ConditionCompare$ GE1 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +DeckHints:Type$Angel +DeckHas:Ability$Mill & Ability$LifeGain +Oracle:Flying\nWhen Renegade Reaper enters the battlefield, mill four cards. If at least one Angel card is milled this way, you gain 4 life. diff --git a/forge-gui/res/cardsfolder/upcoming/sarulf_realm_eater.txt b/forge-gui/res/cardsfolder/upcoming/sarulf_realm_eater.txt new file mode 100644 index 00000000000..f6442d074ca --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sarulf_realm_eater.txt @@ -0,0 +1,15 @@ +Name:Sarulf, Realm Eater +ManaCost:1 B G +Types:Legendary Creature Wolf +PT:3/3 +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Permanent.OppCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever a permanent an opponent controls is put into a graveyard from the battlefield, put a +1/+1 counter on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | CheckSVar$ X | SVarCompare$ GE1 | References$ X | Execute$ TrigRemoveAll | TriggerZones$ Battlefield | OptionalDecider$ You | TriggerDescription$ At the beginning of your upkeep, if NICKNAME has one or more +1/+1 counters on it, you may remove all of them. If you do, exile each other nonland permanent with converted mana cost less than or equal to the number of counters removed this way. +SVar:TrigRemoveAll:DB$ RemoveCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ All | RememberAmount$ True | SubAbility$ DBChangeZoneAll +SVar:DBChangeZoneAll:DB$ ChangeZoneAll | Origin$ Battlefield | Destination$ Exile | ChangeType$ Permanent.nonLand+Other+cmcLEY | References$ Y | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:Count$CardCounters.P1P1 +SVar:Y:Count$RememberedNumber +DeckHas:Ability$Counters +AI:RemoveDeck:All +Oracle:Whenever a permanent an opponent controls is put into a graveyard from the battlefield, put a +1/+1 counter on Sarulf, Realm Eater.\nAt the beginning of your upkeep, if Sarulf has one or more +1/+1 counters on it, you may remove all of them. If you do, exile each other nonland permanent with converted mana cost less than or equal to the number of counters removed this way. diff --git a/forge-gui/res/cardsfolder/upcoming/showdown_of_the_skalds.txt b/forge-gui/res/cardsfolder/upcoming/showdown_of_the_skalds.txt new file mode 100644 index 00000000000..8da01e09df3 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/showdown_of_the_skalds.txt @@ -0,0 +1,13 @@ +Name:Showdown of the Skalds +ManaCost:2 R W +Types:Enchantment Saga +K:Saga:3:TrigExile,TrigEffect,TrigEffect +SVar:TrigExile:DB$ Dig | Defined$ You | DigNum$ 4 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect | SpellDescription$ Exile the top four cards of your library. Until the end of your next turn, you may play those cards. +SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ MayPlay | Duration$ UntilTheEndOfYourNextTurn | SubAbility$ DBCleanup | ForgetOnMoved$ Exile +SVar:MayPlay:Mode$ Continuous | Affected$ Card.IsRemembered | EffectZone$ Command | AffectedZone$ Exile | MayPlay$ True | Description$ Until the end of your next turn, you may play those cards. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:TrigEffect:DB$ Effect | Triggers$ CastSpell | SpellDescription$ Whenever you cast a spell this turn, put a +1/+1 counter on target creature you control. +SVar:CastSpell:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ You | TriggerZones$ Command | Execute$ DBPutCounter | TriggerDescription$ Whenever you cast a spell this turn, put a +1/+1 counter on target creature you control. +SVar:DBPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1 | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control +DeckHas:Ability$Counters +Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI - Exile the top four cards of your library. Until the end of your next turn, you may play those cards.\nII, III - Whenever you cast a spell this turn, put a +1/+1 counter on target creature you control. diff --git a/forge-gui/res/cardsfolder/upcoming/starnheim_aspirant.txt b/forge-gui/res/cardsfolder/upcoming/starnheim_aspirant.txt new file mode 100644 index 00000000000..d75a242a0d4 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/starnheim_aspirant.txt @@ -0,0 +1,7 @@ +Name:Starnheim Aspirant +ManaCost:2 W +Types:Creature Human Cleric +PT:2/2 +S:Mode$ ReduceCost | EffectZone$ Battlefield | ValidCard$ Angel | Type$ Spell | Activator$ You | Amount$ 2 | Description$ Angel spells you cast cost {2} less to cast. +DeckNeeds:Type$Angel +Oracle:Angel spells you cast cost {2} less to cast. diff --git a/forge-gui/res/cardsfolder/upcoming/surtland_elementalist.txt b/forge-gui/res/cardsfolder/upcoming/surtland_elementalist.txt new file mode 100644 index 00000000000..8c2518b1fa7 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/surtland_elementalist.txt @@ -0,0 +1,10 @@ +Name:Surtland Elementalist +ManaCost:5 U U +Types:Creature Giant Wizard +PT:8/8 +K:AlternateAdditionalCost:Reveal<1/Giant/Giant>:2 +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPlay | TriggerDescription$ Whenever CARDNAME attacks, you may cast an instant or sorcery spell from your hand without paying its mana cost. +SVar:TrigPlay:DB$ Play | ValidZone$ Hand | Valid$ Instant.YouOwn,Sorcery.YouOwn | Controller$ You | WithoutManaCost$ True | Optional$ True +SVar:HasAttackEffect:TRUE +DeckHints:Type$Instant|Sorcery|Giant +Oracle:As an additional cost to cast this spell, reveal a Giant card from your hand or pay {2}.\nWhenever Surtland Elementalist attacks, you may cast an instant or sorcery spell from your hand without paying its mana cost. diff --git a/forge-gui/res/cardsfolder/upcoming/tayam_luminous_enigma.txt b/forge-gui/res/cardsfolder/upcoming/tayam_luminous_enigma.txt new file mode 100644 index 00000000000..c4c5feea2fa --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/tayam_luminous_enigma.txt @@ -0,0 +1,12 @@ +Name:Tayam, Luminous Enigma +ManaCost:1 W B G +Types:Legendary Creature Nightmare Beast +PT:3/3 +K:ETBReplacement:Other:AddExtraCounter:Mandatory:Battlefield:Creature.Other+YouCtrl +SVar:AddExtraCounter:DB$ PutCounter | ETB$ True | Defined$ ReplacedCard | CounterType$ Vigilance | CounterNum$ 1 | SpellDescription$ Each other creature you control enters the battlefield with an additional vigilance counter on it. +A:AB$ Mill | Cost$ 3 RemoveAnyCounter<3/Any/Permanent.YouCtrl/among permanents you control> | NumCards$ 3 | Defined$ You | SubAbility$ DBChangeZone | StackDescription$ SpellDescription | SpellDescription$ Mill three cards, then return a permanent card with converted mana cost 3 or less from your graveyard to the battlefield. +SVar:DBChangeZone:DB$ ChangeZone | Hidden$ True | Mandatory$ True | ChangeType$ Permanent.YouOwn+cmcLE3 | ChangeNum$ 1 | Origin$ Graveyard | Destination$ Battlefield +DeckHas:Ability$Counters +SVar:AIRemoveCounterCostPriority:Vigilance +Oracle:Each other creature you control enters the battlefield with an additional vigilance counter on it.\n{3}, Remove three counters from among creatures you control: Mill three cards, then return a permanent card with converted mana cost 3 or less from your graveyard to the battlefield. + diff --git a/forge-gui/res/cardsfolder/upcoming/thornmantle_striker.txt b/forge-gui/res/cardsfolder/upcoming/thornmantle_striker.txt new file mode 100644 index 00000000000..e2838446169 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/thornmantle_striker.txt @@ -0,0 +1,11 @@ +Name:Thornmantle Striker +ManaCost:4 B +Types:Creature Elf Rogue +PT:4/3 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ When CARDNAME enters the battlefield, ABILITY +SVar:TrigCharm:DB$ Charm | Choices$ DBRemoveCounter,DBPump +SVar:DBRemoveCounter:DB$ RemoveCounter | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | CounterType$ Any | CounterNum$ X | References$ X | SpellDescription$ Remove X counters from target permanent, where X is the number of Elves you control. +SVar:DBPump:DB$ Pump | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | IsCurse$ True | NumAtt$ -X | NumDef$ -X | References$ X | SpellDescription$ Target creature an opponent controls gets -X/-X until end of turn, where X is the number of Elves you control. +SVar:X:Count$Valid Elf.YouCtrl +DeckNeeds:Type$Elf +Oracle:When Thornmantle Striker enters the battlefield, choose one —\n• Remove X counters from target permanent, where X is the number of Elves you control.\n• Target creature an opponent controls gets -X/-X until end of turn, where X is the number of Elves you control. diff --git a/forge-gui/res/cardsfolder/upcoming/valkyrie_harbinger.txt b/forge-gui/res/cardsfolder/upcoming/valkyrie_harbinger.txt new file mode 100644 index 00000000000..cc45a829add --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/valkyrie_harbinger.txt @@ -0,0 +1,12 @@ +Name:Valkyrie Harbinger +ManaCost:4 W W +Types:Creature Angel Cleric +PT:4/5 +K:Flying +K:Lifelink +T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE4 | Execute$ TrigToken | TriggerDescription$ At the beginning of each end step, if you gained 4 or more life this turn, create a 4/4 white Angel creature token with flying and vigilance. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_4_4_angel_flying_vigilance | TokenOwner$ You +SVar:X:Count$LifeYouGainedThisTurn +DeckHas:Ability$Token & Ability$LifeGain +DeckHints:Ability$LifeGain +Oracle:Flying, lifelink\nAt the beginning of each end step, if you gained 4 or more life this turn, create a 4/4 white Angel creature token with flying and vigilance. diff --git a/forge-gui/res/cardsfolder/upcoming/warchanter_skald.txt b/forge-gui/res/cardsfolder/upcoming/warchanter_skald.txt new file mode 100644 index 00000000000..57cbe5c30bc --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/warchanter_skald.txt @@ -0,0 +1,11 @@ +Name:Warchanter Skald +ManaCost:2 W +Types:Creature Dwarf Cleric +PT:2/3 +T:Mode$ Taps | ValidCard$ Card.Self+enchanted,Card.Self+equipped | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever Warchanter Skald becomes tapped, if it's enchanted or equipped, create a 2/1 red Dwarf Berserker creature token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ r_2_1_dwarf_berserker | TokenOwner$ You +SVar:EnchantMe:Once +SVar:EquipMe:Once +DeckNeeds:Type$Enchantment|Equipment +DeckHas:Ability$Token +Oracle:Whenever Warchanter Skald becomes tapped, if it's enchanted or equipped, create a 2/1 red Dwarf Berserker creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/youthful_valkyrie.txt b/forge-gui/res/cardsfolder/upcoming/youthful_valkyrie.txt new file mode 100644 index 00000000000..635cb21943a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/youthful_valkyrie.txt @@ -0,0 +1,10 @@ +Name:Youthful Valkyrie +ManaCost:1 W +Types:Creature Angel +K:Flying +PT:1/3 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Angel.Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever another Angel enters the battlefield under your control, put a +1/+1 counter on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +DeckHas:Ability$Counters +DeckHints:Type$Angel +Oracle:Flying\nWhenever another Angel enters the battlefield under your control, put a +1/+1 counter on Youthful Valkyrie. diff --git a/forge-gui/res/cardsfolder/upcoming/vault_of_champions.txt b/forge-gui/res/cardsfolder/v/vault_of_champions.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/vault_of_champions.txt rename to forge-gui/res/cardsfolder/v/vault_of_champions.txt diff --git a/forge-gui/res/cardsfolder/upcoming/volcanic_torrent.txt b/forge-gui/res/cardsfolder/v/volcanic_torrent.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/volcanic_torrent.txt rename to forge-gui/res/cardsfolder/v/volcanic_torrent.txt diff --git a/forge-gui/res/cardsfolder/upcoming/war_room.txt b/forge-gui/res/cardsfolder/w/war_room.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/war_room.txt rename to forge-gui/res/cardsfolder/w/war_room.txt diff --git a/forge-gui/res/cardsfolder/w/waxmane_baku.txt b/forge-gui/res/cardsfolder/w/waxmane_baku.txt index 9a7f91d050b..0feb2634a46 100644 --- a/forge-gui/res/cardsfolder/w/waxmane_baku.txt +++ b/forge-gui/res/cardsfolder/w/waxmane_baku.txt @@ -3,10 +3,9 @@ ManaCost:2 W Types:Creature Spirit PT:2/2 T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, you may put a ki counter on CARDNAME. -SVar:TrigPutCounter:DB$PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 -A:AB$ Tap | Announce$ X | XMaxLimit$ Ki | Cost$ 1 SubCounter | CostDesc$ {1}, Remove X ki counters from CARDNAME: | TargetMin$ X | TargetMax$ X | ValidTgts$ Creature | IsCurse$ True | AILogic$ TapForXCounters | TgtPrompt$ Select X target creatures | References$ X,Ki | SpellDescription$ Tap X target creatures. -SVar:X:XChoice -SVar:Ki:Count$CardCounters.KI +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 +A:AB$ Tap | Cost$ 1 SubCounter | TargetMin$ X | TargetMax$ X | ValidTgts$ Creature | IsCurse$ True | TgtPrompt$ Select X target creatures | References$ X | SpellDescription$ Tap X target creatures. +SVar:X:Count$xPaid AI:RemoveDeck:Random DeckHints:Type$Spirit|Arcane Oracle:Whenever you cast a Spirit or Arcane spell, you may put a ki counter on Waxmane Baku.\n{1}, Remove X ki counters from Waxmane Baku: Tap X target creatures. diff --git a/forge-gui/res/cardsfolder/upcoming/wheel_of_misfortune.txt b/forge-gui/res/cardsfolder/w/wheel_of_misfortune.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/wheel_of_misfortune.txt rename to forge-gui/res/cardsfolder/w/wheel_of_misfortune.txt diff --git a/forge-gui/res/cardsfolder/w/forge/forge-gui/res/cardsfolder/upcoming/wrong_turn.txt b/forge-gui/res/cardsfolder/w/wrong_turn.txt similarity index 100% rename from forge-gui/res/cardsfolder/w/forge/forge-gui/res/cardsfolder/upcoming/wrong_turn.txt rename to forge-gui/res/cardsfolder/w/wrong_turn.txt diff --git a/forge-gui/res/cardsfolder/upcoming/wyleth_soul_of_steel.txt b/forge-gui/res/cardsfolder/w/wyleth_soul_of_steel.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/wyleth_soul_of_steel.txt rename to forge-gui/res/cardsfolder/w/wyleth_soul_of_steel.txt diff --git a/forge-gui/res/cardsfolder/upcoming/yurlok_of_scorch_thrash.txt b/forge-gui/res/cardsfolder/y/yurlok_of_scorch_thrash.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/yurlok_of_scorch_thrash.txt rename to forge-gui/res/cardsfolder/y/yurlok_of_scorch_thrash.txt diff --git a/forge-gui/res/cardsfolder/upcoming/zara_renegade_recruiter.txt b/forge-gui/res/cardsfolder/z/zara_renegade_recruiter.txt similarity index 100% rename from forge-gui/res/cardsfolder/upcoming/zara_renegade_recruiter.txt rename to forge-gui/res/cardsfolder/z/zara_renegade_recruiter.txt diff --git a/forge-gui/res/deckgendecks/Modern.lda.dat b/forge-gui/res/deckgendecks/Modern.lda.dat index 34457271906..55c523f20a2 100644 Binary files a/forge-gui/res/deckgendecks/Modern.lda.dat and b/forge-gui/res/deckgendecks/Modern.lda.dat differ diff --git a/forge-gui/res/deckgendecks/Modern.raw.dat b/forge-gui/res/deckgendecks/Modern.raw.dat index a8bd2c837cd..23f4a0f9b09 100644 Binary files a/forge-gui/res/deckgendecks/Modern.raw.dat and b/forge-gui/res/deckgendecks/Modern.raw.dat differ diff --git a/forge-gui/res/deckgendecks/Pioneer.lda.dat b/forge-gui/res/deckgendecks/Pioneer.lda.dat index 2e31263483d..85bfa3aa24a 100644 Binary files a/forge-gui/res/deckgendecks/Pioneer.lda.dat and b/forge-gui/res/deckgendecks/Pioneer.lda.dat differ diff --git a/forge-gui/res/deckgendecks/Pioneer.raw.dat b/forge-gui/res/deckgendecks/Pioneer.raw.dat index 6ac8e38761f..312ffa83d67 100644 Binary files a/forge-gui/res/deckgendecks/Pioneer.raw.dat and b/forge-gui/res/deckgendecks/Pioneer.raw.dat differ diff --git a/forge-gui/res/deckgendecks/Standard.lda.dat b/forge-gui/res/deckgendecks/Standard.lda.dat index 04bd607051f..91a567391a7 100644 Binary files a/forge-gui/res/deckgendecks/Standard.lda.dat and b/forge-gui/res/deckgendecks/Standard.lda.dat differ diff --git a/forge-gui/res/deckgendecks/Standard.raw.dat b/forge-gui/res/deckgendecks/Standard.raw.dat index 2943612f3e3..7f31f2a199b 100644 Binary files a/forge-gui/res/deckgendecks/Standard.raw.dat and b/forge-gui/res/deckgendecks/Standard.raw.dat differ diff --git a/forge-gui/res/editions/Aether Revolt.txt b/forge-gui/res/editions/Aether Revolt.txt index 528323bd070..0d806127341 100644 --- a/forge-gui/res/editions/Aether Revolt.txt +++ b/forge-gui/res/editions/Aether Revolt.txt @@ -6,7 +6,7 @@ Code2=AER Type=Expansion MciCode=aer BoosterCovers=3 -Booster=10 Common, 3 Uncommon, 1 RareMythic, 1 BasicLand KLD +Booster=10 Common:fromSheet("AER cards"), 3 Uncommon:fromSheet("AER cards"), 1 RareMythic:fromSheet("AER cards"), 1 BasicLand KLD AdditionalSheetForFoils=fromSheet("AER Aether Revolt Inventions") AdditionalSetUnlockedInQuest=MPS_KLD diff --git a/forge-gui/res/editions/Amonkhet Remastered.txt b/forge-gui/res/editions/Amonkhet Remastered.txt index ea0bd1adc30..526c70c4403 100644 --- a/forge-gui/res/editions/Amonkhet Remastered.txt +++ b/forge-gui/res/editions/Amonkhet Remastered.txt @@ -3,7 +3,7 @@ Code=AKR Date=2020-08-13 Name=Amonkhet Remastered BoosterCovers=1 -Booster=10 Common, 3 Uncommon, 1 RareMythic +Booster=10 Common, 3 Uncommon, 1 RareMythic:!fromSheet("AKR buy a box") Type=Expansion [cards] diff --git a/forge-gui/res/editions/Ikoria Lair of Behemoths.txt b/forge-gui/res/editions/Ikoria Lair of Behemoths.txt index 1c9335ebd93..d952cb95b04 100644 --- a/forge-gui/res/editions/Ikoria Lair of Behemoths.txt +++ b/forge-gui/res/editions/Ikoria Lair of Behemoths.txt @@ -6,7 +6,7 @@ Code2=IKO MciCode=iko Type=Expansion BoosterCovers=3 -Booster=10 Common:!fromSheet("IKO Lands"), 3 Uncommon, 1 RareMythic, 1 fromSheet("IKO Lands") +Booster=10 Common:fromSheet("IKO cards"):!fromSheet("IKO Lands"), 3 Uncommon:fromSheet("IKO cards"), 1 RareMythic:fromSheet("IKO cards"), 1 fromSheet("IKO Lands") Prerelease=6 Boosters, 1 RareMythic+ [cards] diff --git a/forge-gui/res/editions/Ixalan.txt b/forge-gui/res/editions/Ixalan.txt index 83260821c73..0029ba49862 100644 --- a/forge-gui/res/editions/Ixalan.txt +++ b/forge-gui/res/editions/Ixalan.txt @@ -6,7 +6,7 @@ Code2=XLN Type=Expansion MciCode=xln BoosterCovers=5 -Booster=10 Common, 3 Uncommon, 1 RareMythic, 1 BasicLand +Booster=10 Common:fromSheet("XLN cards"), 3 Uncommon:fromSheet("XLN cards"), 1 RareMythic:fromSheet("XLN cards"), 1 BasicLand [cards] 1 U Adanto Vanguard diff --git a/forge-gui/res/editions/Kaladesh.txt b/forge-gui/res/editions/Kaladesh.txt index 6a4bd7e8358..8c6526c7e0a 100644 --- a/forge-gui/res/editions/Kaladesh.txt +++ b/forge-gui/res/editions/Kaladesh.txt @@ -6,7 +6,7 @@ Code2=KLD Type=Expansion MciCode=kld BoosterCovers=3 -Booster=10 Common, 3 Uncommon, 1 RareMythic, 1 BasicLand +Booster=10 Common:fromSheet("KLD cards"), 3 Uncommon:fromSheet("KLD cards"), 1 RareMythic:fromSheet("KLD cards"), 1 BasicLand AdditionalSheetForFoils=fromSheet("KLD Kaladesh Inventions") AdditionalSetUnlockedInQuest=MPS_KLD diff --git a/forge-gui/res/editions/Kaldheim.txt b/forge-gui/res/editions/Kaldheim.txt new file mode 100644 index 00000000000..5fdf15d833b --- /dev/null +++ b/forge-gui/res/editions/Kaldheim.txt @@ -0,0 +1,48 @@ +[metadata] +Code=KHM +Date=2021-02-05 +Name=Kaldheim +Type=Expansion + +[cards] +15 M Halvar, God of Battle +142 R Magda, Brazen Outlaw +188 R Realmwalker +218 M Kaya the Inexorable +228 R Sarulf, Realm Eater +229 R Showdown of the Skalds +241 R Pyre of Heroes +251 R Barkchannel Pathway +252 R Blightstep Pathway +254 R Darkbore Pathway +260 R Hengegate Pathway +288 M Kaya the Inexorable +290 R Barkchannel Pathway +291 R Blightstep Pathway +292 R Darkbore Pathway +293 R Hengegate Pathway +299 M Halvar, God of Battle +312 R Magda, Brazen Outlaw +330 R Sarulf, Realm Eater +370 R Pyre of Heroes +374 R Valkyrie Harbinger +375 R Surtland Elementalist +376 R Cleaving Reaper +377 R Surtland Flinger +378 R Canopy Tactician +379 U Armed and Armored +380 U Starnhiem Aspirant +381 U Warchanter Skald +382 U Youthful Valyrie +383 U Absorb Identity +384 U Giant's Grasp +385 U Elderfang Ritualist +386 U Renegade Reaper +387 U Thornmantle Striker +388 U Bearded Axe +389 U Fire Giant's Fury +390 U Gilded Assault Cart +391 U Elven Ambush +392 U Gladewalker Ritualist +393 U Rampage of the Valkyries +399 R Realmwalker diff --git a/forge-gui/res/editions/Magic 2021.txt b/forge-gui/res/editions/Magic 2021.txt index 59608e8d811..fd44f5881af 100644 --- a/forge-gui/res/editions/Magic 2021.txt +++ b/forge-gui/res/editions/Magic 2021.txt @@ -4,7 +4,7 @@ Date=2020-06-04 Name=Core Set 2021 Type=Core BoosterCovers=3 -Booster=10 Common:!fromSheet(M21 Lands"), 3 Uncommon, 1 RareMythic, 1 fromSheet("M21 Lands") +Booster=10 Common:fromSheet("M21 cards"):!fromSheet("M21 Lands"), 3 Uncommon:fromSheet("M21 cards"), 1 RareMythic:fromSheet("M21 cards"), 1 fromSheet("M21 Lands") Prerelease=6 Boosters, 1 RareMythic+ ChaosDraftThemes=CORE_SET diff --git a/forge-gui/res/editions/Rivals of Ixalan.txt b/forge-gui/res/editions/Rivals of Ixalan.txt index 4e8864bd87d..adba859f598 100644 --- a/forge-gui/res/editions/Rivals of Ixalan.txt +++ b/forge-gui/res/editions/Rivals of Ixalan.txt @@ -6,7 +6,7 @@ Code2=RIX Type=Expansion MciCode=rix BoosterCovers=5 -Booster=10 Common, 3 Uncommon, 1 RareMythic, 1 BasicLand:fromSheet("RIX Basic Lands") +Booster=10 Common:fromSheet("RIX cards"), 3 Uncommon:fromSheet("RIX cards"), 1 RareMythic:fromSheet("RIX cards"), 1 BasicLand:fromSheet("RIX Basic Lands") TreatAsSmallSet=true [cards] diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 868f0f9539f..17d5619a230 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -184,6 +184,7 @@ Type=Other 535 U Samut, Tyrant Smasher 536 U Vraska, Swarm's Eminence 537 M Tibalt, the Fiend-Blooded +538 R Evolving Wilds 581 M Lucille [tokens] diff --git a/forge-gui/res/editions/Secret Lair Ultimate Edition.txt b/forge-gui/res/editions/Secret Lair Ultimate Edition.txt index 33fc84e11c6..9abff16b57b 100644 --- a/forge-gui/res/editions/Secret Lair Ultimate Edition.txt +++ b/forge-gui/res/editions/Secret Lair Ultimate Edition.txt @@ -10,3 +10,9 @@ Type=Reprint 3 R Verdant Catacombs 4 R Arid Mesa 5 R Misty Rainforest +13 R Branchloft Pathway +14 R Brightclimb Pathway +15 R Clearwater Pathway +16 R Cragcrown Pathway +19 R Needleverge Pathway +20 R Riverglide Pathway diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 850991e95a7..b3227352818 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -994,6 +994,8 @@ lblEnableUnknownCards=Erlaube unbekannte Karten nlEnableUnknownCards=Erlaube unbekannte Karten von unbekannten Sets. (Erfordert Neustart) lblExperimentalNetworkCompatibility=Experimentelle Netzwerkkompatibilität nlExperimentalNetworkCompatibility=Forge wechselt auf kompatiblen Netzwerk-Stream. (Im Zweifel bitte ausschalten) +lblAutoCacheSize=Aktiviere automatische Cache-Größe +nlAutoCacheSize=Wenn aktiviert, wird die Cache-Größe beim Start automatisch ermittelt und eingestellt. (Im Zweifel bitte ausschalten) #MatchScreen.java lblPlayers=Spieler lblLog=Bericht @@ -1168,6 +1170,7 @@ lblDraw=Ziehen lblTooFewCardsMainDeck=Zu wenig Karten in deinem Deck (mindestens {0}). Bitte passe dein Deck an. lblTooManyCardsSideboard=Zu viele Karten in deinem Deck (maximal {0}). Bitte passe dein Deck an. lblAssignCombatDamageWerentBlocked=Möchtest du den Kampfschaden deklarieren, als wäre nicht geblockt worden? +lblAssignCombatDamageAsChoose=Do you want to divide {0}''s combat damage as you choose? lblChosenCards=Wähle Karten lblAttacker=Angreifer lblTriggeredby=Ausgelöst durch diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index b84f92620de..d58cacccd0c 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -994,6 +994,8 @@ lblEnableUnknownCards=Enable Unknown Cards nlEnableUnknownCards=Enable Unknown Cards to be loaded to Unknown Set. (Requires restart) lblExperimentalNetworkCompatibility=Experimental Network Compatibility nlExperimentalNetworkCompatibility=Forge switches to compatible network stream. (If unsure, turn OFF this option) +lblAutoCacheSize=Enable Auto Cache Size +nlAutoCacheSize=When enabled, Cache size are automatically determined on startup. (If unsure, turn OFF this option) #MatchScreen.java lblPlayers=Players lblLog=Log @@ -1168,6 +1170,7 @@ lblDraw=Draw lblTooFewCardsMainDeck=Too few cards in your main deck (minimum {0}), please make modifications to your deck again. lblTooManyCardsSideboard=Too many cards in your sideboard (maximum {0}), please make modifications to your deck again. lblAssignCombatDamageWerentBlocked=Do you want to assign its combat damage as though it weren''t blocked? +lblAssignCombatDamageAsChoose=Do you want to divide {0}''s combat damage as you choose? lblChosenCards=Chosen Cards lblAttacker=Attacker lblTriggeredby=Triggered by diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index b216379240a..69636b23831 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -994,6 +994,8 @@ lblEnableUnknownCards=Habilitar Cartas Desconocidas nlEnableUnknownCards=Habilitar que las cartas desconocidas se carguen en el Unknown Set. (Requiere reinicio) lblExperimentalNetworkCompatibility=Compatibilidad de red experimental nlExperimentalNetworkCompatibility=Forge cambia a un flujo de red compatible. (Si no estás seguro, deshabilita esta opción) +lblAutoCacheSize=Enable Auto Cache Size +nlAutoCacheSize=When enabled, Cache size are automatically determined on startup. (If unsure, turn OFF this option) #MatchScreen.java lblPlayers=Jugadores lblLog=Log @@ -1168,6 +1170,7 @@ lblDraw=Ceder lblTooFewCardsMainDeck=Muy pocas cartas en tu mazo principal (mínimo {0}), por favor realiza modificaciones a tu mazo de nuevo. lblTooManyCardsSideboard=Demasiadas cartas en tu banquillo (máximo {0}), por favor realiza modificaciones a tu mazo de nuevo. lblAssignCombatDamageWerentBlocked=¿Quieres asignar su daño de combate como si no estuviera bloqueado? +lblAssignCombatDamageAsChoose=¿Quieres dividir el daño de combate del {0} como elijas? lblChosenCards=Cartas elegidas lblAttacker=Atacante lblTriggeredby=Activado por diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index 239f63a061e..f76b760c73a 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -994,6 +994,8 @@ lblEnableUnknownCards=Enable Unknown Cards nlEnableUnknownCards=Enable Unknown Cards to be loaded to Unknown Set. (Requires restart) lblExperimentalNetworkCompatibility=Experimental Network Compatibility nlExperimentalNetworkCompatibility=Forge switches to compatible network stream. (If unsure, turn OFF this option) +lblAutoCacheSize=Enable Auto Cache Size +nlAutoCacheSize=When enabled, Cache size are automatically determined on startup. (If unsure, turn OFF this option) #MatchScreen.java lblPlayers=Giocatori lblLog=Login @@ -1168,6 +1170,7 @@ lblDraw=Disegnare lblTooFewCardsMainDeck=Troppe carte nel tuo mazzo principale (minimo %s), per favore apporta nuovamente modifiche al tuo mazzo. lblTooManyCardsSideboard=Troppe carte nel tuo sideboard (massimo %s), modifica nuovamente il tuo mazzo. lblAssignCombatDamageWerentBlocked=Vuoi assegnare il suo danno da combattimento come se non fosse bloccato? +lblAssignCombatDamageAsChoose=Vuoi suddividere come preferisci il danno da combattimento dell''{0}? lblChosenCards=Carte scelte lblAttacker=aggressore lblTriggeredby=Innescato da diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index c6dc2843020..b05f7adcdeb 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -993,7 +993,9 @@ nlShowFPSDisplay=启用后,将在画面左上角显示当前Forge的FPS(实 lblEnableUnknownCards=启用未知卡牌 nlEnableUnknownCards=将未知卡牌加载到未知系列中。(需要重启) lblExperimentalNetworkCompatibility=实验性网络兼容 -nlExperimentalNetworkCompatibility=Forge将切换为兼容性的网络流。(如果不清楚,请关闭此选项) +nlExperimentalNetworkCompatibility=Forge将切换为兼容性的网络流。(如果不清楚此选项的作用,请关闭此选项) +lblAutoCacheSize=启用自动缓存大小 +nlAutoCacheSize=启用后,Forge将于启动时自动确定缓存大小。(如果不清楚此选项的作用,请关闭此选项) #MatchScreen.java lblPlayers=玩家列表 lblLog=日志 @@ -1168,6 +1170,7 @@ lblDraw=后手 lblTooFewCardsMainDeck=主牌中卡牌数过少(最少为{0}),请重新修改套牌。 lblTooManyCardsSideboard=备牌中卡牌数过多(最多为{0}),请重新修改套牌。 lblAssignCombatDamageWerentBlocked=是否要像没有被阻挡一样分配战斗伤害? +lblAssignCombatDamageAsChoose=你想要按你所选的分配方式对{0}造成战斗伤害吗? lblChosenCards=选择牌 lblAttacker=进攻者 lblTriggeredby=触发者 diff --git a/forge-gui/res/lists/borderlessCardList.txt b/forge-gui/res/lists/borderlessCardList.txt index 2b05ed874be..f3b654c592a 100644 --- a/forge-gui/res/lists/borderlessCardList.txt +++ b/forge-gui/res/lists/borderlessCardList.txt @@ -104,6 +104,18 @@ SLD/Oona, Queen of the Fae.fullborder SLD/Saskia the Unyielding.fullborder SLD/The Mimeoplasm.fullborder SLD/Voidslime.fullborder +SLU/Brightclimb Pathway.fullborder +SLU/Clearwater Pathway.fullborder +SLU/Cragcrown Pathway.fullborder +SLU/Grimclimb Pathway.fullborder +SLU/Lavaglide Pathway.fullborder +SLU/Murkwater Pathway.fullborder +SLU/Needleverge Pathway.fullborder +SLU/Pillarverge Pathway.fullborder +SLU/Riverglide Pathway.fullborder +SLU/Timbercrown Pathway.fullborder +SLU/Boulderloft Pathway.fullborder +SLU/Branchloft Pathway.fullborder THB/Ashiok, Nightmare Muse2.fullborder THB/Calix, Destiny's Hand2.fullborder THB/Elspeth, Sun's Nemesis2.fullborder diff --git a/forge-gui/res/quest/world/worlds.txt b/forge-gui/res/quest/world/worlds.txt index d8b278d53a0..1ebdfaf1ac1 100644 --- a/forge-gui/res/quest/world/worlds.txt +++ b/forge-gui/res/quest/world/worlds.txt @@ -18,7 +18,7 @@ Name:Dominaria|Dir:1994-06 Legends|Sets:LEG, DOM|Banned:Arena of the Ancients; F Name:Terisiare: The Dark|Dir:1994-08 The Dark|Sets:DRK|Banned:Maze of Ith Name:Sarpadia|Dir:1994-11 Fallen Empires|Sets:FEM Name:Homelands|Dir:1995-10 Homelands|Sets:HML|Banned:Apocalypse Chime -Name:Terisiare: Ice Age|Dir:1996-05 Ice Age|Sets:ICE, ALL +Name:Terisiare: Ice Age|Dir:1996-05 Ice Age|Sets:ICE, ALL, CSP Name:Jamuraa: The Mirage Wars|Dir:1997-01 Mirage|Sets:MIR, VIS Name:Portal|Dir:1997-05 Portal|Sets:POR Name:Caliman|Dir:1998-06 Portal Second Age|Sets:PO2 @@ -32,6 +32,6 @@ Name:Random Core Set|Sets:LEA, LEB, 2ED, 3ED, 4ED, 5ED, 6ED, 7ED, 8ED, 9ED, 10E, Name:Ixalan|Sets:XLN, RIX Name:Innistrad|Sets:ISD, DKA, AVR, SOI, EMN Name:Kaladesh|Sets:AER, KLD, MPS, KLR -Name:Non-Block Sets|Sets:IKO, THB, ELD, WAR, RNA, GRN, DOM, HML, FEM, DRK, LEG, ATQ, ARN, JMP, CNS, CN2, BBD, MB1, MMA, MM2, EMA, MM3, IMA, A25, UMA, 2XM, ZNR +Name:Non-Block Sets|Sets:IKO, THB, ELD, WAR, RNA, GRN, DOM, HML, FEM, DRK, LEG, ATQ, ARN, JMP, CNS, CN2, BBD, MB1, MMA, MM2, EMA, MM3, IMA, A25, UMA, 2XM, ZNR, CMR Name:Tarkir|Sets:KTK, FRF, DTK Name:Alara|Sets:ALA, CFX, ARB \ No newline at end of file diff --git a/forge-gui/res/skins/default/sprite_ability.png b/forge-gui/res/skins/default/sprite_ability.png index 7b25e07fcbe..c33b526a74f 100644 Binary files a/forge-gui/res/skins/default/sprite_ability.png and b/forge-gui/res/skins/default/sprite_ability.png differ diff --git a/forge-gui/res/tokenscripts/lathril_blade_of_the_elves.txt b/forge-gui/res/tokenscripts/lathril_blade_of_the_elves.txt new file mode 100644 index 00000000000..f5e45e2d584 --- /dev/null +++ b/forge-gui/res/tokenscripts/lathril_blade_of_the_elves.txt @@ -0,0 +1,13 @@ +Name:Lathril, Blade of the Elves +ManaCost:2 B G +Types:Legendary Creature Elf Noble +PT:2/3 +K:Menace +T:Mode$ DamageDone | ValidSource$ Card.Self | CombatDamage$ True | ValidTarget$ Player | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, create that many 1/1 green Elf Warrior creature tokens. +SVar:TrigToken:DB$ Token | TokenAmount$ X | TokenScript$ g_1_1_elf_warrior | TokenOwner$ You | References$ X +SVar:X:TriggerCount$DamageAmount +A:AB$ LoseLife | Cost$ T tapXType<10/Elf> | Defined$ Player.Opponent | LifeAmount$ 10 | SubAbility$ DBGainLife | SpellDescription$ Each opponent loses 10 life and you gain 10 life. +SVar:DBGainLife:DB$ GainLife | LifeAmount$ 10 +DeckHas:Ability$Token & Ability$LifeGain +DeckHints:Type$Elf +Oracle:Menace\nWhenever Lathril, Blade of the Elves deals combat damage to a player, create that many 1/1 green Elf Warrior creature tokens.\n{T}, tap ten untapped Elves you control: Each opponent loses 10 life and you gain 10 life. diff --git a/forge-gui/res/tokenscripts/r_2_1_dwarf_berserker.txt b/forge-gui/res/tokenscripts/r_2_1_dwarf_berserker.txt new file mode 100644 index 00000000000..556b17128fe --- /dev/null +++ b/forge-gui/res/tokenscripts/r_2_1_dwarf_berserker.txt @@ -0,0 +1,6 @@ +Name:Dwarf Berserker +ManaCost:no cost +Types:Creature Dwarf Berserker +Colors:red +PT:2/1 +Oracle: diff --git a/forge-gui/src/main/java/forge/assets/FSkinProp.java b/forge-gui/src/main/java/forge/assets/FSkinProp.java index 6b124b29dba..50370303c4d 100644 --- a/forge-gui/src/main/java/forge/assets/FSkinProp.java +++ b/forge-gui/src/main/java/forge/assets/FSkinProp.java @@ -391,7 +391,9 @@ public enum FSkinProp { IMG_FAVNONE (new int[] {500, 0, 100, 100}, PropType.FAVICON), IMG_QUEST_DRAFT_DECK (new int[] {0, 0, 680, 475}, PropType.IMAGE), - + //COMMANDER + IMG_ABILITY_COMMANDER (new int[] {330, 576, 80, 80}, PropType.ABILITY), + //Ability Icons IMG_ABILITY_DEATHTOUCH (new int[] {2, 2, 80, 80}, PropType.ABILITY), IMG_ABILITY_DEFENDER (new int[] {84, 2, 80, 80}, PropType.ABILITY), IMG_ABILITY_DOUBLE_STRIKE (new int[] {166, 2, 80, 80}, PropType.ABILITY), @@ -404,6 +406,7 @@ public enum FSkinProp { IMG_ABILITY_HORSEMANSHIP (new int[] {2, 576, 80, 80}, PropType.ABILITY), IMG_ABILITY_INDESTRUCTIBLE (new int[] {2, 84, 80, 80}, PropType.ABILITY), IMG_ABILITY_INTIMIDATE (new int[] {166, 412, 80, 80}, PropType.ABILITY), + IMG_ABILITY_LANDWALK (new int[] {248, 576, 80, 80}, PropType.ABILITY), IMG_ABILITY_LIFELINK (new int[] {84, 84, 80, 80}, PropType.ABILITY), IMG_ABILITY_MENACE (new int[] {166, 84, 80, 80}, PropType.ABILITY), IMG_ABILITY_REACH (new int[] {248, 330, 80, 80}, PropType.ABILITY), diff --git a/forge-gui/src/main/java/forge/card/CardDetailUtil.java b/forge-gui/src/main/java/forge/card/CardDetailUtil.java index af141b97c6e..18597a7e7e4 100644 --- a/forge-gui/src/main/java/forge/card/CardDetailUtil.java +++ b/forge-gui/src/main/java/forge/card/CardDetailUtil.java @@ -416,6 +416,14 @@ public class CardDetailUtil { area.append(")"); } + // chosen number + if (!card.getChosenNumber().isEmpty()) { + if (area.length() != 0) { + area.append("\n"); + } + area.append("(chosen number: ").append(card.getChosenNumber()).append(")"); + } + // chosen player if (card.getChosenPlayer() != null) { if (area.length() != 0) { @@ -561,6 +569,14 @@ public class CardDetailUtil { area.append("Current Storm Count: ").append(gameView.getStormCount()); } } + + //show owner if being controlled by a different player + if (card.getOwner() != card.getController()) { + if (area.length() != 0) { + area.append("\n\n"); + } + area.append("Owner: ").append(card.getOwner().toString()); + } return area.toString(); } } diff --git a/forge-gui/src/main/java/forge/download/AutoUpdater.java b/forge-gui/src/main/java/forge/download/AutoUpdater.java index 8e731be5c55..99ce308bd8c 100644 --- a/forge-gui/src/main/java/forge/download/AutoUpdater.java +++ b/forge-gui/src/main/java/forge/download/AutoUpdater.java @@ -79,7 +79,7 @@ public class AutoUpdater { String message = localizer.getMessage("lblYouHaventSetUpdateChannel"); List options = ImmutableList.of("Cancel", "release", "snapshot"); int option = SOptionPane.showOptionDialog(message, localizer.getMessage("lblManualCheck"), null, options, 0); - if (option == 0) { + if (option < 1) { return false; } else { updateChannel = options.get(option); diff --git a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java index fbff635cf47..dd6490bebb3 100644 --- a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java +++ b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java @@ -188,7 +188,12 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { return true; } if(spectator!=null) { //workaround fix!! this is needed on above code or it will - gameControllers.remove(spectator); //bug the UI! remove spectator here since its must not be here... + for (Map.Entry e : gameControllers.entrySet()) { + if (e.getValue().equals(spectator)) { + gameControllers.remove(e.getKey()); + break; + } + } return true; } try{ @@ -368,11 +373,14 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { return autoPassUntilEndOfTurn.contains(player); } - private final Timer awaitNextInputTimer = new Timer(); + private Timer awaitNextInputTimer; private TimerTask awaitNextInputTask; @Override public final void awaitNextInput() { + if (awaitNextInputTimer == null) { + awaitNextInputTimer = new Timer("awaitNextInputTimer Game:" + this.gameView.getId() + " Player:" + this.currentPlayer.getLobbyPlayerName()); + } //delay updating prompt to await next input briefly so buttons don't flicker disabled then enabled awaitNextInputTask = new TimerTask() { @Override @@ -400,6 +408,9 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { @Override public final void cancelAwaitNextInput() { + if (awaitNextInputTimer == null) { + return; + } synchronized (awaitNextInputTimer) { //ensure task doesn't reset awaitNextInputTask during this block if (awaitNextInputTask != null) { try { @@ -725,5 +736,13 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { public void handleLandPlayed(Card land, Zone zone) { } + + @Override + public void afterGameEnd() { + if (awaitNextInputTimer != null) { + awaitNextInputTimer.cancel(); + awaitNextInputTimer = null; + } + } // End of Choice code } diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index 15c31774702..baf6bb7a188 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -3,18 +3,18 @@ package forge.player; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; + import forge.card.CardType; import forge.game.Game; import forge.game.GameEntity; +import forge.game.GameEntityCounterTable; import forge.game.GameEntityView; import forge.game.GameEntityViewMap; import forge.game.ability.AbilityUtils; @@ -527,7 +527,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { if (c == null) { c = AbilityUtils.calculateAmount(source, amount, ability); } - + if (!player.getController().confirmPayment(cost, Localizer.getInstance().getMessage("lblDoYouWantFlipNCoinAction", String.valueOf(c)), ability)) { return null; } @@ -907,57 +907,59 @@ public class HumanCostDecision extends CostDecisionMakerBase { c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability); } - CardCollectionView list = new CardCollection(player.getCardsIn(ZoneType.Battlefield)); - list = CardLists.getValidCards(list, type.split(";"), player, source, ability); + CardCollectionView list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"), player, source, ability); + list = CardLists.filter(list, CardPredicates.hasCounters()); - - list = CardLists.filter(list, new Predicate() { - @Override - public boolean apply(final Card card) { - return card.hasCounters(); - } - }); - final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, 1, 1, list, ability); - inp.setMessage(Localizer.getInstance().getMessage("lblSelectTargetCounter", cost.getDescriptiveType())); - inp.setCancelAllowed(false); + final InputSelectCardToRemoveCounter inp = new InputSelectCardToRemoveCounter(controller, c, cost, cost.counter, list, ability); + inp.setCancelAllowed(true); inp.showAndWait(); - final Card selected = inp.getFirstSelected(); - final Map tgtCounters = selected.getCounters(); - final List typeChoices = new ArrayList<>(); - for (final CounterType key : tgtCounters.keySet()) { - if (tgtCounters.get(key) > 0) { - typeChoices.add(key); - } + if (inp.hasCancelled()) { + return null; } - final String prompt = Localizer.getInstance().getMessage("lblSelectRemoveCounterType"); - cost.setCounterType(controller.getGui().one(prompt, typeChoices)); - - return PaymentDecision.card(selected, cost.getCounter()); + return PaymentDecision.counters(inp.getCounterTable()); } - public static final class InputSelectCardToRemoveCounter extends InputSelectManyBase { + public static final class InputSelectCardToRemoveCounter extends InputSelectManyBase { private static final long serialVersionUID = 2685832214519141903L; - private final Map cardsChosen; private final CounterType counterType; private final CardCollectionView validChoices; - public InputSelectCardToRemoveCounter(final PlayerControllerHuman controller, final int cntCounters, final CounterType cType, final CardCollectionView validCards, final SpellAbility sa) { + private final GameEntityCounterTable counterTable = new GameEntityCounterTable(); + + public InputSelectCardToRemoveCounter(final PlayerControllerHuman controller, final int cntCounters, final CostPart costPart, final CounterType cType, final CardCollectionView validCards, final SpellAbility sa) { super(controller, cntCounters, cntCounters, sa); this.validChoices = validCards; counterType = cType; - cardsChosen = cntCounters > 0 ? new HashMap<>() : null; + + setMessage(Localizer.getInstance().getMessage("lblRemoveNTargetCounterFromCardPayCostConfirm", "%d", counterType == null ? "any" : counterType.getName(), costPart.getDescriptiveType())); } @Override protected boolean onCardSelected(final Card c, final List otherCardsToSelect, final ITriggerEvent triggerEvent) { - if (!isValidChoice(c) || c.getCounters(counterType) <= getTimesSelected(c)) { + if (!isValidChoice(c)) { return false; } - final int tc = getTimesSelected(c); - cardsChosen.put(c, tc + 1); + CounterType cType = this.counterType; + if (cType == null) { + Map cmap = counterTable.filterToRemove(c); + + String prompt = Localizer.getInstance().getMessage("lblSelectCountersTypeToRemove"); + + cType = getController().chooseCounterType(Lists.newArrayList(cmap.keySet()), sa, prompt, null); + } + + if (cType == null) { + return false; + } + + if (c.getCounters(cType) <= counterTable.get(c, cType)) { + return false; + } + + counterTable.put(c, cType, 1); onSelectStateChanged(c, true); refresh(); @@ -966,9 +968,25 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public String getActivateAction(final Card c) { - if (!isValidChoice(c) || c.getCounters(counterType) <= getTimesSelected(c)) { + if (!isValidChoice(c)) { return null; } + if (counterType != null) { + if (c.getCounters(counterType) <= counterTable.get(c, counterType)) { + return null; + } + } else { + boolean found = false; + for (Map.Entry e : c.getCounters().entrySet()) { + if (e.getValue() > counterTable.get(c, e.getKey())) { + found = true; + break; + } + } + if (!found) { + return null; + } + } return Localizer.getInstance().getMessage("lblRemoveCounterFromCard"); } @@ -992,9 +1010,11 @@ public class HumanCostDecision extends CostDecisionMakerBase { private int getDistibutedCounters() { int sum = 0; - for (final Entry kv : cardsChosen.entrySet()) { - sum += kv.getValue().intValue(); + + for (Integer v : this.counterTable.values()) { + sum += v; } + return sum; } @@ -1002,13 +1022,13 @@ public class HumanCostDecision extends CostDecisionMakerBase { return validChoices.contains(choice); } - public int getTimesSelected(final Card c) { - return cardsChosen.containsKey(c) ? cardsChosen.get(c).intValue() : 0; + public GameEntityCounterTable getCounterTable() { + return this.counterTable; } @Override - public Collection getSelected() { - return cardsChosen.keySet(); + public Collection getSelected() { + return counterTable.rowKeySet(); } } @@ -1063,45 +1083,28 @@ public class HumanCostDecision extends CostDecisionMakerBase { return PaymentDecision.card(ability.getOriginalHost(), cntRemoved >= 0 ? cntRemoved : maxCounters); } - final CardCollectionView validCards = CardLists.getValidCards(player.getCardsIn(cost.zone), type.split(";"), player, source, ability); - if (cost.zone.equals(ZoneType.Battlefield)) { - if (cntRemoved == 0) { - return PaymentDecision.card(source, 0); - } - - final InputSelectCardToRemoveCounter inp = new InputSelectCardToRemoveCounter(controller, cntRemoved, cost.counter, validCards, ability); - inp.setMessage(Localizer.getInstance().getMessage("lblRemoveNTargetCounterFromCardPayCostConfirm", "%d", cost.counter.getName(), cost.getDescriptiveType())); - inp.setCancelAllowed(true); - inp.showAndWait(); - if (inp.hasCancelled()) { - return null; - } - - // Have to hack here: remove all counters minus one, without firing any triggers, - // triggers will fire when last is removed by executePayment. - // They don't care how many were removed anyway - // int sum = 0; - for (final Card crd : inp.getSelected()) { - final int removed = inp.getTimesSelected(crd); - // sum += removed; - if (removed < 2) { - continue; - } - final int oldVal = crd.getCounters().get(cost.counter).intValue(); - crd.getCounters().put(cost.counter, Integer.valueOf(oldVal - removed + 1)); - } - return PaymentDecision.card(inp.getSelected(), 1); - } - - // Rift Elemental only - always removes 1 counter, so there will be no code for N counters. - GameEntityViewMap gameCacheSuspended = GameEntityView.getMap(CardLists.filter(validCards, CardPredicates.hasCounter(cost.counter))); - - final CardView cv = controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblRemoveCountersFromAInZoneCard", cost.zone.getTranslatedName()), gameCacheSuspended.getTrackableKeys()); - if (cv == null || !gameCacheSuspended.containsKey(cv)) { + CardCollectionView validCards = CardLists.getValidCards(player.getCardsIn(cost.zone), type.split(";"), player, source, ability); + // you can only select 1 card to remove N counters from + validCards = CardLists.filter(validCards, CardPredicates.hasCounter(cost.counter, cntRemoved)); + if (validCards.isEmpty()) { return null; } - return PaymentDecision.card(gameCacheSuspended.get(cv), c); + final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, 1, 1, validCards, ability); + inp.setMessage(Localizer.getInstance().getMessage("lblRemoveCountersFromAInZoneCard", cost.zone.getTranslatedName())); + inp.setCancelAllowed(true); + inp.showAndWait(); + + if (inp.hasCancelled()) { + return null; + } + + final Card selected = inp.getFirstSelected(); + if (selected == null) { + return null; + } + + return PaymentDecision.card(selected, cntRemoved); } @Override diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index 2cae915eb2d..5663e55fc40 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -1,6 +1,6 @@ package forge.player; -import com.google.common.base.Predicate; + import com.google.common.collect.Iterables; import forge.FThreads; import forge.card.mana.ManaCost; @@ -29,12 +29,10 @@ import forge.util.TextUtil; import forge.util.collect.FCollectionView; import forge.util.gui.SGuiChoose; import forge.util.Localizer; -import forge.util.CardTranslation; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.List; -import java.util.Map; public class HumanPlay { @@ -362,74 +360,20 @@ public class HumanPlay { part.payAsDecided(p, pd, sourceAbility); } else if (part instanceof CostRemoveCounter) { - CounterType counterType = ((CostRemoveCounter) part).counter; - int amount = getAmountFromPartX(part, source, sourceAbility); + PaymentDecision pd = part.accept(hcd); - if (!part.canPay(sourceAbility, p)) { + if (pd == null) return false; - } - - if (!mandatory) { - if (!p.getController().confirmPayment(part, Localizer.getInstance().getMessage("lblDoYouWantRemoveNTargetTypeCounterFromCard", String.valueOf(amount), counterType.getName(), CardTranslation.getTranslatedName(source.getName())), sourceAbility)) { - return false; - } - } - - source.subtractCounter(counterType, amount); + else + part.payAsDecided(p, pd, sourceAbility); } else if (part instanceof CostRemoveAnyCounter) { - int amount = getAmountFromPartX(part, source, sourceAbility); - CardCollectionView list = p.getCardsIn(ZoneType.Battlefield); - int allCounters = 0; - for (Card c : list) { - final Map tgtCounters = c.getCounters(); - for (Integer value : tgtCounters.values()) { - allCounters += value; - } - } - if (allCounters < amount) { return false; } + PaymentDecision pd = part.accept(hcd); - if (!mandatory) { - if (!p.getController().confirmPayment(part, Localizer.getInstance().getMessage("lblDoYouWantRemoveCountersFromCard", part.getDescriptiveType()), sourceAbility)) { - return false; - } - } - - list = CardLists.getValidCards(list, part.getType().split(";"), p, source, sourceAbility); - while (amount > 0) { - final CounterType counterType; - list = CardLists.filter(list, new Predicate() { - @Override - public boolean apply(final Card card) { - return card.hasCounters(); - } - }); - if (list.isEmpty()) { return false; } - InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, 1, 1, list, sourceAbility); - inp.setMessage(Localizer.getInstance().getMessage("lblSelectRemoveCounterCard")); - inp.setCancelAllowed(true); - inp.showAndWait(); - if (inp.hasCancelled()) { - continue; - } - Card selected = inp.getFirstSelected(); - final Map tgtCounters = selected.getCounters(); - final List typeChoices = new ArrayList<>(); - for (CounterType key : tgtCounters.keySet()) { - if (tgtCounters.get(key) > 0) { - typeChoices.add(key); - } - } - if (typeChoices.size() > 1) { - String cprompt = Localizer.getInstance().getMessage("lblSelectRemoveCounterType"); - counterType = controller.getGui().one(cprompt, typeChoices); - } - else { - counterType = typeChoices.get(0); - } - selected.subtractCounter(counterType, 1); - amount--; - } + if (pd == null) + return false; + else + part.payAsDecided(p, pd, sourceAbility); } else if (part instanceof CostExile) { CostExile costExile = (CostExile) part; diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index 493512562a0..499532bc431 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -29,7 +29,6 @@ import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardPlayOption; import forge.game.cost.Cost; -import forge.game.cost.CostPart; import forge.game.cost.CostPartMana; import forge.game.cost.CostPayment; import forge.game.keyword.KeywordInterface; @@ -256,16 +255,7 @@ public class HumanPlaySpellAbility { } if (needX && manaCost != null) { - boolean xInCost = manaCost.getAmountOfX() > 0; - if (!xInCost) { - for (final CostPart part : cost.getCostParts()) { - if (part.getAmount().equals("X")) { - xInCost = true; - break; - } - } - } - if (xInCost) { + if (cost.hasXInAnyCostPart()) { final String sVar = ability.getSVar("X"); //only prompt for new X value if card doesn't determine it another way if ("Count$xPaid".equals(sVar) || sVar.isEmpty()) { final Integer value = controller.announceRequirements(ability, "X", allowZero && manaCost.canXbe0()); diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 0d327e6f80b..e2d31e40556 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -286,7 +286,9 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont if (defender != null && assignDamageAsIfNotBlocked(attacker)) { map.put(null, damageDealt); } else { - if ((attacker.hasKeyword(Keyword.TRAMPLE) && defender != null) || (blockers.size() > 1)) { + if ((attacker.hasKeyword(Keyword.TRAMPLE) && defender != null) || (blockers.size() > 1) + || (attacker.hasKeyword("You may assign CARDNAME's combat damage divided as you choose among defending" + + " player and/or any number of creatures they control.")) && overrideOrder && blockers.size() >0) { GameEntityViewMap gameCacheBlockers = GameEntityView.getMap(blockers); final CardView vAttacker = CardView.get(attacker); final GameEntityView vDefender = GameEntityView.get(defender); @@ -321,8 +323,27 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont public Integer announceRequirements(final SpellAbility ability, final String announce, final boolean canChooseZero) { final int min = canChooseZero ? 0 : 1; - final int max = ability.hasParam("XMaxLimit") ? AbilityUtils.calculateAmount(ability.getHostCard(), - ability.getParam("XMaxLimit"), ability) : Integer.MAX_VALUE; + int max = Integer.MAX_VALUE; + + if ("X".equals(announce)) { + Cost cost = ability.getPayCosts(); + if (ability.hasParam("XMaxLimit")) { + max = Math.min(max, AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("XMaxLimit"), ability)); + } + if (cost != null) { + Integer costX = cost.getMaxForNonManaX(ability, player); + if (costX != null) { + max = Math.min(max, min); + } + } + } + + if (ability.usesTargeting()) { + // if announce is used as min targets, check what the max possible number would be + if (announce.equals(ability.getTargetRestrictions().getMinTargets())) { + max = Math.min(max, CardUtil.getValidCardsToTarget(ability.getTargetRestrictions(), ability).size()); + } + } return getGui().getInteger(localizer.getMessage("lblChooseAnnounceForCard", announce, CardTranslation.getTranslatedName(ability.getHostCard().getName())) , min, max, min + 9); } @@ -2515,6 +2536,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont // Human player is choosing targets for an ability // controlled by chosen player. sa.setActivatingPlayer(p); + sa.setSVar("IsCastFromPlayEffect", "True"); HumanPlay.playSaWithoutPayingManaCost(PlayerControllerHuman.this, getGame(), sa, true); } // playSa could fire some triggers diff --git a/forge-gui/src/main/java/forge/player/TargetSelection.java b/forge-gui/src/main/java/forge/player/TargetSelection.java index 3463c6e457d..f62b1a41e29 100644 --- a/forge-gui/src/main/java/forge/player/TargetSelection.java +++ b/forge-gui/src/main/java/forge/player/TargetSelection.java @@ -71,11 +71,10 @@ public class TargetSelection { } public final boolean chooseTargets(Integer numTargets) { - final TargetRestrictions tgt = getTgt(); - final boolean canTarget = tgt != null && tgt.doesTarget(); - if (!canTarget) { + if (!ability.usesTargeting()) { throw new RuntimeException("TargetSelection.chooseTargets called for ability that does not target - " + ability); } + final TargetRestrictions tgt = getTgt(); // Number of targets is explicitly set only if spell is being redirected (ex. Swerve or Redirect) final int minTargets = numTargets != null ? numTargets.intValue() : tgt.getMinTargets(ability.getHostCard(), ability); diff --git a/forge-gui/src/main/java/forge/properties/ForgePreferences.java b/forge-gui/src/main/java/forge/properties/ForgePreferences.java index 8526e1ace69..d7101907172 100644 --- a/forge-gui/src/main/java/forge/properties/ForgePreferences.java +++ b/forge-gui/src/main/java/forge/properties/ForgePreferences.java @@ -144,6 +144,7 @@ public class ForgePreferences extends PreferencesStore { UI_SHOW_FPS("false"), UI_NETPLAY_COMPAT("false"), UI_LOAD_UNKNOWN_CARDS("true"), + UI_AUTO_CACHE_SIZE("false"), UI_ALLOW_ORDER_GRAVEYARD_WHEN_NEEDED ("Never"), UI_DEFAULT_FONT_SIZE("12"), UI_SELECT_FROM_CARD_DISPLAYS("true"), diff --git a/forge-gui/src/main/java/forge/quest/QuestWinLoseController.java b/forge-gui/src/main/java/forge/quest/QuestWinLoseController.java index 1c1366fb616..63ad1f0f292 100644 --- a/forge-gui/src/main/java/forge/quest/QuestWinLoseController.java +++ b/forge-gui/src/main/java/forge/quest/QuestWinLoseController.java @@ -58,8 +58,13 @@ public class QuestWinLoseController { // After the first game, reset the card shop pool to be able to buy back anted cards if (lastGame.getNumPlayedGamesInMatch() == 0) { - qc.getCards().clearShopList(); - qc.getCards().getShopList(); + try { + qc.getCards().clearShopList(); + qc.getCards().getShopList(); + } catch (Exception e) { + //investigate this.. + System.err.println(e.getMessage()); + } } final LobbyPlayer questLobbyPlayer = GamePlayerUtil.getQuestPlayer(); diff --git a/forge-gui/src/main/java/forge/quest/QuestWorld.java b/forge-gui/src/main/java/forge/quest/QuestWorld.java index 651e285624f..f69cbe5d7df 100644 --- a/forge-gui/src/main/java/forge/quest/QuestWorld.java +++ b/forge-gui/src/main/java/forge/quest/QuestWorld.java @@ -27,6 +27,7 @@ import forge.util.storage.StorageReaderFile; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -102,7 +103,7 @@ public class QuestWorld implements Comparable{ return format; } - public List getAllCards() { + public Collection getAllCards() { GameFormat format0 = format; if (format0 == null) { format0 = FModel.getQuest().getMainFormat(); diff --git a/forge-gui/tools/EditionTracking.py b/forge-gui/tools/EditionTracking.py index 4156807e0d1..8963985a87e 100644 --- a/forge-gui/tools/EditionTracking.py +++ b/forge-gui/tools/EditionTracking.py @@ -4,6 +4,7 @@ import json import os,sys,fnmatch,re import requests +import pdb toolsDir = os.path.abspath(os.path.dirname( __file__ )) resDir = os.path.abspath(os.path.join(toolsDir, '..', 'res')) @@ -13,6 +14,8 @@ allJsonUrl = 'http://mtgjson.com/json/AllCards.json' def initializeEditions(): ignoredTypes = [ "From_the_Vault", "Duel_Decks", "Online", "Premium_Deck_Series" ] + editionSections = [ "[cards]", "[precon product]", "[borderless]", "[showcase]", "[extended art]", "[buy a box]", "[promo]" ] + print("Parsing Editions folder") for root, dirnames, filenames in os.walk(editionsDir): for fileName in fnmatch.filter(filenames, '*.txt'): @@ -23,10 +26,11 @@ def initializeEditions(): metadata = True setcode = setname = settype = None for line in currentEdition.readlines(): + line = line.strip() if metadata: - if line.startswith("[cards]"): + if line in editionSections: metadata = False - if setcode: + if setcode and setcode not in setCodes: setCodes.append(setcode) setCodeToName[setcode] = setname if settype in ignoredTypes: @@ -42,24 +46,29 @@ def initializeEditions(): settype = line.split("=")[1].rstrip() else: - if line.startswith("[tokens]"): - # Hopefully tokens are last in the print sheet ordering - metadata = True + if not line: continue - if line.startswith("#") or line.startswith("["): + if line.startswith("["): + if line not in editionSections: + metadata = True continue - if line: - hasSetNumbers = line[0].isdigit() + if line.startswith("#"): + continue - card = line.split(" ", 2 if hasSetNumbers else 1)[-1].rstrip() - if card not in mtgDataCards: - #print card - mtgDataCards[card] = [setcode] + hasSetNumbers = line[0].isdigit() + card = line.split(" ", 2 if hasSetNumbers else 1)[-1].rstrip().split('|')[0] - else: - mtgDataCards[card].append(setcode) + if card.endswith('+'): + card = card[:-1] + + if card not in mtgDataCards: + #print card + mtgDataCards[card] = [setcode] + + else: + mtgDataCards[card].append(setcode) print "Total Cards Found in all editions", len(mtgDataCards) print "These sets will be ignored in some output files", ignoredSet @@ -90,7 +99,7 @@ def initializeOracleText(): return oracleDict def normalizeOracle(oracle): - return oracle.replace(u'\u2014', '-').replace(u'\u2212', '-').replace(u'\u2018', "'").replace(u'\u201c', '"').replace(u'\u201d', '"').replace(u'\u2022', '-').replace(u'\xc6', 'AE').replace(u'\xf6', 'o') + return oracle.replace(u'\u2014', '-').replace(u'\u2212', '-').replace(u'\u2018', "'").replace(u'\u201c', '"').replace(u'\u201d', '"').replace(u'\u2022', '-').replace(u'\xc6', 'AE').replace(u'\xf6', 'o').replace(u'\xb2', '^2').replace(u'\xae', '(R)').replace(u'\u221e', 'INF') def initializeForgeCards(): @@ -347,7 +356,10 @@ if __name__ == '__main__': except: text = '' - output.write(text + '\n\n') + try: + output.write(text + '\n\n') + except: + print everyMissing output.write("\n") output.write("Total: " + str(total) + "\n") output.write("Percentage implemented: " + str(round(percentage,2)) + "%\n")