diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index 9a60ae3eed3..4bc69053d00 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -73,7 +73,6 @@ public class ComputerUtilMana { } manaSpent.clear(); } - private static boolean payManaCost(final SpellAbility sa, final Player ai, final boolean test, final int extraMana, boolean checkPlayable) { ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, test, extraMana); @@ -81,7 +80,6 @@ public class ComputerUtilMana { } private static class ManaProducingCard { - private CoreType cardType; private int manaCount; @@ -1169,10 +1167,11 @@ public class ComputerUtilMana { * @since 1.0.15 */ public static int determineLeftoverMana(final SpellAbility sa, final Player player) { - for (int i = 1; i < 100; i++) - if (!canPayManaCost(sa, player, i)) + for (int i = 1; i < 100; i++) { + if (!canPayManaCost(sa, player, i)) { return i - 1; - + } + } return 99; } 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 1238cb96b04..0c80479e16f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -1133,6 +1133,14 @@ public class ChangeZoneAi extends SpellAbilityAi { if( fetchList.isEmpty() ) return null; + + if (sa.hasParam("AILogic") && sa.getParam("AILogic").equals("NeverBounceItself")) { + Card source = sa.getHostCard(); + if (fetchList.contains(source)) { + // For cards that should never be bounced back to hand with their own [e.g. triggered] abilities, such as guild lands. + fetchList.remove(source); + } + } String type = sa.getParam("ChangeType"); if (type == null) { diff --git a/forge-game/src/main/java/forge/game/GameType.java b/forge-game/src/main/java/forge/game/GameType.java index 735f2f7629b..a30d361b1f6 100644 --- a/forge-game/src/main/java/forge/game/GameType.java +++ b/forge-game/src/main/java/forge/game/GameType.java @@ -13,19 +13,19 @@ import forge.game.player.RegisteredPlayer; public enum GameType { // deck composition rules, isPoolRestricted, can sideboard between matches - Sealed (DeckFormat.Limited, true, true, true, "Sealed", null), - Draft (DeckFormat.Limited, true, true, true, "Draft", null), - Winston (DeckFormat.Limited, true, true, true, "Winston", null), - Gauntlet (DeckFormat.Limited, true, true, true, "Gauntlet", null), - Quest (DeckFormat.QuestDeck, true, true, false, "Quest", null), - QuestDraft (DeckFormat.Limited, true, true, true, "Quest Draft", null), - Constructed (DeckFormat.Constructed, false, true, true, "Constructed", null), - Vanguard (DeckFormat.Vanguard, true, true, true, "Vanguard", null), - Commander (DeckFormat.Commander, false, false, false, "Commander", null), - Planechase (DeckFormat.Planechase, false, false, true, "Planechase", null), - Archenemy (DeckFormat.Archenemy, false, false, true, "Archenemy", null), - ArchenemyRumble (DeckFormat.Archenemy, false, false, true, "Archenemy Rumble", null), - MomirBasic (DeckFormat.Constructed, false, false, false, "Momir Basic", new Function() { + Sealed (DeckFormat.Limited, true, true, true, "Sealed", "", null), + Draft (DeckFormat.Limited, true, true, true, "Draft", "", null), + Winston (DeckFormat.Limited, true, true, true, "Winston", "", null), + Gauntlet (DeckFormat.Limited, true, true, true, "Gauntlet", "", null), + Quest (DeckFormat.QuestDeck, true, true, false, "Quest", "", null), + QuestDraft (DeckFormat.Limited, true, true, true, "Quest Draft", "", null), + Constructed (DeckFormat.Constructed, false, true, true, "Constructed", "", null), + Vanguard (DeckFormat.Vanguard, true, true, true, "Vanguard", "Each player has a special \"Avatar\" card that affects the game.", null), + Commander (DeckFormat.Commander, false, false, false, "Commander", "Each player has a legendary \"General\" card which can be cast at any time and determines deck colors.", null), + Planechase (DeckFormat.Planechase, false, false, true, "Planechase", "Plane cards apply global effects. Plane card changed when a player rolls \"Chaos\" on the planar die.", null), + Archenemy (DeckFormat.Archenemy, false, false, true, "Archenemy", "One player is the Archenemy and can play scheme cards.", null), + ArchenemyRumble (DeckFormat.Archenemy, false, false, true, "Archenemy Rumble", "All players are Archenemies and can play scheme cards.", null), + MomirBasic (DeckFormat.Constructed, false, false, false, "Momir Basic", "Each player has a deck containing 60 basic lands and the Momir Vig avatar.", new Function() { @Override public Deck apply(RegisteredPlayer player) { Deck deck = new Deck(); @@ -43,15 +43,16 @@ public enum GameType { private final DeckFormat deckFormat; private final boolean isCardPoolLimited, canSideboard, addWonCardsMidGame; - private final String name; + private final String name, description; private final Function deckAutoGenerator; - GameType(DeckFormat deckFormat0, boolean isCardPoolLimited0, boolean canSideboard0, boolean addWonCardsMidgame0, String name0, Function deckAutoGenerator0) { + GameType(DeckFormat deckFormat0, boolean isCardPoolLimited0, boolean canSideboard0, boolean addWonCardsMidgame0, String name0, String description0, Function deckAutoGenerator0) { deckFormat = deckFormat0; isCardPoolLimited = isCardPoolLimited0; canSideboard = canSideboard0; addWonCardsMidGame = addWonCardsMidgame0; name = name0; + description = description0; deckAutoGenerator = deckAutoGenerator0; } @@ -104,4 +105,8 @@ public enum GameType { public String toString() { return name; } + + public String getDescription() { + return description; + } } diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java index 8725829cba4..0671de80213 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -78,7 +78,7 @@ import java.util.StringTokenizer; if (abSub != null) { sb.append(abSub.getStackDescription()); } - + if (sa.hasParam("Announce")) { String svar = sa.getParam("Announce"); int amount = CardFactoryUtil.xCount(sa.getHostCard(), sa.getSVar(svar)); 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 c181a9e8884..087ca04df7d 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -2535,7 +2535,7 @@ public class Card extends GameEntity implements Comparable { sb.indexOf("Storm (When you cast this spell, copy it for each spell cast before it this turn.") + 81, " You may choose new targets for the copies."); } - } else if (keyword.contains("Replicate") && !sb.toString().contains("you paid its replicate cost.")) { + } else if (keyword.startsWith("Replicate") && !sb.toString().contains("you paid its replicate cost.")) { if (sb.toString().endsWith("\r\n\r\n")) { sb.delete(sb.lastIndexOf("\r\n"), sb.lastIndexOf("\r\n") + 3); } @@ -4181,8 +4181,9 @@ public class Card extends GameEntity implements Comparable { public final void addMultiKickerMagnitude(final int n) { this.multiKickerMagnitude += n; } public final void setKickerMagnitude(final int n) { this.multiKickerMagnitude = n; } public final int getKickerMagnitude() { - if (this.multiKickerMagnitude > 0) + if (this.multiKickerMagnitude > 0) { return multiKickerMagnitude; + } boolean hasK1 = costsPaid.contains(OptionalCost.Kicker1); return hasK1 == costsPaid.contains(OptionalCost.Kicker2) ? (hasK1 ? 2 : 0) : 1; } 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 3d2b0738595..657501b8a86 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -363,7 +363,6 @@ public class CardFactoryUtil { card.setSVar(playSvar.toString(),playWithoutCost.toString()); } - /** *

* multiplyCost. @@ -392,7 +391,8 @@ public class CardFactoryUtil { cost = multiplier * cost; tokenized[0] = "" + cost; sb.append(tokenized[0]); - } else { + } + else { if (tokenized[0].contains("<")) { final String[] advCostPart = tokenized[0].split("<"); final String costVariable = advCostPart[1].split(">")[0]; @@ -406,7 +406,8 @@ public class CardFactoryUtil { } tokenized[0] = tokenized[0] + ">"; sb.append(tokenized[0]); - } else { + } + else { for (int i = 0; i < multiplier; i++) { // tokenized[0] = tokenized[0] + " " + tokenized[0]; sb.append((" ")); @@ -430,7 +431,8 @@ public class CardFactoryUtil { tokenized[i] = tokenized[i] + ">"; sb.append((" ")); sb.append(tokenized[i]); - } else { + } + else { for (int j = 0; j < multiplier; j++) { // tokenized[i] = tokenized[i] + " " + tokenized[i]; sb.append((" ")); @@ -457,7 +459,6 @@ public class CardFactoryUtil { * @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 @@ -477,7 +478,8 @@ public class CardFactoryUtil { if (!tgt.getZone().contains(zone.getZoneType())) { return false; } - } else { + } + else { // If an Aura's target is removed before it resolves, the Aura // fizzles if (source.isAura() && !target.isInZone(ZoneType.Battlefield)) { @@ -561,7 +563,6 @@ public class CardFactoryUtil { * @return a int. */ public static int countOccurrences(final String arg1, final String arg2) { - int count = 0; int index = 0; while ((index = arg1.indexOf(arg2, index)) != -1) { @@ -650,7 +651,9 @@ public class CardFactoryUtil { } return doXMath(n, m, source); - } else if (l[0].startsWith("Lowest")) { + } + + if (l[0].startsWith("Lowest")) { n = 99999; // if no players have fewer than 99999 valids, the game is frozen anyway for (final Player player : players) { final int current = playerXProperty(player, s.replace("Lowest", ""), source); @@ -669,6 +672,7 @@ public class CardFactoryUtil { if (sq[0].equals("Amount")) { return doXMath(players.size(), m, source); } + if (sq[0].contains("DamageThisTurn")) { int totDmg = 0; for (Player p : players) { @@ -677,8 +681,9 @@ public class CardFactoryUtil { return doXMath(totDmg, m, source); } - if(players.size() > 0) + if (players.size() > 0) { return playerXProperty(players.get(0), s, source); + } return doXMath(n, m, source); } @@ -699,6 +704,7 @@ public class CardFactoryUtil { cards = CardLists.getValidCards(cards, rest, player, source); return doXMath(cards.size(), m, source); } + // count valid cards on the battlefield if (l[0].startsWith("Valid ")) { final String restrictions = l[0].substring(6); @@ -819,8 +825,12 @@ public class CardFactoryUtil { * @return a int. */ public static int xCount(final Card c, final String expression) { - if (StringUtils.isBlank(expression)) return 0; - if (StringUtils.isNumeric(expression)) return Integer.parseInt(expression); + if (StringUtils.isBlank(expression)) { + return 0; + } + if (StringUtils.isNumeric(expression)) { + return Integer.parseInt(expression); + } final Player cc = c.getController(); final Game game = c.getGame(); @@ -834,9 +844,8 @@ public class CardFactoryUtil { final String number = l[0].substring(7); if (number.equals("ChosenNumber")) { return doXMath(c.getChosenNumber(), m, c); - } else { - return doXMath(Integer.parseInt(number), m, c); } + return doXMath(Integer.parseInt(number), m, c); } if (l[0].startsWith("Count$")) { @@ -847,18 +856,17 @@ public class CardFactoryUtil { return doXMath(xCount(c, c.getSVar(l[0].substring(5))), m, c); } - if (l[0].startsWith("Controller$")) + if (l[0].startsWith("Controller$")) { return playerXProperty(cc, l[0].substring(11), c); - + } // Manapool if (l[0].startsWith("ManaPool")) { final String color = l[0].split(":")[1]; if (color.equals("All")) { return cc.getManaPool().totalMana(); - } else { - return cc.getManaPool().getAmountOfColor(MagicColor.fromName(color)); } + return cc.getManaPool().getAmountOfColor(MagicColor.fromName(color)); } // count valid cards in any specified zone/s @@ -874,7 +882,9 @@ public class CardFactoryUtil { return doXMath(cards.size(), m, c); } - if (l[0].startsWith("ImprintedCardManaCost") && !c.getImprinted().isEmpty()) return c.getImprinted().get(0).getCMC(); + if (l[0].startsWith("ImprintedCardManaCost") && !c.getImprinted().isEmpty()) { + return c.getImprinted().get(0).getCMC(); + } if (l[0].startsWith("GreatestPower_")) { final String restriction = l[0].substring(14); @@ -903,7 +913,8 @@ public class CardFactoryUtil { if (crd.getCMC(Card.SplitCMCMode.RightSplitCMC) > highest) { highest = crd.getCMC(Card.SplitCMCMode.RightSplitCMC); } - } else { + } + else { if (crd.getCMC() > highest) { highest = crd.getCMC(); } @@ -983,7 +994,10 @@ public class CardFactoryUtil { final String[] sq; sq = l[0].split("\\."); - if (sq[0].contains("xPaid")) return doXMath(c.getXManaCostPaid(), m, c); + if (sq[0].contains("xPaid")) { + return doXMath(c.getXManaCostPaid(), m, c); + } + if (sq[0].contains("xColorPaid")) { String[] attrs = sq[0].split(" "); String colors = ""; @@ -994,21 +1008,41 @@ public class CardFactoryUtil { } - if (sq[0].equals("YouDrewThisTurn")) return doXMath(c.getController().getNumDrawnThisTurn(), m, c); + if (sq[0].equals("YouDrewThisTurn")) { + return doXMath(c.getController().getNumDrawnThisTurn(), m, c); + } - if (sq[0].equals("FirstSpellTotalManaSpent")) return doXMath(c.getFirstSpellAbility().getTotalManaSpent(), m, c); - if (sq[0].equals("StormCount")) return doXMath(game.getStack().getCardsCastThisTurn().size() - 1, m, c); - if (sq[0].equals("DamageDoneThisTurn")) return doXMath(c.getDamageDoneThisTurn(), m, c); - if (sq[0].equals("BloodthirstAmount")) return doXMath(c.getController().getBloodthirstAmount(), m, c); - if (sq[0].equals("RegeneratedThisTurn")) return doXMath(c.getRegeneratedThisTurn(), m, c); + if (sq[0].equals("FirstSpellTotalManaSpent")) { + return doXMath(c.getFirstSpellAbility().getTotalManaSpent(), m, c); + } + if (sq[0].equals("StormCount")) { + return doXMath(game.getStack().getCardsCastThisTurn().size() - 1, m, c); + } + if (sq[0].equals("DamageDoneThisTurn")) { + return doXMath(c.getDamageDoneThisTurn(), m, c); + } + if (sq[0].equals("BloodthirstAmount")) { + return doXMath(c.getController().getBloodthirstAmount(), m, c); + } + if (sq[0].equals("RegeneratedThisTurn")) { + return doXMath(c.getRegeneratedThisTurn(), m, c); + } // TriggeringObjects - if (sq[0].startsWith("Triggered")) return doXMath(xCount((Card) c.getTriggeringObject("Card"), sq[0].substring(9)), m, c); + if (sq[0].startsWith("Triggered")) { + return doXMath(xCount((Card) c.getTriggeringObject("Card"), sq[0].substring(9)), m, c); + } - if (sq[0].contains("YourStartingLife")) return doXMath(cc.getStartingLife(), m, c); + if (sq[0].contains("YourStartingLife")) { + return doXMath(cc.getStartingLife(), m, c); + } - if (sq[0].contains("YourLifeTotal")) return doXMath(cc.getLife(), m, c); - if (sq[0].contains("OppGreatestLifeTotal")) return doXMath(cc.getOpponentsGreatestLifeTotal(), m, c); + if (sq[0].contains("YourLifeTotal")) { + return doXMath(cc.getLife(), m, c); + } + if (sq[0].contains("OppGreatestLifeTotal")) { + return doXMath(cc.getOpponentsGreatestLifeTotal(), m, c); + } if (sq[0].contains("OppsAtLifeTotal")) { final int lifeTotal = xCount(c, sq[1]); int number = 0; @@ -1032,8 +1066,12 @@ public class CardFactoryUtil { } } - if (sq[0].contains("LifeYouLostThisTurn")) return doXMath(cc.getLifeLostThisTurn(), m, c); - if (sq[0].contains("LifeYouGainedThisTurn")) return doXMath(cc.getLifeGainedThisTurn(), m, c); + if (sq[0].contains("LifeYouLostThisTurn")) { + return doXMath(cc.getLifeLostThisTurn(), m, c); + } + if (sq[0].contains("LifeYouGainedThisTurn")) { + return doXMath(cc.getLifeGainedThisTurn(), m, c); + } if (sq[0].contains("LifeOppsLostThisTurn")) { int lost = 0; for (Player opp : cc.getOpponents()) { @@ -1042,18 +1080,34 @@ public class CardFactoryUtil { return doXMath(lost, m, c); } - if (sq[0].equals("TotalDamageDoneByThisTurn")) return doXMath(c.getTotalDamageDoneBy(), m, c); - if (sq[0].equals("TotalDamageReceivedThisTurn")) return doXMath(c.getTotalDamageRecievedThisTurn(), m, c); + if (sq[0].equals("TotalDamageDoneByThisTurn")) { + return doXMath(c.getTotalDamageDoneBy(), m, c); + } + if (sq[0].equals("TotalDamageReceivedThisTurn")) { + return doXMath(c.getTotalDamageRecievedThisTurn(), m, c); + } - if (sq[0].contains("YourPoisonCounters")) return doXMath(cc.getPoisonCounters(), m, c); - if (sq[0].contains("TotalOppPoisonCounters")) return doXMath(cc.getOpponentsTotalPoisonCounters(), m, c); + if (sq[0].contains("YourPoisonCounters")) { + return doXMath(cc.getPoisonCounters(), m, c); + } + if (sq[0].contains("TotalOppPoisonCounters")) { + return doXMath(cc.getOpponentsTotalPoisonCounters(), m, c); + } - if (sq[0].contains("YourDamageThisTurn")) return doXMath(cc.getAssignedDamage(), m, c); - if (sq[0].contains("TotalOppDamageThisTurn")) return doXMath(cc.getOpponentsAssignedDamage(), m, c); - if (sq[0].contains("MaxOppDamageThisTurn")) return doXMath(cc.getMaxOpponentAssignedDamage(), m, c); + if (sq[0].contains("YourDamageThisTurn")) { + return doXMath(cc.getAssignedDamage(), m, c); + } + if (sq[0].contains("TotalOppDamageThisTurn")) { + return doXMath(cc.getOpponentsAssignedDamage(), m, c); + } + if (sq[0].contains("MaxOppDamageThisTurn")) { + return doXMath(cc.getMaxOpponentAssignedDamage(), m, c); + } // Count$YourTypeDamageThisTurn Type - if (sq[0].contains("YourTypeDamageThisTurn")) return doXMath(cc.getAssignedDamage(sq[0].split(" ")[1]), m, c); + if (sq[0].contains("YourTypeDamageThisTurn")) { + return doXMath(cc.getAssignedDamage(sq[0].split(" ")[1]), m, c); + } if (sq[0].contains("YourDamageSourcesThisTurn")) { Iterable allSrc = cc.getAssignedDamageSources(); String restriction = sq[0].split(" ")[1]; @@ -1061,7 +1115,9 @@ public class CardFactoryUtil { return doXMath(filtered.size(), m, c); } - if (sq[0].contains("YourLandsPlayed")) return doXMath(cc.getNumLandsPlayed(), m, c); + if (sq[0].contains("YourLandsPlayed")) { + return doXMath(cc.getNumLandsPlayed(), m, c); + } // Count$TopOfLibraryCMC if (sq[0].contains("TopOfLibraryCMC")) { @@ -1095,14 +1151,15 @@ public class CardFactoryUtil { final List cards; if (sq[0].contains("ChromaSource")) { // Runs Chroma for passed in Source card cards = Lists.newArrayList(c); - } else { + } + else { cards = cc.getCardsIn(sourceZone); } int colorOcurrencices = 0; byte colorCode = MagicColor.fromName(colorName); - for(Card c0 : cards) { - for(ManaCostShard sh : c0.getManaCost()){ + for (Card c0 : cards) { + for (ManaCostShard sh : c0.getManaCost()){ if ((sh.getColorMask() & colorCode) != 0) colorOcurrencices++; } @@ -1114,7 +1171,7 @@ public class CardFactoryUtil { int colorOcurrencices = 0; byte color1 = MagicColor.fromName(sq[1]); byte color2 = MagicColor.fromName(sq[2]); - for(Card c0 : cc.getCardsIn(ZoneType.Battlefield)) { + for (Card c0 : cc.getCardsIn(ZoneType.Battlefield)) { for (ManaCostShard sh : c0.getManaCost()) { if ((sh.getColorMask() & (color1 | color2)) != 0) { colorOcurrencices++; @@ -1124,14 +1181,28 @@ public class CardFactoryUtil { return doXMath(colorOcurrencices, m, c); } - if (sq[0].contains("Hellbent")) return doXMath(Integer.parseInt(sq[cc.hasHellbent() ? 1 : 2]), m, c); - if (sq[0].contains("Metalcraft")) return doXMath(Integer.parseInt(sq[cc.hasMetalcraft() ? 1 : 2]), m, c); - if (sq[0].contains("FatefulHour")) return doXMath(Integer.parseInt(sq[cc.getLife() <= 5 ? 1 : 2]), m, c); + if (sq[0].contains("Hellbent")) { + return doXMath(Integer.parseInt(sq[cc.hasHellbent() ? 1 : 2]), m, c); + } + if (sq[0].contains("Metalcraft")) { + return doXMath(Integer.parseInt(sq[cc.hasMetalcraft() ? 1 : 2]), m, c); + } + if (sq[0].contains("FatefulHour")) { + return doXMath(Integer.parseInt(sq[cc.getLife() <= 5 ? 1 : 2]), m, c); + } - if (sq[0].contains("Landfall")) return doXMath(Integer.parseInt(sq[cc.hasLandfall() ? 1 : 2]), m, c); - if (sq[0].contains("Threshold")) return doXMath(Integer.parseInt(sq[cc.hasThreshold() ? 1 : 2]), m, c); - if (sq[0].startsWith("Kicked")) return doXMath(Integer.parseInt(sq[c.getKickerMagnitude() > 0 ? 1 : 2]), m, c); - if (sq[0].startsWith("AltCost")) return doXMath(Integer.parseInt(sq[c.isOptionalCostPaid(OptionalCost.AltCost) ? 1 : 2]), m, c); + if (sq[0].contains("Landfall")) { + return doXMath(Integer.parseInt(sq[cc.hasLandfall() ? 1 : 2]), m, c); + } + if (sq[0].contains("Threshold")) { + return doXMath(Integer.parseInt(sq[cc.hasThreshold() ? 1 : 2]), m, c); + } + if (sq[0].startsWith("Kicked")) { + return doXMath(Integer.parseInt(sq[c.getKickerMagnitude() > 0 ? 1 : 2]), m, c); + } + if (sq[0].startsWith("AltCost")) { + return doXMath(Integer.parseInt(sq[c.isOptionalCostPaid(OptionalCost.AltCost) ? 1 : 2]), m, c); + } // Count$wasCastFrom.. if (sq[0].startsWith("wasCastFrom")) { @@ -1145,9 +1216,15 @@ public class CardFactoryUtil { return doXMath(cl.size(), m, c); } - if (sq[0].contains("CardPower")) return doXMath(c.getNetAttack(), m, c); - if (sq[0].contains("CardToughness")) return doXMath(c.getNetDefense(), m, c); - if (sq[0].contains("CardSumPT")) return doXMath((c.getNetAttack() + c.getNetDefense()), m, c); + if (sq[0].contains("CardPower")) { + return doXMath(c.getNetAttack(), m, c); + } + if (sq[0].contains("CardToughness")) { + return doXMath(c.getNetDefense(), m, c); + } + if (sq[0].contains("CardSumPT")) { + return doXMath((c.getNetAttack() + c.getNetDefense()), m, c); + } // Count$SumPower_valid if (sq[0].contains("SumPower")) { @@ -1161,9 +1238,11 @@ public class CardFactoryUtil { Card ce; if (sq[0].contains("Equipped") && c.isEquipping()) { ce = c.getEquipping().get(0); - } else if (sq[0].contains("Remembered")) { + } + else if (sq[0].contains("Remembered")) { ce = (Card) c.getRemembered().get(0); - } else { + } + else { ce = c; } return doXMath(ce.getCMC(), m, c); @@ -1177,18 +1256,23 @@ public class CardFactoryUtil { return Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetCmc); } - if (sq[0].contains("CardNumColors")) return doXMath(CardUtil.getColors(c).countColors(), m, c); - if (sq[0].contains("ChosenNumber")) return doXMath(c.getChosenNumber(), m, c); + if (sq[0].contains("CardNumColors")) { + return doXMath(CardUtil.getColors(c).countColors(), m, c); + } + if (sq[0].contains("ChosenNumber")) { + return doXMath(c.getChosenNumber(), m, c); + } if (sq[0].contains("CardCounters")) { // CardCounters.ALL to be used for Kinsbaile Borderguard and anything that cares about all counters int count = 0; if (sq[1].equals("ALL")) { - for(Integer i : c.getCounters().values()) { + for (Integer i : c.getCounters().values()) { if (i != null && i > 0) { count += i; } } - } else { + } + else { count = c.getCounters(CounterType.getType(sq[1])); } return doXMath(count, m, c); @@ -1208,12 +1292,19 @@ public class CardFactoryUtil { return doXMath(cCount, m, c); } - if (sq[0].contains("CardTypes")) return doXMath(getCardTypesFromList(game.getCardsIn(ZoneType.smartValueOf(sq[1]))), m, c); - - if (sq[0].contains("BushidoPoint")) return doXMath(c.getKeywordMagnitude("Bushido"), m, c); - if (sq[0].contains("TimesKicked")) return doXMath(c.getKickerMagnitude(), m, c); - if (sq[0].contains("NumCounters")) return doXMath(c.getCounters(CounterType.getType(sq[1])), m, c); + if (sq[0].contains("CardTypes")) { + return doXMath(getCardTypesFromList(game.getCardsIn(ZoneType.smartValueOf(sq[1]))), m, c); + } + if (sq[0].contains("BushidoPoint")) { + return doXMath(c.getKeywordMagnitude("Bushido"), m, c); + } + if (sq[0].contains("TimesKicked")) { + return doXMath(c.getKickerMagnitude(), m, c); + } + if (sq[0].contains("NumCounters")) { + return doXMath(c.getCounters(CounterType.getType(sq[1])), m, c); + } // Count$IfMainPhase.. // 7/10 if (sq[0].contains("IfMainPhase")) { @@ -1272,7 +1363,8 @@ public class CardFactoryUtil { if (workingCopy[0].contains("This")) { res = CardUtil.getThisTurnCast(validFilter, c); - } else { + } + else { res = CardUtil.getLastTurnCast(validFilter, c); } @@ -1285,9 +1377,8 @@ public class CardFactoryUtil { final List res = CardUtil.getThisTurnEntered(ZoneType.Graveyard, ZoneType.Battlefield, "Creature", c); if (res.size() > 0) { return doXMath(Integer.parseInt(sq[1]), m, c); - } else { - return doXMath(Integer.parseInt(sq[2]), m, c); } + return doXMath(Integer.parseInt(sq[2]), m, c); } if (sq[0].equals("YourTurns")) { @@ -1489,8 +1580,8 @@ public class CardFactoryUtil { // if (sq[0].contains("Tapped")) { someCards = CardLists.filter(someCards, Presets.TAPPED); } // String sq0 = sq[0].toLowerCase(); -// for(String color : MagicColor.Constant.ONLY_COLORS) { -// if( sq0.contains(color) ) +// for (String color : MagicColor.Constant.ONLY_COLORS) { +// if (sq0.contains(color)) // someCards = someCards.filter(CardListFilter.WHITE); // } // "White Creatures" - Count$WhiteTypeYouCtrl.Creature @@ -1658,24 +1749,24 @@ public class CardFactoryUtil { public static byte getMostProminentColors(final List list) { int cntColors = MagicColor.WUBRG.length; final Integer[] map = new Integer[cntColors]; - for(int i = 0; i < cntColors; i++) { + for (int i = 0; i < cntColors; i++) { map[i] = 0; } for (final Card crd : list) { ColorSet color = CardUtil.getColors(crd); - for(int i = 0; i < cntColors; i++) { - if( color.hasAnyColor(MagicColor.WUBRG[i])) + for (int i = 0; i < cntColors; i++) { + if (color.hasAnyColor(MagicColor.WUBRG[i])) map[i]++; } } // for byte mask = 0; int nMax = -1; - for(int i = 0; i < cntColors; i++) { - if ( map[i] > nMax ) + for (int i = 0; i < cntColors; i++) { + if (map[i] > nMax) mask = MagicColor.WUBRG[i]; - else if ( map[i] == nMax ) + else if (map[i] == nMax) mask |= MagicColor.WUBRG[i]; else continue; @@ -1696,14 +1787,14 @@ public class CardFactoryUtil { public static int[] SortColorsFromList(final List list) { int cntColors = MagicColor.WUBRG.length; final int[] map = new int[cntColors]; - for(int i = 0; i < cntColors; i++) { + for (int i = 0; i < cntColors; i++) { map[i] = 0; } for (final Card crd : list) { ColorSet color = CardUtil.getColors(crd); - for(int i = 0; i < cntColors; i++) { - if( color.hasAnyColor(MagicColor.WUBRG[i])) + for (int i = 0; i < cntColors; i++) { + if (color.hasAnyColor(MagicColor.WUBRG[i])) map[i]++; } } // for @@ -1727,7 +1818,7 @@ public class CardFactoryUtil { } int cntColors = colorRestrictions.size(); final Integer[] map = new Integer[cntColors]; - for(int i = 0; i < cntColors; i++) { + for (int i = 0; i < cntColors; i++) { map[i] = 0; } @@ -1742,10 +1833,10 @@ public class CardFactoryUtil { byte mask = 0; int nMax = -1; - for(int i = 0; i < cntColors; i++) { - if ( map[i] > nMax ) + for (int i = 0; i < cntColors; i++) { + if (map[i] > nMax) mask = colorRestrictions.get(i); - else if ( map[i] == nMax ) + else if (map[i] == nMax) mask |= colorRestrictions.get(i); else continue; @@ -1928,7 +2019,7 @@ public class CardFactoryUtil { String description = String.format("Bushido %d (When this blocks or becomes blocked, it gets +%d/+%d until end of turn).", magnitude, magnitude, magnitude); String regularPart = String.format("AB$ Pump | Cost$ 0 | Defined$ CardUID_%d | NumAtt$ +%d | NumDef$ +%d | StackDescription$ %s", c.getUniqueNumber(), magnitude, magnitude, description); - SpellAbility ability = AbilityFactory.getAbility( regularPart, c); + SpellAbility ability = AbilityFactory.getAbility(regularPart, c); ability.setDescription(ability.getStackDescription()); ability.setTrigger(true); // can be copied by Strionic Resonator list.add(ability); @@ -1990,7 +2081,7 @@ public class CardFactoryUtil { "Description$ If a commander would be put into its owner's graveyard or exile from anywhere, that player may put it into the command zone instead.", cmd, true); cmd.addReplacementEffect(re); - if(StringUtils.isBlank(cmd.getSVar("CommanderCostRaise"))) // why condition check is needed? + if (StringUtils.isBlank(cmd.getSVar("CommanderCostRaise"))) // why condition check is needed? cmd.setSVar("CommanderCostRaise", "Number$0"); String cmdManaCost = cmd.getManaCost().toString(); @@ -2003,18 +2094,19 @@ public class CardFactoryUtil { cmd.addStaticAbility("Mode$ RaiseCost | Amount$ CommanderCostRaise | Type$ Spell | ValidCard$ Card.Self+wasCastFromCommand | EffectZone$ All | AffectedZone$ Stack"); } */ - public static final String getCommanderInfo(final Player originPlayer ) { + public static final String getCommanderInfo(final Player originPlayer) { StringBuilder sb = new StringBuilder(); - for(Player p : originPlayer.getGame().getPlayers()) { + for (Player p : originPlayer.getGame().getPlayers()) { final String text; - if(p.equals(originPlayer)) { + if (p.equals(originPlayer)) { text = "Commander Damage from own Commander: "; - } else { + } + else { text = "Commander Damage to " + p.getName() + ": "; } final Map map = p.getCommanderDamage(); - if(map.containsKey(originPlayer.getCommander())) { + if (map.containsKey(originPlayer.getCommander())) { sb.append(text + map.get(originPlayer.getCommander()) + "\r\n"); } } @@ -2036,55 +2128,46 @@ public class CardFactoryUtil { // Cards with Cycling abilities // -1 means keyword "Cycling" not found - if (hasKeyword(card, "Multikicker") != -1) { - final int n = hasKeyword(card, "Multikicker"); - if (n != -1) { - final String parse = card.getKeyword().get(n).toString(); - final String[] k = parse.split("kicker "); + for (String keyword : card.getKeyword()) { + if (keyword.startsWith("Multikicker")) { + final String[] k = keyword.split("kicker "); final SpellAbility sa = card.getFirstSpellAbility(); sa.setMultiKickerManaCost(new ManaCost(new ManaCostParser(k[1]))); + sa.addAnnounceVar("Multikicker"); } - } - - if(hasKeyword(card, "Fuse") != -1) { - card.getState(CardCharacteristicName.Original).getSpellAbility().add(AbilityFactory.buildFusedAbility(card)); - } + else if (keyword.startsWith("Replicate")) { + card.getFirstSpellAbility().addAnnounceVar("Replicate"); + } + else if (keyword.startsWith("Fuse")) { + card.getState(CardCharacteristicName.Original).getSpellAbility().add(AbilityFactory.buildFusedAbility(card)); + } + else if (keyword.startsWith("Evoke")) { + card.addSpellAbility(makeEvokeSpell(card, keyword)); + } + else if (keyword.startsWith("Monstrosity")) { + final String[] k = keyword.split(":"); + final String magnitude = k[0].substring(12); + final String manacost = k[1]; + card.removeIntrinsicKeyword(keyword); - final int evokePos = hasKeyword(card, "Evoke"); - if (evokePos != -1) { - card.addSpellAbility(makeEvokeSpell(card, card.getKeyword().get(evokePos))); - } - final int monstrousPos = hasKeyword(card, "Monstrosity"); - if (monstrousPos != -1) { - final String parse = card.getKeyword().get(monstrousPos).toString(); - final String[] k = parse.split(":"); - final String magnitude = k[0].substring(12); - final String manacost = k[1]; - card.removeIntrinsicKeyword(parse); + String ref = "X".equals(magnitude) ? " | References$ X" : ""; + String counters = StringUtils.isNumeric(magnitude) + ? Lang.nounWithNumeral(Integer.parseInt(magnitude), "+1/+1 counter"): "X +1/+1 counters"; + String effect = "AB$ PutCounter | Cost$ " + manacost + " | ConditionPresent$ " + + "Card.Self+IsNotMonstrous | Monstrosity$ True | CounterNum$ " + + magnitude + " | CounterType$ P1P1 | SpellDescription$ Monstrosity " + + magnitude + " (If this creature isn't monstrous, put " + + counters + " on it and it becomes monstrous.) | StackDescription$ SpellDescription" + ref; - String ref = "X".equals(magnitude) ? " | References$ X" : ""; - String counters = StringUtils.isNumeric(magnitude) - ? Lang.nounWithNumeral(Integer.parseInt(magnitude), "+1/+1 counter"): "X +1/+1 counters"; - String effect = "AB$ PutCounter | Cost$ " + manacost + " | ConditionPresent$ " + - "Card.Self+IsNotMonstrous | Monstrosity$ True | CounterNum$ " + - magnitude + " | CounterType$ P1P1 | SpellDescription$ Monstrosity " + - magnitude + " (If this creature isn't monstrous, put " + - counters + " on it and it becomes monstrous.) | StackDescription$ SpellDescription" + ref; - - card.addSpellAbility(AbilityFactory.getAbility(effect, card)); - // add ability to instrinic strings so copies/clones create the ability also - card.getUnparsedAbilities().add(effect); - } - - if (hasKeyword(card, "Unearth") != -1) { - final int n = hasKeyword(card, "Unearth"); - if (n != -1) { - final String parse = card.getKeyword().get(n).toString(); - card.removeIntrinsicKeyword(parse); - - final String[] k = parse.split(":"); + card.addSpellAbility(AbilityFactory.getAbility(effect, card)); + // add ability to instrinic strings so copies/clones create the ability also + card.getUnparsedAbilities().add(effect); + } + else if (keyword.startsWith("Unearth")) { + card.removeIntrinsicKeyword(keyword); + final String[] k = keyword.split(":"); final String manacost = k[1]; String effect = "AB$ ChangeZone | Cost$ " + manacost + " | Defined$ Self" + @@ -2110,63 +2193,44 @@ public class CardFactoryUtil { // add ability to instrinic strings so copies/clones create the ability also card.getUnparsedAbilities().add(effect); } - } // unearth + else if (keyword.startsWith("Level up")) { + final String strMaxLevel = card.getSVar("maxLevel"); + card.removeIntrinsicKeyword(keyword); - final int iLvlUp = hasKeyword(card, "Level up"); - - if (iLvlUp != -1) { - final String strLevelCost = card.getKeyword().get(iLvlUp); - final String strMaxLevel = card.getSVar("maxLevel"); - card.removeIntrinsicKeyword(strLevelCost); + final String[] k = keyword.split(":"); + final String manacost = k[1]; - final String[] k = strLevelCost.split(":"); - final String manacost = k[1]; + String effect = "AB$ PutCounter | Cost$ " + manacost + " | " + + "SorcerySpeed$ True | LevelUp$ True | CounterNum$ 1" + + " | CounterType$ LEVEL | PrecostDesc$ Level Up | MaxLevel$ " + + strMaxLevel + " | SpellDescription$ (Put a level counter on" + + " this permanent. Activate this ability only any time you" + + " could cast a sorcery.)"; - String effect = "AB$ PutCounter | Cost$ " + manacost + " | " + - "SorcerySpeed$ True | LevelUp$ True | CounterNum$ 1" + - " | CounterType$ LEVEL | PrecostDesc$ Level Up | MaxLevel$ " + - strMaxLevel + " | SpellDescription$ (Put a level counter on" + - " this permanent. Activate this ability only any time you" + - " could cast a sorcery.)"; + card.addSpellAbility(AbilityFactory.getAbility(effect, card)); + // add ability to instrinic strings so copies/clones create the ability also + card.getUnparsedAbilities().add(effect); + } + else if (keyword.startsWith("Cycling")) { + card.removeIntrinsicKeyword(keyword); - card.addSpellAbility(AbilityFactory.getAbility(effect, card)); - // add ability to instrinic strings so copies/clones create the ability also - card.getUnparsedAbilities().add(effect); - } // level up - - if (hasKeyword(card, "Cycling") != -1) { - final int n = hasKeyword(card, "Cycling"); - if (n != -1) { - final String parse = card.getKeyword().get(n).toString(); - card.removeIntrinsicKeyword(parse); - - final String[] k = parse.split(":"); + final String[] k = keyword.split(":"); final String manacost = k[1]; card.addSpellAbility(abilityCycle(card, manacost)); } - } // Cycling + else if (keyword.startsWith("TypeCycling")) { + card.removeIntrinsicKeyword(keyword); - while (hasKeyword(card, "TypeCycling") != -1) { - final int n = hasKeyword(card, "TypeCycling"); - if (n != -1) { - final String parse = card.getKeyword().get(n).toString(); - card.removeIntrinsicKeyword(parse); - - final String[] k = parse.split(":"); + final String[] k = keyword.split(":"); final String type = k[1]; final String manacost = k[2]; card.addSpellAbility(abilityTypecycle(card, manacost, type)); } - } // TypeCycling - - if (hasKeyword(card, "Transmute") != -1) { - final int n = hasKeyword(card, "Transmute"); - if (n != -1) { - final String parse = card.getKeyword().get(n); - card.removeIntrinsicKeyword(parse); - final String manacost = parse.split(":")[1]; + else if (keyword.startsWith("Transmute")) { + card.removeIntrinsicKeyword(keyword); + final String manacost = keyword.split(":")[1]; final String sbTransmute = "AB$ ChangeZone | Cost$ " + manacost + " Discard<1/CARDNAME>" + " | CostDesc$ Transmute " + ManaCostParser.parse(manacost)+ " | ActivationZone$ Hand" + " | Origin$ Library | Destination$ Hand | ChangeType$ Card.cmcEQ" + card.getManaCost().getCMC() @@ -2179,114 +2243,95 @@ public class CardFactoryUtil { card.addSpellAbility(abTransmute); card.getUnparsedAbilities().add(sbTransmute); } - } // transmute + else if (keyword.startsWith("Soulshift")) { + final String[] k = keyword.split(" "); + final int manacost = Integer.parseInt(k[1]); - int shiftPos = hasKeyword(card, "Soulshift"); - while (shiftPos != -1) { - final int n = shiftPos; - final String parse = card.getKeyword().get(n); - final String[] k = parse.split(" "); - final int manacost = Integer.parseInt(k[1]); - - final String actualTrigger = "Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard" - + "| OptionalDecider$ You | ValidCard$ Card.Self | Execute$ SoulshiftAbility" - + "| TriggerController$ TriggeredCardController | TriggerDescription$ " + parse - + " (When this creature dies, you may return target Spirit card with converted mana cost " - + manacost + " or less from your graveyard to your hand.)"; - final String abString = "DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand" - + "| ValidTgts$ Spirit.YouOwn+cmcLE" + manacost; - final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, true); - card.addTrigger(parsedTrigger); - card.setSVar("SoulshiftAbility", abString); - shiftPos = hasKeyword(card, "Soulshift", n + 1); - } // Soulshift - - final int championPos = hasKeyword(card, "Champion"); - if (championPos != -1) { - String parse = card.getKeyword().get(championPos); - card.removeIntrinsicKeyword(parse); - - final String[] k = parse.split(":"); - final String[] valid = k[1].split(","); - String desc = k.length > 2 ? k[2] : k[1]; - String article = Lang.startsWithVowel(desc) ? "an" : "a"; - if (desc.equals("Creature")) { - desc = "creature"; //use lowercase for "Champion a creature" + final String actualTrigger = "Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard" + + "| OptionalDecider$ You | ValidCard$ Card.Self | Execute$ SoulshiftAbility" + + "| TriggerController$ TriggeredCardController | TriggerDescription$ " + keyword + + " (When this creature dies, you may return target Spirit card with converted mana cost " + + manacost + " or less from your graveyard to your hand.)"; + final String abString = "DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand" + + "| ValidTgts$ Spirit.YouOwn+cmcLE" + manacost; + final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, true); + card.addTrigger(parsedTrigger); + card.setSVar("SoulshiftAbility", abString); } + else if (keyword.startsWith("Champion")) { + card.removeIntrinsicKeyword(keyword); - StringBuilder changeType = new StringBuilder(); - for (String v : valid) { - if (changeType.length() != 0) { - changeType.append(","); + final String[] k = keyword.split(":"); + final String[] valid = k[1].split(","); + String desc = k.length > 2 ? k[2] : k[1]; + String article = Lang.startsWithVowel(desc) ? "an" : "a"; + if (desc.equals("Creature")) { + desc = "creature"; //use lowercase for "Champion a creature" } - changeType.append(v).append(".YouCtrl+Other"); + + StringBuilder changeType = new StringBuilder(); + for (String v : valid) { + if (changeType.length() != 0) { + changeType.append(","); + } + changeType.append(v).append(".YouCtrl+Other"); + } + + StringBuilder trig = new StringBuilder(); + trig.append("Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | "); + trig.append("Execute$ ChampionAbility | TriggerDescription$ Champion ").append(article + " "); + trig.append(desc).append(" (When this enters the battlefield, sacrifice it unless you exile another "); + trig.append(desc).append(" you control. When this leaves the battlefield, that card returns to the battlefield.)"); + + StringBuilder trigReturn = new StringBuilder(); + trigReturn.append("Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | "); + trigReturn.append("Execute$ ChampionReturn | Secondary$ True | TriggerDescription$ When this leaves the battlefield, that card returns to the battlefield."); + + StringBuilder ab = new StringBuilder(); + ab.append("DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | Champion$ True | "); + ab.append("Hidden$ True | Optional$ True | SubAbility$ ChampionSacrifice | ChangeType$ ").append(changeType); + + StringBuilder subAb = new StringBuilder(); + subAb.append("DB$ Sacrifice | Defined$ Card.Self | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0"); + + String returnChampion = "DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield"; + final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig.toString(), card, true); + final Trigger parsedTrigReturn = TriggerHandler.parseTrigger(trigReturn.toString(), card, true); + card.addTrigger(parsedTrigger); + card.addTrigger(parsedTrigReturn); + card.setSVar("ChampionAbility", ab.toString()); + card.setSVar("ChampionReturn", returnChampion); + card.setSVar("ChampionSacrifice", subAb.toString()); } + else if (keyword.startsWith("If CARDNAME would be put into a graveyard " + + "from anywhere, reveal CARDNAME and shuffle it into its owner's library instead.")) { + String replacement = "Event$ Moved | Destination$ Graveyard | ValidCard$ Card.Self | ReplaceWith$ GraveyardToLibrary"; + String ab = "DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Library | Defined$ ReplacedCard | Reveal$ True | Shuffle$ True"; - StringBuilder trig = new StringBuilder(); - trig.append("Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | "); - trig.append("Execute$ ChampionAbility | TriggerDescription$ Champion ").append(article + " "); - trig.append(desc).append(" (When this enters the battlefield, sacrifice it unless you exile another "); - trig.append(desc).append(" you control. When this leaves the battlefield, that card returns to the battlefield.)"); + card.addReplacementEffect(ReplacementHandler.parseReplacement(replacement, card, true)); + card.setSVar("GraveyardToLibrary", ab); + } + else if (keyword.startsWith("Echo")) { + final String[] k = keyword.split(":"); + final String manacost = k[1]; - StringBuilder trigReturn = new StringBuilder(); - trigReturn.append("Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | "); - trigReturn.append("Execute$ ChampionReturn | Secondary$ True | TriggerDescription$ When this leaves the battlefield, that card returns to the battlefield."); + card.setEchoCost(manacost); - StringBuilder ab = new StringBuilder(); - ab.append("DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | Champion$ True | "); - ab.append("Hidden$ True | Optional$ True | SubAbility$ ChampionSacrifice | ChangeType$ ").append(changeType); + final GameCommand intoPlay = new GameCommand() { - StringBuilder subAb = new StringBuilder(); - subAb.append("DB$ Sacrifice | Defined$ Card.Self | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0"); + private static final long serialVersionUID = -7913835645603984242L; - String returnChampion = "DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield"; - final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig.toString(), card, true); - final Trigger parsedTrigReturn = TriggerHandler.parseTrigger(trigReturn.toString(), card, true); - card.addTrigger(parsedTrigger); - card.addTrigger(parsedTrigReturn); - card.setSVar("ChampionAbility", ab.toString()); - card.setSVar("ChampionReturn", returnChampion); - card.setSVar("ChampionSacrifice", subAb.toString()); - } - - if (card.hasKeyword("If CARDNAME would be put into a graveyard " - + "from anywhere, reveal CARDNAME and shuffle it into its owner's library instead.")) { - - String replacement = "Event$ Moved | Destination$ Graveyard | ValidCard$ Card.Self | ReplaceWith$ GraveyardToLibrary"; - String ab = "DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Library | Defined$ ReplacedCard | Reveal$ True | Shuffle$ True"; - - card.addReplacementEffect(ReplacementHandler.parseReplacement(replacement, card, true)); - card.setSVar("GraveyardToLibrary", ab); - } - - final int echoPos = hasKeyword(card, "Echo"); - if (echoPos != -1) { - // card.removeIntrinsicKeyword(parse); - final String[] k = card.getKeyword().get(echoPos).split(":"); - final String manacost = k[1]; - - card.setEchoCost(manacost); - - final GameCommand intoPlay = new GameCommand() { - - private static final long serialVersionUID = -7913835645603984242L; - - @Override - public void run() { - card.addExtrinsicKeyword("(Echo unpaid)"); - } - }; - card.addComesIntoPlayCommand(intoPlay); - } // echo - - if (hasKeyword(card, "Suspend") != -1) { - // Suspend:: - final int n = hasKeyword(card, "Suspend"); - if (n != -1) { - final String parse = card.getKeyword().get(n); - card.removeIntrinsicKeyword(parse); + @Override + public void run() { + card.addExtrinsicKeyword("(Echo unpaid)"); + } + }; + card.addComesIntoPlayCommand(intoPlay); + } + else if (keyword.startsWith("Suspend")) { + card.removeIntrinsicKeyword(keyword); card.setSuspend(true); - final String[] k = parse.split(":"); + final String[] k = keyword.split(":"); final String timeCounters = k[1]; final String cost = k[2]; @@ -2294,12 +2339,8 @@ public class CardFactoryUtil { addSuspendUpkeepTrigger(card); addSuspendPlayTrigger(card); } - } // Suspend - - if (hasKeyword(card, "Fading") != -1) { - final int n = hasKeyword(card, "Fading"); - if (n != -1) { - final String[] k = card.getKeyword().get(n).split(":"); + else if (keyword.startsWith("Fading")) { + final String[] k = keyword.split(":"); card.addIntrinsicKeyword("etbCounter:FADE:" + k[1] + ":no Condition:no desc"); @@ -2308,43 +2349,323 @@ public class CardFactoryUtil { "your upkeep, remove a fade counter from CARDNAME. If you can't, sacrifice CARDNAME."; card.setSVar("TrigUpkeepFading", "AB$ RemoveCounter | Cost$ 0 | Defined$ Self | CounterType$ FADE" + - " | CounterNum$ 1 | RememberRemoved$ True | SubAbility$ DBUpkeepFadingSac"); + " | CounterNum$ 1 | RememberRemoved$ True | SubAbility$ DBUpkeepFadingSac"); card.setSVar("DBUpkeepFadingSac","DB$ Sacrifice | SacValid$ Self | ConditionCheckSVar$ FadingCheckSVar" + - " | ConditionSVarCompare$ EQ0 | References$ FadingCheckSVar | SubAbility$ FadingCleanup"); + " | ConditionSVarCompare$ EQ0 | References$ FadingCheckSVar | SubAbility$ FadingCleanup"); card.setSVar("FadingCleanup","DB$ Cleanup | ClearRemembered$ True"); card.setSVar("FadingCheckSVar","Count$RememberedSize"); final Trigger parsedUpkeepTrig = TriggerHandler.parseTrigger(upkeepTrig, card, true); card.addTrigger(parsedUpkeepTrig); } - } // Fading - - if (hasKeyword(card, "Vanishing") != -1) { - final int n = hasKeyword(card, "Vanishing"); - if (n != -1) { - final String[] k = card.getKeyword().get(n).split(":"); + else if (keyword.startsWith("Vanishing")) { + final String[] k = keyword.split(":"); // etbcounter card.addIntrinsicKeyword("etbCounter:TIME:" + k[1] + ":no Condition:no desc"); // Remove Time counter trigger String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | " + - "TriggerZones$ Battlefield | IsPresent$ Card.Self+counters_GE1_TIME" + - " | Execute$ TrigUpkeepVanishing | TriggerDescription$ At the " + - "beginning of your upkeep, if CARDNAME has a time counter on it, " + - "remove a time counter from it. | Secondary$ True"; + "TriggerZones$ Battlefield | IsPresent$ Card.Self+counters_GE1_TIME" + + " | Execute$ TrigUpkeepVanishing | TriggerDescription$ At the " + + "beginning of your upkeep, if CARDNAME has a time counter on it, " + + "remove a time counter from it. | Secondary$ True"; card.setSVar("TrigUpkeepVanishing", "AB$ RemoveCounter | Cost$ 0 | Defined$ Self" + - " | CounterType$ TIME | CounterNum$ 1"); + " | CounterType$ TIME | CounterNum$ 1"); final Trigger parsedUpkeepTrig = TriggerHandler.parseTrigger(upkeepTrig, card, true); card.addTrigger(parsedUpkeepTrig); // sacrifice trigger String sacTrig = "Mode$ CounterRemoved | TriggerZones$ Battlefield | ValidCard$" + - " Card.Self | NewCounterAmount$ 0 | Secondary$ True | CounterType$ TIME |" + - " Execute$ TrigVanishingSac | TriggerDescription$ When the last time " + - "counter is removed from CARDNAME, sacrifice it."; + " Card.Self | NewCounterAmount$ 0 | Secondary$ True | CounterType$ TIME |" + + " Execute$ TrigVanishingSac | TriggerDescription$ When the last time " + + "counter is removed from CARDNAME, sacrifice it."; card.setSVar("TrigVanishingSac", "AB$ Sacrifice | Cost$ 0 | SacValid$ Self"); final Trigger parsedSacTrigger = TriggerHandler.parseTrigger(sacTrig, card, true); card.addTrigger(parsedSacTrigger); } - } // Vanishing + else if (keyword.equals("Delve")) { + card.getSpellAbilities().get(0).setDelve(true); + } + else if (keyword.startsWith("Haunt")) { + setupHauntSpell(card); + } + else if (keyword.equals("Provoke")) { + final String actualTrigger = "Mode$ Attacks | ValidCard$ Card.Self | " + + "OptionalDecider$ You | Execute$ ProvokeAbility | Secondary$ True | TriggerDescription$ " + + "When this attacks, you may have target creature defending player " + + "controls untap and block it if able."; + final String abString = "DB$ MustBlock | ValidTgts$ Creature.DefenderCtrl | " + + "TgtPrompt$ Select target creature defending player controls | SubAbility$ ProvokeUntap"; + final String dbString = "DB$ Untap | Defined$ Targeted"; + final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, true); + card.addTrigger(parsedTrigger); + card.setSVar("ProvokeAbility", abString); + card.setSVar("ProvokeUntap", dbString); + } + else if (keyword.equals("Living Weapon")) { + card.removeIntrinsicKeyword(keyword); + + final StringBuilder sbTrig = new StringBuilder(); + sbTrig.append("Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | "); + sbTrig.append("ValidCard$ Card.Self | Execute$ TrigGerm | TriggerDescription$ "); + sbTrig.append("Living Weapon (When this Equipment enters the battlefield, "); + sbTrig.append("put a 0/0 black Germ creature token onto the battlefield, then attach this to it.)"); + + final StringBuilder sbGerm = new StringBuilder(); + sbGerm.append("DB$ Token | TokenAmount$ 1 | TokenName$ Germ | TokenTypes$ Creature,Germ | RememberTokens$ True | "); + sbGerm.append("TokenOwner$ You | TokenColors$ Black | TokenPower$ 0 | TokenToughness$ 0 | TokenImage$ B 0 0 Germ | SubAbility$ DBGermAttach"); + + final StringBuilder sbAttach = new StringBuilder(); + sbAttach.append("DB$ Attach | Defined$ Remembered | SubAbility$ DBGermClear"); + + final StringBuilder sbClear = new StringBuilder(); + sbClear.append("DB$ Cleanup | ClearRemembered$ True"); + + card.setSVar("TrigGerm", sbGerm.toString()); + card.setSVar("DBGermAttach", sbAttach.toString()); + card.setSVar("DBGermClear", sbClear.toString()); + + final Trigger etbTrigger = TriggerHandler.parseTrigger(sbTrig.toString(), card, true); + card.addTrigger(etbTrigger); + } + else if (keyword.equals("Epic")) { + makeEpic(card); + } + else if (keyword.equals("Soulbond")) { + // Setup ETB trigger for card with Soulbond keyword + final String actualTriggerSelf = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | " + + "ValidCard$ Card.Self | Execute$ TrigBondOther | OptionalDecider$ You | " + + "IsPresent$ Creature.Other+YouCtrl+NotPaired | Secondary$ True | " + + "TriggerDescription$ When CARDNAME enters the battlefield, " + + "you may pair CARDNAME with another unpaired creature you control"; + final String abStringSelf = "AB$ Bond | Cost$ 0 | Defined$ Self | ValidCards$ Creature.Other+YouCtrl+NotPaired"; + final Trigger parsedTriggerSelf = TriggerHandler.parseTrigger(actualTriggerSelf, card, true); + card.addTrigger(parsedTriggerSelf); + card.setSVar("TrigBondOther", abStringSelf); + // Setup ETB trigger for other creatures you control + final String actualTriggerOther = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | " + + "ValidCard$ Creature.Other+YouCtrl | TriggerZones$ Battlefield | OptionalDecider$ You | " + + "Execute$ TrigBondSelf | IsPresent$ Creature.Self+NotPaired | Secondary$ True | " + + " TriggerDescription$ When another unpaired creature you control enters the battlefield, " + + "you may pair it with CARDNAME"; + final String abStringOther = "AB$ Bond | Cost$ 0 | Defined$ TriggeredCard | ValidCards$ Creature.Self+NotPaired"; + final Trigger parsedTriggerOther = TriggerHandler.parseTrigger(actualTriggerOther, card, true); + card.addTrigger(parsedTriggerOther); + card.setSVar("TrigBondSelf", abStringOther); + } + else if (keyword.equals("Extort")) { + final String extortTrigger = "Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ You | " + + "TriggerZones$ Battlefield | Execute$ ExtortOpps | Secondary$ True" + + " | TriggerDescription$ Extort (Whenever you cast a spell, you may pay W/B. If you do, " + + "each opponent loses 1 life and you gain that much life.)"; + final String abString = "AB$ LoseLife | Cost$ WB | Defined$ Player.Opponent | " + + "LifeAmount$ 1 | SubAbility$ ExtortGainLife"; + final String dbString = "DB$ GainLife | Defined$ You | LifeAmount$ AFLifeLost | References$ AFLifeLost"; + final Trigger parsedTrigger = TriggerHandler.parseTrigger(extortTrigger, card, true); + card.addTrigger(parsedTrigger); + card.setSVar("ExtortOpps", abString); + card.setSVar("ExtortGainLife", dbString); + card.setSVar("AFLifeLost", "Number$0"); + } + else if (keyword.equals("Evolve")) { + final String evolveTrigger = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | " + + " ValidCard$ Creature.YouCtrl+Other | EvolveCondition$ True | " + + "TriggerZones$ Battlefield | Execute$ EvolveAddCounter | Secondary$ True | " + + "TriggerDescription$ Evolve (Whenever a creature enters the battlefield under your " + + "control, if that creature has greater power or toughness than this creature, put a " + + "+1/+1 counter on this creature.)"; + final String abString = "AB$ PutCounter | Cost$ 0 | Defined$ Self | CounterType$ P1P1 | " + + "CounterNum$ 1 | Evolve$ True"; + final Trigger parsedTrigger = TriggerHandler.parseTrigger(evolveTrigger, card, true); + card.addTrigger(parsedTrigger); + card.setSVar("EvolveAddCounter", abString); + } + else if (keyword.startsWith("Dredge")) { + final int dredgeAmount = card.getKeywordMagnitude("Dredge"); + + final String actualRep = "Event$ Draw | ActiveZones$ Graveyard | ValidPlayer$ You | " + + "ReplaceWith$ DredgeCards | Secondary$ True | Optional$ True | CheckSVar$ " + + "DredgeCheckLib | SVarCompare$ GE" + dredgeAmount + " | References$ " + + "DredgeCheckLib | AICheckDredge$ True | Description$ " + card.getName() + + " - Dredge " + dredgeAmount; + final String abString = "DB$ Mill | Defined$ You | NumCards$ " + dredgeAmount + " | " + + "SubAbility$ DredgeMoveToPlay"; + final String moveToPlay = "DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | " + + "Defined$ Self"; + final String checkSVar = "Count$ValidLibrary Card.YouOwn"; + card.setSVar("DredgeCards", abString); + card.setSVar("DredgeMoveToPlay", moveToPlay); + card.setSVar("DredgeCheckLib", checkSVar); + card.addReplacementEffect(ReplacementHandler.parseReplacement(actualRep, card, true)); + } + else if (keyword.startsWith("Tribute")) { + final int tributeAmount = card.getKeywordMagnitude("Tribute"); + + final String actualRep = "Event$ Moved | Destination$ Battlefield | ValidCard$ Card.Self |" + + " ReplaceWith$ TributeAddCounter | Secondary$ True | Description$ Tribute " + + tributeAmount + " (As this creature enters the battlefield, an opponent of your" + + " choice may place " + tributeAmount + " +1/+1 counter on it.)"; + final String abString = "DB$ PutCounter | Defined$ ReplacedCard | Tribute$ True | " + + "CounterType$ P1P1 | CounterNum$ " + tributeAmount + + " | SubAbility$ TributeMoveToPlay"; + final String moveToPlay = "DB$ ChangeZone | Origin$ All | Destination$ Battlefield | " + + "Defined$ ReplacedCard | Hidden$ True"; + card.setSVar("TributeAddCounter", abString); + card.setSVar("TributeMoveToPlay", moveToPlay); + card.addReplacementEffect(ReplacementHandler.parseReplacement(actualRep, card, true)); + } + else if (keyword.startsWith("Amplify")) { + // find position of Amplify keyword + final int ampPos = card.getKeywordPosition("Amplify"); + final String[] ampString = card.getKeyword().get(ampPos).split(":"); + final String amplifyMagnitude = ampString[1]; + final String suffix = !amplifyMagnitude.equals("1") ? "s" : ""; + final String ampTypes = ampString[2]; + String[] refinedTypes = ampTypes.split(","); + final StringBuilder types = new StringBuilder(); + for (int i = 0; i < refinedTypes.length; i++) { + types.append("Card.").append(refinedTypes[i]).append("+YouCtrl"); + if (i + 1 != refinedTypes.length) { + types.append(","); + } + } + // Setup ETB replacement effects + final String actualRep = "Event$ Moved | Destination$ Battlefield | ValidCard$ Card.Self |" + + " ReplaceWith$ AmplifyReveal | Secondary$ True | Description$ As this creature " + + "enters the battlefield, put " + amplifyMagnitude + " +1/+1 counter" + suffix + + " on it for each " + ampTypes.replace(",", " and/or ") + + " card you reveal in your hand.)"; + final String abString = "DB$ Reveal | AnyNumber$ True | RevealValid$ " + + types.toString() + " | RememberRevealed$ True | SubAbility$ Amplify"; + final String dbString = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ P1P1 | " + + "CounterNum$ AmpMagnitude | References$ Revealed,AmpMagnitude | SubAbility$" + + " AmplifyMoveToPlay"; + final String moveToPlay = "DB$ ChangeZone | Origin$ All | Destination$ Battlefield | " + + "Defined$ ReplacedCard | Hidden$ True | SubAbility$ DBCleanup"; + card.addReplacementEffect(ReplacementHandler.parseReplacement(actualRep, card, true)); + card.setSVar("AmplifyReveal", abString); + card.setSVar("AmplifyMoveToPlay", moveToPlay); + card.setSVar("Amplify", dbString); + card.setSVar("DBCleanup", "DB$ Cleanup | ClearRemembered$ True"); + card.setSVar("AmpMagnitude", "SVar$Revealed/Times." + amplifyMagnitude); + card.setSVar("Revealed", "Remembered$Amount"); + } + else if (keyword.startsWith("Equip")) { + // Check for additional params such as preferred AI targets + final String equipString = keyword.substring(5); + final String[] equipExtras = equipString.contains("|") ? equipString.split("\\|", 2) : null; + // Get cost string + String equipCost = ""; + if (equipExtras != null) { + equipCost = equipExtras[0].trim(); + } else { + equipCost = equipString.trim(); + } + // Create attach ability string + final StringBuilder abilityStr = new StringBuilder(); + abilityStr.append("AB$ Attach | Cost$ "); + abilityStr.append(equipCost); + abilityStr.append(" | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control "); + abilityStr.append("| SorcerySpeed$ True | Equip$ True | AILogic$ Pump | IsPresent$ Card.Self+nonCreature "); + if (equipExtras != null) { + abilityStr.append("| ").append(equipExtras[1]).append(" "); + } + abilityStr.append("| PrecostDesc$ Equip "); + Cost cost = new Cost(equipCost, true); + if (!cost.isOnlyManaCost()) { //Something other than a mana cost + abilityStr.append("- "); + } + abilityStr.append("| CostDesc$ " + cost.toSimpleString() + " "); + abilityStr.append("| SpellDescription$ (" + cost.toSimpleString() + ": Attach to target creature you control. Equip only as a sorcery.)"); + // instantiate attach ability + final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card); + card.addSpellAbility(sa); + // add ability to instrinic strings so copies/clones create the ability also + card.getUnparsedAbilities().add(abilityStr.toString()); + } + else if (keyword.startsWith("Outlast")) { + final String outlastString = keyword.substring(7); + final String[] outlastExtras = outlastString.contains("|") ? outlastString.split("\\|", 2) : null; + // Get cost string + String outlastCost = ""; + if (outlastExtras != null) { + outlastCost = outlastExtras[0].trim(); + } else { + outlastCost = outlastString.trim(); + } + // Create outlast ability string + final StringBuilder abilityStr = new StringBuilder(); + abilityStr.append("AB$ PutCounter | Cost$ "); + abilityStr.append(outlastCost); + abilityStr.append(" T | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 "); + abilityStr.append("| SorcerySpeed$ True | Outlast$ True "); + if (outlastExtras != null) { + abilityStr.append("| ").append(outlastExtras[1]).append(" "); + } + abilityStr.append("| PrecostDesc$ Outlast "); + Cost cost = new Cost(outlastCost, true); + if (!cost.isOnlyManaCost()) { //Something other than a mana cost + abilityStr.append("- "); + } + abilityStr.append("| CostDesc$ " + cost.toSimpleString() + " "); + abilityStr.append("| SpellDescription$ (" + cost.toSimpleString() + ", {T}: Put a +1/+1 counter on this creature. Outlast only as a sorcery.)"); + final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card); + card.addSpellAbility(sa); + // add ability to instrinic strings so copies/clones create the ability also + card.getUnparsedAbilities().add(abilityStr.toString()); + } + else if (keyword.startsWith("Fortify")) { + final String equipString = keyword.substring(7); + final String[] equipExtras = equipString.contains("|") ? equipString.split("\\|", 2) : null; + // Get cost string + String equipCost = ""; + if (equipExtras != null) { + equipCost = equipExtras[0].trim(); + } else { + equipCost = equipString.trim(); + } + // Create attach ability string + final StringBuilder abilityStr = new StringBuilder(); + abilityStr.append("AB$ Attach | Cost$ "); + abilityStr.append(equipCost); + abilityStr.append(" | ValidTgts$ Land.YouCtrl | TgtPrompt$ Select target land you control "); + abilityStr.append("| SorcerySpeed$ True | AILogic$ Pump | IsPresent$ Card.Self+nonCreature "); + if (equipExtras != null) { + abilityStr.append("| ").append(equipExtras[1]).append(" "); + } + abilityStr.append("| PrecostDesc$ Fortify "); + Cost cost = new Cost(equipCost, true); + if (!cost.isOnlyManaCost()) { //Something other than a mana cost + abilityStr.append("- "); + } + abilityStr.append("| CostDesc$ " + cost.toSimpleString() + " "); + abilityStr.append("| SpellDescription$ (" + cost.toSimpleString() + ": Attach to target land you control. Fortify only as a sorcery.)"); + + // instantiate attach ability + final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card); + card.addSpellAbility(sa); + // add ability to intrinsic strings so copies/clones create the ability also + card.getUnparsedAbilities().add(abilityStr.toString()); + } + else if (keyword.startsWith("Bestow")) { + final String[] params = keyword.split(":"); + final String cost = params[1]; + card.removeIntrinsicKeyword(keyword); + + final StringBuilder sbAttach = new StringBuilder(); + sbAttach.append("SP$ Attach | Cost$ "); + sbAttach.append(cost); + sbAttach.append(" | AILogic$ ").append(params.length > 2 ? params[2] : "Pump"); + sbAttach.append(" | Bestow$ True | ValidTgts$ Creature"); + final SpellAbility bestow = AbilityFactory.getAbility(sbAttach.toString(), card); + + bestow.setDescription("Bestow " + ManaCostParser.parse(cost) + " (If you cast this" + + " card for its bestow cost, it's an Aura spell with enchant creature. It" + + " becomes a creature again if it's not attached to a creature.)"); + bestow.setStackDescription("Bestow - " + card.getName()); + bestow.setBasicSpell(false); + card.addSpellAbility(bestow); + card.getUnparsedAbilities().add(sbAttach.toString()); + } + } // AddCost if (card.hasSVar("FullCost")) { @@ -2362,310 +2683,6 @@ public class CardFactoryUtil { card.addSpellAbility(makeAltCostAbility(card, altCost, sa1)); } } - if (card.hasKeyword("Delve")) { - card.getSpellAbilities().get(0).setDelve(true); - } - - if (card.hasStartOfKeyword("Haunt")) { - setupHauntSpell(card); - } - - if (card.hasKeyword("Provoke")) { - final String actualTrigger = "Mode$ Attacks | ValidCard$ Card.Self | " - + "OptionalDecider$ You | Execute$ ProvokeAbility | Secondary$ True | TriggerDescription$ " - + "When this attacks, you may have target creature defending player " - + "controls untap and block it if able."; - final String abString = "DB$ MustBlock | ValidTgts$ Creature.DefenderCtrl | " - + "TgtPrompt$ Select target creature defending player controls | SubAbility$ ProvokeUntap"; - final String dbString = "DB$ Untap | Defined$ Targeted"; - final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, card, true); - card.addTrigger(parsedTrigger); - card.setSVar("ProvokeAbility", abString); - card.setSVar("ProvokeUntap", dbString); - } - - if (card.hasKeyword("Living Weapon")) { - card.removeIntrinsicKeyword("Living Weapon"); - - final StringBuilder sbTrig = new StringBuilder(); - sbTrig.append("Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | "); - sbTrig.append("ValidCard$ Card.Self | Execute$ TrigGerm | TriggerDescription$ "); - sbTrig.append("Living Weapon (When this Equipment enters the battlefield, "); - sbTrig.append("put a 0/0 black Germ creature token onto the battlefield, then attach this to it.)"); - - final StringBuilder sbGerm = new StringBuilder(); - sbGerm.append("DB$ Token | TokenAmount$ 1 | TokenName$ Germ | TokenTypes$ Creature,Germ | RememberTokens$ True | "); - sbGerm.append("TokenOwner$ You | TokenColors$ Black | TokenPower$ 0 | TokenToughness$ 0 | TokenImage$ B 0 0 Germ | SubAbility$ DBGermAttach"); - - final StringBuilder sbAttach = new StringBuilder(); - sbAttach.append("DB$ Attach | Defined$ Remembered | SubAbility$ DBGermClear"); - - final StringBuilder sbClear = new StringBuilder(); - sbClear.append("DB$ Cleanup | ClearRemembered$ True"); - - card.setSVar("TrigGerm", sbGerm.toString()); - card.setSVar("DBGermAttach", sbAttach.toString()); - card.setSVar("DBGermClear", sbClear.toString()); - - final Trigger etbTrigger = TriggerHandler.parseTrigger(sbTrig.toString(), card, true); - card.addTrigger(etbTrigger); - } - - if (card.hasKeyword("Epic")) { - makeEpic(card); - } - - if (card.hasKeyword("Soulbond")) { - // Setup ETB trigger for card with Soulbond keyword - final String actualTriggerSelf = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | " - + "ValidCard$ Card.Self | Execute$ TrigBondOther | OptionalDecider$ You | " - + "IsPresent$ Creature.Other+YouCtrl+NotPaired | Secondary$ True | " - + "TriggerDescription$ When CARDNAME enters the battlefield, " - + "you may pair CARDNAME with another unpaired creature you control"; - final String abStringSelf = "AB$ Bond | Cost$ 0 | Defined$ Self | ValidCards$ Creature.Other+YouCtrl+NotPaired"; - final Trigger parsedTriggerSelf = TriggerHandler.parseTrigger(actualTriggerSelf, card, true); - card.addTrigger(parsedTriggerSelf); - card.setSVar("TrigBondOther", abStringSelf); - // Setup ETB trigger for other creatures you control - final String actualTriggerOther = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | " - + "ValidCard$ Creature.Other+YouCtrl | TriggerZones$ Battlefield | OptionalDecider$ You | " - + "Execute$ TrigBondSelf | IsPresent$ Creature.Self+NotPaired | Secondary$ True | " - + " TriggerDescription$ When another unpaired creature you control enters the battlefield, " - + "you may pair it with CARDNAME"; - final String abStringOther = "AB$ Bond | Cost$ 0 | Defined$ TriggeredCard | ValidCards$ Creature.Self+NotPaired"; - final Trigger parsedTriggerOther = TriggerHandler.parseTrigger(actualTriggerOther, card, true); - card.addTrigger(parsedTriggerOther); - card.setSVar("TrigBondSelf", abStringOther); - } - - if (card.hasKeyword("Extort")) { - final String extortTrigger = "Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ You | " - + "TriggerZones$ Battlefield | Execute$ ExtortOpps | Secondary$ True" - + " | TriggerDescription$ Extort (Whenever you cast a spell, you may pay W/B. If you do, " - + "each opponent loses 1 life and you gain that much life.)"; - final String abString = "AB$ LoseLife | Cost$ WB | Defined$ Player.Opponent | " - + "LifeAmount$ 1 | SubAbility$ ExtortGainLife"; - final String dbString = "DB$ GainLife | Defined$ You | LifeAmount$ AFLifeLost | References$ AFLifeLost"; - final Trigger parsedTrigger = TriggerHandler.parseTrigger(extortTrigger, card, true); - card.addTrigger(parsedTrigger); - card.setSVar("ExtortOpps", abString); - card.setSVar("ExtortGainLife", dbString); - card.setSVar("AFLifeLost", "Number$0"); - } - - if (card.hasKeyword("Evolve")) { - final String evolveTrigger = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | " - + " ValidCard$ Creature.YouCtrl+Other | EvolveCondition$ True | " - + "TriggerZones$ Battlefield | Execute$ EvolveAddCounter | Secondary$ True | " - + "TriggerDescription$ Evolve (Whenever a creature enters the battlefield under your " - + "control, if that creature has greater power or toughness than this creature, put a " - + "+1/+1 counter on this creature.)"; - final String abString = "AB$ PutCounter | Cost$ 0 | Defined$ Self | CounterType$ P1P1 | " - + "CounterNum$ 1 | Evolve$ True"; - final Trigger parsedTrigger = TriggerHandler.parseTrigger(evolveTrigger, card, true); - card.addTrigger(parsedTrigger); - card.setSVar("EvolveAddCounter", abString); - } - - if (card.hasStartOfKeyword("Dredge")) { - final int dredgeAmount = card.getKeywordMagnitude("Dredge"); - - final String actualRep = "Event$ Draw | ActiveZones$ Graveyard | ValidPlayer$ You | " - + "ReplaceWith$ DredgeCards | Secondary$ True | Optional$ True | CheckSVar$ " - + "DredgeCheckLib | SVarCompare$ GE" + dredgeAmount + " | References$ " - + "DredgeCheckLib | AICheckDredge$ True | Description$ " + card.getName() - + " - Dredge " + dredgeAmount; - final String abString = "DB$ Mill | Defined$ You | NumCards$ " + dredgeAmount + " | " - + "SubAbility$ DredgeMoveToPlay"; - final String moveToPlay = "DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | " - + "Defined$ Self"; - final String checkSVar = "Count$ValidLibrary Card.YouOwn"; - card.setSVar("DredgeCards", abString); - card.setSVar("DredgeMoveToPlay", moveToPlay); - card.setSVar("DredgeCheckLib", checkSVar); - card.addReplacementEffect(ReplacementHandler.parseReplacement(actualRep, card, true)); - } - - if (card.hasStartOfKeyword("Tribute")) { - final int tributeAmount = card.getKeywordMagnitude("Tribute"); - - final String actualRep = "Event$ Moved | Destination$ Battlefield | ValidCard$ Card.Self |" - + " ReplaceWith$ TributeAddCounter | Secondary$ True | Description$ Tribute " - + tributeAmount + " (As this creature enters the battlefield, an opponent of your" - + " choice may place " + tributeAmount + " +1/+1 counter on it.)"; - final String abString = "DB$ PutCounter | Defined$ ReplacedCard | Tribute$ True | " - + "CounterType$ P1P1 | CounterNum$ " + tributeAmount - + " | SubAbility$ TributeMoveToPlay"; - final String moveToPlay = "DB$ ChangeZone | Origin$ All | Destination$ Battlefield | " - + "Defined$ ReplacedCard | Hidden$ True"; - card.setSVar("TributeAddCounter", abString); - card.setSVar("TributeMoveToPlay", moveToPlay); - card.addReplacementEffect(ReplacementHandler.parseReplacement(actualRep, card, true)); - } - - if (card.hasStartOfKeyword("Amplify")) { - // find position of Amplify keyword - final int ampPos = card.getKeywordPosition("Amplify"); - final String[] ampString = card.getKeyword().get(ampPos).split(":"); - final String amplifyMagnitude = ampString[1]; - final String suffix = !amplifyMagnitude.equals("1") ? "s" : ""; - final String ampTypes = ampString[2]; - String[] refinedTypes = ampTypes.split(","); - final StringBuilder types = new StringBuilder(); - for (int i = 0; i < refinedTypes.length; i++) { - types.append("Card.").append(refinedTypes[i]).append("+YouCtrl"); - if (i + 1 != refinedTypes.length) { - types.append(","); - } - } - // Setup ETB replacement effects - final String actualRep = "Event$ Moved | Destination$ Battlefield | ValidCard$ Card.Self |" - + " ReplaceWith$ AmplifyReveal | Secondary$ True | Description$ As this creature " - + "enters the battlefield, put " + amplifyMagnitude + " +1/+1 counter" + suffix - + " on it for each " + ampTypes.replace(",", " and/or ") - + " card you reveal in your hand.)"; - final String abString = "DB$ Reveal | AnyNumber$ True | RevealValid$ " - + types.toString() + " | RememberRevealed$ True | SubAbility$ Amplify"; - final String dbString = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ P1P1 | " - + "CounterNum$ AmpMagnitude | References$ Revealed,AmpMagnitude | SubAbility$" - + " AmplifyMoveToPlay"; - final String moveToPlay = "DB$ ChangeZone | Origin$ All | Destination$ Battlefield | " - + "Defined$ ReplacedCard | Hidden$ True | SubAbility$ DBCleanup"; - card.addReplacementEffect(ReplacementHandler.parseReplacement(actualRep, card, true)); - card.setSVar("AmplifyReveal", abString); - card.setSVar("AmplifyMoveToPlay", moveToPlay); - card.setSVar("Amplify", dbString); - card.setSVar("DBCleanup", "DB$ Cleanup | ClearRemembered$ True"); - card.setSVar("AmpMagnitude", "SVar$Revealed/Times." + amplifyMagnitude); - card.setSVar("Revealed", "Remembered$Amount"); - } - - if (card.hasStartOfKeyword("Equip")) { - // find position of Equip keyword - final int equipPos = card.getKeywordPosition("Equip"); - // Check for additional params such as preferred AI targets - final String equipString = card.getKeyword().get(equipPos).substring(5); - final String[] equipExtras = equipString.contains("|") ? equipString.split("\\|", 2) : null; - // Get cost string - String equipCost = ""; - if (equipExtras != null) { - equipCost = equipExtras[0].trim(); - } else { - equipCost = equipString.trim(); - } - // Create attach ability string - final StringBuilder abilityStr = new StringBuilder(); - abilityStr.append("AB$ Attach | Cost$ "); - abilityStr.append(equipCost); - abilityStr.append(" | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control "); - abilityStr.append("| SorcerySpeed$ True | Equip$ True | AILogic$ Pump | IsPresent$ Card.Self+nonCreature "); - if (equipExtras != null) { - abilityStr.append("| ").append(equipExtras[1]).append(" "); - } - abilityStr.append("| PrecostDesc$ Equip "); - Cost cost = new Cost(equipCost, true); - if (!cost.isOnlyManaCost()) { //Something other than a mana cost - abilityStr.append("- "); - } - abilityStr.append("| CostDesc$ " + cost.toSimpleString() + " "); - abilityStr.append("| SpellDescription$ (" + cost.toSimpleString() + ": Attach to target creature you control. Equip only as a sorcery.)"); - // instantiate attach ability - final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card); - card.addSpellAbility(sa); - // add ability to instrinic strings so copies/clones create the ability also - card.getUnparsedAbilities().add(abilityStr.toString()); - } - - if (card.hasStartOfKeyword("Outlast")) { - final int outlastPos = card.getKeywordPosition("Outlast"); - final String outlastString = card.getKeyword().get(outlastPos).substring(7); - final String[] outlastExtras = outlastString.contains("|") ? outlastString.split("\\|", 2) : null; - // Get cost string - String outlastCost = ""; - if (outlastExtras != null) { - outlastCost = outlastExtras[0].trim(); - } else { - outlastCost = outlastString.trim(); - } - // Create outlast ability string - final StringBuilder abilityStr = new StringBuilder(); - abilityStr.append("AB$ PutCounter | Cost$ "); - abilityStr.append(outlastCost); - abilityStr.append(" T | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 "); - abilityStr.append("| SorcerySpeed$ True | Outlast$ True "); - if (outlastExtras != null) { - abilityStr.append("| ").append(outlastExtras[1]).append(" "); - } - abilityStr.append("| PrecostDesc$ Outlast "); - Cost cost = new Cost(outlastCost, true); - if (!cost.isOnlyManaCost()) { //Something other than a mana cost - abilityStr.append("- "); - } - abilityStr.append("| CostDesc$ " + cost.toSimpleString() + " "); - abilityStr.append("| SpellDescription$ (" + cost.toSimpleString() + ", {T}: Put a +1/+1 counter on this creature. Outlast only as a sorcery.)"); - final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card); - card.addSpellAbility(sa); - // add ability to instrinic strings so copies/clones create the ability also - card.getUnparsedAbilities().add(abilityStr.toString()); - } - - if (card.hasStartOfKeyword("Fortify")) { - final int equipPos = card.getKeywordPosition("Fortify"); - final String equipString = card.getKeyword().get(equipPos).substring(7); - final String[] equipExtras = equipString.contains("|") ? equipString.split("\\|", 2) : null; - // Get cost string - String equipCost = ""; - if (equipExtras != null) { - equipCost = equipExtras[0].trim(); - } else { - equipCost = equipString.trim(); - } - // Create attach ability string - final StringBuilder abilityStr = new StringBuilder(); - abilityStr.append("AB$ Attach | Cost$ "); - abilityStr.append(equipCost); - abilityStr.append(" | ValidTgts$ Land.YouCtrl | TgtPrompt$ Select target land you control "); - abilityStr.append("| SorcerySpeed$ True | AILogic$ Pump | IsPresent$ Card.Self+nonCreature "); - if (equipExtras != null) { - abilityStr.append("| ").append(equipExtras[1]).append(" "); - } - abilityStr.append("| PrecostDesc$ Fortify "); - Cost cost = new Cost(equipCost, true); - if (!cost.isOnlyManaCost()) { //Something other than a mana cost - abilityStr.append("- "); - } - abilityStr.append("| CostDesc$ " + cost.toSimpleString() + " "); - abilityStr.append("| SpellDescription$ (" + cost.toSimpleString() + ": Attach to target land you control. Fortify only as a sorcery.)"); - - // instantiate attach ability - final SpellAbility sa = AbilityFactory.getAbility(abilityStr.toString(), card); - card.addSpellAbility(sa); - // add ability to instrinic strings so copies/clones create the ability also - card.getUnparsedAbilities().add(abilityStr.toString()); - } - - if (card.hasStartOfKeyword("Bestow")) { - final int bestowPos = card.getKeywordPosition("Bestow"); - final String[] params = card.getKeyword().get(bestowPos).split(":"); - final String cost = params[1]; - card.removeIntrinsicKeyword(card.getKeyword().get(bestowPos)); - - final StringBuilder sbAttach = new StringBuilder(); - sbAttach.append("SP$ Attach | Cost$ "); - sbAttach.append(cost); - sbAttach.append(" | AILogic$ ").append(params.length > 2 ? params[2] : "Pump"); - sbAttach.append(" | Bestow$ True | ValidTgts$ Creature"); - final SpellAbility bestow = AbilityFactory.getAbility(sbAttach.toString(), card); - - bestow.setDescription("Bestow " + ManaCostParser.parse(cost) + " (If you cast this" - + " card for its bestow cost, it's an Aura spell with enchant creature. It" - + " becomes a creature again if it's not attached to a creature.)"); - bestow.setStackDescription("Bestow - " + card.getName()); - bestow.setBasicSpell(false); - card.addSpellAbility(bestow); - card.getUnparsedAbilities().add(sbAttach.toString()); - - } setupEtbKeywords(card); } @@ -3363,15 +3380,15 @@ public class CardFactoryUtil { boolean hasKw = c.hasKeyword("Totem armor"); List res = c.getReplacementEffects(); - for ( int ix = 0; ix < res.size(); ix++ ) { + for (int ix = 0; ix < res.size(); ix++) { ReplacementEffect re = res.get(ix); - if( re.getMapParams().containsKey("TotemArmor") ) { - if(hasKw) return; // has re and kw - nothing to do here + if (re.getMapParams().containsKey("TotemArmor")) { + if (hasKw) return; // has re and kw - nothing to do here res.remove(ix--); } } - if( hasKw ) { + if (hasKw) { ReplacementEffect re = ReplacementHandler.parseReplacement("Event$ Destroy | ActiveZones$ Battlefield | ValidCard$ Card.EnchantedBy | ReplaceWith$ RegenTA | Secondary$ True | TotemArmor$ True | Description$ Totem armor - " + c, c, true); c.getSVars().put("RegenTA", "AB$ DealDamage | Cost$ 0 | Defined$ ReplacedCard | Remove$ All | SubAbility$ DestroyMe"); c.getSVars().put("DestroyMe", "DB$ Destroy | Defined$ Self"); diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index 97928c4a6aa..a91670ae200 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -51,7 +51,7 @@ public final class CardUtil { "Enchant", "Protection", "Cumulative upkeep", "Equip", "Buyback", "Cycling", "Echo", "Kicker", "Flashback", "Madness", "Morph", "Affinity", "Entwine", "Splice", "Ninjutsu", - "Transute", "Replicate", "Recover", "Suspend", "Aura swap", + "Transmute", "Replicate", "Recover", "Suspend", "Aura swap", "Fortify", "Transfigure", "Champion", "Evoke", "Prowl", "Reinforce", "Unearth", "Level up", "Miracle", "Overload", "Scavenge", "Bestow", "Outlast").build(); diff --git a/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java b/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java index ff804c46b19..8abc988c308 100644 --- a/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java +++ b/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java @@ -31,6 +31,8 @@ import forge.util.maps.MapToAmount; import java.util.*; import java.util.Map.Entry; +import org.apache.commons.lang3.StringUtils; + /** *

* ManaCostBeingPaid class. @@ -215,6 +217,20 @@ public class ManaCostBeingPaid { return unpaidShards.isEmpty(); } + public final void setXManaCostPaid(final int xPaid, final String xColor) { + int xCost = xPaid * cntX; + cntX = 0; + + ManaCostShard increaseShard; + if (StringUtils.isEmpty(xColor)) { + increaseShard = ManaCostShard.COLORLESS; + } + else { + increaseShard = ManaCostShard.valueOf(MagicColor.fromName(xColor)); + } + unpaidShards.add(increaseShard, xCost); + } + public final void increaseColorlessMana(final int manaToAdd) { increaseShard(ManaCostShard.COLORLESS, manaToAdd); } 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 a0d5975d079..25afce5d650 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -131,8 +131,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public final AbilityManaPart getManaPartRecursive() { SpellAbility tail = this; while (tail != null) { - if(tail.manaPart != null) + if (tail.manaPart != null) { return tail.manaPart; + } tail = tail.getSubAbility(); } return null; @@ -140,14 +141,17 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public final boolean isManaAbility() { // Check whether spell or ability first - if (this.isSpell()) + if (this.isSpell()) { return false; + } // without a target - if (this.usesTargeting()) return false; - if (getRestrictions() != null && getRestrictions().getPlaneswalker()) + if (this.usesTargeting()) { return false; } + if (getRestrictions() != null && getRestrictions().getPlaneswalker()) { return false; //Loyalty ability, not a mana ability. - if (this.isWrapper() && ((WrappedAbility) this).getTrigger().getMode() != TriggerType.TapsForMana) + } + if (this.isWrapper() && ((WrappedAbility) this).getTrigger().getMode() != TriggerType.TapsForMana) { return false; + } return getManaPartRecursive() != null; } @@ -157,7 +161,22 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public final String getSVar(final String name) { - return sVars.get(name) != null ? sVars.get(name) : ""; + String var = sVars.get(name); + if (var == null) { + var = ""; + } + return var; + } + + public final Integer getSVarInt(final String name) { + String var = sVars.get(name); + if (var != null) { + try { + return Integer.parseInt(var); + } + catch (Exception e) {} + } + return null; } public final void setSVar(final String name, final String value) { @@ -298,18 +317,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public boolean isSpell() { return false; } public boolean isAbility() { return true; } - - /** - *

- * isMultiKicker. - *

- * - * @return a boolean. - */ - public boolean isMultiKicker() { - return this.multiKickerManaCost != null && !this.isAnnouncing("Multikicker"); - } - /** *

* setIsMorphUp. @@ -380,14 +387,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public String getParamOrDefault(String key, String defaultValue) { - return mapParams == null || !mapParams.containsKey(key) ? defaultValue : mapParams.get(key); + return mapParams.containsKey(key) ? mapParams.get(key) : defaultValue; } public String getParam(String key) { - return mapParams == null ? null : mapParams.get(key); + return mapParams.get(key); } public boolean hasParam(String key) { - return mapParams == null ? false : mapParams.containsKey(key); + return mapParams.containsKey(key); } /** @@ -395,9 +402,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit * @param mapParams */ public void copyParamsToMap(Map mapParams) { - if (null != this.mapParams) { - mapParams.putAll(this.mapParams); - } + mapParams.putAll(this.mapParams); } // If this is not null, then ability was made in a factory @@ -1127,8 +1132,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } String[] validTgt = tr.getValidTgts(); - if (entity instanceof GameEntity && !((GameEntity) entity).isValid(validTgt, this.getActivatingPlayer(), this.getHostCard())) + if (entity instanceof GameEntity && !((GameEntity) entity).isValid(validTgt, this.getActivatingPlayer(), this.getHostCard())) { return false; + } } // Restrictions coming from target @@ -1298,7 +1304,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit while (null != parent.getParent()) { parent = parent.getParent(); } - return parent; } @@ -1351,15 +1356,32 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit */ public boolean isAnnouncing(String variable) { String announce = getParam("Announce"); - if (StringUtils.isBlank(announce)) return false; + if (StringUtils.isBlank(announce)) { return false; } + String[] announcedOnes = TextUtil.split(announce, ','); - for(String a : announcedOnes) { - if( a.trim().equalsIgnoreCase(variable)) + for (String a : announcedOnes) { + if (a.trim().equalsIgnoreCase(variable)) { return true; + } } return false; } + public void addAnnounceVar(String variable) { + String announce = getParam("Announce"); + if (StringUtils.isBlank(announce)) { + mapParams.put("Announce", variable); + return; + } + String[] announcedOnes = TextUtil.split(announce, ','); + for (String a : announcedOnes) { + if (a.trim().equalsIgnoreCase(variable)) { + return; //don't add announce variable that already exists + } + } + mapParams.put("Announce", announce + ";" + variable); + } + public boolean isXCost() { CostPartMana cm = payCosts != null ? getPayCosts().getCostMana() : null; return cm != null && cm.getAmountOfX() > 0; @@ -1486,10 +1508,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit targetChosen.add(card); final String desc; - if (!card.isFaceDown()) { desc = this.getHostCard().getName() + " - targeting " + card; - } else { + } + else { desc = this.getHostCard().getName() + " - targeting Morph(" + card.getUniqueNumber() + ")"; } this.setStackDescription(desc); @@ -1532,7 +1554,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return ImmutableList.of(); } - public SpellAbility getSATargetingCard() { return targetChosen.isTargetingAnyCard() ? this : getParentTargetingCard(); } @@ -1543,8 +1564,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit parent = ((WrappedAbility) parent).getWrappedAbility(); } while (parent != null) { - if (parent.targetChosen.isTargetingAnyCard()) + if (parent.targetChosen.isTargetingAnyCard()) { return parent; + } parent = parent.getParent(); } return null; @@ -1674,15 +1696,21 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit final String[] incR = restriction.split("\\.", 2); if (incR[0].equals("Spell")) { - if (!this.isSpell()) + if (!this.isSpell()) { return false; - } else if (incR[0].equals("Triggered")) { - if (!this.isTrigger()) + } + } + else if (incR[0].equals("Triggered")) { + if (!this.isTrigger()) { return false; - } else if (incR[0].equals("Activated")) { - if (!(this instanceof AbilityActivated)) + } + } + else if (incR[0].equals("Activated")) { + if (!(this instanceof AbilityActivated)) { return false; - } else { //not a spell/ability type + } + } + else { //not a spell/ability type return false; } @@ -1811,5 +1839,4 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit this.subAbility.setIntrinsic(i); } } - } \ No newline at end of file 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 55763615c64..f9b36e39cbd 100644 --- a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java +++ b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java @@ -243,11 +243,6 @@ public class WrappedAbility extends Ability implements ISpellAbility { return sa.isFlashBackAbility(); } - @Override - public boolean isMultiKicker() { - return sa.isMultiKicker(); - } - @Override public boolean isSpell() { return sa.isSpell(); 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 211c9a0812a..4b3f61666cd 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -332,27 +332,15 @@ public class MagicStack /* extends MyObservable */ implements Iterable { pnlShop.add(new FLabel.Builder().text("Maximum Packs").fontAlign(SwingConstants.RIGHT).build(), constraints2); pnlShop.add(new PrefInput(QPref.SHOP_MAX_PACKS, QuestPreferencesErrType.SHOP), constraints1); + pnlShop.add(new FLabel.Builder().text("Minimum Packs").fontAlign(SwingConstants.RIGHT).build(), constraints2); + pnlShop.add(new PrefInput(QPref.SHOP_MIN_PACKS, QuestPreferencesErrType.SHOP), constraints1); + pnlShop.add(new FLabel.Builder().text("Starting Packs").fontAlign(SwingConstants.RIGHT).build(), constraints2); pnlShop.add(new PrefInput(QPref.SHOP_STARTING_PACKS, QuestPreferencesErrType.SHOP), constraints1); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/CSubmenuConstructed.java b/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/CSubmenuConstructed.java index af0b39685f7..d31f3f10e02 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/CSubmenuConstructed.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/CSubmenuConstructed.java @@ -54,104 +54,103 @@ public enum CSubmenuConstructed implements ICDoc, IMenuProvider { */ @Override public void update() { - MenuUtil.setMenuProvider(this); SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { - final CardCollections cColl = FModel.getDecks(); - FList deckList; - Vector listData; - Object val; + @Override public void run() { + final CardCollections cColl = FModel.getDecks(); + FList deckList; + Vector listData; + Object val; - for (int i = 0; i < 8; i++) { - // Commander: reinit deck list and restore last selections (if any) - deckList = view.getCommanderDeckLists().get(i); - listData = new Vector(); - listData.add("Generate"); - if (cColl.getCommander().size() > 0) { - listData.add("Random"); - for (Deck comDeck : cColl.getCommander()) { - listData.add(comDeck); - } - } - val = deckList.getSelectedValue(); - deckList.setListData(listData); - if (null != val) { - deckList.setSelectedValue(val, true); - } - if (-1 == deckList.getSelectedIndex()) { - deckList.setSelectedIndex(0); - } // End Commander + for (int i = 0; i < 8; i++) { + // Commander: reinit deck list and restore last selections (if any) + deckList = view.getCommanderDeckLists().get(i); + listData = new Vector(); + listData.add("Generate"); + if (cColl.getCommander().size() > 0) { + listData.add("Random"); + for (Deck comDeck : cColl.getCommander()) { + listData.add(comDeck); + } + } + val = deckList.getSelectedValue(); + deckList.setListData(listData); + if (null != val) { + deckList.setSelectedValue(val, true); + } + if (-1 == deckList.getSelectedIndex()) { + deckList.setSelectedIndex(0); + } // End Commander - // Archenemy: reinit deck list and restore last selections (if any) - deckList = view.getSchemeDeckLists().get(i); - listData = new Vector(); - listData.add("Use deck's scheme section (random if unavailable)"); - listData.add("Generate"); - if (cColl.getScheme().size() > 0) { - listData.add("Random"); - for (Deck schemeDeck : cColl.getScheme()) { - listData.add(schemeDeck); - } - } - val = deckList.getSelectedValue(); - deckList.setListData(listData); - if (null != val) { - deckList.setSelectedValue(val, true); - } - if (-1 == deckList.getSelectedIndex()) { - deckList.setSelectedIndex(0); - } // End Archenemy + // Archenemy: reinit deck list and restore last selections (if any) + deckList = view.getSchemeDeckLists().get(i); + listData = new Vector(); + listData.add("Use deck's scheme section (random if unavailable)"); + listData.add("Generate"); + if (cColl.getScheme().size() > 0) { + listData.add("Random"); + for (Deck schemeDeck : cColl.getScheme()) { + listData.add(schemeDeck); + } + } + val = deckList.getSelectedValue(); + deckList.setListData(listData); + if (null != val) { + deckList.setSelectedValue(val, true); + } + if (-1 == deckList.getSelectedIndex()) { + deckList.setSelectedIndex(0); + } // End Archenemy - // Planechase: reinit deck lists and restore last selections (if any) - deckList = view.getPlanarDeckLists().get(i); - listData = new Vector(); + // Planechase: reinit deck lists and restore last selections (if any) + deckList = view.getPlanarDeckLists().get(i); + listData = new Vector(); - listData.add("Use deck's planes section (random if unavailable)"); - listData.add("Generate"); - if (cColl.getPlane().size() > 0) { - listData.add("Random"); - for (Deck planarDeck : cColl.getPlane()) { - listData.add(planarDeck); - } - } + listData.add("Use deck's planes section (random if unavailable)"); + listData.add("Generate"); + if (cColl.getPlane().size() > 0) { + listData.add("Random"); + for (Deck planarDeck : cColl.getPlane()) { + listData.add(planarDeck); + } + } - val = deckList.getSelectedValue(); - deckList.setListData(listData); - if (null != val) { - deckList.setSelectedValue(val, true); - } + val = deckList.getSelectedValue(); + deckList.setListData(listData); + if (null != val) { + deckList.setSelectedValue(val, true); + } - if (-1 == deckList.getSelectedIndex()) { - deckList.setSelectedIndex(0); - } // End Planechase + if (-1 == deckList.getSelectedIndex()) { + deckList.setSelectedIndex(0); + } // End Planechase - view.updateVanguardList(i); - } + view.updateVanguardList(i); + } - // General updates when switching back to this view - view.updatePlayersFromPrefs(); - view.getBtnStart().requestFocusInWindow(); - } + // General updates when switching back to this view + view.updatePlayersFromPrefs(); + view.getBtnStart().requestFocusInWindow(); + } }); } /* (non-Javadoc) * @see forge.gui.home.ICSubmenu#initialize() */ - @Override + @Override public void initialize() { - view.getDeckChooser(0).initialize(FPref.CONSTRUCTED_P1_DECK_STATE, DeckType.PRECONSTRUCTED_DECK); - view.getDeckChooser(1).initialize(FPref.CONSTRUCTED_P2_DECK_STATE, DeckType.COLOR_DECK); - view.getDeckChooser(2).initialize(FPref.CONSTRUCTED_P3_DECK_STATE, DeckType.COLOR_DECK); - view.getDeckChooser(3).initialize(FPref.CONSTRUCTED_P4_DECK_STATE, DeckType.COLOR_DECK); - view.getDeckChooser(4).initialize(FPref.CONSTRUCTED_P5_DECK_STATE, DeckType.COLOR_DECK); - view.getDeckChooser(5).initialize(FPref.CONSTRUCTED_P6_DECK_STATE, DeckType.COLOR_DECK); - view.getDeckChooser(6).initialize(FPref.CONSTRUCTED_P7_DECK_STATE, DeckType.COLOR_DECK); - view.getDeckChooser(7).initialize(FPref.CONSTRUCTED_P8_DECK_STATE, DeckType.COLOR_DECK); + view.getDeckChooser(0).initialize(FPref.CONSTRUCTED_P1_DECK_STATE, DeckType.PRECONSTRUCTED_DECK); + view.getDeckChooser(1).initialize(FPref.CONSTRUCTED_P2_DECK_STATE, DeckType.COLOR_DECK); + view.getDeckChooser(2).initialize(FPref.CONSTRUCTED_P3_DECK_STATE, DeckType.COLOR_DECK); + view.getDeckChooser(3).initialize(FPref.CONSTRUCTED_P4_DECK_STATE, DeckType.COLOR_DECK); + view.getDeckChooser(4).initialize(FPref.CONSTRUCTED_P5_DECK_STATE, DeckType.COLOR_DECK); + view.getDeckChooser(5).initialize(FPref.CONSTRUCTED_P6_DECK_STATE, DeckType.COLOR_DECK); + view.getDeckChooser(6).initialize(FPref.CONSTRUCTED_P7_DECK_STATE, DeckType.COLOR_DECK); + view.getDeckChooser(7).initialize(FPref.CONSTRUCTED_P8_DECK_STATE, DeckType.COLOR_DECK); - // Start button event handling + // Start button event handling view.getBtnStart().addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent arg0) { @@ -184,171 +183,196 @@ public enum CSubmenuConstructed implements ICDoc, IMenuProvider { /** Starts a match with the applied variants. */ private void startGame(final Set variantTypes) { - if (!view.isEnoughTeams()) { - FOptionPane.showMessageDialog("There are not enough teams! Please adjust team allocations."); - return; - } + if (!view.isEnoughTeams()) { + FOptionPane.showMessageDialog("There are not enough teams! Please adjust team allocations."); + return; + } for (final int i : view.getParticipants()) { - if (view.getDeckChooser(i).getPlayer() == null) { + if (view.getDeckChooser(i).getPlayer() == null) { FOptionPane.showMessageDialog("Please specify a deck for " + view.getPlayerName(i)); return; } } // Is it even possible anymore? I think current implementation assigns decks automatically. - boolean checkLegality = FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY); - if (checkLegality && !variantTypes.contains(GameType.Commander)) { //Commander deck replaces regular deck and is checked later - for (final int i : view.getParticipants()) { - String name = view.getPlayerName(i); - String errMsg = GameType.Constructed.getDeckFormat().getDeckConformanceProblem(view.getDeckChooser(i).getPlayer().getDeck()); - if (null != errMsg) { - FOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid Deck"); - return; - } - } + GameType autoGenerateVariant = null; + boolean isCommanderMatch = false; + if (!variantTypes.isEmpty()) { + isCommanderMatch = variantTypes.contains(GameType.Commander); + if (!isCommanderMatch) { + for (GameType variant : variantTypes) { + if (variant.isAutoGenerated()) { + autoGenerateVariant = variant; + break; + } + } + } + } + + boolean checkLegality = FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY); + + //Auto-generated decks don't need to be checked here + //Commander deck replaces regular deck and is checked later + if (checkLegality && autoGenerateVariant == null && !isCommanderMatch) { + for (final int i : view.getParticipants()) { + String name = view.getPlayerName(i); + String errMsg = GameType.Constructed.getDeckFormat().getDeckConformanceProblem(view.getDeckChooser(i).getPlayer().getDeck()); + if (null != errMsg) { + FOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid Deck"); + return; + } + } } IGuiBase fc = GuiBase.getInterface(); List players = new ArrayList(); for (final int i : view.getParticipants()) { - String name = view.getPlayerName(i); + String name = view.getPlayerName(i); LobbyPlayer lobbyPlayer = view.isPlayerAI(i) - ? fc.createAiPlayer(name, view.getPlayerAvatar(i)) - : fc.getGuiPlayer(); + ? fc.createAiPlayer(name, view.getPlayerAvatar(i)) + : fc.getGuiPlayer(); RegisteredPlayer rp = view.getDeckChooser(i).getPlayer(); if (variantTypes.isEmpty()) { rp.setTeamNumber(view.getTeam(i)); players.add(rp.setPlayer(lobbyPlayer)); - } else { + } + else { Deck deck = null; - boolean isCommanderMatch = variantTypes.contains(GameType.Commander); - if (isCommanderMatch) { + PaperCard vanguardAvatar = null; + if (isCommanderMatch) { Object selected = view.getCommanderDeckLists().get(i).getSelectedValue(); if (selected instanceof String) { String sel = (String) selected; IStorage comDecks = FModel.getDecks().getCommander(); if (sel.equals("Random") && comDecks.size() > 0) { - deck = Aggregates.random(comDecks); + deck = Aggregates.random(comDecks); } - } else { - deck = (Deck) selected; + } + else { + deck = (Deck) selected; } if (deck == null) { //Can be null if player deselects the list selection or chose Generate - deck = DeckgenUtil.generateCommanderDeck(view.isPlayerAI(i)); + deck = DeckgenUtil.generateCommanderDeck(view.isPlayerAI(i)); } if (checkLegality) { - String errMsg = GameType.Commander.getDeckFormat().getDeckConformanceProblem(deck); + String errMsg = GameType.Commander.getDeckFormat().getDeckConformanceProblem(deck); if (null != errMsg) { FOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid Commander Deck"); return; } } - } + } + else if (autoGenerateVariant != null) { + deck = autoGenerateVariant.autoGenerateDeck(rp); + CardPool avatarPool = deck.get(DeckSection.Avatar); + if (avatarPool != null) { + vanguardAvatar = avatarPool.get(0); + } + } - // Initialise variables for other variants - deck = deck == null ? rp.getDeck() : deck; - Iterable schemes = null; - boolean playerIsArchenemy = view.isPlayerArchenemy(i); - Iterable planes = null; - PaperCard vanguardAvatar = null; + // Initialise variables for other variants + deck = deck == null ? rp.getDeck() : deck; + Iterable schemes = null; + boolean playerIsArchenemy = view.isPlayerArchenemy(i); + Iterable planes = null; - //Archenemy - if (variantTypes.contains(GameType.ArchenemyRumble) + //Archenemy + if (variantTypes.contains(GameType.ArchenemyRumble) || (variantTypes.contains(GameType.Archenemy) && playerIsArchenemy)) { Object selected = view.getSchemeDeckLists().get(i).getSelectedValue(); CardPool schemePool = null; if (selected instanceof String) { String sel = (String) selected; if (sel.contains("Use deck's scheme section")) { - if (deck.has(DeckSection.Schemes)) { - schemePool = deck.get(DeckSection.Schemes); - } else { - sel = "Random"; - } + if (deck.has(DeckSection.Schemes)) { + schemePool = deck.get(DeckSection.Schemes); + } else { + sel = "Random"; + } } IStorage sDecks = FModel.getDecks().getScheme(); if (sel.equals("Random") && sDecks.size() != 0) { - schemePool = Aggregates.random(sDecks).get(DeckSection.Schemes); + schemePool = Aggregates.random(sDecks).get(DeckSection.Schemes); } } else { - schemePool = ((Deck) selected).get(DeckSection.Schemes); + schemePool = ((Deck) selected).get(DeckSection.Schemes); } if (schemePool == null) { //Can be null if player deselects the list selection or chose Generate - schemePool = DeckgenUtil.generateSchemePool(); + schemePool = DeckgenUtil.generateSchemePool(); } if (checkLegality) { - String errMsg = GameType.Archenemy.getDeckFormat().getSchemeSectionConformanceProblem(schemePool); + String errMsg = GameType.Archenemy.getDeckFormat().getSchemeSectionConformanceProblem(schemePool); if (null != errMsg) { FOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid Scheme Deck"); return; } } schemes = schemePool.toFlatList(); - } + } - //Planechase - if (variantTypes.contains(GameType.Planechase)) { + //Planechase + if (variantTypes.contains(GameType.Planechase)) { Object selected = view.getPlanarDeckLists().get(i).getSelectedValue(); CardPool planePool = null; if (selected instanceof String) { String sel = (String) selected; if (sel.contains("Use deck's planes section")) { - if (deck.has(DeckSection.Planes)) { - planePool = deck.get(DeckSection.Planes); - } else { - sel = "Random"; - } + if (deck.has(DeckSection.Planes)) { + planePool = deck.get(DeckSection.Planes); + } else { + sel = "Random"; + } } IStorage pDecks = FModel.getDecks().getPlane(); if (sel.equals("Random") && pDecks.size() != 0) { - planePool = Aggregates.random(pDecks).get(DeckSection.Planes); + planePool = Aggregates.random(pDecks).get(DeckSection.Planes); } } else { - planePool = ((Deck) selected).get(DeckSection.Planes); + planePool = ((Deck) selected).get(DeckSection.Planes); } if (planePool == null) { //Can be null if player deselects the list selection or chose Generate - planePool = DeckgenUtil.generatePlanarPool(); + planePool = DeckgenUtil.generatePlanarPool(); } if (checkLegality) { - String errMsg = GameType.Planechase.getDeckFormat().getPlaneSectionConformanceProblem(planePool); + String errMsg = GameType.Planechase.getDeckFormat().getPlaneSectionConformanceProblem(planePool); if (null != errMsg) { FOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid Planar Deck"); return; } } planes = planePool.toFlatList(); - } + } - //Vanguard - if (variantTypes.contains(GameType.Vanguard)) { + //Vanguard + if (variantTypes.contains(GameType.Vanguard)) { Object selected = view.getVanguardLists().get(i).getSelectedValue(); if (selected instanceof String) { String sel = (String) selected; if (sel.contains("Use deck's default avatar") && deck.has(DeckSection.Avatar)) { - vanguardAvatar = deck.get(DeckSection.Avatar).get(0); + vanguardAvatar = deck.get(DeckSection.Avatar).get(0); } else { //Only other string is "Random" - if (!view.isPlayerAI(i)) { //Human - vanguardAvatar = Aggregates.random(view.getNonRandomHumanAvatars()); - } else { //AI - vanguardAvatar = Aggregates.random(view.getNonRandomAiAvatars()); - } + if (!view.isPlayerAI(i)) { //Human + vanguardAvatar = Aggregates.random(view.getNonRandomHumanAvatars()); + } else { //AI + vanguardAvatar = Aggregates.random(view.getNonRandomAiAvatars()); + } } } else { - vanguardAvatar = (PaperCard)selected; + vanguardAvatar = (PaperCard)selected; } if (vanguardAvatar == null) { //ERROR! null if avatar deselected on list GuiDialog.message("No Vanguard avatar selected for " + name - + ". Please choose one or disable the Vanguard variant"); + + ". Please choose one or disable the Vanguard variant"); return; } - } + } - rp = RegisteredPlayer.forVariants(variantTypes, deck, schemes, playerIsArchenemy, planes, vanguardAvatar); - rp.setTeamNumber(view.getTeam(i)); - players.add(rp.setPlayer(lobbyPlayer)); - } - view.getDeckChooser(i).saveState(); + rp = RegisteredPlayer.forVariants(variantTypes, deck, schemes, playerIsArchenemy, planes, vanguardAvatar); + rp.setTeamNumber(view.getTeam(i)); + players.add(rp.setPlayer(lobbyPlayer)); + } + view.getDeckChooser(i).saveState(); } Singletons.getControl().startMatch(GameType.Constructed, variantTypes, players); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/VSubmenuConstructed.java b/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/VSubmenuConstructed.java index ef2564a30d2..226260d92db 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/VSubmenuConstructed.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/VSubmenuConstructed.java @@ -110,13 +110,12 @@ public enum VSubmenuConstructed implements IVSubmenu { // Variants frame and variables private final Set appliedVariants = new TreeSet(); private final FPanel variantsPanel = new FPanel(new MigLayout("insets 10, gapx 10")); - private final FCheckBox vntVanguard = new FCheckBox("Vanguard"); - private final FCheckBox vntCommander = new FCheckBox("Commander"); - private final FCheckBox vntPlanechase = new FCheckBox("Planechase"); - private final FCheckBox vntArchenemy = new FCheckBox("Archenemy"); - private String archenemyType = "Classic"; - private final FComboBoxWrapper comboArchenemy = new FComboBoxWrapper(new String[]{ - "Archenemy (Classic - One player is the Archenemy)", "Supervillan Rumble (All players are Archenemies)"}); + private final VariantCheckBox vntVanguard = new VariantCheckBox(GameType.Vanguard); + private final VariantCheckBox vntMomirBasic = new VariantCheckBox(GameType.MomirBasic); + private final VariantCheckBox vntCommander = new VariantCheckBox(GameType.Commander); + private final VariantCheckBox vntPlanechase = new VariantCheckBox(GameType.Planechase); + private final VariantCheckBox vntArchenemy = new VariantCheckBox(GameType.Archenemy); + private final VariantCheckBox vntArchenemyRumble = new VariantCheckBox(GameType.ArchenemyRumble); // Player frame elements private final JPanel playersFrame = new JPanel(new MigLayout("insets 0, gap 0 5, wrap, hidemode 3")); @@ -160,22 +159,14 @@ public enum VSubmenuConstructed implements IVSubmenu { //////////////////////////////////////////////////////// //////////////////// Variants Panel //////////////////// - // Populate and add variants panel - vntVanguard.addItemListener(iListenerVariants); - vntCommander.addItemListener(iListenerVariants); - vntPlanechase.addItemListener(iListenerVariants); - vntArchenemy.addItemListener(iListenerVariants); - comboArchenemy.setSelectedIndex(0); - comboArchenemy.setEnabled(vntArchenemy.isSelected()); - comboArchenemy.addActionListener(aeComboListener); - variantsPanel.setOpaque(false); variantsPanel.add(newLabel("Variants:")); variantsPanel.add(vntVanguard); + variantsPanel.add(vntMomirBasic); variantsPanel.add(vntCommander); variantsPanel.add(vntPlanechase); variantsPanel.add(vntArchenemy); - comboArchenemy.addTo(variantsPanel); + variantsPanel.add(vntArchenemyRumble); constructedFrame.add(new FScrollPane(variantsPanel, false, true, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, @@ -459,6 +450,8 @@ public enum VSubmenuConstructed implements IVSubmenu { container.validate(); container.repaint(); } + + changePlayerFocus(playerWithFocus, currentGameMode); } /** @return {@link javax.swing.JButton} */ @@ -682,29 +675,62 @@ public enum VSubmenuConstructed implements IVSubmenu { }; public void updateVariantControlsVisibility() { - // Commander deck replaces basic deck, so hide that - deckLabel.setVisible(!appliedVariants.contains(GameType.Commander)); - deckBtn.setVisible(!appliedVariants.contains(GameType.Commander)); - cmdDeckSelectorBtn.setVisible(appliedVariants.contains(GameType.Commander)); - cmdDeckEditor.setVisible(appliedVariants.contains(GameType.Commander)); - cmdLabel.setVisible(appliedVariants.contains(GameType.Commander)); + boolean isCommanderApplied = false; + boolean isPlanechaseApplied = false; + boolean isVanguardApplied = false; + boolean isArchenemyApplied = false; + boolean archenemyVisiblity = false; + boolean isDeckBuildingAllowed = true; + + for (GameType variant : appliedVariants) { + switch (variant) { + case Archenemy: + isArchenemyApplied = true; + if (playerIsArchenemy) { + archenemyVisiblity = true; + } + break; + case ArchenemyRumble: + archenemyVisiblity = true; + break; + case Commander: + isCommanderApplied = true; + isDeckBuildingAllowed = false; //Commander deck replaces basic deck, so hide that + break; + case Planechase: + isPlanechaseApplied = true; + break; + case Vanguard: + isVanguardApplied = true; + break; + default: + if (variant.isAutoGenerated()) { + isDeckBuildingAllowed = false; + } + break; + } + } - boolean archenemyVisiblity = appliedVariants.contains(GameType.ArchenemyRumble) - || (appliedVariants.contains(GameType.Archenemy) && playerIsArchenemy); - scmDeckSelectorBtn.setVisible(archenemyVisiblity); + deckLabel.setVisible(isDeckBuildingAllowed); + deckBtn.setVisible(isDeckBuildingAllowed); + cmdDeckSelectorBtn.setVisible(isCommanderApplied); + cmdDeckEditor.setVisible(isCommanderApplied); + cmdLabel.setVisible(isCommanderApplied); + + scmDeckSelectorBtn.setVisible(archenemyVisiblity); scmDeckEditor.setVisible(archenemyVisiblity); scmLabel.setVisible(archenemyVisiblity); - teamComboBox.setVisible(!appliedVariants.contains(GameType.Archenemy)); - aeTeamComboBox.setVisible(appliedVariants.contains(GameType.Archenemy)); - aeTeamComboBox.setEnabled(!(appliedVariants.contains(GameType.Archenemy) && playerIsArchenemy)); + teamComboBox.setVisible(!isArchenemyApplied); + aeTeamComboBox.setVisible(isArchenemyApplied); + aeTeamComboBox.setEnabled(!(isArchenemyApplied && playerIsArchenemy)); - pchDeckSelectorBtn.setVisible(appliedVariants.contains(GameType.Planechase)); - pchDeckEditor.setVisible(appliedVariants.contains(GameType.Planechase)); - pchLabel.setVisible(appliedVariants.contains(GameType.Planechase)); + pchDeckSelectorBtn.setVisible(isPlanechaseApplied); + pchDeckEditor.setVisible(isPlanechaseApplied); + pchLabel.setVisible(isPlanechaseApplied); - vgdSelectorBtn.setVisible(appliedVariants.contains(GameType.Vanguard)); - vgdLabel.setVisible(appliedVariants.contains(GameType.Vanguard)); + vgdSelectorBtn.setVisible(isVanguardApplied); + vgdLabel.setVisible(isVanguardApplied); } @Override @@ -779,7 +805,8 @@ public enum VSubmenuConstructed implements IVSubmenu { public void toggleIsPlayerArchenemy() { if (appliedVariants.contains(GameType.Archenemy)) { playerIsArchenemy = lastArchenemy == index; - } else { + } + else { playerIsArchenemy = appliedVariants.contains(GameType.ArchenemyRumble); } updateVariantControlsVisibility(); @@ -793,7 +820,7 @@ public enum VSubmenuConstructed implements IVSubmenu { scmDeckSelectorBtn.setCommand(new Runnable() { @Override public void run() { - currentGameMode = archenemyType.contains("Classic") ? GameType.Archenemy : GameType.ArchenemyRumble; + currentGameMode = vntArchenemy.isSelected() ? GameType.Archenemy : GameType.ArchenemyRumble; scmDeckSelectorBtn.requestFocusInWindow(); changePlayerFocus(index, currentGameMode); } @@ -802,7 +829,7 @@ public enum VSubmenuConstructed implements IVSubmenu { scmDeckEditor.setCommand(new UiCommand() { @Override public void run() { - currentGameMode = archenemyType.contains("Classic") ? GameType.Archenemy : GameType.ArchenemyRumble; + currentGameMode = vntArchenemy.isSelected() ? GameType.Archenemy : GameType.ArchenemyRumble; Predicate predSchemes = new Predicate() { @Override public boolean apply(PaperCard arg0) { @@ -1139,73 +1166,61 @@ public enum VSubmenuConstructed implements IVSubmenu { ///////////////////////////////////////////// //========== Various listeners in build order - /** This listener unlocks the relevant buttons for players - * and enables/disables archenemy combobox as appropriate. */ - private ItemListener iListenerVariants = new ItemListener() { - @Override - public void itemStateChanged(ItemEvent arg0) { - FCheckBox cb = (FCheckBox) arg0.getSource(); - GameType variantType = null; + @SuppressWarnings("serial") + private class VariantCheckBox extends FCheckBox { + private final GameType variantType; - if (cb == vntVanguard) { - variantType = GameType.Vanguard; - } - else if (cb == vntCommander) { - variantType = GameType.Commander; - } - else if (cb == vntPlanechase) { - variantType = GameType.Planechase; - } - else if (cb == vntArchenemy) { - variantType = archenemyType.contains("Classic") ? GameType.Archenemy : GameType.ArchenemyRumble; - comboArchenemy.setEnabled(vntArchenemy.isSelected()); - if (arg0.getStateChange() != ItemEvent.SELECTED) { - appliedVariants.remove(GameType.Archenemy); - appliedVariants.remove(GameType.ArchenemyRumble); - } - } + private VariantCheckBox(GameType variantType0) { + super(variantType0.toString()); - if (null != variantType) { - if (arg0.getStateChange() == ItemEvent.SELECTED) { - appliedVariants.add(variantType); - currentGameMode = variantType; - } - else { - appliedVariants.remove(variantType); - if (currentGameMode == variantType) { - currentGameMode = GameType.Constructed; + variantType = variantType0; + + setToolTipText(variantType.getDescription()); + + addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + appliedVariants.add(variantType); + currentGameMode = variantType; + + //ensure other necessary variants are unchecked + switch (variantType) { + case Archenemy: + vntArchenemyRumble.setSelected(false); + break; + case ArchenemyRumble: + vntArchenemy.setSelected(false); + break; + case Commander: + vntMomirBasic.setSelected(false); + break; + case Vanguard: + vntMomirBasic.setSelected(false); + break; + case MomirBasic: + vntCommander.setSelected(false); + vntVanguard.setSelected(false); + break; + default: + break; + } } + else { + appliedVariants.remove(variantType); + if (currentGameMode == variantType) { + currentGameMode = GameType.Constructed; + } + } + + for (PlayerPanel pp : playerPanels) { + pp.toggleIsPlayerArchenemy(); + } + changePlayerFocus(playerWithFocus, currentGameMode); } - } - - for (PlayerPanel pp : playerPanels) { - pp.toggleIsPlayerArchenemy(); - pp.updateVariantControlsVisibility(); - } - changePlayerFocus(playerWithFocus, currentGameMode); + }); } - }; - - // Listens to the archenemy combo box - private ActionListener aeComboListener = new ActionListener() { - @SuppressWarnings("unchecked") - @Override - public void actionPerformed(ActionEvent e) { - FComboBox cb = (FComboBox)e.getSource(); - archenemyType = (String)cb.getSelectedItem(); - GameType mode = archenemyType.contains("Classic") ? GameType.Archenemy : GameType.ArchenemyRumble; - appliedVariants.remove(GameType.Archenemy); - appliedVariants.remove(GameType.ArchenemyRumble); - appliedVariants.add(mode); - - currentGameMode = mode; - for (PlayerPanel pp : playerPanels) { - pp.toggleIsPlayerArchenemy(); - pp.updateVariantControlsVisibility(); - } - changePlayerFocus(playerWithFocus, currentGameMode); - } - }; + } private ActionListener nameListener = new ActionListener() { @Override diff --git a/forge-gui/CHANGES.txt b/forge-gui/CHANGES.txt index 2ed11b7c7da..6a1894b86e7 100644 --- a/forge-gui/CHANGES.txt +++ b/forge-gui/CHANGES.txt @@ -14,6 +14,15 @@ The details previously available by hovering over that rectangle will now appear The dialog for the commander replacement effect will now display the commander's name. +- Momir Basic variant type - +Momir Basic is now available as its own variant option on the Constructed screen +For this format. each player will automatically be given a deck with 12 of each basic land and the Momir Vig avatar + + +- Choose value for X mana costs - +Now, when playing spells/abilities with X in its mana cost, you will now be prompted for a value for X prior to mana payment, ensuring the final mana cost is calculated properly from cost adjustment effects and allowing using the "Auto" button to pay the entire cost. +This also applies to spells with Replicate and Multikicker to allow picking the Replicate or Multikicker amount prior to paying the final mana cost. + - Auto-targeting support - When playing spells and abilities with the text "target opponent", if you only have one opponent, you will no longer be asked to choose the opponent to target. When triggered abilities have only one valid target, that target will now be auto-selected. diff --git a/forge-gui/res/cardsfolder/a/azorius_chancery.txt b/forge-gui/res/cardsfolder/a/azorius_chancery.txt index b7540fd6613..4328c9df1ef 100644 --- a/forge-gui/res/cardsfolder/a/azorius_chancery.txt +++ b/forge-gui/res/cardsfolder/a/azorius_chancery.txt @@ -4,7 +4,7 @@ Types:Land K:CARDNAME enters the battlefield tapped. A:AB$ Mana | Cost$ T | Produced$ W U | SpellDescription$ Add {W}{U} to your mana pool. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME enters the battlefield, return a land you control to its owner's hand. -SVar:TrigReturn:DB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl +SVar:TrigReturn:DB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl | AILogic$ NeverBounceItself SVar:NeedsToPlay:Land.Basic+YouCtrl SVar:Picture:http://www.wizards.com/global/images/magic/general/azorius_chancery.jpg Oracle:Azorius Chancery enters the battlefield tapped.\nWhen Azorius Chancery enters the battlefield, return a land you control to its owner's hand.\n{T}: Add {W}{U} to your mana pool. diff --git a/forge-gui/res/cardsfolder/b/boros_garrison.txt b/forge-gui/res/cardsfolder/b/boros_garrison.txt index c9cb313ac02..8433ec4fb29 100644 --- a/forge-gui/res/cardsfolder/b/boros_garrison.txt +++ b/forge-gui/res/cardsfolder/b/boros_garrison.txt @@ -4,7 +4,7 @@ Types:Land K:CARDNAME enters the battlefield tapped. A:AB$ Mana | Cost$ T | Produced$ R W | SpellDescription$ Add {R}{W} to your mana pool. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME enters the battlefield, return a land you control to its owner's hand. -SVar:TrigReturn:DB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl +SVar:TrigReturn:DB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl | AILogic$ NeverBounceItself SVar:NeedsToPlay:Land.Basic+YouCtrl SVar:Picture:http://www.wizards.com/global/images/magic/general/boros_garrison.jpg Oracle:Boros Garrison enters the battlefield tapped.\nWhen Boros Garrison enters the battlefield, return a land you control to its owner's hand.\n{T}: Add {R}{W} to your mana pool. diff --git a/forge-gui/res/cardsfolder/d/dimir_aqueduct.txt b/forge-gui/res/cardsfolder/d/dimir_aqueduct.txt index c92e4bf570e..e5c3e69e5bf 100644 --- a/forge-gui/res/cardsfolder/d/dimir_aqueduct.txt +++ b/forge-gui/res/cardsfolder/d/dimir_aqueduct.txt @@ -4,7 +4,7 @@ Types:Land K:CARDNAME enters the battlefield tapped. A:AB$ Mana | Cost$ T | Produced$ U B | SpellDescription$ Add {U}{B} to your mana pool. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME enters the battlefield, return a land you control to its owner's hand. -SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl +SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl | AILogic$ NeverBounceItself SVar:NeedsToPlay:Land.Basic+YouCtrl SVar:Picture:http://www.wizards.com/global/images/magic/general/dimir_aqueduct.jpg Oracle:Dimir Aqueduct enters the battlefield tapped.\nWhen Dimir Aqueduct enters the battlefield, return a land you control to its owner's hand.\n{T}: Add {U}{B} to your mana pool. diff --git a/forge-gui/res/cardsfolder/g/golgari_rot_farm.txt b/forge-gui/res/cardsfolder/g/golgari_rot_farm.txt index 9c8f7d53225..a99c28da5d1 100644 --- a/forge-gui/res/cardsfolder/g/golgari_rot_farm.txt +++ b/forge-gui/res/cardsfolder/g/golgari_rot_farm.txt @@ -4,7 +4,7 @@ Types:Land K:CARDNAME enters the battlefield tapped. A:AB$ Mana | Cost$ T | Produced$ B G | SpellDescription$ Add {B}{G} to your mana pool. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME enters the battlefield, return a land you control to its owner's hand. -SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl +SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl | AILogic$ NeverBounceItself SVar:NeedsToPlay:Land.Basic+YouCtrl SVar:Picture:http://www.wizards.com/global/images/magic/general/golgari_rot_farm.jpg Oracle:Golgari Rot Farm enters the battlefield tapped.\nWhen Golgari Rot Farm enters the battlefield, return a land you control to its owner's hand.\n{T}: Add {B}{G} to your mana pool. diff --git a/forge-gui/res/cardsfolder/g/gruul_turf.txt b/forge-gui/res/cardsfolder/g/gruul_turf.txt index b10d339ce18..0133b313660 100644 --- a/forge-gui/res/cardsfolder/g/gruul_turf.txt +++ b/forge-gui/res/cardsfolder/g/gruul_turf.txt @@ -4,7 +4,7 @@ Types:Land K:CARDNAME enters the battlefield tapped. A:AB$ Mana | Cost$ T | Produced$ R G | SpellDescription$ Add {R}{G} to your mana pool. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME enters the battlefield, return a land you control to its owner's hand. -SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl +SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl | AILogic$ NeverBounceItself SVar:NeedsToPlay:Land.Basic+YouCtrl SVar:Picture:http://www.wizards.com/global/images/magic/general/gruul_turf.jpg Oracle:Gruul Turf enters the battlefield tapped.\nWhen Gruul Turf enters the battlefield, return a land you control to its owner's hand.\n{T}: Add {R}{G} to your mana pool. diff --git a/forge-gui/res/cardsfolder/i/izzet_boilerworks.txt b/forge-gui/res/cardsfolder/i/izzet_boilerworks.txt index 0a00d61b675..f97e01baec4 100644 --- a/forge-gui/res/cardsfolder/i/izzet_boilerworks.txt +++ b/forge-gui/res/cardsfolder/i/izzet_boilerworks.txt @@ -4,7 +4,7 @@ Types:Land K:CARDNAME enters the battlefield tapped. A:AB$ Mana | Cost$ T | Produced$ U R | SpellDescription$ Add {U}{R} to your mana pool. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME enters the battlefield, return a land you control to its owner's hand. -SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl +SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl | AILogic$ NeverBounceItself SVar:NeedsToPlay:Land.Basic+YouCtrl SVar:Picture:http://www.wizards.com/global/images/magic/general/izzet_boilerworks.jpg Oracle:Izzet Boilerworks enters the battlefield tapped.\nWhen Izzet Boilerworks enters the battlefield, return a land you control to its owner's hand.\n{T}: Add {U}{R} to your mana pool. diff --git a/forge-gui/res/cardsfolder/o/orzhov_basilica.txt b/forge-gui/res/cardsfolder/o/orzhov_basilica.txt index f3226fd5eaa..3ed27cedd42 100644 --- a/forge-gui/res/cardsfolder/o/orzhov_basilica.txt +++ b/forge-gui/res/cardsfolder/o/orzhov_basilica.txt @@ -3,7 +3,7 @@ ManaCost:no cost Types:Land K:CARDNAME enters the battlefield tapped. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME enters the battlefield, return a land you control to its owner's hand. -SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl +SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl | AILogic$ NeverBounceItself A:AB$ Mana | Cost$ T | Produced$ W B | SpellDescription$ Add {W}{B} to your mana pool. SVar:NeedsToPlay:Land.Basic+YouCtrl SVar:Picture:http://www.wizards.com/global/images/magic/general/orzhov_basilica.jpg diff --git a/forge-gui/res/cardsfolder/r/rakdos_carnarium.txt b/forge-gui/res/cardsfolder/r/rakdos_carnarium.txt index 22023a18b5d..824677170c8 100644 --- a/forge-gui/res/cardsfolder/r/rakdos_carnarium.txt +++ b/forge-gui/res/cardsfolder/r/rakdos_carnarium.txt @@ -4,7 +4,7 @@ Types:Land K:CARDNAME enters the battlefield tapped. A:AB$ Mana | Cost$ T | Produced$ B R | SpellDescription$ Add {B}{R} to your mana pool. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME enters the battlefield, return a land you control to its owner's hand. -SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl +SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl | AILogic$ NeverBounceItself SVar:NeedsToPlay:Land.Basic+YouCtrl SVar:Picture:http://www.wizards.com/global/images/magic/general/rakdos_carnarium.jpg Oracle:Rakdos Carnarium enters the battlefield tapped.\nWhen Rakdos Carnarium enters the battlefield, return a land you control to its owner's hand.\n{T}: Add {B}{R} to your mana pool. diff --git a/forge-gui/res/cardsfolder/s/selesnya_sanctuary.txt b/forge-gui/res/cardsfolder/s/selesnya_sanctuary.txt index 9e6b4392232..b691d664119 100644 --- a/forge-gui/res/cardsfolder/s/selesnya_sanctuary.txt +++ b/forge-gui/res/cardsfolder/s/selesnya_sanctuary.txt @@ -4,7 +4,7 @@ Types:Land K:CARDNAME enters the battlefield tapped. A:AB$ Mana | Cost$ T | Produced$ G W | SpellDescription$ Add {G}{W} to your mana pool. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME enters the battlefield, return a land you control to its owner's hand. -SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl +SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl | AILogic$ NeverBounceItself SVar:NeedsToPlay:Land.Basic+YouCtrl SVar:Picture:http://www.wizards.com/global/images/magic/general/selesnya_sanctuary.jpg Oracle:Selesnya Sanctuary enters the battlefield tapped.\nWhen Selesnya Sanctuary enters the battlefield, return a land you control to its owner's hand.\n{T}: Add {G}{W} to your mana pool. diff --git a/forge-gui/res/cardsfolder/s/simic_growth_chamber.txt b/forge-gui/res/cardsfolder/s/simic_growth_chamber.txt index 609898a37c8..d855af0a86b 100644 --- a/forge-gui/res/cardsfolder/s/simic_growth_chamber.txt +++ b/forge-gui/res/cardsfolder/s/simic_growth_chamber.txt @@ -4,7 +4,7 @@ Types:Land K:CARDNAME enters the battlefield tapped. A:AB$ Mana | Cost$ T | Produced$ G U | SpellDescription$ Add {G}{U} to your mana pool. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME enters the battlefield, return a land you control to its owner's hand. -SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl +SVar:TrigReturn:AB$ChangeZone | Origin$ Battlefield | Destination$ Hand | Cost$ 0 | Hidden$ True | Mandatory$ True | ChangeType$ Land.YouCtrl | AILogic$ NeverBounceItself SVar:NeedsToPlay:Land.Basic+YouCtrl SVar:Picture:http://www.wizards.com/global/images/magic/general/simic_growth_chamber.jpg Oracle:Simic Growth Chamber enters the battlefield tapped.\nWhen Simic Growth Chamber enters the battlefield, return a land you control to its owner's hand.\n{T}: Add {G}{U} to your mana pool. diff --git a/forge-gui/src/main/java/forge/match/input/InputPayMana.java b/forge-gui/src/main/java/forge/match/input/InputPayMana.java index df07aa70d0c..f3c1b94be08 100644 --- a/forge-gui/src/main/java/forge/match/input/InputPayMana.java +++ b/forge-gui/src/main/java/forge/match/input/InputPayMana.java @@ -41,7 +41,7 @@ public abstract class InputPayMana extends InputSyncronizedBase { private final Object zoneToRestore; private boolean bPaid = false; - private Boolean canPayManaCost = null; + protected Boolean canPayManaCost = null; private boolean locked = false; @@ -343,7 +343,7 @@ public abstract class InputPayMana extends InputSyncronizedBase { return true; } - private void runAsAi(Runnable proc) { + protected void runAsAi(Runnable proc) { player.runWithController(proc, new PlayerControllerAi(game, player, player.getOriginalLobbyPlayer())); } diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index f67acda35e2..63ebeb7241c 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -4,9 +4,7 @@ import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import forge.FThreads; -import forge.card.MagicColor; import forge.card.mana.ManaCost; -import forge.card.mana.ManaCostShard; import forge.game.Game; import forge.game.GameActionUtil; import forge.game.GameLogEntryType; @@ -27,7 +25,6 @@ import forge.game.zone.ZoneType; import forge.match.input.InputPayMana; import forge.match.input.InputPayManaOfCostPayment; import forge.match.input.InputPayManaSimple; -import forge.match.input.InputPayManaX; import forge.match.input.InputSelectCardsFromList; import forge.util.Lang; import forge.util.gui.SGuiChoose; @@ -41,7 +38,7 @@ import java.util.Map; public class HumanPlay { - private HumanPlay() { + private HumanPlay() { } /** @@ -635,8 +632,9 @@ public class HumanPlay { prompt = source + "\n" + promptCurrent; } - if( sourceAbility != null ) + if (sourceAbility != null) { sourceAbility.clearManaPaid(); + } boolean paid = p.getController().payManaCost(cost.getCostMana(), sourceAbility, prompt, false); if (!paid) { p.getManaPool().refundManaPaid(sourceAbility); @@ -692,24 +690,32 @@ public class HumanPlay { final Card source = ability.getHostCard(); ManaCostBeingPaid toPay = new ManaCostBeingPaid(realCost, mc.getRestiction()); - boolean xWasBilled = false; String xInCard = source.getSVar("X"); if (mc.getAmountOfX() > 0 && !"Count$xPaid".equals(xInCard)) { // announce X will overwrite whatever was in card script - // this currently only works for things about Targeted object - int xCost = AbilityUtils.calculateAmount(source, "X", ability) * mc.getAmountOfX(); - byte xColor = MagicColor.fromName(ability.hasParam("XColor") ? ability.getParam("XColor") : "1"); - toPay.increaseShard(ManaCostShard.valueOf(xColor), xCost); - xWasBilled = true; - } - int timesMultikicked = ability.getHostCard().getKickerMagnitude(); - if ( timesMultikicked > 0 && ability.isAnnouncing("Multikicker")) { - ManaCost mkCost = ability.getMultiKickerManaCost(); - for(int i = 0; i < timesMultikicked; i++) - toPay.addManaCost(mkCost); + int xPaid = AbilityUtils.calculateAmount(source, "X", ability); + toPay.setXManaCostPaid(xPaid, ability.getParam("XColor")); + source.setXManaCostPaid(xPaid); } - if( isActivatedSa ) - ManaCostAdjustment.adjust(toPay, ability, false); + int timesMultikicked = source.getKickerMagnitude(); + if (timesMultikicked > 0 && ability.isAnnouncing("Multikicker")) { + ManaCost mkCost = ability.getMultiKickerManaCost(); + for (int i = 0; i < timesMultikicked; i++) { + toPay.addManaCost(mkCost); + } + } + + Integer replicate = ability.getSVarInt("Replicate"); + if (replicate != null) { + ManaCost rCost = source.getManaCost(); + for (int i = 0; i < replicate; i++) { + toPay.addManaCost(rCost); + } + } + + if (isActivatedSa) { + ManaCostAdjustment.adjust(toPay, ability, false); + } InputPayMana inpPayment; if (ability.isOffering() && ability.getSacrificedAsOffering() == null) { @@ -726,20 +732,7 @@ public class HumanPlay { source.setColorsPaid(toPay.getColorsPaid()); source.setSunburstValue(toPay.getSunburst()); - } - if (mc.getAmountOfX() > 0) { - if (!ability.isAnnouncing("X") && !xWasBilled) { - source.setXManaCostPaid(0); - inpPayment = new InputPayManaX(controller, ability, mc.getAmountOfX(), mc.canXbe0()); - inpPayment.showAndWait(); - if (!inpPayment.isPaid()) { - return false; - } - } else { - int x = AbilityUtils.calculateAmount(source, "X", ability); - source.setXManaCostPaid(x); - } - } + } // Handle convoke and offerings if (ability.isOffering() && ability.getSacrificedAsOffering() != null) { diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index a4c3c4a27c2..57ba0f3d1ac 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -196,40 +196,59 @@ public class HumanPlaySpellAbility { } private boolean announceValuesLikeX() { + if (ability.isCopied()) { return true; } //don't re-announce for spell copies + + boolean needX = true; + boolean allowZero = !ability.hasParam("XCantBe0"); + CostPartMana manaCost = ability.getPayCosts().getCostMana(); + PlayerController controller = ability.getActivatingPlayer().getController(); + Card card = ability.getHostCard(); + // Announcing Requirements like Choosing X or Multikicker // SA Params as comma delimited list String announce = ability.getParam("Announce"); if (announce != null) { - for(String aVar : announce.split(",")) { + for (String aVar : announce.split(",")) { String varName = aVar.trim(); boolean isX = "X".equalsIgnoreCase(varName); - CostPartMana manaCost = ability.getPayCosts().getCostMana(); - boolean allowZero = !ability.hasParam("XCantBe0") && (!isX || manaCost == null || manaCost.canXbe0()); + if (isX) { needX = false; } - Integer value = ability.getActivatingPlayer().getController().announceRequirements(ability, varName, allowZero); + Integer value = controller.announceRequirements(ability, varName, allowZero && (!isX || manaCost == null || manaCost.canXbe0())); if (value == null) { return false; } ability.setSVar(varName, value.toString()); if ("Multikicker".equals(varName)) { - ability.getHostCard().setKickerMagnitude(value); + card.setKickerMagnitude(value); } else { - ability.getHostCard().setSVar(varName, value.toString()); + card.setSVar(varName, value.toString()); } } } + + if (needX && manaCost != null && manaCost.getAmountOfX() > 0) { + Integer value = controller.announceRequirements(ability, "X", allowZero && manaCost.canXbe0()); + if (value == null) { + return false; + } + + ability.setSVar("X", value.toString()); + card.setSVar("X", value.toString()); + } return true; } + // Announcing Requirements like choosing creature type or number private boolean announceType() { - // Announcing Requirements like choosing creature type or number + if (ability.isCopied()) { return true; } //don't re-announce for spell copies + String announce = ability.getParam("AnnounceType"); PlayerController pc = ability.getActivatingPlayer().getController(); if (announce != null) { - for(String aVar : announce.split(",")) { + for (String aVar : announce.split(",")) { String varName = aVar.trim(); if ("CreatureType".equals(varName)) { String choice = pc.chooseSomeType("Creature", ability, CardType.getCreatureTypes(), new ArrayList()); diff --git a/forge-gui/src/main/java/forge/quest/QuestUtilCards.java b/forge-gui/src/main/java/forge/quest/QuestUtilCards.java index 55f4ec20ecd..d576020ab0a 100644 --- a/forge-gui/src/main/java/forge/quest/QuestUtilCards.java +++ b/forge-gui/src/main/java/forge/quest/QuestUtilCards.java @@ -665,11 +665,12 @@ public final class QuestUtilCards { final int startPacks = this.qpref.getPrefInt(QPref.SHOP_STARTING_PACKS); final int winsForPack = this.qpref.getPrefInt(QPref.SHOP_WINS_FOR_ADDITIONAL_PACK); final int maxPacks = this.qpref.getPrefInt(QPref.SHOP_MAX_PACKS); + final int minPacks = this.qpref.getPrefInt(QPref.SHOP_MIN_PACKS); int level = this.qc.getAchievements().getLevel(); final int levelPacks = level > 0 ? startPacks / level : startPacks; final int winPacks = this.qc.getAchievements().getWin() / winsForPack; - final int totalPacks = Math.min(levelPacks + winPacks, maxPacks); + final int totalPacks = Math.min(Math.max(levelPacks + winPacks, minPacks), maxPacks); SealedProduct.Template tpl = getShopBoosterTemplate(); diff --git a/forge-gui/src/main/java/forge/quest/data/QuestPreferences.java b/forge-gui/src/main/java/forge/quest/data/QuestPreferences.java index 8a37372003e..a9595a2b1bf 100644 --- a/forge-gui/src/main/java/forge/quest/data/QuestPreferences.java +++ b/forge-gui/src/main/java/forge/quest/data/QuestPreferences.java @@ -135,7 +135,8 @@ public class QuestPreferences extends PreferencesStore i WINS_UNLOCK_SET("20"), // Maximum amount of "Packs" opened by the Shop and available as singles - SHOP_MAX_PACKS("6"), + SHOP_MAX_PACKS("7"), + SHOP_MIN_PACKS("3"), // Rarity distribution of Singles in an Opened Shop Pack SHOP_SINGLES_COMMON("7"), @@ -145,7 +146,7 @@ public class QuestPreferences extends PreferencesStore i // How many wins it takes to open an additional pack in the shop SHOP_WINS_FOR_ADDITIONAL_PACK("10"), // How many packs the shop start with. - SHOP_STARTING_PACKS("4"); + SHOP_STARTING_PACKS("5"); private final String strDefaultVal; @@ -313,7 +314,7 @@ public class QuestPreferences extends PreferencesStore i return "Value too large (maximum 15)."; } break; - case SHOP_WINS_FOR_ADDITIONAL_PACK: case SHOP_MAX_PACKS: + case SHOP_WINS_FOR_ADDITIONAL_PACK: case SHOP_MAX_PACKS: case SHOP_MIN_PACKS: if (val < 1) { return "Value too small (minimum 1)."; } else if (val > 25) { diff --git a/forge-gui/src/main/java/forge/util/GuiDisplayUtil.java b/forge-gui/src/main/java/forge/util/GuiDisplayUtil.java index 5a857542b47..35d73f4b2bf 100644 --- a/forge-gui/src/main/java/forge/util/GuiDisplayUtil.java +++ b/forge-gui/src/main/java/forge/util/GuiDisplayUtil.java @@ -33,5 +33,5 @@ public final class GuiDisplayUtil { final String playerName = FModel.getPreferences().getPref(FPref.PLAYER_NAME); return text.replaceAll("(?i)human", playerName); } - + } // end class GuiDisplayUtil diff --git a/forge-gui/src/main/java/forge/util/gui/SGuiChoose.java b/forge-gui/src/main/java/forge/util/gui/SGuiChoose.java index c1874e66171..462ae03238e 100644 --- a/forge-gui/src/main/java/forge/util/gui/SGuiChoose.java +++ b/forge-gui/src/main/java/forge/util/gui/SGuiChoose.java @@ -99,12 +99,15 @@ public class SGuiChoose { // Get Integer in range public static Integer getInteger(final IGuiBase gui, final String message) { - return getInteger(gui, message, 0, Integer.MAX_VALUE); + return getInteger(gui, message, 0, Integer.MAX_VALUE, false); } public static Integer getInteger(final IGuiBase gui, final String message, int min) { - return getInteger(gui, message, min, Integer.MAX_VALUE); + return getInteger(gui, message, min, Integer.MAX_VALUE, false); } public static Integer getInteger(final IGuiBase gui, final String message, int min, int max) { + return getInteger(gui, message, min, max, false); + } + public static Integer getInteger(final IGuiBase gui, final String message, int min, int max, boolean sortDesc) { if (max <= min) { return min; } //just return min if max <= min //force cutting off after 100 numbers at most @@ -117,8 +120,15 @@ public class SGuiChoose { } final Integer[] choices = new Integer[count]; - for (int i = 0; i < count; i++) { - choices[i] = Integer.valueOf(i + min); + if (sortDesc) { + for (int i = 0; i < count; i++) { + choices[count - i - 1] = Integer.valueOf(i + min); + } + } + else { + for (int i = 0; i < count; i++) { + choices[i] = Integer.valueOf(i + min); + } } return SGuiChoose.oneOrNone(gui, message, choices); }