diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index bd12ad6c7e0..8ab5228fc26 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -1063,6 +1063,10 @@ public class ComputerUtil { return true; } + if (cardState.hasKeyword(Keyword.EXALTED) || cardState.hasKeyword(Keyword.EXTORT)) { + return true; + } + if (cardState.hasKeyword(Keyword.RIOT) && ChooseGenericEffectAi.preferHasteForRiot(sa, ai)) { // Planning to choose Haste for Riot, so do this in Main 1 return true; @@ -1070,6 +1074,7 @@ public class ComputerUtil { // if we have non-persistent mana in our pool, would be good to try to use it and not waste it if (ai.getManaPool().willManaBeLostAtEndOfPhase()) { + // TODO should check if some will be kept and skip those boolean canUseToPayCost = false; for (byte color : ManaAtom.MANATYPES) { // tries to reuse any amount of colorless if cost only has generic @@ -1089,10 +1094,6 @@ public class ComputerUtil { return true; } - if (cardState.hasKeyword(Keyword.EXALTED) || cardState.hasKeyword(Keyword.EXTORT)) { - return true; - } - //cast equipments in Main1 when there are creatures to equip and no other unequipped equipment if (card.isEquipment()) { boolean playNow = false; diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index f08f5a69332..87cc9c397b7 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -93,6 +93,7 @@ public class ComputerUtilMana { ability.setActivatingPlayer(card.getController(), true); if (ability.isManaAbility()) { score += ability.calculateScoreForManaAbility(); + // TODO check TriggersWhenSpent } else if (!ability.isTrigger() && ability.isPossible()) { score += 13; //add 13 for any non-mana activated abilities @@ -393,9 +394,9 @@ public class ComputerUtilMana { String manaProduced = toPay.isSnow() && hostCard.isSnow() ? "S" : GameActionUtil.generatedTotalMana(saPayment); //String originalProduced = manaProduced; - final Map repParams = AbilityKey.mapFromPlayer(ai); + final Map repParams = AbilityKey.mapFromAffected(hostCard); repParams.put(AbilityKey.Mana, manaProduced); - repParams.put(AbilityKey.Affected, hostCard); + repParams.put(AbilityKey.Activator, ai); repParams.put(AbilityKey.AbilityMana, saPayment); // RootAbility // TODO Damping Sphere might replace later? @@ -1614,9 +1615,9 @@ public class ComputerUtilMana { // setup produce mana replacement effects String origin = mp.getOrigProduced(); - final Map repParams = AbilityKey.mapFromPlayer(ai); + final Map repParams = AbilityKey.mapFromAffected(sourceCard); repParams.put(AbilityKey.Mana, origin); - repParams.put(AbilityKey.Affected, sourceCard); + repParams.put(AbilityKey.Activator, ai); repParams.put(AbilityKey.AbilityMana, m); // RootAbility List reList = game.getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other); diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java index d677c7fc8f0..49d9697ce9f 100644 --- a/forge-ai/src/main/java/forge/ai/GameState.java +++ b/forge-ai/src/main/java/forge/ai/GameState.java @@ -7,6 +7,7 @@ import java.util.*; import java.util.Map.Entry; import com.google.common.collect.*; +import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import forge.StaticData; @@ -52,27 +53,24 @@ public abstract class GameState { ZONES.put(ZoneType.Sideboard, "sideboard"); } - private int humanLife = -1; - private int computerLife = -1; - private String humanCounters = ""; - private String computerCounters = ""; - private String humanManaPool = ""; - private String computerManaPool = ""; - private String humanPersistentMana = ""; - private String computerPersistentMana = ""; - private int humanLandsPlayed = 0; - private int computerLandsPlayed = 0; - private int humanLandsPlayedLastTurn = 0; - private int computerLandsPlayedLastTurn = 0; + static class PlayerState { + private int life = -1; + private String counters = ""; + private String manaPool = ""; + private String persistentMana = ""; + private int landsPlayed = 0; + private int landsPlayedLastTurn = 0; + private String precast = null; + private String putOnStack = null; + private final Map cardTexts = new EnumMap<>(ZoneType.class); + } + private final List playerStates = new ArrayList<>(); private boolean puzzleCreatorState = false; - private final Map humanCardTexts = new EnumMap<>(ZoneType.class); - private final Map aiCardTexts = new EnumMap<>(ZoneType.class); - private final Map idToCard = new HashMap<>(); private final Map cardToAttachId = new HashMap<>(); - private final Map cardToEnchantPlayerId = new HashMap<>(); + private final Map cardToEnchantPlayerId = new HashMap<>(); private final Map markedDamage = new HashMap<>(); private final Map> cardToChosenClrs = new HashMap<>(); private final Map cardToChosenCards = new HashMap<>(); @@ -98,12 +96,6 @@ public abstract class GameState { private String tAdvancePhase = "NONE"; - private String precastHuman = null; - private String precastAI = null; - - private String putOnStackHuman = null; - private String putOnStackAI = null; - private int turn = 1; private boolean removeSummoningSickness = false; @@ -134,32 +126,27 @@ public abstract class GameState { sb.append("[state]\n"); } - sb.append(TextUtil.concatNoSpace("humanlife=", String.valueOf(humanLife), "\n")); - sb.append(TextUtil.concatNoSpace("ailife=", String.valueOf(computerLife), "\n")); - sb.append(TextUtil.concatNoSpace("humanlandsplayed=", String.valueOf(humanLandsPlayed), "\n")); - sb.append(TextUtil.concatNoSpace("ailandsplayed=", String.valueOf(computerLandsPlayed), "\n")); - sb.append(TextUtil.concatNoSpace("humanlandsplayedlastturn=", String.valueOf(humanLandsPlayedLastTurn), "\n")); - sb.append(TextUtil.concatNoSpace("ailandsplayedlastturn=", String.valueOf(computerLandsPlayedLastTurn), "\n")); sb.append(TextUtil.concatNoSpace("turn=", String.valueOf(turn), "\n")); - - if (!humanCounters.isEmpty()) { - sb.append(TextUtil.concatNoSpace("humancounters=", humanCounters, "\n")); - } - if (!computerCounters.isEmpty()) { - sb.append(TextUtil.concatNoSpace("aicounters=", computerCounters, "\n")); - } - - if (!humanManaPool.isEmpty()) { - sb.append(TextUtil.concatNoSpace("humanmanapool=", humanManaPool, "\n")); - } - if (!computerManaPool.isEmpty()) { - sb.append(TextUtil.concatNoSpace("aimanapool=", computerManaPool, "\n")); - } - sb.append(TextUtil.concatNoSpace("activeplayer=", tChangePlayer, "\n")); sb.append(TextUtil.concatNoSpace("activephase=", tChangePhase, "\n")); - appendCards(humanCardTexts, "human", sb); - appendCards(aiCardTexts, "ai", sb); + + int playerIndex = 0; + for (PlayerState p : playerStates) { + String prefix = "p" + playerIndex++; + sb.append(TextUtil.concatNoSpace(prefix + "life=", String.valueOf(p.life), "\n")); + sb.append(TextUtil.concatNoSpace(prefix + "landsplayed=", String.valueOf(p.landsPlayed), "\n")); + sb.append(TextUtil.concatNoSpace(prefix + "landsplayedlastturn=", String.valueOf(p.landsPlayedLastTurn), "\n")); + if (!p.counters.isEmpty()) { + sb.append(TextUtil.concatNoSpace(prefix + "counters=", p.counters, "\n")); + } + if (!p.manaPool.isEmpty()) { + sb.append(TextUtil.concatNoSpace(prefix + "manapool=", p.manaPool, "\n")); + } + if (!p.persistentMana.isEmpty()) { + sb.append(TextUtil.concatNoSpace(prefix + "persistentmana=", p.persistentMana, "\n")); + } + appendCards(p.cardTexts, prefix, sb); + } return sb.toString(); } @@ -169,33 +156,21 @@ public abstract class GameState { } } - public void initFromGame(Game game) throws Exception { - FCollectionView players = game.getPlayers(); - // Can only serialize a two player game with one AI and one human. - if (players.size() != 2) { - throw new Exception("Game not supported"); + public void initFromGame(Game game) { + playerStates.clear(); + for (Player player : game.getPlayers()) { + PlayerState p = new PlayerState(); + p.life = player.getLife(); + p.landsPlayed = player.getLandsPlayedThisTurn(); + p.landsPlayedLastTurn = player.getLandsPlayedLastTurn(); + p.counters = countersToString(player.getCounters()); + p.manaPool = processManaPool(player.getManaPool()); + playerStates.add(p); } - final Player human = game.getPlayers().get(0); - final Player ai = game.getPlayers().get(1); - if (!human.getController().isGuiPlayer() || !ai.getController().isAI()) { - throw new Exception("Game not supported"); - } - humanLife = human.getLife(); - computerLife = ai.getLife(); - humanLandsPlayed = human.getLandsPlayedThisTurn(); - computerLandsPlayed = ai.getLandsPlayedThisTurn(); - humanLandsPlayedLastTurn = human.getLandsPlayedLastTurn(); - computerLandsPlayedLastTurn = ai.getLandsPlayedLastTurn(); - humanCounters = countersToString(human.getCounters()); - computerCounters = countersToString(ai.getCounters()); - humanManaPool = processManaPool(human.getManaPool()); - computerManaPool = processManaPool(ai.getManaPool()); - tChangePlayer = game.getPhaseHandler().getPlayerTurn() == ai ? "ai" : "human"; + tChangePlayer = "p" + game.getPlayers().indexOf(game.getPhaseHandler().getPlayerTurn()); tChangePhase = game.getPhaseHandler().getPhase().toString(); turn = game.getPhaseHandler().getTurn(); - aiCardTexts.clear(); - humanCardTexts.clear(); // Mark the cards that need their ID remembered for various reasons cardsReferencedByID.clear(); @@ -238,8 +213,9 @@ public abstract class GameState { for (ZoneType zone : ZONES.keySet()) { // Init texts to empty, so that restoring will clear the state // if the zone had no cards in it (e.g. empty hand). - aiCardTexts.put(zone, ""); - humanCardTexts.put(zone, ""); + for (PlayerState p : playerStates) { + p.cardTexts.put(zone, ""); + } for (Card card : game.getCardsIncludePhasingIn(zone)) { if (card.getName().equals("Puzzle Goal") && card.getOracleText().contains("New Puzzle")) { puzzleCreatorState = true; @@ -247,18 +223,35 @@ public abstract class GameState { if (card instanceof DetachedCardEffect) { continue; } - addCard(zone, card.getController() == ai ? aiCardTexts : humanCardTexts, card); + int playerIndex = game.getPlayers().indexOf(card.getController()); + addCard(zone, playerStates.get(playerIndex).cardTexts, card); } } } + private String getPlayerString(Player p) { + return "P" + p.getGame().getPlayers().indexOf(p); + } + + private Player parsePlayerString(Game game, String str) { + if (str.equalsIgnoreCase("HUMAN")) { + return game.getPlayers().get(0); + } else if (str.equalsIgnoreCase("AI")) { + return game.getPlayers().get(1); + } else if (str.startsWith("P") && Character.isDigit(str.charAt(1))) { + return game.getPlayers().get(Integer.parseInt(String.valueOf(str.charAt(1)))); + } else { + return game.getPlayers().get(0); + } + } + private void addCard(ZoneType zoneType, Map cardTexts, Card c) { StringBuilder newText = new StringBuilder(cardTexts.get(zoneType)); if (newText.length() > 0) { newText.append(";"); } if (c.isToken()) { - newText.append("t:").append(new TokenInfo(c).toString()); + newText.append("t:").append(new TokenInfo(c)); } else { if (c.getPaperCard() == null) { return; @@ -281,8 +274,7 @@ public abstract class GameState { if (zoneType == ZoneType.Battlefield) { if (c.getOwner() != c.getController()) { - // TODO: Handle more than 2-player games. - newText.append("|Owner:" + (c.getOwner().isAI() ? "AI" : "Human")); + newText.append("|Owner:").append(getPlayerString(c.getOwner())); } if (c.isTapped()) { newText.append("|Tapped"); @@ -298,7 +290,7 @@ public abstract class GameState { } if (c.isPhasedOut()) { newText.append("|PhasedOut:"); - newText.append(c.getPhasedOut().isAI() ? "AI" : "HUMAN"); + newText.append(getPlayerString(c.getPhasedOut())); } if (c.isFaceDown()) { newText.append("|FaceDown"); @@ -319,10 +311,8 @@ public abstract class GameState { } if (c.getPlayerAttachedTo() != null) { - // TODO: improve this for game states with more than two players newText.append("|EnchantingPlayer:"); - Player p = c.getPlayerAttachedTo(); - newText.append(p.getController().isAI() ? "AI" : "HUMAN"); + newText.append(getPlayerString(c.getPlayerAttachedTo())); } else if (c.isAttachedToEntity()) { newText.append("|AttachedTo:").append(c.getEntityAttachedTo().getId()); } @@ -348,11 +338,8 @@ public abstract class GameState { } List chosenCardIds = Lists.newArrayList(); - for (Object obj : c.getChosenCards()) { - if (obj instanceof Card) { - int id = ((Card)obj).getId(); - chosenCardIds.add(String.valueOf(id)); - } + for (Card obj : c.getChosenCards()) { + chosenCardIds.add(String.valueOf(obj.getId())); } if (!chosenCardIds.isEmpty()) { newText.append("|ChosenCards:").append(TextUtil.join(chosenCardIds, ",")); @@ -465,16 +452,36 @@ public abstract class GameState { public void parse(InputStream in) throws Exception { final BufferedReader br = new BufferedReader(new InputStreamReader(in)); - - String line; - while ((line = br.readLine()) != null) { - parseLine(line); - } + parse(br.lines()); } public void parse(List lines) { - for (String line : lines) { - parseLine(line); + parse(lines.stream()); + } + + public void parse(Stream lines) { + playerStates.clear(); + lines.forEach(this::parseLine); + } + + + private PlayerState getPlayerState(int index) { + while (index >= playerStates.size()) { + playerStates.add(new PlayerState()); + } + return playerStates.get(index); + } + + private PlayerState getPlayerState(String key) { + if (key.startsWith("human")) { + return getPlayerState(0); + } else if (key.startsWith("ai")) { + return getPlayerState(1); + } else if (key.startsWith("p") && Character.isDigit(key.charAt(1))) { + return getPlayerState(Integer.parseInt(String.valueOf(key.charAt(1)))); + } else { + System.err.println("Unknown player state key: " + key); + return new PlayerState(); } } @@ -495,142 +502,56 @@ public abstract class GameState { return; } - boolean isHuman = categoryName.startsWith("human"); - if (categoryName.equals("turn")) { turn = Integer.parseInt(categoryValue); - } - - else if (categoryName.equals("removesummoningsickness")) { + } else if (categoryName.equals("removesummoningsickness")) { removeSummoningSickness = categoryValue.equalsIgnoreCase("true"); - } - - else if (categoryName.endsWith("life")) { - if (isHuman) - humanLife = Integer.parseInt(categoryValue); - else - computerLife = Integer.parseInt(categoryValue); - } - - else if (categoryName.endsWith("counters")) { - if (isHuman) - humanCounters = categoryValue; - else - computerCounters = categoryValue; - } - - else if (categoryName.endsWith("landsplayed")) { - if (isHuman) - humanLandsPlayed = Integer.parseInt(categoryValue); - else - computerLandsPlayed = Integer.parseInt(categoryValue); - } - - else if (categoryName.endsWith("landsplayedlastturn")) { - if (isHuman) - humanLandsPlayedLastTurn = Integer.parseInt(categoryValue); - else - computerLandsPlayedLastTurn = Integer.parseInt(categoryValue); - } - - else if (categoryName.endsWith("play") || categoryName.endsWith("battlefield")) { - if (isHuman) - humanCardTexts.put(ZoneType.Battlefield, categoryValue); - else - aiCardTexts.put(ZoneType.Battlefield, categoryValue); - } - - else if (categoryName.endsWith("hand")) { - if (isHuman) - humanCardTexts.put(ZoneType.Hand, categoryValue); - else - aiCardTexts.put(ZoneType.Hand, categoryValue); - } - - else if (categoryName.endsWith("graveyard")) { - if (isHuman) - humanCardTexts.put(ZoneType.Graveyard, categoryValue); - else - aiCardTexts.put(ZoneType.Graveyard, categoryValue); - } - - else if (categoryName.endsWith("library")) { - if (isHuman) - humanCardTexts.put(ZoneType.Library, categoryValue); - else - aiCardTexts.put(ZoneType.Library, categoryValue); - } - - else if (categoryName.endsWith("exile")) { - if (isHuman) - humanCardTexts.put(ZoneType.Exile, categoryValue); - else - aiCardTexts.put(ZoneType.Exile, categoryValue); - } - - else if (categoryName.endsWith("command")) { - if (isHuman) - humanCardTexts.put(ZoneType.Command, categoryValue); - else - aiCardTexts.put(ZoneType.Command, categoryValue); - } - - else if (categoryName.endsWith("sideboard")) { - if (isHuman) - humanCardTexts.put(ZoneType.Sideboard, categoryValue); - else - aiCardTexts.put(ZoneType.Sideboard, categoryValue); - } - - else if (categoryName.startsWith("ability")) { + } else if (categoryName.endsWith("life")) { + getPlayerState(categoryName).life = Integer.parseInt(categoryValue); + } else if (categoryName.endsWith("counters")) { + getPlayerState(categoryName).counters = categoryValue; + } else if (categoryName.endsWith("landsplayed")) { + getPlayerState(categoryName).landsPlayed = Integer.parseInt(categoryValue); + } else if (categoryName.endsWith("landsplayedlastturn")) { + getPlayerState(categoryName).landsPlayedLastTurn = Integer.parseInt(categoryValue); + } else if (categoryName.endsWith("play") || categoryName.endsWith("battlefield")) { + getPlayerState(categoryName).cardTexts.put(ZoneType.Battlefield, categoryValue); + } else if (categoryName.endsWith("hand")) { + getPlayerState(categoryName).cardTexts.put(ZoneType.Hand, categoryValue); + } else if (categoryName.endsWith("graveyard")) { + getPlayerState(categoryName).cardTexts.put(ZoneType.Graveyard, categoryValue); + } else if (categoryName.endsWith("library")) { + getPlayerState(categoryName).cardTexts.put(ZoneType.Library, categoryValue); + } else if (categoryName.endsWith("exile")) { + getPlayerState(categoryName).cardTexts.put(ZoneType.Exile, categoryValue); + } else if (categoryName.endsWith("command")) { + getPlayerState(categoryName).cardTexts.put(ZoneType.Command, categoryValue); + } else if (categoryName.endsWith("sideboard")) { + getPlayerState(categoryName).cardTexts.put(ZoneType.Sideboard, categoryValue); + } else if (categoryName.startsWith("ability")) { abilityString.put(categoryName.substring("ability".length()), categoryValue); - } - - else if (categoryName.endsWith("precast")) { - if (isHuman) - precastHuman = categoryValue; - else - precastAI = categoryValue; - } - - else if (categoryName.endsWith("putonstack")) { - if (isHuman) - putOnStackHuman = categoryValue; - else - putOnStackAI = categoryValue; - } - - else if (categoryName.endsWith("manapool")) { - if (isHuman) - humanManaPool = categoryValue; - else - computerManaPool = categoryValue; - } - - else if (categoryName.endsWith("persistentmana")) { - if (isHuman) - humanPersistentMana = categoryValue; - else - computerPersistentMana = categoryValue; - } - - else { - System.out.println("Unknown key: " + categoryName); + } else if (categoryName.endsWith("precast")) { + getPlayerState(categoryName).precast = categoryValue; + } else if (categoryName.endsWith("putonstack")) { + getPlayerState(categoryName).putOnStack = categoryValue; + } else if (categoryName.endsWith("manapool")) { + getPlayerState(categoryName).manaPool = categoryValue; + } else if (categoryName.endsWith("persistentmana")) { + getPlayerState(categoryName).persistentMana = categoryValue; + } else { + System.err.println("Unknown key: " + categoryName); } } public void applyToGame(final Game game) { - game.getAction().invoke(new Runnable() { - @Override - public void run() { - applyGameOnThread(game); - } - }); + game.getAction().invoke(() -> applyGameOnThread(game)); } protected void applyGameOnThread(final Game game) { - final Player human = game.getPlayers().get(0); - final Player ai = game.getPlayers().get(1); + if (game.getPlayers().size() != playerStates.size()) { + throw new RuntimeException("Non-matching number of players, (" + + game.getPlayers().size() + " vs. " + playerStates.size() + ")"); + } idToCard.clear(); cardToAttachId.clear(); @@ -647,32 +568,21 @@ public abstract class GameState { cardToScript.clear(); cardAttackMap.clear(); - Player newPlayerTurn = tChangePlayer.equalsIgnoreCase("human") ? human : tChangePlayer.equalsIgnoreCase("ai") ? ai : null; + int playerTurn = playerStates.indexOf(getPlayerState(tChangePlayer)); + Player newPlayerTurn = game.getPlayers().get(playerTurn); PhaseType newPhase = tChangePhase.equalsIgnoreCase("none") ? null : PhaseType.smartValueOf(tChangePhase); PhaseType advPhase = tAdvancePhase.equalsIgnoreCase("none") ? null : PhaseType.smartValueOf(tAdvancePhase); // Set stack to resolving so things won't trigger/effects be checked right away game.getStack().setResolving(true); - updateManaPool(human, humanManaPool, true, false); - updateManaPool(ai, computerManaPool, true, false); - updateManaPool(human, humanPersistentMana, false, true); - updateManaPool(ai, computerPersistentMana, false, true); - - if (!humanCounters.isEmpty()) { - applyCountersToGameEntity(human, humanCounters); - } - if (!computerCounters.isEmpty()) { - applyCountersToGameEntity(ai, computerCounters); - } - game.getPhaseHandler().devModeSet(newPhase, newPlayerTurn, turn); game.getTriggerHandler().setSuppressAllTriggers(true); - setupPlayerState(humanLife, humanCardTexts, human, humanLandsPlayed, humanLandsPlayedLastTurn); - setupPlayerState(computerLife, aiCardTexts, ai, computerLandsPlayed, computerLandsPlayedLastTurn); - + for (int i = 0; i < playerStates.size(); i++) { + setupPlayerState(game.getPlayers().get(i), playerStates.get(i)); + } handleCardAttachments(); handleChosenEntities(); handleRememberedEntities(); @@ -712,10 +622,11 @@ public abstract class GameState { // Set negative or zero life after state effects if need be, important for some puzzles that rely on // pre-setting negative life (e.g. PS_NEO4). - if (humanLife <= 0) { - human.setLife(humanLife, null); - } else if (computerLife <= 0) { - ai.setLife(computerLife, null); + for (int i = 0; i < playerStates.size(); i++) { + int life = playerStates.get(i).life; + if (life <= 0) { + game.getPlayers().get(i).setLife(life, null); + } } } @@ -746,12 +657,7 @@ public abstract class GameState { produced.put("PersistentMana", "True"); } final AbilityManaPart abMana = new AbilityManaPart(dummy, produced); - game.getAction().invoke(new Runnable() { - @Override - public void run() { - abMana.produceMana(null); - } - }); + game.getAction().invoke(() -> abMana.produceMana(null)); } } @@ -832,8 +738,7 @@ public abstract class GameState { } private int parseTargetInScript(final String tgtDef) { - int tgtID = TARGET_NONE; - + int tgtID; if (tgtDef.equalsIgnoreCase("human")) { tgtID = TARGET_HUMAN; } else if (tgtDef.equalsIgnoreCase("ai")) { @@ -972,37 +877,23 @@ public abstract class GameState { } private void handlePrecastSpells(final Game game) { - Player human = game.getPlayers().get(0); - Player ai = game.getPlayers().get(1); - - if (precastHuman != null) { - String[] spellList = TextUtil.split(precastHuman, ';'); - for (String spell : spellList) { - precastSpellFromCard(spell, human, game); - } - } - if (precastAI != null) { - String[] spellList = TextUtil.split(precastAI, ';'); - for (String spell : spellList) { - precastSpellFromCard(spell, ai, game); + for (int i = 0; i < playerStates.size(); i++) { + if (playerStates.get(i).precast != null) { + String[] spellList = TextUtil.split(playerStates.get(i).precast, ';'); + for (String spell : spellList) { + precastSpellFromCard(spell, game.getPlayers().get(i), game); + } } } } private void handleAddSAsToStack(final Game game) { - Player human = game.getPlayers().get(0); - Player ai = game.getPlayers().get(1); - - if (putOnStackHuman != null) { - String[] spellList = TextUtil.split(putOnStackHuman, ';'); - for (String spell : spellList) { - precastSpellFromCard(spell, human, game, true); - } - } - if (putOnStackAI != null) { - String[] spellList = TextUtil.split(putOnStackAI, ';'); - for (String spell : spellList) { - precastSpellFromCard(spell, ai, game, true); + for (int i = 0; i < playerStates.size(); i++) { + if (playerStates.get(i).putOnStack != null) { + String[] spellList = TextUtil.split(playerStates.get(i).putOnStack, ';'); + for (String spell : spellList) { + precastSpellFromCard(spell, game.getPlayers().get(i), game, true); + } } } } @@ -1138,14 +1029,9 @@ public abstract class GameState { } } - // Enchant players by ID - for (Entry entry : cardToEnchantPlayerId.entrySet()) { - // TODO: improve this for game states with more than two players - Card attacher = entry.getKey(); - Game game = attacher.getGame(); - Player attachedTo = entry.getValue() == TARGET_AI ? game.getPlayers().get(1) : game.getPlayers().get(0); - - attacher.attachToEntity(attachedTo, null); + // Enchant players + for (Entry entry : cardToEnchantPlayerId.entrySet()) { + entry.getKey().attachToEntity(entry.getValue(), null); } } @@ -1184,7 +1070,7 @@ public abstract class GameState { top.removeCloneState(top.getMutatedTimestamp()); } - final Long ts = game.getNextTimestamp(); + final long ts = game.getNextTimestamp(); top.setMutatedTimestamp(ts); if (top.getCurrentStateName() != CardStateName.FaceDown) { final CardCloneStates mutatedStates = CardFactory.getMutatedCloneStates(top, null/*FIXME*/); @@ -1207,7 +1093,7 @@ public abstract class GameState { } } - private void setupPlayerState(int life, Map cardTexts, final Player p, final int landsPlayed, final int landsPlayedLastTurn) { + private void setupPlayerState(final Player p, final PlayerState state) { // Lock check static as we setup player state // Clear all zones first, this ensures that any lingering cards and effects (e.g. in command zone) get cleared up @@ -1219,14 +1105,14 @@ public abstract class GameState { p.setCommanders(Lists.newArrayList()); Map playerCards = new EnumMap<>(ZoneType.class); - for (Entry kv : cardTexts.entrySet()) { + for (Entry kv : state.cardTexts.entrySet()) { String value = kv.getValue(); playerCards.put(kv.getKey(), processCardsForZone(value.isEmpty() ? new String[0] : value.split(";"), p)); } - if (life >= 0) p.setLife(life, null); - p.setLandsPlayedThisTurn(landsPlayed); - p.setLandsPlayedLastTurn(landsPlayedLastTurn); + if (state.life >= 0) p.setLife(state.life, null); + p.setLandsPlayedThisTurn(state.landsPlayed); + p.setLandsPlayedLastTurn(state.landsPlayedLastTurn); p.clearPaidForSA(); @@ -1275,6 +1161,13 @@ public abstract class GameState { for (Card cmd : p.getCommanders()) { p.getZone(ZoneType.Command).add(Player.createCommanderEffect(p.getGame(), cmd)); } + + updateManaPool(p, state.manaPool, true, false); + updateManaPool(p, state.persistentMana, false, true); + + if (!state.counters.isEmpty()) { + applyCountersToGameEntity(p, state.counters); + } } /** @@ -1330,9 +1223,7 @@ public abstract class GameState { c.setMonstrous(true); } else if (info.startsWith("PhasedOut")) { String tgt = info.substring(info.indexOf(':') + 1); - Player human = player.getGame().getPlayers().get(0); - Player ai = player.getGame().getPlayers().get(1); - c.setPhasedOut(tgt.equalsIgnoreCase("AI") ? ai : human); + c.setPhasedOut(parsePlayerString(player.getGame(), tgt)); } else if (info.startsWith("Counters:")) { applyCountersToGameEntity(c, info.substring(info.indexOf(':') + 1)); } else if (info.startsWith("SummonSick")) { @@ -1377,16 +1268,12 @@ public abstract class GameState { int id = Integer.parseInt(info.substring(info.indexOf(':') + 1)); cardToAttachId.put(c, id); } else if (info.startsWith("EnchantingPlayer:")) { - // TODO: improve this for game states with more than two players String tgt = info.substring(info.indexOf(':') + 1); - cardToEnchantPlayerId.put(c, tgt.equalsIgnoreCase("AI") ? TARGET_AI : TARGET_HUMAN); + cardToEnchantPlayerId.put(c, parsePlayerString(player.getGame(), tgt)); } else if (info.startsWith("Owner:")) { - // TODO: improve this for game states with more than two players - Player human = player.getGame().getPlayers().get(0); - Player ai = player.getGame().getPlayers().get(1); String owner = info.substring(info.indexOf(':') + 1); Player controller = c.getController(); - c.setOwner(owner.equalsIgnoreCase("AI") ? ai : human); + c.setOwner(parsePlayerString(player.getGame(), owner)); c.setController(controller, c.getGame().getNextTimestamp()); } else if (info.startsWith("Ability:")) { String abString = info.substring(info.indexOf(':') + 1).toLowerCase(); diff --git a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java index 0d6452f4975..69264b310c1 100644 --- a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java @@ -115,8 +115,7 @@ public class EffectAi extends SpellAbilityAi { } randomReturn = true; } else if (logic.equals("ChainVeil")) { - if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2) - || CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Planeswalker").isEmpty()) { + if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2) || ai.getPlaneswalkersInPlay().isEmpty()) { return false; } randomReturn = true; diff --git a/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java b/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java index 22c5a093338..eb8104e47db 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java @@ -266,6 +266,7 @@ public class ManaEffectAi extends SpellAbilityAi { ManaPool mp = ai.getManaPool(); Mana test = null; if (mp.isEmpty()) { + // TODO use color from ability test = new Mana((byte) ManaAtom.COLORLESS, source, null); mp.addMana(test, false); } diff --git a/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java b/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java index b807069b859..891fabb44ba 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java @@ -101,7 +101,7 @@ public class ManifestAi extends SpellAbilityAi { repParams.put(AbilityKey.Origin, card.getZone().getZoneType()); repParams.put(AbilityKey.Destination, ZoneType.Battlefield); repParams.put(AbilityKey.Source, sa.getHostCard()); - List list = game.getReplacementHandler().getReplacementList(ReplacementType.Moved, repParams, ReplacementLayer.Other); + List list = game.getReplacementHandler().getReplacementList(ReplacementType.Moved, repParams, ReplacementLayer.CantHappen); if (!list.isEmpty()) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java index e9223b1b708..61ebcd32291 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -423,7 +423,7 @@ public class PumpAi extends PumpAiBase { } if (sa.hasParam("TargetingPlayer") && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) { - if (ComputerUtilAbility.isFullyTargetable(sa)) { // Volcanic Offering: only prompt if second part can happen too + if (!ComputerUtilAbility.isFullyTargetable(sa)) { // Volcanic Offering: only prompt if second part can happen too return false; } Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0); diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java index 6376ecbf37b..1485e3ed1c9 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java @@ -76,21 +76,23 @@ public class GameCopier { for (RegisteredPlayer p : origPlayers) { newPlayers.add(clonePlayer(p)); } + GameRules currentRules = origGame.getRules(); Match newMatch = new Match(currentRules, newPlayers, origGame.getView().getTitle()); Game newGame = new Game(newPlayers, currentRules, newMatch); + for (int i = 0; i < origGame.getPlayers().size(); i++) { Player origPlayer = origGame.getPlayers().get(i); Player newPlayer = newGame.getPlayers().get(i); newPlayer.setLife(origPlayer.getLife(), null); - newPlayer.setActivateLoyaltyAbilityThisTurn(origPlayer.getActivateLoyaltyAbilityThisTurn()); - for (int j = 0; j < origPlayer.getSpellsCastThisTurn(); j++) - newPlayer.addSpellCastThisTurn(); - newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn()); - newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters())); newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn()); newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn()); newPlayer.setLifeGainedThisTurn(origPlayer.getLifeGainedThisTurn()); + newPlayer.setLifeStartedThisTurnWith(origPlayer.getLifeStartedThisTurnWith()); + newPlayer.setDamageReceivedThisTurn(origPlayer.getDamageReceivedThisTurn()); + newPlayer.setActivateLoyaltyAbilityThisTurn(origPlayer.getActivateLoyaltyAbilityThisTurn()); + newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn()); + newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters())); newPlayer.setBlessing(origPlayer.hasBlessing()); newPlayer.setRevolt(origPlayer.hasRevolt()); newPlayer.setLibrarySearched(origPlayer.getLibrarySearched()); @@ -350,6 +352,7 @@ public class GameCopier { newCard.setPTBoost(c.getPTBoostTable()); // TODO copy by map newCard.setDamage(c.getDamage()); + newCard.setDamageReceivedThisTurn(c.getDamageReceivedThisTurn()); newCard.setChangedCardColors(c.getChangedCardColorsTable()); newCard.setChangedCardColorsCharacterDefining(c.getChangedCardColorsCharacterDefiningTable()); diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java index 0cbd88374b5..2b6b0f120b9 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java @@ -198,13 +198,10 @@ public class GameSimulator { final SpellAbility playingSa = sa; simGame.copyLastState(); - boolean success = ComputerUtil.handlePlayingSpellAbility(aiPlayer, sa, simGame, new Runnable() { - @Override - public void run() { - if (interceptor != null) { - interceptor.announceX(playingSa); - interceptor.chooseTargets(playingSa, GameSimulator.this); - } + boolean success = ComputerUtil.handlePlayingSpellAbility(aiPlayer, sa, simGame, () -> { + if (interceptor != null) { + interceptor.announceX(playingSa); + interceptor.chooseTargets(playingSa, GameSimulator.this); } }); if (!success) { diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java index b2294c0cf39..fbf17632d44 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java @@ -1,5 +1,6 @@ package forge.ai.simulation; +import forge.ai.ComputerUtilAbility; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -16,7 +17,7 @@ import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; public class SpellAbilityChoicesIterator { - private SimulationController controller; + private final SimulationController controller; private Iterator modeIterator; private int[] selectedModes; @@ -34,11 +35,13 @@ public class SpellAbilityChoicesIterator { Card selectedChoice; Score bestScoreForChoice = new Score(Integer.MIN_VALUE); } - private ArrayList choicePoints = new ArrayList<>(); + private final ArrayList choicePoints = new ArrayList<>(); private int incrementedCpIndex = 0; private int cpIndex = -1; private int evalDepth; + // Maps from filtered mode indexes to original ones. + private List modesMap; public SpellAbilityChoicesIterator(SimulationController controller) { this.controller = controller; @@ -46,17 +49,28 @@ public class SpellAbilityChoicesIterator { public List chooseModesForAbility(List choices, int min, int num, boolean allowRepeat) { if (modeIterator == null) { - // TODO: Need to skip modes that are invalid (e.g. targets don't exist)! + // Skip modes that don't have legal targets. + modesMap = new ArrayList<>(); + int origIndex = -1; + for (AbilitySub sub : choices) { + origIndex++; + if (!ComputerUtilAbility.isFullyTargetable(sub)) { + continue; + } + modesMap.add(origIndex); + } // TODO: Do we need to do something special to support cards that have extra costs // when choosing more modes, like Blessed Alliance? - if (!allowRepeat) { - modeIterator = CombinatoricsUtils.combinationsIterator(choices.size(), num); + if (modesMap.isEmpty()) { + return null; + } else if (!allowRepeat) { + modeIterator = CombinatoricsUtils.combinationsIterator(modesMap.size(), num); } else { // Note: When allowRepeat is true, it does result in many possibilities being tried. // We should ideally prune some of those at a higher level. - modeIterator = new AllowRepeatModesIterator(choices.size(), min, num); + modeIterator = new AllowRepeatModesIterator(modesMap.size(), min, num); } - selectedModes = modeIterator.next(); + selectedModes = remapModes(modeIterator.next()); advancedToNextMode = true; } // Note: If modeIterator already existed, selectedModes would have been updated in advance(). @@ -78,6 +92,13 @@ public class SpellAbilityChoicesIterator { return result; } + private int[] remapModes(int[] modes) { + for (int i = 0; i < modes.length; i++) { + modes[i] = modesMap.get(modes[i]); + } + return modes; + } + public Card chooseCard(CardCollection fetchList) { cpIndex++; if (cpIndex >= choicePoints.size()) { @@ -86,8 +107,7 @@ public class SpellAbilityChoicesIterator { ChoicePoint cp = choicePoints.get(cpIndex); // Prune duplicates. HashSet uniqueCards = new HashSet<>(); - for (int i = 0; i < fetchList.size(); i++) { - Card card = fetchList.get(i); + for (Card card : fetchList) { if (uniqueCards.add(card.getName()) && uniqueCards.size() == cp.nextChoice + 1) { cp.selectedChoice = card; } @@ -137,14 +157,9 @@ public class SpellAbilityChoicesIterator { evalDepth++; pushTarget = false; } - return; } } - public int[] getSelectModes() { - return selectedModes; - } - public boolean advance(Score lastScore) { cpIndex = -1; for (ChoicePoint cp : choicePoints) { @@ -195,7 +210,7 @@ public class SpellAbilityChoicesIterator { doneEvaluating(bestScoreForMode); bestScoreForMode = new Score(Integer.MIN_VALUE); if (modeIterator.hasNext()) { - selectedModes = modeIterator.next(); + selectedModes = remapModes(modeIterator.next()); advancedToNextMode = true; return true; } @@ -232,8 +247,8 @@ public class SpellAbilityChoicesIterator { } private static class AllowRepeatModesIterator implements Iterator { - private int numChoices; - private int max; + private final int numChoices; + private final int max; private int[] indexes; public AllowRepeatModesIterator(int numChoices, int min, int max) { diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java index 03885c8fd4d..e02e90c6be6 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java @@ -39,6 +39,7 @@ public class SpellAbilityPicker { private SpellAbilityChoicesIterator interceptor; private Plan plan; + private int numSimulations; public SpellAbilityPicker(Game game, Player player) { this.game = game; @@ -66,19 +67,7 @@ public class SpellAbilityPicker { private List getCandidateSpellsAndAbilities() { CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player); List all = ComputerUtilAbility.getSpellAbilities(cards, player); - CardCollection landsToPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player); - if (landsToPlay != null) { - HashMap landsDeDupe = new HashMap<>(); - for (Card land : landsToPlay) { - Card previousLand = landsDeDupe.get(land.getName()); - // Skip identical lands. - if (previousLand != null && previousLand.getZone() == land.getZone() && previousLand.getOwner() == land.getOwner()) { - continue; - } - landsDeDupe.put(land.getName(), land); - all.add(new LandAbility(land)); - } - } + HashMap landsDeDupe = new HashMap<>(); List candidateSAs = ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player); int writeIndex = 0; for (int i = 0; i < candidateSAs.size(); i++) { @@ -86,6 +75,16 @@ public class SpellAbilityPicker { if (sa.isManaAbility()) { continue; } + // Skip identical lands. + if (sa instanceof LandAbility) { + Card land = sa.getHostCard(); + Card previousLand = landsDeDupe.get(sa.getHostCard().getName()); + if (previousLand != null && previousLand.getZone() == land.getZone() && + previousLand.getOwner() == land.getOwner()) { + continue; + } + landsDeDupe.put(land.getName(), land); + } sa.setActivatingPlayer(player, true); AiPlayDecision opinion = canPlayAndPayForSim(sa); @@ -95,7 +94,7 @@ public class SpellAbilityPicker { if (opinion != AiPlayDecision.WillPlay) continue; - candidateSAs.set(writeIndex, sa); + candidateSAs.set(writeIndex, sa); writeIndex++; } candidateSAs.subList(writeIndex, candidateSAs.size()).clear(); @@ -353,7 +352,9 @@ public class SpellAbilityPicker { if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) { return AiPlayDecision.CantAfford; } - + if (!ComputerUtilAbility.isFullyTargetable(sa)) { + return AiPlayDecision.TargetingFailed; + } if (shouldWaitForLater(sa)) { return AiPlayDecision.AnotherTime; } @@ -375,10 +376,13 @@ public class SpellAbilityPicker { final SpellAbilityChoicesIterator choicesIterator = new SpellAbilityChoicesIterator(controller); Score lastScore; do { + // TODO: MyRandom should be an instance on the game object, so that we could do + // simulations in parallel without messing up global state. MyRandom.setRandom(new Random(randomSeedToUse)); GameSimulator simulator = new GameSimulator(controller, game, player, phase); simulator.setInterceptor(choicesIterator); lastScore = simulator.simulateSpellAbility(sa); + numSimulations++; if (lastScore.value > bestScore.value) { bestScore = lastScore; } @@ -462,4 +466,8 @@ public class SpellAbilityPicker { } return ComputerUtil.chooseSacrificeType(player, type, ability, ability.getTargetCard(), effect, amount, exclude); } + + public int getNumSimulations() { + return numSimulations; + } } diff --git a/forge-core/src/main/java/forge/item/PaperCard.java b/forge-core/src/main/java/forge/item/PaperCard.java index 32e9550b253..b835109f866 100644 --- a/forge-core/src/main/java/forge/item/PaperCard.java +++ b/forge-core/src/main/java/forge/item/PaperCard.java @@ -240,6 +240,9 @@ public class PaperCard implements Comparable, InventoryItemFromSet, // cannot still decide, if this "name|set" format is needed anymore // return String.format("%s|%s", name, cardSet); } + public String getCardName() { + return name; + } /* * This (utility) method transform a collectorNumber String into a key string for sorting. diff --git a/forge-core/src/main/java/forge/util/Localizer.java b/forge-core/src/main/java/forge/util/Localizer.java index 173e936ce49..5f244afab4c 100644 --- a/forge-core/src/main/java/forge/util/Localizer.java +++ b/forge-core/src/main/java/forge/util/Localizer.java @@ -159,7 +159,7 @@ public class Localizer { e.printStackTrace(); } - System.out.println("Language '" + resourceBundle.toString() + "' loaded successfully."); + System.out.println("Language '" + resourceBundle.getBaseBundleName() + "' loaded successfully."); notifyObservers(); diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 91c4f25e68c..a51816f5ff4 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -164,8 +164,8 @@ public class GameAction { } if (!found) { c.clearControllers(); - if (c.removeChangedState()) { - c.updateStateForView(); + if (cause != null) { + unanimateOnAbortedChange(cause, c); } return c; } @@ -365,7 +365,18 @@ public class GameAction { copied.getOwner().removeInboundToken(copied); if (repres == ReplacementResult.Prevented) { - if (game.getStack().isResolving(c) && !zoneTo.is(ZoneType.Graveyard)) { + c.clearEtbCounters(); + c.clearControllers(); + if (cause != null) { + unanimateOnAbortedChange(cause, c); + if (cause.hasParam("Transformed") || cause.hasParam("FaceDown")) { + c.setBackSide(false); + c.changeToState(CardStateName.Original); + } + unattachCardLeavingBattlefield(c); + } + + if (c.isInZone(ZoneType.Stack) && !zoneTo.is(ZoneType.Graveyard)) { return moveToGraveyard(c, cause, params); } @@ -373,10 +384,8 @@ public class GameAction { copied.clearDelved(); copied.clearConvoked(); copied.clearExploited(); - } - - // was replaced with another Zone Change - if (toBattlefield && !c.isInPlay()) { + } else if (toBattlefield && !c.isInPlay()) { + // was replaced with another Zone Change if (c.removeChangedState()) { c.updateStateForView(); } @@ -570,6 +579,12 @@ public class GameAction { c.setZone(zoneTo); } + if (fromBattlefield) { + // order here is important so it doesn't unattach cards that might have returned from UntilHostLeavesPlay + unattachCardLeavingBattlefield(copied); + c.runLeavesPlayCommands(); + } + // do ETB counters after zone add if (!suppress && toBattlefield && !copied.getEtbCounters().isEmpty()) { game.getTriggerHandler().registerActiveTrigger(copied, false); @@ -697,7 +712,6 @@ public class GameAction { copied.setState(CardStateName.Original, true); } - unattachCardLeavingBattlefield(copied); } else if (toBattlefield) { for (Player p : game.getPlayers()) { copied.getDamageHistory().setNotAttackedSinceLastUpkeepOf(p); @@ -973,9 +987,15 @@ public class GameAction { if (c.isInZone(ZoneType.Stack)) { c.getGame().getStack().remove(c); } + + final Zone z = c.getZone(); // in some corner cases there's no zone yet (copied spell that failed targeting) - if (c.getZone() != null) { - c.getZone().remove(c); + if (z != null) { + z.remove(c); + if (z.is(ZoneType.Battlefield)) { + c.runLeavesPlayCommands(); + } + } // CR 603.6c other players LTB triggers should work @@ -1036,20 +1056,13 @@ public class GameAction { } game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - for (Player p : game.getPlayers()) { - ((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(false); - } - - final int tiz = c.getTurnInZone(); oldBattlefield.remove(c); newBattlefield.add(c); - c.setSickness(true); if (game.getPhaseHandler().inCombat()) { game.getCombat().removeFromCombat(c); } - c.setTurnInZone(tiz); c.setCameUnderControlSinceLastUpkeep(true); final Map runParams = AbilityKey.mapFromCard(c); @@ -1057,9 +1070,6 @@ public class GameAction { game.getTriggerHandler().runTrigger(TriggerType.ChangesController, runParams, false); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); - for (Player p : game.getPlayers()) { - ((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(true); - } c.runChangeControllerCommands(); } @@ -2560,4 +2570,17 @@ public class GameAction { } return false; } + + private static void unanimateOnAbortedChange(final SpellAbility cause, final Card c) { + if (cause.hasParam("AnimateSubAbility")) { + long unanimateTimestamp = Long.valueOf(cause.getAdditionalAbility("AnimateSubAbility").getSVar("unanimateTimestamp")); + c.removeChangedCardKeywords(unanimateTimestamp, 0); + c.removeChangedCardTypes(unanimateTimestamp, 0); + c.removeChangedName(unanimateTimestamp, 0); + c.removeNewPT(unanimateTimestamp, 0); + if (c.removeChangedCardTraits(unanimateTimestamp, 0)) { + c.updateStateForView(); + } + } + } } diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index 6830d9b4fc9..b8c7e2a1e9f 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -895,7 +895,7 @@ public final class GameActionUtil { } CardCollection completeList = new CardCollection(); PlayerCollection players = new PlayerCollection(game.getPlayers()); - // CR 613.7k use APNAP + // CR 613.7m use APNAP int indexAP = players.indexOf(game.getPhaseHandler().getPlayerTurn()); if (indexAP != -1) { Collections.rotate(players, - indexAP); diff --git a/forge-game/src/main/java/forge/game/GameEntity.java b/forge-game/src/main/java/forge/game/GameEntity.java index 13f1787de40..8396405f6ed 100644 --- a/forge-game/src/main/java/forge/game/GameEntity.java +++ b/forge-game/src/main/java/forge/game/GameEntity.java @@ -340,6 +340,13 @@ public abstract class GameEntity extends GameObject implements IIdentifiable { addCounterInternal(CounterType.get(counterType), n, source, fireEvents, table, params); } + public List> getDamageReceivedThisTurn() { + return damageReceivedThisTurn; + } + public void setDamageReceivedThisTurn(List> dmg) { + damageReceivedThisTurn.addAll(dmg); + } + public void receiveDamage(Pair dmg) { damageReceivedThisTurn.add(dmg); } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index 4a176304034..a3e122b4559 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -511,12 +511,8 @@ public class AbilityUtils { } else if (hType.equals("Other")) { players.addAll(player.getAllOtherPlayers()); val = playerXCount(players, calcX[1], card, ability); - } else if (hType.equals("Remembered")) { - for (final Object o : card.getRemembered()) { - if (o instanceof Player) { - players.add((Player) o); - } - } + } else if (hType.startsWith("Remembered")) { + addPlayer(card.getRemembered(), hType, players); val = playerXCount(players, calcX[1], card, ability); } else if (hType.equals("NonActive")) { players.addAll(game.getPlayers()); 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 6ec34b5119b..0bf5d8306fc 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -400,6 +400,23 @@ public abstract class SpellAbilityEffect { addedTrigger.setIntrinsic(true); } + protected static void addExileOnCastOrMoveTrigger(final Card card, final String zone) { + String trig = "Mode$ SpellCast | ValidCard$ Card.IsRemembered | TriggerZones$ Command | Static$ True"; + String effect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"; + final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true); + parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(effect, card)); + final Trigger addedTrigger = card.addTrigger(parsedTrigger); + addedTrigger.setIntrinsic(true); + //Any on Destination will cause the effect to remove itself when cancelling to play the card + String trig2 = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ " + zone + " | Destination$ Hand,Library,Graveyard,Battlefield,Command,Sideboard | TriggerZones$ Command | Static$ True"; + String effect2 = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"; + final Trigger parsedTrigger2 = TriggerHandler.parseTrigger(trig2, card, true); + parsedTrigger2.setOverridingAbility(AbilityFactory.getAbility(effect2, card)); + final Trigger addedTrigger2 = card.addTrigger(parsedTrigger2); + addedTrigger2.setIntrinsic(true); + + } + protected static void addExileOnCounteredTrigger(final Card card) { String trig = "Mode$ Countered | ValidCard$ Card.IsRemembered | TriggerZones$ Command | Static$ True"; String effect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"; @@ -434,7 +451,7 @@ public abstract class SpellAbilityEffect { protected static void addLeaveBattlefieldReplacement(final Card card, final SpellAbility sa, final String zone) { final Card host = sa.getHostCard(); final Game game = card.getGame(); - final Card eff = createEffect(sa, sa.getActivatingPlayer(), host.getName() + "'s Effect", host.getImageKey()); + final Card eff = createEffect(sa, sa.getActivatingPlayer(), host + "'s Effect", host.getImageKey()); addLeaveBattlefieldReplacement(eff, zone); diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java index 0276fb32bc1..357331bd1d0 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java @@ -136,10 +136,10 @@ public class AnimateAllEffect extends AnimateEffectBase { CardCollectionView list; - if (!sa.usesTargeting() && !sa.hasParam("Defined")) { - list = game.getCardsIn(ZoneType.Battlefield); - } else { + if (sa.usesTargeting() || sa.hasParam("Defined")) { list = getTargetPlayers(sa).getCardsIn(ZoneType.Battlefield); + } else { + list = game.getCardsIn(ZoneType.Battlefield); } list = CardLists.getValidCards(list, valid, sa.getActivatingPlayer(), host, sa); @@ -155,18 +155,18 @@ public class AnimateAllEffect extends AnimateEffectBase { game.fireEvent(new GameEventCardStatsChanged(c)); - final GameCommand unanimate = new GameCommand() { - private static final long serialVersionUID = -5861759814760561373L; - - @Override - public void run() { - doUnanimate(c, timestamp); - - game.fireEvent(new GameEventCardStatsChanged(c)); - } - }; - if (!permanent) { + final GameCommand unanimate = new GameCommand() { + private static final long serialVersionUID = -5861759814760561373L; + + @Override + public void run() { + doUnanimate(c, timestamp); + + game.fireEvent(new GameEventCardStatsChanged(c)); + } + }; + addUntilCommand(sa, unanimate); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java index 727c782a689..233151770b5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java @@ -62,7 +62,7 @@ public class BalanceEffect extends SpellAbilityEffect { discardedMap.put(p, p.getController().chooseCardsToDiscardFrom(p, sa, validCards.get(i), numToBalance, numToBalance)); } else { // Battlefield for (Card card : p.getController().choosePermanentsToSacrifice(sa, numToBalance, numToBalance, validCards.get(i), valid)) { - if ( null == card ) continue; + if (null == card) continue; game.getAction().sacrifice(card, sa, true, table, params); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/BecomesBlockedEffect.java b/forge-game/src/main/java/forge/game/ability/effects/BecomesBlockedEffect.java index 8547d6bd190..226daa867c3 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/BecomesBlockedEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/BecomesBlockedEffect.java @@ -3,8 +3,6 @@ package forge.game.ability.effects; import java.util.List; import java.util.Map; -import org.apache.commons.lang3.StringUtils; - import com.google.common.collect.Lists; import forge.game.Game; @@ -14,6 +12,7 @@ import forge.game.card.Card; import forge.game.event.GameEventCombatChanged; import forge.game.spellability.SpellAbility; import forge.game.trigger.TriggerType; +import forge.util.Lang; public class BecomesBlockedEffect extends SpellAbilityEffect { @@ -21,9 +20,7 @@ public class BecomesBlockedEffect extends SpellAbilityEffect { protected String getStackDescription(SpellAbility sa) { final StringBuilder sb = new StringBuilder(); - final List tgtCards = getTargetCards(sa); - - sb.append(StringUtils.join(tgtCards, ", ")); + sb.append(Lang.joinHomogenous(getTargetCards(sa))); sb.append(" becomes blocked."); return sb.toString(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeCombatantsEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeCombatantsEffect.java index 4d138cad810..128ce54a8b0 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeCombatantsEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeCombatantsEffect.java @@ -1,10 +1,7 @@ package forge.game.ability.effects; -import java.util.List; import java.util.Map; -import org.apache.commons.lang3.StringUtils; - import com.google.common.collect.Maps; import forge.game.Game; @@ -19,6 +16,7 @@ import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; import forge.util.CardTranslation; +import forge.util.Lang; import forge.util.Localizer; import forge.util.collect.FCollection; @@ -28,10 +26,9 @@ public class ChangeCombatantsEffect extends SpellAbilityEffect { protected String getStackDescription(SpellAbility sa) { final StringBuilder sb = new StringBuilder(); - final List tgtCards = getTargetCards(sa); // should update when adding effects for defined blocker sb.append("Reselect the defender of "); - sb.append(StringUtils.join(tgtCards, ", ")); + sb.append(Lang.joinHomogenous(getTargetCards(sa))); return sb.toString(); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java index a5c036f608a..9468d9a3cd4 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java @@ -68,7 +68,6 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { if (origin.contains(ZoneType.Library) && sa.hasParam("Search") && !sa.getActivatingPlayer().canSearchLibraryWith(sa, p)) { cards.removeAll(p.getCardsIn(ZoneType.Library)); } - } if (origin.contains(ZoneType.Library) && sa.hasParam("Search")) { // Search library using changezoneall effect need a param "Search" @@ -95,8 +94,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { if (!libCards.isEmpty()) { sa.getActivatingPlayer().getController().reveal(libCards, ZoneType.Library, libCards.get(0).getOwner()); } - final Map runParams = AbilityKey.newMap(); - runParams.put(AbilityKey.Player, sa.getActivatingPlayer()); + final Map runParams = AbilityKey.mapFromPlayer(sa.getActivatingPlayer()); runParams.put(AbilityKey.Target, tgtPlayers); game.getTriggerHandler().runTrigger(TriggerType.SearchedLibrary, runParams, false); } @@ -172,9 +170,11 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { // need LKI before Animate does apply moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c)); + final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility"); source.addRemembered(c); - AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility")); + AbilityUtils.resolve(animate); source.removeRemembered(c); + animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp())); } if (sa.hasParam("Tapped")) { c.setTapped(true); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index 9a8ca6c6a6b..68d6f40bac1 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -133,10 +133,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (sa.hasParam("ExileFaceDown")) { sb.append(" face down"); } + sb.append("."); } else if (destination.equals("Ante")) { - sb.append("Add the top card of your library to the ante"); + sb.append("Add the top card of your library to the ante."); } - sb.append("."); } else if (origin.equals("Library")) { final boolean originAlt = sa.hasParam("OriginAlternative"); sb.append(chooserNames).append(" search").append(choosers.size() > 1 ? " " : "es "); @@ -575,6 +575,17 @@ public class ChangeZoneEffect extends SpellAbilityEffect { movedCard = game.getAction().moveToLibrary(gameCard, libraryPosition, sa); } else { if (destination.equals(ZoneType.Battlefield)) { + Map moveParams = AbilityKey.newMap(); + moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield); + moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard); + if (sa.isReplacementAbility()) { + ReplacementEffect re = sa.getReplacementEffect(); + moveParams.put(AbilityKey.ReplacementEffect, re); + if (ReplacementType.Moved.equals(re.getMode()) && sa.getReplacingObject(AbilityKey.CardLKI) != null) { + moveParams.put(AbilityKey.CardLKI, sa.getReplacingObject(AbilityKey.CardLKI)); + } + } + if (sa.hasParam("Tapped") || sa.isNinjutsu()) { gameCard.setTapped(true); } @@ -583,6 +594,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } if (sa.hasParam("Transformed")) { if (gameCard.isDoubleFaced()) { + // need LKI before Animate does apply + if (!moveParams.containsKey(AbilityKey.CardLKI)) { + moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(gameCard)); + } gameCard.changeCardState("Transform", null, sa); } else { // If it can't Transform, don't change zones. @@ -650,26 +665,17 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } } - Map moveParams = AbilityKey.newMap(); - moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield); - moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard); - if (sa.isReplacementAbility()) { - ReplacementEffect re = sa.getReplacementEffect(); - moveParams.put(AbilityKey.ReplacementEffect, re); - if (ReplacementType.Moved.equals(re.getMode()) && sa.getReplacingObject(AbilityKey.CardLKI) != null) { - moveParams.put(AbilityKey.CardLKI, sa.getReplacingObject(AbilityKey.CardLKI)); - } - } - if (sa.hasAdditionalAbility("AnimateSubAbility")) { // need LKI before Animate does apply if (!moveParams.containsKey(AbilityKey.CardLKI)) { moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(gameCard)); } + final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility"); hostCard.addRemembered(gameCard); - AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility")); + AbilityUtils.resolve(animate); hostCard.removeRemembered(gameCard); + animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp())); } // need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger @@ -776,7 +782,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } if (sa.hasParam("TrackDiscarded")) { - movedCard.setMadnessWithoutCast(true); + movedCard.setDiscarded(true); } } } @@ -1109,7 +1115,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { fetchList = (CardCollection)AbilityUtils.filterListByType(fetchList, sa.getParam("ChangeType"), sa); } - if (sa.hasParam("NoShuffle")) { + if (sa.hasParam("NoShuffle") || "False".equals(sa.getParam("Shuffle"))) { shuffleMandatory = false; } @@ -1249,7 +1255,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { CardLists.shuffle(chosenCards); } // do not shuffle the library once we have placed a fetched card on top. - if (origin.contains(ZoneType.Library) && (destination == ZoneType.Library) && !"False".equals(sa.getParam("Shuffle"))) { + if (origin.contains(ZoneType.Library) && (destination == ZoneType.Library) && shuffleMandatory) { player.shuffle(sa); } @@ -1310,9 +1316,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect { // need LKI before Animate does apply moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c)); + final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility"); source.addRemembered(c); - AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility")); + AbilityUtils.resolve(animate); source.removeRemembered(c); + animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp())); } if (sa.hasParam("GainControl")) { final String g = sa.getParam("GainControl"); @@ -1331,6 +1339,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } if (sa.hasParam("Transformed")) { if (c.isDoubleFaced()) { + // need LKI before Animate does apply + if (!moveParams.containsKey(AbilityKey.CardLKI)) { + moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c)); + } c.changeCardState("Transform", null, sa); } else { // If it can't Transform, don't change zones. @@ -1390,7 +1402,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { movedCard.setTimestamp(ts); - if (sa.hasParam("AttachAfter") && movedCard.isAttachment()) { + if (sa.hasParam("AttachAfter") && movedCard.isAttachment() && movedCard.isInPlay()) { CardCollection list = AbilityUtils.getDefinedCards(source, sa.getParam("AttachAfter"), sa); if (list.isEmpty()) { list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("AttachAfter"), c.getController(), c, sa); diff --git a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java index bd5b134ccda..2e02ff333bc 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java @@ -25,19 +25,15 @@ public class CharmEffect extends SpellAbilityEffect { List restriction = null; if (sa.hasParam("ChoiceRestriction")) { - String rest = sa.getParam("ChoiceRestriction"); - if (rest.equals("ThisGame")) { - restriction = source.getChosenModesGame(sa); - } else if (rest.equals("ThisTurn")) { - restriction = source.getChosenModesTurn(sa); - } + restriction = source.getChosenModes(sa, sa.getParam("ChoiceRestriction")); } List choices = Lists.newArrayList(sa.getAdditionalAbilityList("Choices")); List toRemove = Lists.newArrayList(); for (AbilitySub ch : choices) { // 603.3c If one of the modes would be illegal, that mode can't be chosen. - if ((ch.usesTargeting() && ch.isTrigger() && ch.getTargetRestrictions().getNumCandidates(ch, true) == 0) || + if ((ch.usesTargeting() && ch.isTrigger() && ch.getMinTargets() > 0 && + ch.getTargetRestrictions().getNumCandidates(ch, true) == 0) || (restriction != null && restriction.contains(ch.getDescription()))) { toRemove.add(ch); } @@ -97,6 +93,8 @@ public class CharmEffect extends SpellAbilityEffect { sb.append(" that hasn't been chosen"); } else if (rest.equals("ThisTurn")) { sb.append(" that hasn't been chosen this turn"); + } else if (rest.equals("YourLastCombat")) { + sb.append(" that wasn't chosen during your last combat"); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java index 2490857f88f..29110762def 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java @@ -20,7 +20,7 @@ public class ControlPlayerEffect extends SpellAbilityEffect { @Override protected String getStackDescription(SpellAbility sa) { List tgtPlayers = getTargetPlayers(sa); - return TextUtil.concatWithSpace(sa.getActivatingPlayer().toString(),"controls", Lang.joinHomogenous(tgtPlayers),"during their next turn"); + return TextUtil.concatWithSpace(sa.getActivatingPlayer().toString(), "controls", Lang.joinHomogenous(tgtPlayers), "during their next turn"); } @SuppressWarnings("serial") diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java index c36f92001f4..bb41d885831 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java @@ -28,8 +28,9 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect { @Override protected String getStackDescription(SpellAbility sa) { final StringBuilder sb = new StringBuilder(); - final Player pl = !sa.hasParam("DefinedPlayer") ? sa.getActivatingPlayer() : - AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("DefinedPlayer"), sa).getFirst(); + final Player pl = sa.hasParam("DefinedPlayer") ? + AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("DefinedPlayer"), sa).getFirst() + : sa.getActivatingPlayer(); sb.append(pl.getName()); if (sa.hasParam("CounterType")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java index e17cd12cc04..2b17995ad58 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java @@ -18,6 +18,7 @@ import forge.game.player.PlayerController; import forge.game.spellability.SpellAbility; import forge.game.zone.Zone; import forge.game.zone.ZoneType; +import forge.util.Lang; import forge.util.Localizer; public class CountersRemoveEffect extends SpellAbilityEffect { @@ -53,13 +54,9 @@ public class CountersRemoveEffect extends SpellAbilityEffect { } sb.append(" from"); - for (final Card c : getTargetCards(sa)) { - sb.append(" ").append(c); - } + sb.append(Lang.joinHomogenous(getTargetCards(sa))); - for (final Player tgtPlayer : getTargetPlayers(sa)) { - sb.append(" ").append(tgtPlayer); - } + sb.append(Lang.joinHomogenous(getTargetPlayers(sa))); sb.append("."); diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java index 26020eb59eb..93d39d5951d 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java @@ -9,6 +9,7 @@ import forge.game.card.CardLists; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; +import forge.util.Lang; import forge.util.collect.FCollectionView; public class DamageEachEffect extends DamageBaseEffect { @@ -38,9 +39,7 @@ public class DamageEachEffect extends DamageBaseEffect { sb.append(sa.getParam("StackDescription")); } else { sb.append("Each ").append(desc).append(" deals ").append(dmg).append(" to "); - for (final Player p : getTargetPlayers(sa)) { - sb.append(p); - } + Lang.joinHomogenous(getTargetPlayers(sa)); if (sa.hasParam("DefinedCards")) { if (sa.getParam("DefinedCards").equals("Self")) { sb.append(" itself"); diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java index 415ea98a006..e6a92d4cb74 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java @@ -417,13 +417,13 @@ public class DigEffect extends SpellAbilityEffect { } if (sa.hasAdditionalAbility("AnimateSubAbility")) { // need LKI before Animate does apply - if (!moveParams.containsKey(AbilityKey.CardLKI)) { - moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c)); - } + moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c)); + final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility"); host.addRemembered(c); - AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility")); + AbilityUtils.resolve(animate); host.removeRemembered(c); + animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp())); } c = game.getAction().moveTo(zone, c, sa, moveParams); if (destZone1.equals(ZoneType.Battlefield)) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java index e815b89f8a9..2f1b7a3534f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java @@ -77,10 +77,20 @@ public class DigMultipleEffect extends SpellAbilityEffect { if (validMap.isEmpty()) { chooser.getController().notifyOfValue(sa, null, Localizer.getInstance().getMessage("lblNoValidCards")); } else { - CardCollection chosen = chooser.getController().chooseCardsForEffectMultiple(validMap, sa, Localizer.getInstance().getMessage("lblChooseCards"), chooseOptional); + CardCollection chosen; + //ensure choosing something when possible and not optional + while (true) { + chosen = chooser.getController().chooseCardsForEffectMultiple(validMap, sa, + Localizer.getInstance().getMessage("lblChooseCards"), chooseOptional); - if (!chosen.isEmpty()) { - game.getAction().reveal(chosen, chooser, true, Localizer.getInstance().getMessage("lblPlayerPickedCardFrom", chooser.getName())); + if (!chosen.isEmpty()) { + game.getAction().reveal(chosen, chooser, true, + Localizer.getInstance().getMessage("lblPlayerPickedCardFrom", chooser.getName())); + break; + } + if (chooseOptional) break; + chooser.getController().notifyOfValue(sa, null, + Localizer.getInstance().getMessage("lblMustChoose")); } if (sa.hasParam("ChooseAmount") || sa.hasParam("ChosenZone")) { @@ -108,21 +118,23 @@ public class DigMultipleEffect extends SpellAbilityEffect { final ZoneType origin = c.getZone().getZoneType(); final PlayerZone zone = c.getOwner().getZone(destZone1); - if (zone.is(ZoneType.Library) || zone.is(ZoneType.PlanarDeck) || zone.is(ZoneType.SchemeDeck)) { - if (libraryPosition == -1 || libraryPosition > zone.size()) { - libraryPosition = zone.size(); - } - c = game.getAction().moveTo(zone, c, libraryPosition, sa); - } else { - if (destZone1.equals(ZoneType.Battlefield)) { - if (sa.hasParam("Tapped")) { - c.setTapped(true); + if (!sa.hasParam("ChangeLater")) { + if (zone.is(ZoneType.Library) || zone.is(ZoneType.PlanarDeck) || zone.is(ZoneType.SchemeDeck)) { + if (libraryPosition == -1 || libraryPosition > zone.size()) { + libraryPosition = zone.size(); } + c = game.getAction().moveTo(zone, c, libraryPosition, sa); + } else { + if (destZone1.equals(ZoneType.Battlefield)) { + if (sa.hasParam("Tapped")) { + c.setTapped(true); + } + } + c = game.getAction().moveTo(zone, c, sa); + } + if (!origin.equals(c.getZone().getZoneType())) { + table.put(origin, c.getZone().getZoneType(), c); } - c = game.getAction().moveTo(zone, c, sa); - } - if (!origin.equals(c.getZone().getZoneType())) { - table.put(origin, c.getZone().getZoneType(), c); } if (sa.hasParam("ExileFaceDown")) { @@ -142,38 +154,45 @@ public class DigMultipleEffect extends SpellAbilityEffect { } // now, move the rest to destZone2 - if (destZone2 == ZoneType.Library || destZone2 == ZoneType.PlanarDeck || destZone2 == ZoneType.SchemeDeck - || destZone2 == ZoneType.Graveyard) { - CardCollection afterOrder = rest; - if (sa.hasParam("RestRandomOrder")) { - CardLists.shuffle(afterOrder); - } - if (libraryPosition2 != -1) { - // Closest to top - Collections.reverse(afterOrder); - } - for (final Card c : afterOrder) { - final ZoneType origin = c.getZone().getZoneType(); - Card m; - if (destZone2 == ZoneType.Library) { - m = game.getAction().moveToLibrary(c, libraryPosition2, sa); - } else { - m = game.getAction().moveToVariantDeck(c, destZone2, libraryPosition2, sa); + if (!sa.hasParam("ChangeLater")) { + if (destZone2 == ZoneType.Library || destZone2 == ZoneType.PlanarDeck + || destZone2 == ZoneType.SchemeDeck || destZone2 == ZoneType.Graveyard) { + CardCollection afterOrder = rest; + if (sa.hasParam("RestRandomOrder")) { + CardLists.shuffle(afterOrder); } - if (m != null && !origin.equals(m.getZone().getZoneType())) { - table.put(origin, m.getZone().getZoneType(), m); + if (libraryPosition2 != -1) { + // Closest to top + Collections.reverse(afterOrder); + } + for (final Card c : afterOrder) { + final ZoneType origin = c.getZone().getZoneType(); + Card m; + if (destZone2 == ZoneType.Library) { + m = game.getAction().moveToLibrary(c, libraryPosition2, sa); + } else { + m = game.getAction().moveToVariantDeck(c, destZone2, libraryPosition2, sa); + } + if (m != null && !origin.equals(m.getZone().getZoneType())) { + table.put(origin, m.getZone().getZoneType(), m); + } + } + } else { + // just move them randomly + for (int i = 0; i < rest.size(); i++) { + Card c = rest.get(i); + final ZoneType origin = c.getZone().getZoneType(); + final PlayerZone toZone = c.getOwner().getZone(destZone2); + c = game.getAction().moveTo(toZone, c, sa); + if (!origin.equals(c.getZone().getZoneType())) { + table.put(origin, c.getZone().getZoneType(), c); + } } } - } else { - // just move them randomly - for (int i = 0; i < rest.size(); i++) { - Card c = rest.get(i); - final ZoneType origin = c.getZone().getZoneType(); - final PlayerZone toZone = c.getOwner().getZone(destZone2); - c = game.getAction().moveTo(toZone, c, sa); - if (!origin.equals(c.getZone().getZoneType())) { - table.put(origin, c.getZone().getZoneType(), c); - } + } + if (sa.hasParam("ImprintRest")) { + for (Card c : rest) { + host.addImprintedCard(c); } } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java index 51da7b83dc2..90f473d08aa 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java @@ -227,6 +227,8 @@ public class EffectEffect extends SpellAbilityEffect { addForgetOnCastTrigger(eff); } else if (sa.hasParam("ExileOnMoved")) { addExileOnMovedTrigger(eff, sa.getParam("ExileOnMoved")); + } else if (sa.hasParam("ExileOnCast")) { + addExileOnCastOrMoveTrigger(eff, sa.getParam("ExileOnCast")); } if (sa.hasParam("ForgetOnPhasedIn")) { addForgetOnPhasedInTrigger(eff); diff --git a/forge-game/src/main/java/forge/game/ability/effects/RemoveFromCombatEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RemoveFromCombatEffect.java index dba17adcc12..77906ed0664 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RemoveFromCombatEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RemoveFromCombatEffect.java @@ -1,9 +1,5 @@ package forge.game.ability.effects; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; - import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; @@ -12,6 +8,7 @@ import forge.game.card.CardCollection; import forge.game.combat.Combat; import forge.game.player.Player; import forge.game.spellability.SpellAbility; +import forge.util.Lang; public class RemoveFromCombatEffect extends SpellAbilityEffect { @@ -19,10 +16,8 @@ public class RemoveFromCombatEffect extends SpellAbilityEffect { protected String getStackDescription(SpellAbility sa) { final StringBuilder sb = new StringBuilder(); - final List tgtCards = getTargetCards(sa); - sb.append("Remove "); - sb.append(StringUtils.join(tgtCards, ", ")); + sb.append(Lang.joinHomogenous(getTargetCards(sa))); sb.append(" from combat."); return sb.toString(); 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 6e318fc2c0c..ad4f65c5634 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -226,7 +226,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private boolean tributed = false; private boolean embalmed = false; private boolean eternalized = false; - private boolean madnessWithoutCast = false; + private boolean discarded = false; private boolean flipped = false; private boolean facedown = false; @@ -324,6 +324,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private SpellAbility[] basicLandAbilities = new SpellAbility[MagicColor.WUBRG.length]; private int planeswalkerAbilityActivated; + private boolean planeswalkerActivationLimitUsed; private final ActivationTable numberTurnActivations = new ActivationTable(); private final ActivationTable numberGameActivations = new ActivationTable(); @@ -331,9 +332,13 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private final Map> chosenModesTurn = Maps.newHashMap(); private final Map> chosenModesGame = Maps.newHashMap(); + private final Map> chosenModesYourCombat = Maps.newHashMap(); + private final Map> chosenModesYourLastCombat = Maps.newHashMap(); private final Table> chosenModesTurnStatic = HashBasedTable.create(); private final Table> chosenModesGameStatic = HashBasedTable.create(); + private final Table> chosenModesYourCombatStatic = HashBasedTable.create(); + private final Table> chosenModesYourLastCombatStatic = HashBasedTable.create(); private CombatLki combatLKI; @@ -3263,7 +3268,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { public final boolean isFlipped() { return flipped; } - public final void setFlipped(boolean value) { flipped = value; } @@ -3271,7 +3275,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { public final void setCanCounter(final boolean b) { canCounter = b; } - public final boolean getCanCounter() { return canCounter; } @@ -5458,13 +5461,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { damageHistory = history; } - public List> getDamageReceivedThisTurn() { - return damageReceivedThisTurn; - } - public void setDamageReceivedThisTurn(List> dmg) { - damageReceivedThisTurn.addAll(dmg); - } - public final boolean hasDealtDamageToOpponentThisTurn() { return getDamageHistory().getDamageDoneThisTurn(null, true, null, "Player.Opponent", this, getController(), null) > 0; } @@ -5805,8 +5801,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } return getCastSA().isMadness(); } - public boolean getMadnessWithoutCast() { return madnessWithoutCast; } - public void setMadnessWithoutCast(boolean state) { madnessWithoutCast = state; } + public boolean wasDiscarded() { return discarded; } + public void setDiscarded(boolean state) { discarded = state; } public final boolean isMonstrous() { return monstrous; @@ -6345,6 +6341,18 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return isInZone(ZoneType.Battlefield); } + public void onEndOfCombat(final Player active) { + if (this.getController().equals(active)) { + chosenModesYourLastCombat.clear(); + chosenModesYourLastCombatStatic.clear(); + chosenModesYourLastCombat.putAll(chosenModesYourCombat); + chosenModesYourLastCombatStatic.putAll(chosenModesYourCombatStatic); + chosenModesYourCombat.clear(); + chosenModesYourCombatStatic.clear(); + updateAbilityTextForView(); + } + } + public void onCleanupPhase(final Player turn) { if (!this.hasKeyword("Damage isn't removed from CARDNAME during cleanup steps.")) { setDamage(0); @@ -7037,7 +7045,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { numberAbilityResolved.clear(); } - public List getChosenModesTurn(SpellAbility ability) { + public List getChosenModes(SpellAbility ability, String type) { SpellAbility original = null; SpellAbility root = ability.getRootAbility(); @@ -7051,32 +7059,26 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } } - if (ability.getGrantorStatic() != null) { - return chosenModesTurnStatic.get(original, ability.getGrantorStatic()); - } - return chosenModesTurn.get(original); - } - public List getChosenModesGame(SpellAbility ability) { - SpellAbility original = null; - SpellAbility root = ability.getRootAbility(); - - // because trigger spell abilities are copied, try to get original one - if (root.isTrigger()) { - original = root.getTrigger().getOverridingAbility(); - } else { - original = ability.getOriginalAbility(); - if (original == null) { - original = ability; + if (type.equals("ThisTurn")) { + if (ability.getGrantorStatic() != null) { + return chosenModesTurnStatic.get(original, ability.getGrantorStatic()); } + return chosenModesTurn.get(original); + } else if (type.equals("ThisGame")) { + if (ability.getGrantorStatic() != null) { + return chosenModesGameStatic.get(original, ability.getGrantorStatic()); + } + return chosenModesGame.get(original); + } else if (type.equals("YourLastCombat")) { + if (ability.getGrantorStatic() != null) { + return chosenModesYourLastCombatStatic.get(original, ability.getGrantorStatic()); + } + return chosenModesYourLastCombat.get(original); } - - if (ability.getGrantorStatic() != null) { - return chosenModesGameStatic.get(original, ability.getGrantorStatic()); - } - return chosenModesGame.get(original); + return null; } - public void addChosenModes(SpellAbility ability, String mode) { + public void addChosenModes(SpellAbility ability, String mode, boolean yourCombat) { SpellAbility original = null; SpellAbility root = ability.getRootAbility(); @@ -7103,6 +7105,13 @@ public class Card extends GameEntity implements Comparable, IHasSVars { chosenModesGameStatic.put(original, ability.getGrantorStatic(), result); } result.add(mode); + if (yourCombat) { + result = chosenModesYourCombatStatic.get(original, ability.getGrantorStatic()); + if (result == null) { + result = Lists.newArrayList(); + chosenModesYourCombatStatic.put(original, ability.getGrantorStatic(), result); + } + } } else { List result = chosenModesTurn.get(original); if (result == null) { @@ -7117,6 +7126,15 @@ public class Card extends GameEntity implements Comparable, IHasSVars { chosenModesGame.put(original, result); } result.add(mode); + + if (yourCombat) { + result = chosenModesYourCombat.get(original); + if (result == null) { + result = Lists.newArrayList(); + chosenModesYourCombat.put(original, result); + } + result.add(mode); + } } } @@ -7130,11 +7148,19 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } public void addPlaneswalkerAbilityActivated() { - planeswalkerAbilityActivated++; + // track if increased limit was used for activation because if there are also additional ones they can count on top + if (++planeswalkerAbilityActivated == 2 && StaticAbilityNumLoyaltyAct.limitIncrease(this)) { + planeswalkerActivationLimitUsed = true; + } + } + + public boolean planeswalkerActivationLimitUsed() { + return planeswalkerActivationLimitUsed; } public void resetActivationsPerTurn() { planeswalkerAbilityActivated = 0; + planeswalkerActivationLimitUsed = false; numberTurnActivations.clear(); } 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 51917427525..ba826116d40 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -3855,7 +3855,7 @@ public class CardFactoryUtil { SpellAbility saExile = AbilityFactory.getAbility(abExile, card); - String abEffect = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell"; + String abEffect = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnCast$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell"; AbilitySub saEffect = (AbilitySub)AbilityFactory.getAbility(abEffect, card); StringBuilder sbPlay = new StringBuilder(); diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 1cd1130174a..2d13ad41bb2 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -984,8 +984,7 @@ public class CardProperty { return false; } - List cards = CardUtil.getThisTurnEntered(ZoneType.Graveyard, ZoneType.Hand, "Card", source, spellAbility); - if (!cards.contains(card) && !card.getMadnessWithoutCast()) { + if (!card.wasDiscarded()) { return false; } } else if (property.startsWith("ControlledByPlayerInTheDirection")) { diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java index ea61da97ce0..ae9ce2d17e4 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -357,6 +357,9 @@ public class PhaseHandler implements java.io.Serializable { case COMBAT_END: // End Combat always happens + for (final Card c : game.getCardsIn(ZoneType.Battlefield)) { + c.onEndOfCombat(playerTurn); + } game.getEndOfCombat().executeAt(); //SDisplayUtil.showTab(EDocID.REPORT_STACK.getDoc()); diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 50191ded7fd..747fb5dfc9b 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -1444,6 +1444,9 @@ public class Player extends GameEntity implements Comparable { newCard = game.getAction().moveToGraveyard(c, sa, params); // Play the Discard sound } + + newCard.setDiscarded(true); + if (table != null) { table.put(origin, newCard.getZone().getZoneType(), newCard); } diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementLayer.java b/forge-game/src/main/java/forge/game/replacement/ReplacementLayer.java index ca71573f4b8..38ab284bcd9 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementLayer.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementLayer.java @@ -6,6 +6,7 @@ package forge.game.replacement; * */ public enum ReplacementLayer { + CantHappen, // 614.17 Control, // 616.1b Copy, // 616.1c Transform, // 616.1d diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java index b95f6119b3a..c5966bf587c 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -664,9 +664,9 @@ public class AbilityManaPart implements java.io.Serializable { // check for produce mana replacement effects - they mess this up, so just use the mana ability final Card source = am.getHostCard(); final Player activator = am.getActivatingPlayer(); - final Map repParams = AbilityKey.mapFromPlayer(activator); + final Map repParams = AbilityKey.mapFromAffected(source); repParams.put(AbilityKey.Mana, getOrigProduced()); - repParams.put(AbilityKey.Affected, source); + repParams.put(AbilityKey.Activator, activator); repParams.put(AbilityKey.AbilityMana, am.getRootAbility()); if (!source.getGame().getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other).isEmpty()) { 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 d5d4b01d726..a86cdcf21e5 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -1254,10 +1254,16 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return false; } - final TargetRestrictions tr = getTargetRestrictions(); + final SpellAbility rootAbility = this.getRootAbility(); + // 115.5. A spell or ability on the stack is an illegal target for itself. + // (This covers the spell case.) + if (rootAbility.isSpell() && rootAbility.getHostCard() == entity) { + return false; + } // Restriction related to this ability if (usesTargeting()) { + final TargetRestrictions tr = getTargetRestrictions(); if (tr.isUniqueTargets() && getUniqueTargets().contains(entity)) return false; diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java index b22803c1936..536c140727e 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java @@ -475,12 +475,15 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { } if (sa.isPwAbility()) { - final int initialLimit = StaticAbilityNumLoyaltyAct.limitIncrease(c) ? 1 : 0; - final int limit = StaticAbilityNumLoyaltyAct.additionalActivations(c, sa) + initialLimit; - int numActivates = c.getPlaneswalkerAbilityActivated(); - if (numActivates > limit) { - return false; + int limit = StaticAbilityNumLoyaltyAct.limitIncrease(c) ? 2 : 1; + + if (numActivates >= limit) { + // increased limit only counts if it's been used already + limit += StaticAbilityNumLoyaltyAct.additionalActivations(c, sa) - (limit == 1 || c.planeswalkerActivationLimitUsed() ? 0 : 1); + if (numActivates >= limit) { + return false; + } } } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java index 651c0421140..3e531be8fde 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -563,6 +563,11 @@ public final class StaticAbilityContinuous { p.setMaxHandSize(max); } } + if (params.containsKey("RaiseMaxHandSize")) { + String rmhs = params.get("RaiseMaxHandSize"); + int rmax = AbilityUtils.calculateAmount(hostCard, rmhs, stAb); + p.setMaxHandSize(p.getMaxHandSize() + rmax); + } if (params.containsKey("AdjustLandPlays")) { String mhs = params.get("AdjustLandPlays"); @@ -573,6 +578,7 @@ public final class StaticAbilityContinuous { p.addMaxLandPlays(se.getTimestamp(), add); } } + if (params.containsKey("ControlOpponentsSearchingLibrary")) { Player cntl = Iterables.getFirst(AbilityUtils.getDefinedPlayers(hostCard, params.get("ControlOpponentsSearchingLibrary"), stAb), null); p.addControlledWhileSearching(se.getTimestamp(), cntl); @@ -592,12 +598,6 @@ public final class StaticAbilityContinuous { p.addAdditionalOptionalVote(se.getTimestamp(), add); } - if (params.containsKey("RaiseMaxHandSize")) { - String rmhs = params.get("RaiseMaxHandSize"); - int rmax = AbilityUtils.calculateAmount(hostCard, rmhs, stAb); - p.setMaxHandSize(p.getMaxHandSize() + rmax); - } - if (params.containsKey("ManaConversion")) { AbilityUtils.applyManaColorConversion(p.getManaPool(), params); } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java index e71134638b5..00ee0ccc414 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java @@ -100,8 +100,8 @@ public class StaticAbilityPanharmonicon { } } else if (trigMode.equals(TriggerType.ChangesZoneAll)) { // Check if the cards have a trigger at all - final String origin = stAb.getParamOrDefault("Origin", null); - final String destination = stAb.getParamOrDefault("Destination", null); + final String origin = stAb.getParam("Origin"); + final String destination = stAb.getParam("Destination"); final CardZoneTable table = (CardZoneTable) runParams.get(AbilityKey.Cards); if (table.filterCards(origin == null ? null : ImmutableList.of(ZoneType.smartValueOf(origin)), ZoneType.smartValueOf(destination), stAb.getParam("ValidCause"), card, stAb).isEmpty()) { diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerDamageDoneOnce.java b/forge-game/src/main/java/forge/game/trigger/TriggerDamageDoneOnce.java index bfdb696212a..b401e8e5959 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerDamageDoneOnce.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerDamageDoneOnce.java @@ -1,12 +1,10 @@ package forge.game.trigger; import java.util.Map; -import java.util.Set; - -import com.google.common.collect.Sets; import forge.game.ability.AbilityKey; import forge.game.card.Card; +import forge.game.card.CardCollection; import forge.game.card.CardUtil; import forge.game.spellability.SpellAbility; import forge.util.Localizer; @@ -79,11 +77,11 @@ public class TriggerDamageDoneOnce extends Trigger { return result; } - public Set getDamageSources(Map damageMap) { + public CardCollection getDamageSources(Map damageMap) { if (!hasParam("ValidSource")) { - return Sets.newHashSet(damageMap.keySet()); + return new CardCollection(damageMap.keySet()); } - Set result = Sets.newHashSet(); + CardCollection result = new CardCollection(); for (Card c : damageMap.keySet()) { if (matchesValid(c, getParam("ValidSource").split(","))) { result.add(c); 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 479e7439b03..4f26ab23c1f 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -288,7 +288,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable(); - } else { - return Arrays.asList(savedState.split(";")[1].split(SELECTED_DECK_DELIMITER)); } + final String[] parts = savedState.split(";", -1); + return Arrays.asList(parts[1].split(SELECTED_DECK_DELIMITER)); } catch (final Exception ex) { System.err.println(ex + " [savedState=" + savedState + "]"); return new ArrayList<>(); diff --git a/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckViewer.java b/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckViewer.java index 5f889736ddf..60cb60b8a06 100644 --- a/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckViewer.java +++ b/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckViewer.java @@ -189,7 +189,7 @@ public class FDeckViewer extends FDialog { sectionCards = new TreeMap<>(); deckList.append(nl); for (final Entry ev : cp) { - cardName = ev.getKey().toString(); + cardName = ev.getKey().getCardName(); if (sectionCards.containsKey(cardName)) { sectionCards.put(cardName, (int)sectionCards.get(cardName) + ev.getValue()); } diff --git a/forge-gui-desktop/src/main/java/forge/itemmanager/filters/CardSetFilter.java b/forge-gui-desktop/src/main/java/forge/itemmanager/filters/CardSetFilter.java index 22b4e39f60d..6b2c98f1c04 100644 --- a/forge-gui-desktop/src/main/java/forge/itemmanager/filters/CardSetFilter.java +++ b/forge-gui-desktop/src/main/java/forge/itemmanager/filters/CardSetFilter.java @@ -9,7 +9,7 @@ import forge.item.PaperCard; import forge.itemmanager.ItemManager; import forge.screens.home.quest.DialogChooseSets; -/** +/** * TODO: Write javadoc for this type. * */ @@ -26,7 +26,9 @@ public class CardSetFilter extends CardFormatFilter { public CardSetFilter(ItemManager itemManager0, Collection sets0, Collection limitedSets0, boolean allowReprints0){ this(itemManager0, sets0, allowReprints0); - this.limitedSets.addAll(limitedSets0); + if (limitedSets0 != null) { + this.limitedSets.addAll(limitedSets0); + } } @Override @@ -60,7 +62,7 @@ public class CardSetFilter extends CardFormatFilter { final DialogChooseSets dialog = new DialogChooseSets(this.sets, null, this.limitedSets, true, this.allowReprints); final CardSetFilter itemFilter = this; - + dialog.setOkCallback(new Runnable() { @Override public void run() { diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java index 7c9cd1a8fa1..efc7d8793b6 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java @@ -1,7 +1,5 @@ package forge.screens.home; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.util.Arrays; import java.util.Vector; @@ -10,8 +8,6 @@ import javax.swing.SwingUtilities; import com.google.common.collect.Iterables; import forge.deck.DeckProxy; -import forge.deck.DeckType; -import forge.deckchooser.FDeckChooser; import forge.localinstance.properties.ForgePreferences; import forge.localinstance.properties.ForgePreferences.FPref; import forge.model.FModel; @@ -22,12 +18,10 @@ public class CLobby { private final VLobby view; public CLobby(final VLobby view) { this.view = view; - this.view.setForCommander(true); } private void addDecks(final Iterable commanderDecks, FList deckList, String... initialItems) { - Vector listData = new Vector<>(); - listData.addAll(Arrays.asList(initialItems)); + Vector listData = new Vector<>(Arrays.asList(initialItems)); listData.add("Generate"); if (!Iterables.isEmpty(commanderDecks)) { listData.add("Random"); @@ -46,89 +40,38 @@ public class CLobby { } public void update() { - SwingUtilities.invokeLater(new Runnable() { - @Override public final void run() { - final Iterable schemeDecks = DeckProxy.getAllSchemeDecks(); - final Iterable planarDecks = DeckProxy.getAllPlanarDecks(); + SwingUtilities.invokeLater(() -> { + final Iterable schemeDecks = DeckProxy.getAllSchemeDecks(); + final Iterable planarDecks = DeckProxy.getAllPlanarDecks(); - for (int i = 0; i < VLobby.MAX_PLAYERS; i++) { - addDecks(schemeDecks, view.getSchemeDeckLists().get(i), - "Use deck's scheme section (random if unavailable)"); - addDecks(planarDecks, view.getPlanarDeckLists().get(i), - "Use deck's planes section (random if unavailable)"); - view.updateVanguardList(i); - } - - // General updates when switching back to this view - view.getBtnStart().requestFocusInWindow(); + for (int i = 0; i < VLobby.MAX_PLAYERS; i++) { + addDecks(schemeDecks, view.getSchemeDeckLists().get(i), + "Use deck's scheme section (random if unavailable)"); + addDecks(planarDecks, view.getPlanarDeckLists().get(i), + "Use deck's planes section (random if unavailable)"); + view.updateVanguardList(i); } + + // General updates when switching back to this view + view.getBtnStart().requestFocusInWindow(); }); } public void initialize() { - for (int iSlot = 0; iSlot < VLobby.MAX_PLAYERS; iSlot++) { - final FDeckChooser fdc = view.getDeckChooser(iSlot); - fdc.initialize(FPref.CONSTRUCTED_DECK_STATES[iSlot], defaultDeckTypeForSlot(iSlot)); - fdc.populate(); - /*fdc.getDecksComboBox().addListener(new IDecksComboBoxListener() { - @Override public final void deckTypeSelected(final DecksComboBoxEvent ev) { - view.focusOnAvatar(); - } - });*/ - final FDeckChooser fdccom = view.getCommanderDeckChooser(iSlot); - fdccom.initialize(FPref.COMMANDER_DECK_STATES[iSlot], defaultDeckTypeForCommanderSlot(iSlot)); - fdccom.populate(); - final FDeckChooser fdobcom = view.getOathbreakerDeckChooser(iSlot); - fdobcom.initialize(FPref.OATHBREAKER_DECK_STATES[iSlot], defaultDeckTypeForOathbreakerSlot(iSlot)); - fdobcom.populate(); - final FDeckChooser fdtlcom = view.getTinyLeaderDeckChooser(iSlot); - fdtlcom.initialize(FPref.TINY_LEADER_DECK_STATES[iSlot], defaultDeckTypeForTinyLeaderSlot(iSlot)); - fdtlcom.populate(); - final FDeckChooser fdbcom = view.getBrawlDeckChooser(iSlot); - fdbcom.initialize(FPref.BRAWL_DECK_STATES[iSlot], defaultDeckTypeForBrawlSlot(iSlot)); - fdbcom.populate(); - } - final ForgePreferences prefs = FModel.getPreferences(); // Checkbox event handling - view.getCbSingletons().addActionListener(new ActionListener() { - @Override - public void actionPerformed(final ActionEvent arg0) { - prefs.setPref(FPref.DECKGEN_SINGLETONS, String.valueOf(view.getCbSingletons().isSelected())); - prefs.save(); - } + view.getCbSingletons().addActionListener(arg0 -> { + prefs.setPref(FPref.DECKGEN_SINGLETONS, String.valueOf(view.getCbSingletons().isSelected())); + prefs.save(); }); - view.getCbArtifacts().addActionListener(new ActionListener() { - @Override - public void actionPerformed(final ActionEvent arg0) { - prefs.setPref(FPref.DECKGEN_ARTIFACTS, String.valueOf(view.getCbArtifacts().isSelected())); - prefs.save(); - } + view.getCbArtifacts().addActionListener(arg0 -> { + prefs.setPref(FPref.DECKGEN_ARTIFACTS, String.valueOf(view.getCbArtifacts().isSelected())); + prefs.save(); }); // Pre-select checkboxes view.getCbSingletons().setSelected(prefs.getPrefBoolean(FPref.DECKGEN_SINGLETONS)); view.getCbArtifacts().setSelected(prefs.getPrefBoolean(FPref.DECKGEN_ARTIFACTS)); } - - private static DeckType defaultDeckTypeForSlot(final int iSlot) { - return iSlot == 0 ? DeckType.PRECONSTRUCTED_DECK : DeckType.COLOR_DECK; - } - - private static DeckType defaultDeckTypeForCommanderSlot(final int iSlot) { - return iSlot == 0 ? DeckType.COMMANDER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; - } - - private static DeckType defaultDeckTypeForOathbreakerSlot(final int iSlot) { - return iSlot == 0 ? DeckType.OATHBREAKER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; - } - - private static DeckType defaultDeckTypeForTinyLeaderSlot(final int iSlot) { - return iSlot == 0 ? DeckType.TINY_LEADERS_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; - } - - private static DeckType defaultDeckTypeForBrawlSlot(final int iSlot) { - return iSlot == 0 ? DeckType.BRAWL_DECK : DeckType.CUSTOM_DECK; - } } diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java b/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java index 9a1e6d69f4e..4073dc543c2 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java @@ -1,5 +1,6 @@ package forge.screens.home; +import forge.deckchooser.FDeckChooser; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -103,6 +104,8 @@ public class PlayerPanel extends FPanel { private boolean allowNetworking; + private FDeckChooser deckChooser; + private final VLobby lobby; public PlayerPanel(final VLobby lobby, final boolean allowNetworking, final int index, final LobbySlot slot, final boolean mayEdit, final boolean mayControl) { super(); @@ -463,10 +466,6 @@ public class PlayerPanel extends FPanel { radioAiUseSimulation.setSelected(useSimulation); } - public boolean isLocal() { - return type == LobbySlotType.LOCAL; - } - public boolean isArchenemy() { return aeTeamComboBox.getSelectedIndex() == 0; } @@ -540,9 +539,6 @@ public class PlayerPanel extends FPanel { } }; - /** - * @param index - */ private void addHandlersToVariantsControls() { // Archenemy buttons scmDeckSelectorBtn.setCommand(new Runnable() { @@ -621,9 +617,6 @@ public class PlayerPanel extends FPanel { }); } - /** - * @param index - */ private void createPlayerTypeOptions() { radioHuman = new FRadioButton(localizer.getMessage("lblHuman")); radioAi = new FRadioButton(localizer.getMessage("lblAI")); @@ -674,9 +667,6 @@ public class PlayerPanel extends FPanel { }); } - /** - * @param index - */ private void addHandlersDeckSelector() { deckBtn.setCommand(new Runnable() { @Override @@ -688,10 +678,6 @@ public class PlayerPanel extends FPanel { }); } - /** - * @param index - * @return - */ private FLabel createNameRandomizer() { final FLabel newNameBtn = new FLabel.Builder().tooltip(localizer.getMessage("lblGetaNewRandomName")).iconInBackground(false) .icon(FSkin.getIcon(FSkinProp.ICO_EDIT)).hoverable(true).opaque(false) @@ -717,10 +703,6 @@ public class PlayerPanel extends FPanel { return newNameBtn; } - /** - * @param index - * @return - */ private void createNameEditor() { String name; if (index == 0) { @@ -898,4 +880,12 @@ public class PlayerPanel extends FPanel { public void setMayRemove(final boolean mayRemove) { this.mayRemove = mayRemove; } + + FDeckChooser getDeckChooser() { + return deckChooser; + } + + void setDeckChooser(final FDeckChooser deckChooser) { + this.deckChooser = deckChooser; + } } \ No newline at end of file diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java index 0aaa4ba989c..ec39ffbcacb 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java @@ -4,11 +4,11 @@ import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Vector; import javax.swing.*; @@ -36,7 +36,6 @@ import forge.gamemodes.match.LobbySlotType; import forge.gamemodes.net.event.UpdateLobbyPlayerEvent; import forge.gui.CardDetailPanel; import forge.gui.GuiBase; -import forge.gui.UiCommand; import forge.gui.interfaces.ILobbyView; import forge.gui.util.SOptionPane; import forge.interfaces.IPlayerChangeListener; @@ -105,22 +104,18 @@ public class VLobby implements ILobbyView { private final JPanel playersFrame = new JPanel(new MigLayout("insets 0, gap 0 5, wrap, hidemode 3")); private final FScrollPanel playersScroll = new FScrollPanel(new MigLayout("insets 0, gap 0, wrap, hidemode 3"), true); private final List playerPanels = new ArrayList<>(MAX_PLAYERS); + // Cache deck choosers so switching settings doesn't re-generate random decks. + private final Map cachedDeckChoosers = new HashMap<>(); private final FLabel addPlayerBtn = new FLabel.ButtonBuilder().fontSize(14).text(localizer.getMessage("lblAddAPlayer")).build(); // Deck frame elements private final JPanel decksFrame = new JPanel(new MigLayout("insets 0, gap 0, wrap, hidemode 3")); - private final List deckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS); private final FCheckBox cbSingletons = new FCheckBox(localizer.getMessage("cbSingletons")); private final FCheckBox cbArtifacts = new FCheckBox(localizer.getMessage("cbRemoveArtifacts")); private final Deck[] decks = new Deck[MAX_PLAYERS]; // Variants - private final List commanderDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS); - private final List oathbreakerDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS); - private final List tinyLeadersDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS); - private final List brawlDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS); - private final List> schemeDeckLists = new ArrayList<>(); private final List schemeDeckPanels = new ArrayList<>(MAX_PLAYERS); @@ -131,22 +126,11 @@ public class VLobby implements ILobbyView { private final List vgdPanels = new ArrayList<>(MAX_PLAYERS); private final List vgdAvatarDetails = new ArrayList<>(); private final List vgdAllAvatars = new ArrayList<>(); - private final List vgdAllAiAvatars = new ArrayList<>(); private final List nonRandomHumanAvatars = new ArrayList<>(); private final List nonRandomAiAvatars = new ArrayList<>(); private final Vector humanListData = new Vector<>(); private final Vector aiListData = new Vector<>(); - public boolean isForCommander() { - return isForCommander; - } - - public void setForCommander(boolean forCommander) { - isForCommander = forCommander; - } - - private boolean isForCommander = false; - // CTR public VLobby(final GameLobby lobby) { this.lobby = lobby; @@ -178,12 +162,7 @@ public class VLobby implements ILobbyView { if (lobby.hasControl()) { addPlayerBtn.setFocusable(true); - addPlayerBtn.setCommand(new Runnable() { - @Override - public final void run() { - lobby.addSlot(); - } - }); + addPlayerBtn.setCommand(lobby::addSlot); playersFrame.add(addPlayerBtn, "height 30px!, growx, pushx"); } @@ -207,7 +186,7 @@ public class VLobby implements ILobbyView { // Start button event handling btnStart.addActionListener(new ActionListener() { @Override - public final void actionPerformed(final ActionEvent arg0) { + public void actionPerformed(final ActionEvent arg0) { Runnable startGame = lobby.startGame(); if (startGame != null) { if (!gamesInMatch.getSelectedItem().equals(ForgePreferences.FPref.UI_MATCHES_PER_GAME)) { @@ -226,17 +205,8 @@ public class VLobby implements ILobbyView { } public void updateDeckPanel() { - for (int iPlayer = 0; iPlayer < activePlayersNum; iPlayer++) { - final FDeckChooser fdc = getDeckChooser(iPlayer); - fdc.restoreSavedState(); - final FDeckChooser fdcom = getCommanderDeckChooser(iPlayer); - fdcom.restoreSavedState(); - final FDeckChooser fdob = getOathbreakerDeckChooser(iPlayer); - fdob.restoreSavedState(); - final FDeckChooser fdtl = getTinyLeaderDeckChooser(iPlayer); - fdtl.restoreSavedState(); - final FDeckChooser fdbr = getBrawlDeckChooser(iPlayer); - fdbr.restoreSavedState(); + for (final PlayerPanel playerPanel : playerPanels) { + playerPanel.getDeckChooser().restoreSavedState(); } } @@ -244,8 +214,12 @@ public class VLobby implements ILobbyView { getPlayerPanelWithFocus().focusOnAvatar(); } + private PlayerPanel getPlayerPanel(int slot) { + return playerPanels.get(slot); + } + @Override - public void update(final int slot, final LobbySlotType type){ + public void update(final int slot, final LobbySlotType type) { final FDeckChooser deckChooser = getDeckChooser(slot); deckChooser.setIsAi(type==LobbySlotType.AI); DeckType selectedDeckType = deckChooser.getSelectedDeckType(); @@ -259,28 +233,13 @@ public class VLobby implements ILobbyView { case COLOR_DECK: case STANDARD_COLOR_DECK: case MODERN_COLOR_DECK: + case RANDOM_CARDGEN_COMMANDER_DECK: + case RANDOM_COMMANDER_DECK: deckChooser.refreshDeckListForAI(); break; default: break; } - updateCommanderStyleDeckChooser(getCommanderDeckChooser(slot), type); - updateCommanderStyleDeckChooser(getOathbreakerDeckChooser(slot), type); - updateCommanderStyleDeckChooser(getTinyLeaderDeckChooser(slot), type); - updateCommanderStyleDeckChooser(getBrawlDeckChooser(slot), type); - } - - private void updateCommanderStyleDeckChooser(final FDeckChooser deckChooser, final LobbySlotType type) { - deckChooser.setIsAi(type==LobbySlotType.AI); - DeckType selectedDeckType = deckChooser.getSelectedDeckType(); - switch (selectedDeckType){ - case RANDOM_CARDGEN_COMMANDER_DECK: - case RANDOM_COMMANDER_DECK: - deckChooser.refreshDeckListForAI(); - break; - default: - break; - } } @Override @@ -308,11 +267,6 @@ public class VLobby implements ILobbyView { if (i < activePlayersNum) { // visible panels final LobbySlot slot = lobby.getSlot(i); - final FDeckChooser deckChooser = getDeckChooser(i); - final FDeckChooser commanderDeckChooser = getCommanderDeckChooser(i); - final FDeckChooser oathbreakerDeckChooser = getOathbreakerDeckChooser(i); - final FDeckChooser tinyLeaderDeckChooser = getTinyLeaderDeckChooser(i); - final FDeckChooser brawlDeckChooser = getBrawlDeckChooser(i); final PlayerPanel panel; final boolean isNewPanel; if (hasPanel) { @@ -326,15 +280,6 @@ public class VLobby implements ILobbyView { constraints += ", gaptop 5px"; } playersScroll.add(panel, constraints); - deckChooser.restoreSavedState(); - commanderDeckChooser.restoreSavedState(); - oathbreakerDeckChooser.restoreSavedState(); - tinyLeaderDeckChooser.restoreSavedState(); - brawlDeckChooser.restoreSavedState(); - if (i == 0) { - slot.setIsDevMode(prefs.getPrefBoolean(FPref.DEV_MODE_ENABLED)); - changePlayerFocus(0); - } isNewPanel = true; } @@ -353,14 +298,24 @@ public class VLobby implements ILobbyView { panel.update(); final boolean isSlotAI = slot.getType() == LobbySlotType.AI; - - deckChooser.setIsAi(isSlotAI); - commanderDeckChooser.setIsAi(isSlotAI); - oathbreakerDeckChooser.setIsAi(isSlotAI); - tinyLeaderDeckChooser.setIsAi(isSlotAI); - brawlDeckChooser.setIsAi(isSlotAI); + if (isNewPanel || fullUpdate) { + final FDeckChooser deckChooser = createDeckChooser(lobby.getGameType(), i, isSlotAI); + deckChooser.populate(); + panel.setDeckChooser(deckChooser); + if (i == 0) { + // TODO: This seems like the wrong place to do this: + slot.setIsDevMode(prefs.getPrefBoolean(FPref.DEV_MODE_ENABLED)); + changePlayerFocus(0); + } + } else { + panel.getDeckChooser().setIsAi(isSlotAI); + } if (fullUpdate && (type == LobbySlotType.LOCAL || isSlotAI)) { - selectDeck(i); + // Deck section selection + panel.getDeckChooser().getLstDecks().getSelectCommand().run(); + selectSchemeDeck(i); + selectPlanarDeck(i); + selectVanguardAvatar(i); } if (isNewPanel) { panel.setVisible(true); @@ -373,7 +328,7 @@ public class VLobby implements ILobbyView { if (playerWithFocus >= activePlayersNum) { changePlayerFocus(activePlayersNum - 1); } else { - populateDeckPanel(getCurrentGameMode()); + populateDeckPanel(lobby.getGameType()); } refreshPanels(true, true); } @@ -395,8 +350,7 @@ public class VLobby implements ILobbyView { void setDevMode(final int index) { // clear ready for everyone for (int i = 0; i < activePlayersNum; i++) { - final PlayerPanel panel = playerPanels.get(i); - panel.setIsReady(false); + getPlayerPanel(i).setIsReady(false); firePlayerChangeListener(i); } changePlayerFocus(index); @@ -430,7 +384,7 @@ public class VLobby implements ILobbyView { } private UpdateLobbyPlayerEvent getSlot(final int index) { - final PlayerPanel panel = playerPanels.get(index); + final PlayerPanel panel = getPlayerPanel(index); return UpdateLobbyPlayerEvent.create(panel.getType(), panel.getPlayerName(), panel.getAvatarIndex(), -1/*TODO panel.getSleeveIndex()*/, panel.getTeam(), panel.isArchenemy(), panel.isReady(), panel.isDevMode(), panel.getAiOptions()); } @@ -438,16 +392,6 @@ public class VLobby implements ILobbyView { * These are added to a list which can be referenced to populate the deck panel appropriately. */ @SuppressWarnings("serial") private void buildDeckPanels(final int playerIndex) { - // Main deck - final FDeckChooser mainChooser = new FDeckChooser(null, isPlayerAI(playerIndex), GameType.Constructed, false); - mainChooser.getLstDecks().setSelectCommand(new UiCommand() { - @Override public final void run() { - selectMainDeck(playerIndex); - } - }); - mainChooser.initialize(); - deckChoosers.add(mainChooser); - // Scheme deck list buildDeckPanel(localizer.getMessage("lblSchemeDeck"), playerIndex, schemeDeckLists, schemeDeckPanels, new ListSelectionListener() { @Override public final void valueChanged(final ListSelectionEvent e) { @@ -455,42 +399,6 @@ public class VLobby implements ILobbyView { } }); - final FDeckChooser commanderChooser = new FDeckChooser(null, isPlayerAI(playerIndex), GameType.Commander, true); - commanderChooser.getLstDecks().setSelectCommand(new UiCommand() { - @Override public final void run() { - selectCommanderDeck(playerIndex); - } - }); - commanderChooser.initialize(); - commanderDeckChoosers.add(commanderChooser); - - final FDeckChooser oathbreakerChooser = new FDeckChooser(null, isPlayerAI(playerIndex), GameType.Oathbreaker, true); - oathbreakerChooser.getLstDecks().setSelectCommand(new UiCommand() { - @Override public final void run() { - selectOathbreakerDeck(playerIndex); - } - }); - oathbreakerChooser.initialize(); - oathbreakerDeckChoosers.add(oathbreakerChooser); - - final FDeckChooser tinyLeaderChooser = new FDeckChooser(null, isPlayerAI(playerIndex), GameType.TinyLeaders, true); - tinyLeaderChooser.getLstDecks().setSelectCommand(new UiCommand() { - @Override public final void run() { - selectTinyLeadersDeck(playerIndex); - } - }); - tinyLeaderChooser.initialize(); - tinyLeadersDeckChoosers.add(tinyLeaderChooser); - - final FDeckChooser brawlChooser = new FDeckChooser(null, isPlayerAI(playerIndex), GameType.Brawl, true); - brawlChooser.getLstDecks().setSelectCommand(new UiCommand() { - @Override public final void run() { - selectBrawlDeck(playerIndex); - } - }); - brawlChooser.initialize(); - brawlDeckChoosers.add(brawlChooser); - // Planar deck list buildDeckPanel(localizer.getMessage("lblPlanarDeck"), playerIndex, planarDeckLists, planarDeckPanels, new ListSelectionListener() { @Override public final void valueChanged(final ListSelectionEvent e) { @@ -532,26 +440,11 @@ public class VLobby implements ILobbyView { deckPanels.add(deckPanel); } - private void selectDeck(final int playerIndex) { - // Full deck selection - selectMainDeck(playerIndex); - selectCommanderDeck(playerIndex); - selectOathbreakerDeck(playerIndex); - selectTinyLeadersDeck(playerIndex); - selectBrawlDeck(playerIndex); - - // Deck section selection - selectSchemeDeck(playerIndex); - selectPlanarDeck(playerIndex); - selectVanguardAvatar(playerIndex); + private FDeckChooser getDeckChooser(final int iSlot) { + return getPlayerPanel(iSlot).getDeckChooser(); } - private void selectMainDeck(final int playerIndex) { - if (hasVariant(GameType.Commander) || hasVariant(GameType.Oathbreaker) || hasVariant(GameType.TinyLeaders) || hasVariant(GameType.Brawl)) { - // These game types use specific deck panel - return; - } - final FDeckChooser mainChooser = getDeckChooser(playerIndex); + private void selectMainDeck(final FDeckChooser mainChooser, final int playerIndex, final boolean isCommanderDeck) { final DeckType type = mainChooser.getSelectedDeckType(); final Deck deck = mainChooser.getDeck(); // something went wrong, clear selection to prevent error loop @@ -561,75 +454,11 @@ public class VLobby implements ILobbyView { final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - playerPanels.get(playerIndex).setDeckSelectorButtonText(text); - fireDeckChangeListener(playerIndex, deck); - } - mainChooser.saveState(); - } - - private void selectCommanderDeck(final int playerIndex) { - if (!hasVariant(GameType.Commander) && !hasVariant(GameType.Oathbreaker) && !hasVariant(GameType.TinyLeaders) && !hasVariant(GameType.Brawl)) { - // Only these game types use this specific deck panel - return; - } - final FDeckChooser mainChooser = getCommanderDeckChooser(playerIndex); - final DeckType type = mainChooser.getSelectedDeckType(); - final Deck deck = mainChooser.getDeck(); - final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); - if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { - final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - playerPanels.get(playerIndex).setCommanderDeckSelectorButtonText(text); - fireDeckChangeListener(playerIndex, deck); - } - mainChooser.saveState(); - } - - private void selectOathbreakerDeck(final int playerIndex) { - if (!hasVariant(GameType.Oathbreaker)) { - // Only these game types use this specific deck panel - return; - } - final FDeckChooser mainChooser = getOathbreakerDeckChooser(playerIndex); - final DeckType type = mainChooser.getSelectedDeckType(); - final Deck deck = mainChooser.getDeck(); - final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); - if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { - final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - playerPanels.get(playerIndex).setCommanderDeckSelectorButtonText(text); - fireDeckChangeListener(playerIndex, deck); - } - mainChooser.saveState(); - } - - private void selectTinyLeadersDeck(final int playerIndex) { - if (!hasVariant(GameType.TinyLeaders)) { - // Only these game types use this specific deck panel - return; - } - final FDeckChooser mainChooser = getTinyLeaderDeckChooser(playerIndex); - final DeckType type = mainChooser.getSelectedDeckType(); - final Deck deck = mainChooser.getDeck(); - final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); - if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { - final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - playerPanels.get(playerIndex).setCommanderDeckSelectorButtonText(text); - fireDeckChangeListener(playerIndex, deck); - } - mainChooser.saveState(); - } - - private void selectBrawlDeck(final int playerIndex) { - if (!hasVariant(GameType.Brawl)) { - // Only these game types use this specific deck panel - return; - } - final FDeckChooser mainChooser = getBrawlDeckChooser(playerIndex); - final DeckType type = mainChooser.getSelectedDeckType(); - final Deck deck = mainChooser.getDeck(); - final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); - if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { - final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - playerPanels.get(playerIndex).setCommanderDeckSelectorButtonText(text); + if (isCommanderDeck) { + getPlayerPanel(playerIndex).setCommanderDeckSelectorButtonText(text); + } else { + getPlayerPanel(playerIndex).setDeckSelectorButtonText(text); + } fireDeckChangeListener(playerIndex, deck); } mainChooser.saveState(); @@ -653,7 +482,7 @@ public class VLobby implements ILobbyView { } } if (sel.equals("Random")) { - final Deck randomDeck = RandomDeckGenerator.getRandomUserDeck(lobby, playerPanels.get(playerIndex).isAi()); + final Deck randomDeck = RandomDeckGenerator.getRandomUserDeck(lobby, isPlayerAI(playerIndex)); schemePool = randomDeck.get(DeckSection.Schemes); } } else if (selected instanceof Deck) { @@ -684,7 +513,7 @@ public class VLobby implements ILobbyView { } } if (sel.equals("Random")) { - final Deck randomDeck = RandomDeckGenerator.getRandomUserDeck(lobby, playerPanels.get(playerIndex).isAi()); + final Deck randomDeck = RandomDeckGenerator.getRandomUserDeck(lobby, isPlayerAI(playerIndex)); planePool = randomDeck.get(DeckSection.Planes); } } else if (selected instanceof Deck) { @@ -703,7 +532,7 @@ public class VLobby implements ILobbyView { } final Object selected = vgdAvatarLists.get(playerIndex).getSelectedValue(); - final PlayerPanel pp = playerPanels.get(playerIndex); + final PlayerPanel pp = getPlayerPanel(playerIndex); final CardDetailPanel cdp = vgdAvatarDetails.get(playerIndex); PaperCard vanguardAvatar = null; @@ -726,7 +555,7 @@ public class VLobby implements ILobbyView { if (sel.contains("Use deck's default avatar") && deck != null && deck.has(DeckSection.Avatar)) { vanguardAvatar = deck.get(DeckSection.Avatar).get(0); } else { //Only other string is "Random" - if (playerPanels.get(playerIndex).isAi()) { //AI + if (isPlayerAI(playerIndex)) { //AI vanguardAvatar = Aggregates.random(getNonRandomAiAvatars()); } else { //Human vanguardAvatar = Aggregates.random(getNonRandomHumanAvatars()); @@ -750,8 +579,8 @@ public class VLobby implements ILobbyView { switch (forGameType) { case Constructed: - decksFrame.add(deckChoosers.get(playerWithFocus), "grow, push"); - if (deckChoosers.get(playerWithFocus).getSelectedDeckType().toString().contains(localizer.getMessage("lblRandom"))) { + decksFrame.add(getDeckChooser(playerWithFocus), "grow, push"); + if (getDeckChooser(playerWithFocus).getSelectedDeckType().toString().contains(localizer.getMessage("lblRandom"))) { final String strCheckboxConstraints = "h 30px!, gap 0 20px 0 0"; decksFrame.add(cbSingletons, strCheckboxConstraints); decksFrame.add(cbArtifacts, strCheckboxConstraints); @@ -766,16 +595,10 @@ public class VLobby implements ILobbyView { } break; case Commander: - decksFrame.add(commanderDeckChoosers.get(playerWithFocus), "grow, push"); - break; case Oathbreaker: - decksFrame.add(oathbreakerDeckChoosers.get(playerWithFocus), "grow, push"); - break; case TinyLeaders: - decksFrame.add(tinyLeadersDeckChoosers.get(playerWithFocus), "grow, push"); - break; case Brawl: - decksFrame.add(brawlDeckChoosers.get(playerWithFocus), "grow, push"); + decksFrame.add(getDeckChooser(playerWithFocus), "grow, push"); break; case Planechase: decksFrame.add(planarDeckPanels.get(playerWithFocus), "grow, push"); @@ -798,7 +621,13 @@ public class VLobby implements ILobbyView { public LblHeader getLblTitle() { return lblTitle; } public JPanel getConstructedFrame() { return constructedFrame; } public JPanel getPanelStart() { return pnlStart; } - public List getDeckChoosers() { return Collections.unmodifiableList(deckChoosers); } + public List getDeckChoosers() { + List choosers = new ArrayList<>(playerPanels.size()); + for (final PlayerPanel playerPanel : playerPanels) { + choosers.add(playerPanel.getDeckChooser()); + } + return choosers; + } /** Gets the random deck checkbox for Singletons. */ FCheckBox getCbSingletons() { return cbSingletons; } @@ -816,29 +645,6 @@ public class VLobby implements ILobbyView { return iPlayer == playerWithFocus; } - public final FDeckChooser getDeckChooser(final int playernum) { - return deckChoosers.get(playernum); - } - - public final FDeckChooser getCommanderDeckChooser(final int playernum) { - return commanderDeckChoosers.get(playernum); - } - - public final FDeckChooser getOathbreakerDeckChooser(final int playernum) { - return oathbreakerDeckChoosers.get(playernum); - } - - public final FDeckChooser getTinyLeaderDeckChooser(final int playernum) { - return tinyLeadersDeckChoosers.get(playernum); - } - - public final FDeckChooser getBrawlDeckChooser(final int playernum) { - return brawlDeckChoosers.get(playernum); - } - - GameType getCurrentGameMode() { - return lobby.getGameType(); - } void setCurrentGameMode(final GameType mode) { lobby.setGameType(mode); update(true); @@ -851,10 +657,6 @@ public class VLobby implements ILobbyView { return true; } - public int getNumPlayers() { - return activePlayersNum; - } - /** Revalidates the player and deck sections. Necessary after adding or hiding any panels. */ private void refreshPanels(final boolean refreshPlayerFrame, final boolean refreshDeckFrame) { if (refreshPlayerFrame) { @@ -888,8 +690,8 @@ public class VLobby implements ILobbyView { /** Saves avatar prefs for players one and two. */ void updateAvatarPrefs() { - final int pOneIndex = playerPanels.get(0).getAvatarIndex(); - final int pTwoIndex = playerPanels.get(1).getAvatarIndex(); + final int pOneIndex = getPlayerPanel(0).getAvatarIndex(); + final int pTwoIndex = getPlayerPanel(1).getAvatarIndex(); prefs.setPref(FPref.UI_AVATARS, pOneIndex + "," + pTwoIndex); prefs.save(); @@ -897,8 +699,8 @@ public class VLobby implements ILobbyView { /** Saves sleeve prefs for players one and two. */ void updateSleevePrefs() { - final int pOneIndex = playerPanels.get(0).getSleeveIndex(); - final int pTwoIndex = playerPanels.get(1).getSleeveIndex(); + final int pOneIndex = getPlayerPanel(0).getSleeveIndex(); + final int pTwoIndex = getPlayerPanel(1).getSleeveIndex(); prefs.setPref(FPref.UI_SLEEVES, pOneIndex + "," + pTwoIndex); prefs.save(); @@ -925,7 +727,6 @@ public class VLobby implements ILobbyView { return usedSleeves; } - private static final ImmutableList genderOptions = ImmutableList.of("Male", "Female", "Any"), typeOptions = ImmutableList.of("Fantasy", "Generic", "Any"); final String getNewName() { @@ -973,18 +774,57 @@ public class VLobby implements ILobbyView { this.variant = variantType; setToolTipText(variantType.getDescription()); - addItemListener(new ItemListener() { - @Override public final void itemStateChanged(final ItemEvent e) { - if (e.getStateChange() == ItemEvent.SELECTED) { - lobby.applyVariant(variantType); - } else { - lobby.removeVariant(variantType); - } + addItemListener(e -> { + if (e.getStateChange() == ItemEvent.SELECTED) { + lobby.applyVariant(variantType); + } else { + lobby.removeVariant(variantType); } + VLobby.this.update(false); }); } } + private FDeckChooser createDeckChooser(final GameType type, final int iSlot, final boolean ai) { + boolean forCommander; + DeckType deckType; + FPref prefKey; + switch (type) { + case Commander: + forCommander = true; + deckType = iSlot == 0 ? DeckType.COMMANDER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; + prefKey = FPref.COMMANDER_DECK_STATES[iSlot]; + break; + case TinyLeaders: + forCommander = true; + deckType = iSlot == 0 ? DeckType.TINY_LEADERS_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; + prefKey = FPref.TINY_LEADER_DECK_STATES[iSlot]; + break; + case Oathbreaker: + forCommander = true; + deckType = iSlot == 0 ? DeckType.OATHBREAKER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; + prefKey = FPref.OATHBREAKER_DECK_STATES[iSlot]; + break; + case Brawl: + forCommander = true; + deckType = iSlot == 0 ? DeckType.BRAWL_DECK : DeckType.CUSTOM_DECK; + prefKey = FPref.BRAWL_DECK_STATES[iSlot]; + break; + default: + forCommander = false; + deckType = iSlot == 0 ? DeckType.PRECONSTRUCTED_DECK : DeckType.COLOR_DECK; + prefKey = FPref.CONSTRUCTED_DECK_STATES[iSlot]; + break; + } + return cachedDeckChoosers.computeIfAbsent(prefKey, (key) -> { + final GameType gameType = forCommander ? type : GameType.Constructed; + final FDeckChooser fdc = new FDeckChooser(null, ai, gameType, forCommander); + fdc.initialize(prefKey, deckType); + fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot, forCommander)); + return fdc; + }); + } + final ActionListener nameListener = new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { @@ -1007,7 +847,7 @@ public class VLobby implements ILobbyView { } public boolean isPlayerArchenemy(final int playernum) { - return playerPanels.get(playernum).isArchenemy(); + return getPlayerPanel(playernum).isArchenemy(); } /** Gets the list of Vanguard avatar lists. */ @@ -1027,11 +867,6 @@ public class VLobby implements ILobbyView { return vgdAllAvatars; } - /** Return the Vanguard avatars not flagged RemoveDeck:All. */ - public List getAllAiAvatars() { - return vgdAllAiAvatars; - } - /** Return the Vanguard avatars not flagged RemoveDeck:Random. */ public List getNonRandomHumanAvatars() { return nonRandomHumanAvatars; @@ -1055,7 +890,6 @@ public class VLobby implements ILobbyView { } if (!cp.getRules().getAiHints().getRemAIDecks()) { aiListData.add(cp); - vgdAllAiAvatars.add(cp); if (!cp.getRules().getAiHints().getRemRandomDecks()) { nonRandomAiAvatars.add(cp); } diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDev.java b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDev.java index be6b3b8540c..896dfa0dc47 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDev.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDev.java @@ -28,31 +28,32 @@ public final class CDev implements ICDoc { this.view = new VDev(this); addListener(view); - view.getLblUnlimitedLands().addMouseListener(madUnlimited); - view.getLblViewAll().addMouseListener(madViewAll); - view.getLblGenerateMana().addMouseListener(madMana); - view.getLblSetupGame().addMouseListener(madSetup); - view.getLblDumpGame().addMouseListener(madDump); - view.getLblTutor().addMouseListener(madTutor); - view.getLblCardToHand().addMouseListener(madCardToHand); - view.getLblExileFromHand().addMouseListener(madExileFromHand); - view.getLblCardToBattlefield().addMouseListener(madCardToBattlefield); - view.getLblCardToLibrary().addMouseListener(madCardToLibrary); - view.getLblCardToGraveyard().addMouseListener(madCardToGraveyard); - view.getLblCardToExile().addMouseListener(madCardToExile); - view.getLblCastSpell().addMouseListener(madCastASpell); - view.getLblRepeatAddCard().addMouseListener(madRepeatAddCard); - view.getLblAddCounterPermanent().addMouseListener(madAddCounter); - view.getLblSubCounterPermanent().addMouseListener(madSubCounter); - view.getLblTapPermanent().addMouseListener(madTap); - view.getLblUntapPermanent().addMouseListener(madUntap); - view.getLblSetLife().addMouseListener(madLife); - view.getLblWinGame().addMouseListener(madWinGame); - view.getLblExileFromPlay().addMouseListener(madExileFromPlay); - view.getLblRemoveFromGame().addMouseListener(madRemoveFromGame); - view.getLblRiggedRoll().addMouseListener(madRiggedRoll); - view.getLblWalkTo().addMouseListener(madWalkToPlane); - view.getLblAskAI().addMouseListener(madAskAI); + view.getLblUnlimitedLands().addMouseListener(onClick(this::togglePlayManyLandsPerTurn)); + view.getLblViewAll().addMouseListener(onClick(this::toggleViewAllCards)); + view.getLblGenerateMana().addMouseListener(onClick(this::generateMana)); + view.getLblSetupGame().addMouseListener(onClick(this::setupGameState)); + view.getLblDumpGame().addMouseListener(onClick(this::dumpGameState)); + view.getLblTutor().addMouseListener(onClick(this::tutorForCard)); + view.getLblCardToHand().addMouseListener(onClick(this::addCardToHand)); + view.getLblExileFromHand().addMouseListener(onClick(this::exileCardsFromHand)); + view.getLblCardToBattlefield().addMouseListener(onClick(this::addCardToBattlefield)); + view.getLblCardToLibrary().addMouseListener(onClick(this::addCardToLibrary)); + view.getLblCardToGraveyard().addMouseListener(onClick(this::addCardToGraveyard)); + view.getLblCardToExile().addMouseListener(onClick(this::addCardToExile)); + view.getLblCastSpell().addMouseListener(onClick(this::castASpell)); + view.getLblRepeatAddCard().addMouseListener(onClick(this::repeatAddCard)); + view.getLblAddCounterPermanent().addMouseListener(onClick(this::addCounterToPermanent)); + view.getLblSubCounterPermanent().addMouseListener(onClick(this::removeCountersFromPermanent)); + view.getLblTapPermanent().addMouseListener(onClick(this::tapPermanent)); + view.getLblUntapPermanent().addMouseListener(onClick(this::untapPermanent)); + view.getLblSetLife().addMouseListener(onClick(this::setPlayerLife)); + view.getLblWinGame().addMouseListener(onClick(this::winGame)); + view.getLblExileFromPlay().addMouseListener(onClick(this::exileCardsFromPlay)); + view.getLblRemoveFromGame().addMouseListener(onClick(this::removeCardsFromGame)); + view.getLblRiggedRoll().addMouseListener(onClick(this::riggedPlanerRoll)); + view.getLblWalkTo().addMouseListener(onClick(this::planeswalkTo)); + view.getLblAskAI().addMouseListener(onClick(this::askAI)); + view.getLblAskSimulationAI().addMouseListener(onClick(this::askSimulationAI)); } public IGameController getController() { return matchUI.getGameController(); @@ -66,258 +67,99 @@ public final class CDev implements ICDoc { listeners.add(listener); } - private final MouseListener madUnlimited = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - togglePlayManyLandsPerTurn(); - } - }; + private MouseListener onClick(Runnable r) { + return new MouseAdapter() { + @Override + public void mousePressed(final MouseEvent e) { + r.run(); + } + }; + } + public void togglePlayManyLandsPerTurn() { final boolean newValue = !view.getLblUnlimitedLands().getToggled(); getController().cheat().setCanPlayUnlimitedLands(newValue); update(); } - private final MouseListener madViewAll = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - toggleViewAllCards(); - } - }; public void toggleViewAllCards() { final boolean newValue = !view.getLblViewAll().getToggled(); getController().cheat().setViewAllCards(newValue); update(); } - private final MouseListener madMana = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - generateMana(); - } - }; public void generateMana() { getController().cheat().generateMana(); } - - private final MouseListener madSetup = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - setupGameState(); - } - }; public void setupGameState() { getController().cheat().setupGameState(); } - - private final MouseListener madDump = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - dumpGameState(); - } - }; public void dumpGameState() { getController().cheat().dumpGameState(); } - - private final MouseListener madTutor = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - tutorForCard(); - } - }; public void tutorForCard() { getController().cheat().tutorForCard(); } - - private final MouseListener madCardToHand = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - addCardToHand(); - } - }; public void addCardToHand() { getController().cheat().addCardToHand(); } - - private final MouseListener madCardToLibrary = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - addCardToLibrary(); - } - }; public void addCardToLibrary() { getController().cheat().addCardToLibrary(); } - - private final MouseListener madCardToGraveyard = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - addCardToGraveyard(); - } - }; public void addCardToGraveyard() { getController().cheat().addCardToGraveyard(); } - - private final MouseListener madCardToExile = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - addCardToExile(); - } - }; public void addCardToExile() { getController().cheat().addCardToExile(); } - private final MouseListener madCastASpell = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - castASpell(); - } - }; public void castASpell() { getController().cheat().castASpell(); } - - private final MouseListener madRepeatAddCard = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - repeatAddCard(); - } - }; public void repeatAddCard() { getController().cheat().repeatLastAddition(); } - - private final MouseListener madExileFromHand = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - exileCardsFromHand(); - } - }; public void exileCardsFromHand() { getController().cheat().exileCardsFromHand(); } - - private final MouseListener madAddCounter = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - addCounterToPermanent(); - } - }; public void addCounterToPermanent() { getController().cheat().addCountersToPermanent(); } - - private final MouseListener madSubCounter = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - removeCountersFromPermanent(); - } - }; public void removeCountersFromPermanent() { getController().cheat().removeCountersFromPermanent(); } - - private final MouseListener madTap = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - tapPermanent(); - } - }; public void tapPermanent() { getController().cheat().tapPermanents(); } - - private final MouseListener madUntap = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - untapPermanent(); - } - }; public void untapPermanent() { getController().cheat().untapPermanents(); } - - private final MouseListener madLife = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - setPlayerLife(); - } - }; public void setPlayerLife() { getController().cheat().setPlayerLife(); } - - private final MouseListener madWinGame = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - winGame(); - } - }; public void winGame() { getController().cheat().winGame(); } - - private final MouseListener madCardToBattlefield = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - addCardToBattlefield(); - } - }; public void addCardToBattlefield() { getController().cheat().addCardToBattlefield(); } - - private final MouseListener madExileFromPlay = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - exileCardsFromPlay(); - } - }; public void exileCardsFromPlay() { getController().cheat().exileCardsFromBattlefield(); } - - private final MouseListener madRemoveFromGame = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - removeCardsFromGame(); - } - }; public void removeCardsFromGame() { getController().cheat().removeCardsFromGame(); } - - private final MouseListener madRiggedRoll = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - riggedPlanerRoll(); - } - }; public void riggedPlanerRoll() { getController().cheat().riggedPlanarRoll(); } - - private final MouseListener madWalkToPlane = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - planeswalkTo(); - } - }; public void planeswalkTo() { getController().cheat().planeswalkTo(); } - - private final MouseListener madAskAI = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - askAI(); - } - }; public void askAI() { - getController().cheat().askAI(); + getController().cheat().askAI(false); + } + public void askSimulationAI() { + getController().cheat().askAI(true); } //========== End mouse listener inits diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/views/VDev.java b/forge-gui-desktop/src/main/java/forge/screens/match/views/VDev.java index e1deb6fe01c..6de6eb49e30 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/views/VDev.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/views/VDev.java @@ -82,6 +82,7 @@ public class VDev implements IVDoc, IDevListener { private final DevLabel lblWalkTo = new DevLabel(Localizer.getInstance().getMessage("lblWalkTo")); private final DevLabel lblAskAI = new DevLabel(Localizer.getInstance().getMessage("lblAskAI")); + private final DevLabel lblAskSimulationAI = new DevLabel(Localizer.getInstance().getMessage("lblAskSimulationAI")); private final CDev controller; @@ -91,7 +92,6 @@ public class VDev implements IVDoc, IDevListener { public VDev(final CDev controller) { this.controller = controller; - final String constraints = "w 95%!, gap 0 0 4px 0"; final String halfConstraints = "w 47%!, gap 0 0 4px 0"; final String halfConstraintsLeft = halfConstraints + ", split 2"; viewport.setOpaque(false); @@ -113,13 +113,14 @@ public class VDev implements IVDoc, IDevListener { viewport.add(this.lblWinGame, halfConstraints); viewport.add(this.lblAddCounterPermanent, halfConstraintsLeft); viewport.add(this.lblSubCounterPermanent, halfConstraints); - viewport.add(this.lblSetupGame, halfConstraintsLeft); - viewport.add(this.lblDumpGame, halfConstraints); viewport.add(this.lblTapPermanent, halfConstraintsLeft); viewport.add(this.lblUntapPermanent, halfConstraints); viewport.add(this.lblRiggedRoll, halfConstraintsLeft); viewport.add(this.lblWalkTo, halfConstraints); viewport.add(this.lblAskAI, halfConstraintsLeft); + viewport.add(this.lblAskSimulationAI, halfConstraintsLeft); + viewport.add(this.lblSetupGame, halfConstraintsLeft); + viewport.add(this.lblDumpGame, halfConstraints); } //========= Overridden methods @@ -302,20 +303,23 @@ public class VDev implements IVDoc, IDevListener { return this.lblAskAI; } + public DevLabel getLblAskSimulationAI() { + return this.lblAskSimulationAI; + } + /** * Labels that act as buttons which control dev mode functions. Labels are * used to support multiline text. */ - public class DevLabel extends SkinnedLabel { + public static class DevLabel extends SkinnedLabel { private static final long serialVersionUID = 7917311680519060700L; private FSkin.SkinColor defaultBG; private final FSkin.SkinColor hoverBG = FSkin.getColor(FSkin.Colors.CLR_HOVER); private final FSkin.SkinColor pressedBG = FSkin.getColor(FSkin.Colors.CLR_INACTIVE); private boolean toggled; - private int w, h; // Width, height, radius, insets (for paintComponent) - private final int r, i; + private final int r, i; // Radius, insets (for paintComponent) public DevLabel(final String text0) { super(); @@ -380,10 +384,10 @@ public class VDev implements IVDoc, IDevListener { */ @Override protected void paintComponent(final Graphics g) { - this.w = this.getWidth(); - this.h = this.getHeight(); + int w = this.getWidth(); + int h = this.getHeight(); g.setColor(this.getBackground()); - g.fillRoundRect(this.i, this.i, this.w - (2 * this.i), this.h - this.i, this.r, this.r); + g.fillRoundRect(this.i, this.i, w - (2 * this.i), h - this.i, this.r, this.r); super.paintComponent(g); } } diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java index 8aa56bc5053..67780d024a3 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java @@ -483,4 +483,126 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest { AssertJUnit.assertEquals("Chaos Warp", sa.getHostCard().getName()); AssertJUnit.assertEquals(expectedTarget, sa.getTargetCard()); } + + @Test + public void testNoSimulationsWhenNoTargets() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(1); + + addCard("Island", p); + addCard("Island", p); + addCardToZone("Counterspell", p, ZoneType.Hand); + addCardToZone("Unsummon", p, ZoneType.Hand); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + game.getAction().checkStateEffects(true); + + SpellAbilityPicker picker = new SpellAbilityPicker(game, p); + SpellAbility sa = picker.chooseSpellAbilityToPlay(null); + AssertJUnit.assertNull(sa); + AssertJUnit.assertEquals(0, picker.getNumSimulations()); + } + + @Test + public void testLandDropPruning() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(1); + + addCardToZone("Island", p, ZoneType.Hand); + addCardToZone("Island", p, ZoneType.Hand); + addCardToZone("Island", p, ZoneType.Hand); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + game.getAction().checkStateEffects(true); + + SpellAbilityPicker picker = new SpellAbilityPicker(game, p); + SpellAbility sa = picker.chooseSpellAbilityToPlay(null); + AssertJUnit.assertNotNull(sa); + // Only one land drop should be simulated, since the cards are identical. + AssertJUnit.assertEquals(1, picker.getNumSimulations()); + } + + @Test + public void testSpellCantTargetSelf() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(1); + Player opponent = game.getPlayers().get(0); + + addCardToZone("Unsubstantiate", p, ZoneType.Hand); + addCard("Forest", p); + addCard("Island", p); + Card expectedTarget = addCard("Flying Men", opponent); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + game.getAction().checkStateEffects(true); + + SpellAbilityPicker picker = new SpellAbilityPicker(game, p); + SpellAbility sa = picker.chooseSpellAbilityToPlay(null); + AssertJUnit.assertNotNull(sa); + AssertJUnit.assertEquals(expectedTarget, sa.getTargetCard()); + // Only a single simulation expected (no target self). + AssertJUnit.assertEquals(1, picker.getNumSimulations()); + } + + @Test + public void testModalSpellCantTargetSelf() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(1); + Player opponent = game.getPlayers().get(0); + + addCardToZone("Decisive Denial", p, ZoneType.Hand); + addCard("Forest", p); + addCard("Island", p); + addCard("Runeclaw Bear", p); + addCard("Flying Men", opponent); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + game.getAction().checkStateEffects(true); + + SpellAbilityPicker picker = new SpellAbilityPicker(game, p); + SpellAbility sa = picker.chooseSpellAbilityToPlay(null); + AssertJUnit.assertNotNull(sa); + // Expected: Runeclaw Bear fights Flying Men + // Only a single simulation expected (no target self). + AssertJUnit.assertEquals(1, picker.getNumSimulations()); + } + + @Test + public void testModalSpellNoTargetsForModeWithSubAbility() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(1); + + addCardToZone("Temur Charm", p, ZoneType.Hand); + addCard("Forest", p); + addCard("Island", p); + addCard("Mountain", p); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + game.getAction().checkStateEffects(true); + + SpellAbilityPicker picker = new SpellAbilityPicker(game, p); + picker.chooseSpellAbilityToPlay(null); + // Only mode "Creatures with power 3 or less can’t block this turn" should be simulated. + AssertJUnit.assertEquals(1, picker.getNumSimulations()); + } + + @Test + public void testModalSpellNoTargetsForAnyModes() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(1); + + addCardToZone("Drown in the Loch", p, ZoneType.Hand); + addCard("Swamp", p); + addCard("Island", p); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + game.getAction().checkStateEffects(true); + + SpellAbilityPicker picker = new SpellAbilityPicker(game, p); + picker.chooseSpellAbilityToPlay(null); + // TODO: Ideally, this would be 0 simulations, but we currently only determine there are no + // valid modes in SpellAbilityChoicesIterator, which runs already when we're simulating. + // Still, this test case exercises the code path and ensures we don't crash in this case. + AssertJUnit.assertEquals(1, picker.getNumSimulations()); + } } diff --git a/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java b/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java index ec5a953008f..4e1478f690e 100644 --- a/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java +++ b/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java @@ -41,7 +41,7 @@ public class PointOfInterestChanges implements SaveFileContent { } data.storeObject("keys",keys.toArray(new String[0])); for(int i=0;i(); } - else { - return Arrays.asList(savedState.split(";")[1].split(SELECTED_DECK_DELIMITER)); - } + final String[] parts = savedState.split(";", -1); + return Arrays.asList(parts[1].split(SELECTED_DECK_DELIMITER)); } catch (Exception ex) { System.err.println(ex + " [savedState=" + savedState + "]"); diff --git a/forge-gui-mobile/src/forge/deck/FDeckViewer.java b/forge-gui-mobile/src/forge/deck/FDeckViewer.java index 16b359a8766..ddb0894a722 100644 --- a/forge-gui-mobile/src/forge/deck/FDeckViewer.java +++ b/forge-gui-mobile/src/forge/deck/FDeckViewer.java @@ -99,7 +99,7 @@ public class FDeckViewer extends FScreen { deckList.append(s.toString()).append(": "); deckList.append(nl); for (final Entry ev : cp) { - deckList.append(ev.getValue()).append(" ").append(ev.getKey()).append(nl); + deckList.append(ev.getValue()).append(" ").append(ev.getKey().getCardName()).append(nl); } deckList.append(nl); } diff --git a/forge-gui/res/cardsfolder/a/acidic_sliver.txt b/forge-gui/res/cardsfolder/a/acidic_sliver.txt index c6f3144c5da..ee78d610d50 100644 --- a/forge-gui/res/cardsfolder/a/acidic_sliver.txt +++ b/forge-gui/res/cardsfolder/a/acidic_sliver.txt @@ -3,6 +3,6 @@ ManaCost:B R Types:Creature Sliver PT:2/2 S:Mode$ Continuous | Affected$ Sliver | AddAbility$ Damage | Description$ All Slivers have "{2}, Sacrifice this permanent: This permanent deals 2 damage to any target." -SVar:Damage:AB$DealDamage | Cost$ 2 Sac<1/CARDNAME> | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 2 | SpellDescription$ CARDNAME deals 2 damage to any target. +SVar:Damage:AB$ DealDamage | Cost$ 2 Sac<1/CARDNAME> | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 2 | SpellDescription$ CARDNAME deals 2 damage to any target. SVar:BuffedBy:Sliver Oracle:All Slivers have "{2}, Sacrifice this permanent: This permanent deals 2 damage to any target." diff --git a/forge-gui/res/cardsfolder/a/anavolver.txt b/forge-gui/res/cardsfolder/a/anavolver.txt index c049d5713e1..d67913f4d16 100644 --- a/forge-gui/res/cardsfolder/a/anavolver.txt +++ b/forge-gui/res/cardsfolder/a/anavolver.txt @@ -9,7 +9,7 @@ SVar:VolverStrength:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | Counter SVar:VolverLaunch:DB$ Animate | Defined$ Self | Keywords$ Flying | Duration$ Permanent SVar:VolverPumped:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | ETB$ True | SubAbility$ VolverResilience | SpellDescription$ If CARDNAME was kicked with its {B} kicker, it enters the battlefield with a +1/+1 counter on it and with "Pay 3 life: Regenerate CARDNAME." SVar:VolverResilience:DB$ Animate | Defined$ Self | Abilities$ ABRegen | Duration$ Permanent -SVar:ABRegen:AB$Regenerate | Cost$ PayLife<3> | SpellDescription$ Regenerate CARDNAME. +SVar:ABRegen:AB$ Regenerate | Cost$ PayLife<3> | SpellDescription$ Regenerate CARDNAME. AI:RemoveDeck:Random DeckNeeds:Color$Blue|Black DeckHas:Ability$Counters diff --git a/forge-gui/res/cardsfolder/a/animal_boneyard.txt b/forge-gui/res/cardsfolder/a/animal_boneyard.txt index d979cc8c48f..f4181417a46 100644 --- a/forge-gui/res/cardsfolder/a/animal_boneyard.txt +++ b/forge-gui/res/cardsfolder/a/animal_boneyard.txt @@ -4,7 +4,7 @@ Types:Enchantment Aura K:Enchant land A:SP$ Attach | Cost$ 2 W | ValidTgts$ Land | AILogic$ Pump S:Mode$ Continuous | Affected$ Land.AttachedBy | AddAbility$ GainLife | AddSVar$ AnimalBoneyardX | Description$ Enchanted land has "{T}, Sacrifice a creature: You gain life equal to the sacrificed creature's toughness." -SVar:GainLife:AB$GainLife | Cost$ T Sac<1/Creature> | LifeAmount$ AnimalBoneyardX | SpellDescription$ You gain life equal to the sacrificed creature's toughness. +SVar:GainLife:AB$ GainLife | Cost$ T Sac<1/Creature> | LifeAmount$ AnimalBoneyardX | SpellDescription$ You gain life equal to the sacrificed creature's toughness. SVar:AnimalBoneyardX:Sacrificed$CardToughness AI:RemoveDeck:All SVar:NonStackingAttachEffect:True diff --git a/forge-gui/res/cardsfolder/a/animate_dead.txt b/forge-gui/res/cardsfolder/a/animate_dead.txt index 378e22d60dc..ec8b0694c61 100644 --- a/forge-gui/res/cardsfolder/a/animate_dead.txt +++ b/forge-gui/res/cardsfolder/a/animate_dead.txt @@ -3,13 +3,14 @@ ManaCost:1 B Types:Enchantment Aura K:Enchant creature card in a graveyard A:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature | TgtZone$ Graveyard | AILogic$ Reanimate -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters the battlefield, if it's on the battlefield, it loses "enchant creature card in a graveyard" and gains "enchant creature put onto the battlefield with CARDNAME." Return enchanted creature card to the battlefield under your control and attach CARDNAME to it. +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters the battlefield, if it's on the battlefield, it loses "enchant creature card in a graveyard" and gains "enchant creature put onto the battlefield with CARDNAME." Return enchanted creature card to the battlefield under your control and attach CARDNAME to it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it. SVar:TrigReanimate:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ Enchanted | RememberChanged$ True | GainControl$ True | SubAbility$ DBAnimate SVar:DBAnimate:DB$ Animate | Defined$ Self | OverwriteSpells$ True | Abilities$ NewAttach | Keywords$ Enchant creature put onto the battlefield with CARDNAME | RemoveKeywords$ Enchant creature card in a graveyard | Duration$ Permanent | SubAbility$ DBAttach -SVar:DBAttach:DB$ Attach | Defined$ Remembered +SVar:DBAttach:DB$ Attach | Defined$ Remembered | SubAbility$ DBDelay +SVar:DBDelay:DB$ DelayedTrigger | Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Execute$ TrigSacrifice | RememberObjects$ RememberedLKI | TriggerDescription$ When CARDNAME leaves the battlefield, that creature's controller sacrifices it. SVar:NewAttach:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature.IsRemembered | AILogic$ Pump -T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigSacrifice | TriggerDescription$ When Animate Dead leaves the battlefield, that creature's controller sacrifices it. -SVar:TrigSacrifice:DB$ Destroy | Sacrifice$ True | Defined$ DirectRemembered | SubAbility$ DBCleanup +SVar:TrigSacrifice:DB$ Destroy | Sacrifice$ True | Defined$ DelayTriggerRememberedLKI +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ -1 | Description$ Enchanted creature gets -1/-0. DeckHas:Ability$Graveyard diff --git a/forge-gui/res/cardsfolder/a/arcane_teachings.txt b/forge-gui/res/cardsfolder/a/arcane_teachings.txt index b214189fad8..54385c23da4 100644 --- a/forge-gui/res/cardsfolder/a/arcane_teachings.txt +++ b/forge-gui/res/cardsfolder/a/arcane_teachings.txt @@ -4,5 +4,5 @@ Types:Enchantment Aura K:Enchant creature A:SP$ Attach | Cost$ 2 R | ValidTgts$ Creature | AILogic$ Pump S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 2 | AddToughness$ 2 | AddAbility$ Damage | Description$ Enchanted creature gets +2/+2 and has "{T}: This creature deals 1 damage to any target." -SVar:Damage:AB$DealDamage | Cost$ T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to any target. +SVar:Damage:AB$ DealDamage | Cost$ T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to any target. Oracle:Enchant creature (Target a creature as you cast this. This card enters the battlefield attached to that creature.)\nEnchanted creature gets +2/+2 and has "{T}: This creature deals 1 damage to any target." diff --git a/forge-gui/res/cardsfolder/b/barbed_field.txt b/forge-gui/res/cardsfolder/b/barbed_field.txt index 0efbeeee681..e57ce0fbd47 100644 --- a/forge-gui/res/cardsfolder/b/barbed_field.txt +++ b/forge-gui/res/cardsfolder/b/barbed_field.txt @@ -4,6 +4,6 @@ Types:Enchantment Aura K:Enchant land A:SP$ Attach | Cost$ 2 R R | ValidTgts$ Land | AILogic$ Pump S:Mode$ Continuous | Affected$ Land.EnchantedBy | AddAbility$ Damage | Description$ Enchanted land has "{T}: This land deals 1 damage to any target." -SVar:Damage:AB$DealDamage | Cost$ T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to any target. +SVar:Damage:AB$ DealDamage | Cost$ T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to any target. SVar:NonStackingAttachEffect:True Oracle:Enchant land\nEnchanted land has "{T}: This land deals 1 damage to any target." diff --git a/forge-gui/res/cardsfolder/b/basal_sliver.txt b/forge-gui/res/cardsfolder/b/basal_sliver.txt index 5fe4e91b7df..c971ebafc71 100644 --- a/forge-gui/res/cardsfolder/b/basal_sliver.txt +++ b/forge-gui/res/cardsfolder/b/basal_sliver.txt @@ -3,6 +3,6 @@ ManaCost:2 B Types:Creature Sliver PT:2/2 S:Mode$ Continuous | Affected$ Sliver | AddAbility$ Mana | Description$ All Slivers have "Sacrifice this permanent: Add {B}{B}." -SVar:Mana:AB$Mana | Cost$ Sac<1/CARDNAME> | Produced$ B | Amount$ 2 | SpellDescription$ Add {B}{B}. +SVar:Mana:AB$ Mana | Cost$ Sac<1/CARDNAME> | Produced$ B | Amount$ 2 | SpellDescription$ Add {B}{B}. AI:RemoveDeck:All Oracle:All Slivers have "Sacrifice this permanent: Add {B}{B}." diff --git a/forge-gui/res/cardsfolder/b/bloodthirsty_adversary.txt b/forge-gui/res/cardsfolder/b/bloodthirsty_adversary.txt index 8b3c61b2a2e..f1111b4fffb 100644 --- a/forge-gui/res/cardsfolder/b/bloodthirsty_adversary.txt +++ b/forge-gui/res/cardsfolder/b/bloodthirsty_adversary.txt @@ -4,12 +4,11 @@ Types:Creature Vampire PT:2/2 K:Haste T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {2}{R} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs. -SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<2 R\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs. -SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ NumTimes | SubAbility$ DBExile -SVar:DBExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ NumTimes | ValidTgts$ Instant.cmcLE3+YouOwn,Sorcery.cmcLE3+YouOwn | TgtPrompt$ Select up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard | RememberChanged$ True | SubAbility$ DBCopyCast -SVar:DBCopyCast:DB$ Play | Valid$ Card.IsRemembered | ValidZone$ Exile | Controller$ You | CopyCard$ True | WithoutManaCost$ True | ValidSA$ Spell | Optional$ True | Amount$ All | SubAbility$ ExileMe -SVar:ExileMe:DB$ ChangeZoneAll | Origin$ Stack | Destination$ Exile | ChangeType$ Card.Self | SubAbility$ DBCleanup +SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<2 R\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | RememberSVarAmount$ NumTimes | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBExile +SVar:DBExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ X | ValidTgts$ Instant.cmcLE3+YouOwn,Sorcery.cmcLE3+YouOwn | TgtPrompt$ Select up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard | RememberChanged$ True | SubAbility$ DBCopyCast +SVar:DBCopyCast:DB$ Play | Valid$ Card.IsRemembered | ValidZone$ Exile | Controller$ You | CopyCard$ True | WithoutManaCost$ True | ValidSA$ Spell | Optional$ True | Amount$ All | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:NumTimes:Number$0 +SVar:X:Count$TriggerRememberAmount DeckHas:Ability$Counters Oracle:Haste\nWhen Bloodthirsty Adversary enters the battlefield, you may pay {2}{R} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Bloodthirsty Adversary, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs. diff --git a/forge-gui/res/cardsfolder/b/bow_to_my_command.txt b/forge-gui/res/cardsfolder/b/bow_to_my_command.txt index d6eba81a67d..11145dd97f4 100644 --- a/forge-gui/res/cardsfolder/b/bow_to_my_command.txt +++ b/forge-gui/res/cardsfolder/b/bow_to_my_command.txt @@ -16,7 +16,7 @@ SVar:RepeatOpp:DB$ RepeatEach | RepeatSubAbility$ ChooseCardsToTap | RepeatPlaye SVar:ChooseCardsToTap:DB$ ChooseCard | Defined$ Opponent | MinAmount$ 0 | Amount$ NumCreatures | Choices$ Creature.untapped+RememberedPlayerCtrl | ChoiceTitle$ Choose any number of untapped creatures you control | ChoiceZone$ Battlefield | RememberChosen$ True | AILogic$ BowToMyCommand | SubAbility$ TapChosenCards SVar:TapChosenCards:DB$ Tap | Defined$ Remembered | SubAbility$ AbandonSelf | ConditionCheckSVar$ TappedCreaturePower | ConditionSVarCompare$ GE8 SVar:AbandonSelf:DB$ Abandon | SubAbility$ DBCleanup | ConditionCheckSVar$ TappedCreaturePower | ConditionSVarCompare$ GE8 -T:Mode$ Abandoned | ValidCard$ Self | Execute$ DBCleanup +T:Mode$ Abandoned | ValidCard$ Card.Self | Execute$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearChosenCard$ True SVar:NumCreatures:Count$Valid Creature.RememberedPlayerCtrl SVar:TappedCreaturePower:Count$SumPower_Card.IsRemembered diff --git a/forge-gui/res/cardsfolder/c/captain_rex_nebula.txt b/forge-gui/res/cardsfolder/c/captain_rex_nebula.txt index 1711c671f50..5e534afe75f 100644 --- a/forge-gui/res/cardsfolder/c/captain_rex_nebula.txt +++ b/forge-gui/res/cardsfolder/c/captain_rex_nebula.txt @@ -7,7 +7,7 @@ SVar:TrigAnimate:DB$ Animate | ValidTgts$ Permanent.nonLand+YouCtrl | TgtPrompt$ SVar:CrashLand:Mode$ DamageDealtOnce | ValidSource$ Card.Self | ValidTarget$ Player,Permanent | Execute$ RollCounters | TriggerZones$ Battlefield | TriggerDescription$ Crash Land — Whenever this Vehicle deals damage, roll a six-sided die. If the result is equal to this Vehicle's mana value, sacrifice this Vehicle, then it deals that much damage to any target. SVar:RollCounters:DB$ RollDice | ResultSVar$ Result | SubAbility$ Crash SVar:Crash:DB$ Sacrifice | ConditionCheckSVar$ Result | ConditionSVarCompare$ EQY | SubAbility$ CrashDamage -SVar:CrashDamage:DB$ DealDamage | ValidTgt$ Planeswalker,Player,Permanent | TgtPromp$ Choose any target | NumDmg$ Y | ConditionCheckSVar$ Result | ConditionSVarCompare$ EQY +SVar:CrashDamage:DB$ DealDamage | ValidTgts$ Planeswalker,Player,Creature | TgtPrompt$ Choose any target | NumDmg$ Y | ConditionCheckSVar$ Result | ConditionSVarCompare$ EQY SVar:X:Targeted$CardManaCost SVar:Y:Count$CardManaCost DeckHints:Type$Vehicle diff --git a/forge-gui/res/cardsfolder/c/celestial_mantle.txt b/forge-gui/res/cardsfolder/c/celestial_mantle.txt index 35c6d468427..82dcdca1e1e 100644 --- a/forge-gui/res/cardsfolder/c/celestial_mantle.txt +++ b/forge-gui/res/cardsfolder/c/celestial_mantle.txt @@ -5,8 +5,6 @@ K:Enchant creature A:SP$ Attach | Cost$ 3 W W W | ValidTgts$ Creature | AILogic$ Pump S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 3 | AddToughness$ 3 | Description$ Enchanted creature gets +3/+3. T:Mode$ DamageDone | ValidSource$ Card.AttachedBy | ValidTarget$ Player | Execute$ TrigSetLife | CombatDamage$ True | TriggerDescription$ Whenever enchanted creature deals combat damage to a player, double its controller's life total. -SVar:TrigSetLife:DB$ Pump | RememberObjects$ TriggeredSourceController | SubAbility$ DBSet -SVar:DBSet:DB$ SetLife | Defined$ Remembered | LifeAmount$ X | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:PlayerCountRemembered$LifeTotal/Twice +SVar:TrigSetLife:DB$ SetLife | Defined$ TriggeredSourceController | LifeAmount$ X +SVar:X:PlayerCountDefinedTriggeredSourceController$LifeTotal/Twice Oracle:Enchant creature\nEnchanted creature gets +3/+3.\nWhenever enchanted creature deals combat damage to a player, double its controller's life total. diff --git a/forge-gui/res/cardsfolder/c/cephalid_shrine.txt b/forge-gui/res/cardsfolder/c/cephalid_shrine.txt index 3037a0bf0bb..a77fc4f2c45 100644 --- a/forge-gui/res/cardsfolder/c/cephalid_shrine.txt +++ b/forge-gui/res/cardsfolder/c/cephalid_shrine.txt @@ -1,10 +1,8 @@ Name:Cephalid Shrine ManaCost:1 U U Types:Enchantment -T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigCounterRem | TriggerDescription$ Whenever a player casts a spell, counter that spell unless that player pays {X}, where X is the number of cards in all graveyards with the same name as the spell. -SVar:TrigCounterRem:DB$ Pump | RememberObjects$ TriggeredCard | SubAbility$ DBCounter -SVar:DBCounter:DB$ Counter | Defined$ TriggeredSpellAbility | UnlessCost$ X | UnlessPayer$ TriggeredActivator | SubAbility$ DBCleanup -SVar:X:Count$ValidGraveyard Card.sharesNameWith Remembered -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigCounter | TriggerDescription$ Whenever a player casts a spell, counter that spell unless that player pays {X}, where X is the number of cards in all graveyards with the same name as the spell. +SVar:TrigCounter:DB$ Counter | Defined$ TriggeredSpellAbility | UnlessCost$ X | UnlessPayer$ TriggeredActivator +SVar:X:Count$ValidGraveyard Card.sharesNameWith TriggeredCard AI:RemoveDeck:Random Oracle:Whenever a player casts a spell, counter that spell unless that player pays {X}, where X is the number of cards in all graveyards with the same name as the spell. diff --git a/forge-gui/res/cardsfolder/c/chromium_the_mutable.txt b/forge-gui/res/cardsfolder/c/chromium_the_mutable.txt index 037322e3eac..84abd7f7912 100644 --- a/forge-gui/res/cardsfolder/c/chromium_the_mutable.txt +++ b/forge-gui/res/cardsfolder/c/chromium_the_mutable.txt @@ -5,5 +5,8 @@ PT:7/7 K:Flash K:Flying K:This spell can't be countered. -A:AB$ Animate | Cost$ Discard<1/Card> | Types$ Human | Power$ 1 | Toughness$ 1 | Keywords$ Hexproof | HiddenKeywords$ Unblockable | RemoveAllAbilities$ True | RemoveCreatureTypes$ True | SpellDescription$ Until end of turn, CARDNAME becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn. +A:AB$ Animate | Cost$ Discard<1/Card> | Types$ Human | Power$ 1 | Toughness$ 1 | Keywords$ Hexproof | RemoveAllAbilities$ True | RemoveCreatureTypes$ True | SubAbility$ DBUnblockable | SpellDescription$ Until end of turn, CARDNAME becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn. +SVar:DBUnblockable:DB$ Effect | ExileOnMoved$ Battlefield | RememberObjects$ Self | StaticAbilities$ Unblockable +SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn. +DeckHas:Ability$Discard Oracle:Flash\nThis spell can't be countered.\nFlying\nDiscard a card: Until end of turn, Chromium, the Mutable becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn. diff --git a/forge-gui/res/cardsfolder/c/conspiracy_theorist.txt b/forge-gui/res/cardsfolder/c/conspiracy_theorist.txt index c1434492381..c833d71b8f5 100644 --- a/forge-gui/res/cardsfolder/c/conspiracy_theorist.txt +++ b/forge-gui/res/cardsfolder/c/conspiracy_theorist.txt @@ -6,7 +6,7 @@ T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ SVar:TrigDraw:AB$ Draw | Cost$ 1 Discard<1/Card> T:Mode$ DiscardedAll | ValidPlayer$ You | ValidCard$ Card.nonLand | TriggerZones$ Battlefield | Execute$ TrigEffect | TriggerDescription$ Whenever you discard one or more nonland cards, you may exile one of them from your graveyard. If you do, you may cast it this turn. SVar:TrigEffect:AB$ Effect | Cost$ ExileFromGrave<1/Card.TriggeredCards> | RememberObjects$ ExiledCards | StaticAbilities$ MayCast | ExileOnMoved$ Stack -SVar:MayCast:Mode$ Continuous | Affected$ Card.IsRemembered | MayPlay$ True | EffectZone$ Command | AffectedZone$ Exile | Description$ You may cast this spell this turn. +SVar:MayCast:Mode$ Continuous | Affected$ Card.IsRemembered+nonLand | MayPlay$ True | EffectZone$ Command | AffectedZone$ Exile | Description$ You may cast this spell this turn. SVar:HasAttackEffect:TRUE DeckHas:Ability$Discard Oracle:Whenever Conspiracy Theorist attacks, you may pay {1} and discard a card. If you do, draw a card.\nWhenever you discard one or more nonland cards, you may exile one of them from your graveyard. If you do, you may cast it this turn. diff --git a/forge-gui/res/cardsfolder/c/creeping_tar_pit.txt b/forge-gui/res/cardsfolder/c/creeping_tar_pit.txt index b96eeed71f2..28729462f4a 100644 --- a/forge-gui/res/cardsfolder/c/creeping_tar_pit.txt +++ b/forge-gui/res/cardsfolder/c/creeping_tar_pit.txt @@ -3,5 +3,8 @@ ManaCost:no cost Types:Land K:CARDNAME enters the battlefield tapped. A:AB$ Mana | Cost$ T | Produced$ Combo U B | SpellDescription$ Add {U} or {B}. -A:AB$ Animate | Cost$ 1 U B | Defined$ Self | Power$ 3 | Toughness$ 2 | Types$ Creature,Elemental | Colors$ Blue,Black | HiddenKeywords$ Unblockable | SpellDescription$ CARDNAME becomes a 3/2 blue and black Elemental creature until end of turn and can't be blocked this turn. It's still a land. +A:AB$ Animate | Cost$ 1 U B | Defined$ Self | Power$ 3 | Toughness$ 2 | Types$ Creature,Elemental | Colors$ Blue,Black | SubAbility$ DBUnblockable | SpellDescription$ CARDNAME becomes a 3/2 blue and black Elemental creature until end of turn and can't be blocked this turn. It's still a land. +SVar:DBUnblockable:DB$ Effect | ExileOnMoved$ Battlefield | RememberObjects$ Self | StaticAbilities$ Unblockable +SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn. +DeckHas:Type$Elemental Oracle:Creeping Tar Pit enters the battlefield tapped.\n{T}: Add {U} or {B}.\n{1}{U}{B}: Creeping Tar Pit becomes a 3/2 blue and black Elemental creature until end of turn and can't be blocked this turn. It's still a land. diff --git a/forge-gui/res/cardsfolder/d/dance_of_the_dead.txt b/forge-gui/res/cardsfolder/d/dance_of_the_dead.txt index 22c2397ac37..48e91821fd0 100644 --- a/forge-gui/res/cardsfolder/d/dance_of_the_dead.txt +++ b/forge-gui/res/cardsfolder/d/dance_of_the_dead.txt @@ -3,13 +3,14 @@ ManaCost:1 B Types:Enchantment Aura K:Enchant creature card in a graveyard A:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature | TgtZone$ Graveyard | AILogic$ Reanimate -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters the battlefield, if it's on the battlefield, it loses "enchant creature card in a graveyard" and gains "enchant creature put onto the battlefield with CARDNAME." Put enchanted creature card onto the battlefield tapped under your control and attach CARDNAME to it. +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters the battlefield, if it's on the battlefield, it loses "enchant creature card in a graveyard" and gains "enchant creature put onto the battlefield with CARDNAME." Put enchanted creature card onto the battlefield tapped under your control and attach CARDNAME to it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it. SVar:TrigReanimate:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ Enchanted | RememberChanged$ True | GainControl$ True | Tapped$ True | SubAbility$ DBAnimate SVar:DBAnimate:DB$ Animate | Defined$ Self | OverwriteSpells$ True | Abilities$ NewAttach | Keywords$ Enchant creature put onto the battlefield with CARDNAME | RemoveKeywords$ Enchant creature card in a graveyard | Duration$ Permanent | SubAbility$ DBAttach -SVar:DBAttach:DB$ Attach | Defined$ Remembered +SVar:DBAttach:DB$ Attach | Defined$ Remembered | SubAbility$ DBDelay +SVar:DBDelay:DB$ DelayedTrigger | Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Execute$ TrigSacrifice | RememberObjects$ RememberedLKI | TriggerDescription$ When CARDNAME leaves the battlefield, that creature's controller sacrifices it. SVar:NewAttach:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature.IsRemembered | AILogic$ Pump -T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigSacrifice | TriggerDescription$ When CARDNAME leaves the battlefield, that creature's controller sacrifices it. -SVar:TrigSacrifice:DB$ Destroy | Sacrifice$ True | Defined$ DirectRemembered | SubAbility$ DBCleanup +SVar:TrigSacrifice:DB$ Destroy | Sacrifice$ True | Defined$ DirectRemembered +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 1 | AddToughness$ 1 | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step. | Description$ Enchanted creature gets +1/+1 and doesn't untap during its controller's untap step. T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player.EnchantedController | TriggerZones$ Battlefield | OptionalDecider$ EnchantedController | Execute$ TrigUntap | TriggerDescription$ At the beginning of the upkeep of enchanted creature's controller, that player may pay {1}{B}. If the player does, untap that creature. diff --git a/forge-gui/res/cardsfolder/d/deputy_of_detention.txt b/forge-gui/res/cardsfolder/d/deputy_of_detention.txt index 336dde3ebcb..bcd0b368c77 100644 --- a/forge-gui/res/cardsfolder/d/deputy_of_detention.txt +++ b/forge-gui/res/cardsfolder/d/deputy_of_detention.txt @@ -3,6 +3,6 @@ ManaCost:1 W U Types:Creature Vedalken Wizard PT:1/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters the battlefield, exile target nonland permanent an opponent controls and all other nonland permanents that player controls with the same name as that permanent until CARDNAME leaves the battlefield. -SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Permanent.nonLand+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls | SubAbility$ DBChangeZoneAll | Duration$ UntilHostLeavesPlay -SVar:DBChangeZoneAll:DB$ ChangeZoneAll | Origin$ Battlefield | Destination$ Exile | ChangeType$ Permanent.nonLand+NotDefinedTargeted+sharesNameWith Targeted+ControlledBy TargetedOrController | Duration$ UntilHostLeavesPlay +SVar:TrigExile:DB$ Pump | ValidTgts$ Permanent.nonLand+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls | SubAbility$ DBChangeZoneAll +SVar:DBChangeZoneAll:DB$ ChangeZoneAll | Origin$ Battlefield | Destination$ Exile | ChangeType$ TargetedCard.Self,Permanent.nonLand+NotDefinedTargeted+sharesNameWith Targeted+ControlledBy TargetedController | Duration$ UntilHostLeavesPlay Oracle:When Deputy of Detention enters the battlefield, exile target nonland permanent an opponent controls and all other nonland permanents that player controls with the same name as that permanent until Deputy of Detention leaves the battlefield. diff --git a/forge-gui/res/cardsfolder/d/dimir_keyrune.txt b/forge-gui/res/cardsfolder/d/dimir_keyrune.txt index 71526fdfed7..9db5a4b6657 100644 --- a/forge-gui/res/cardsfolder/d/dimir_keyrune.txt +++ b/forge-gui/res/cardsfolder/d/dimir_keyrune.txt @@ -2,7 +2,10 @@ Name:Dimir Keyrune ManaCost:3 Types:Artifact A:AB$ Mana | Cost$ T | Produced$ Combo U B | SpellDescription$ Add {U} or {B}. -A:AB$ Animate | Cost$ U B | Defined$ Self | Power$ 2 | Toughness$ 2 | Types$ Artifact,Creature,Horror | Colors$ Blue,Black | HiddenKeywords$ Unblockable | SpellDescription$ CARDNAME becomes a 2/2 blue and black Horror artifact creature until end of turn and can't be blocked this turn. +A:AB$ Animate | Cost$ U B | Defined$ Self | Power$ 2 | Toughness$ 2 | Types$ Artifact,Creature,Horror | Colors$ Blue,Black | SubAbility$ DBUnblockable | SpellDescription$ CARDNAME becomes a 2/2 blue and black Horror artifact creature until end of turn and can't be blocked this turn. +SVar:DBUnblockable:DB$ Effect | ExileOnMoved$ Battlefield | RememberObjects$ Self | StaticAbilities$ Unblockable +SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn. AI:RemoveDeck:Random +DeckHas:Type$Horror DeckNeeds:Color$Blue|Black Oracle:{T}: Add {U} or {B}.\n{U}{B}: Dimir Keyrune becomes a 2/2 blue and black Horror artifact creature until end of turn and can't be blocked this turn. diff --git a/forge-gui/res/cardsfolder/e/emberwilde_captain.txt b/forge-gui/res/cardsfolder/e/emberwilde_captain.txt index 5ad9b59621d..4da6db5ac1e 100644 --- a/forge-gui/res/cardsfolder/e/emberwilde_captain.txt +++ b/forge-gui/res/cardsfolder/e/emberwilde_captain.txt @@ -4,9 +4,7 @@ Types:Creature Djinn Pirate PT:4/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMonarch | TriggerDescription$ When CARDNAME enters the battlefield, you become the monarch. SVar:TrigMonarch:DB$ BecomeMonarch | Defined$ You -T:Mode$ AttackersDeclared | AttackingPlayer$ Player.Opponent | AttackedTarget$ You | NoResolvingCheck$ True | CheckDefinedPlayer$ You.isMonarch | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever an opponent attacks you while you're the monarch, CARDNAME deals damage to that player equal to the number of cards in their hand. -SVar:TrigPump:DB$ Pump | RememberObjects$ TriggeredAttackingPlayer | SubAbility$ DBDmg -SVar:DBDmg:DB$ DealDamage | Defined$ Remembered | NumDmg$ X | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Count$ValidHand Card.RememberedPlayerCtrl +T:Mode$ AttackersDeclared | AttackingPlayer$ Player.Opponent | AttackedTarget$ You | NoResolvingCheck$ True | CheckDefinedPlayer$ You.isMonarch | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Whenever an opponent attacks you while you're the monarch, CARDNAME deals damage to that player equal to the number of cards in their hand. +SVar:TrigDmg:DB$ DealDamage | Defined$ TriggeredAttackingPlayer | NumDmg$ X +SVar:X:Count$ValidHand Card.OwnedBy TriggeredAttackingPlayer Oracle:When Emberwilde Captain enters the battlefield, you become the monarch.\nWhenever an opponent attacks you while you're the monarch, Emberwilde Captain deals damage to that player equal to the number of cards in their hand. diff --git a/forge-gui/res/cardsfolder/e/eradicate.txt b/forge-gui/res/cardsfolder/e/eradicate.txt index 63d7ab16dbe..7ca2fd2c5fc 100644 --- a/forge-gui/res/cardsfolder/e/eradicate.txt +++ b/forge-gui/res/cardsfolder/e/eradicate.txt @@ -5,7 +5,7 @@ A:SP$ ChangeZone | Cost$ 2 B B | Origin$ Battlefield | Destination$ Exile | Vali SVar:ExileYard:DB$ ChangeZoneAll | Origin$ Graveyard | Destination$ Exile | Defined$ RememberedController | ChangeType$ Remembered.sameName | SubAbility$ ExileHand | StackDescription$ None SVar:ExileHand:DB$ ChangeZone | Origin$ Hand | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInHand | Chooser$ You | SubAbility$ ExileLib | StackDescription$ None SVar:ExileLib:DB$ ChangeZone | Origin$ Library | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInLib | Chooser$ You | Search$ True | Shuffle$ True | SubAbility$ DBCleanup | StackDescription$ None -SVar:NumInHand:RememberedController$CardsInHand -SVar:NumInLib:RememberedController$CardsInLibrary +SVar:NumInHand:PlayerCountRememberedController$CardsInHand +SVar:NumInLib:PlayerCountRememberedController$CardsInLibrary SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Exile target nonblack creature. Search its controller's graveyard, hand, and library for all cards with the same name as that creature and exile them. Then that player shuffles. diff --git a/forge-gui/res/cardsfolder/e/eye_of_singularity.txt b/forge-gui/res/cardsfolder/e/eye_of_singularity.txt index 59c2fb204f6..9c739500b4c 100644 --- a/forge-gui/res/cardsfolder/e/eye_of_singularity.txt +++ b/forge-gui/res/cardsfolder/e/eye_of_singularity.txt @@ -2,11 +2,11 @@ Name:Eye of Singularity ManaCost:3 W Types:World Enchantment T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigRepeat | TriggerDescription$ When CARDNAME enters the battlefield, destroy each permanent with the same name as another permanent, except for basic lands. They can't be regenerated. -SVar:TrigRepeat:DB$ RepeatEach | RepeatCards$ Permanent.nonBasic | RepeatSubAbility$ DBDestroy | UseImprinted$ True | SubAbility$ DBCleanup -SVar:DBDestroy:DB$ Destroy | Defined$ Valid Permanent.sharesNameWith Imprinted+IsNotImprinted | NoRegen$ True -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Permanent.nonBasic | TriggerZones$ Battlefield | Execute$ TrigDestroyRem | TriggerDescription$ Whenever a permanent other than a basic land enters the battlefield, destroy all other permanents with that name. They can't be regenerated. -SVar:TrigDestroyRem:DB$ Pump | RememberObjects$ TriggeredCard | SubAbility$ DBDestroyAll -SVar:DBDestroyAll:DB$ DestroyAll | ValidCards$ Permanent.IsNotRemembered+sharesNameWith Remembered | NoRegen$ True | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:TrigRepeat:DB$ RepeatEach | RepeatCards$ Permanent.nonBasic | RepeatSubAbility$ DBRem | SubAbility$ DBDestroy +SVar:DBRem:DB$ Pump | ImprintCards$ Valid Permanent.sharesNameWith Remembered+IsNotRemembered +SVar:DBDestroy:DB$ DestroyAll | ValidCards$ Card.IsImprinted | NoRegen$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Permanent.nonBasic | TriggerZones$ Battlefield | Execute$ TrigDestroy | TriggerDescription$ Whenever a permanent other than a basic land enters the battlefield, destroy all other permanents with that name. They can't be regenerated. +SVar:TrigDestroy:DB$ DestroyAll | ValidCards$ Permanent.NotTriggeredCard+sharesNameWith TriggeredCard | NoRegen$ True AI:RemoveDeck:All Oracle:When Eye of Singularity enters the battlefield, destroy each permanent with the same name as another permanent, except for basic lands. They can't be regenerated.\nWhenever a permanent other than a basic land enters the battlefield, destroy all other permanents with that name. They can't be regenerated. diff --git a/forge-gui/res/cardsfolder/f/face_of_divinity.txt b/forge-gui/res/cardsfolder/f/face_of_divinity.txt index 5ea6cc3bed6..f5c1bb0f6d9 100644 --- a/forge-gui/res/cardsfolder/f/face_of_divinity.txt +++ b/forge-gui/res/cardsfolder/f/face_of_divinity.txt @@ -4,6 +4,6 @@ Types:Enchantment Aura K:Enchant creature A:SP$ Attach | Cost$ 2 W | ValidTgts$ Creature | AILogic$ Pump S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 2 | AddToughness$ 2 | Description$ Enchanted creature gets +2/+2. -S:Mode$ Continuous | Affected$ Creature.EnchantedBy Aura.Other | AddKeyword$ First Strike & Lifelink | Description$ As long as another Aura is attached to enchanted creature, it has first strike and lifelink. +S:Mode$ Continuous | Affected$ Creature.EnchantedBy+EnchantedBy Aura.Other | AddKeyword$ First Strike & Lifelink | Description$ As long as another Aura is attached to enchanted creature, it has first strike and lifelink. SVar:EnchantMe:Multiple Oracle:Enchant creature\nEnchanted creature gets +2/+2.\nAs long as another Aura is attached to enchanted creature, it has first strike and lifelink. diff --git a/forge-gui/res/cardsfolder/f/farid_enterprising_salvager.txt b/forge-gui/res/cardsfolder/f/farid_enterprising_salvager.txt index 7eb259f1feb..2e99c7a10dc 100644 --- a/forge-gui/res/cardsfolder/f/farid_enterprising_salvager.txt +++ b/forge-gui/res/cardsfolder/f/farid_enterprising_salvager.txt @@ -5,12 +5,12 @@ PT:3/3 T:Mode$ ChangesZone | ValidCard$ Artifact.nonToken+YouCtrl | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever a nontoken artifact you control is put into a graveyard from the battlefield, create a colorless artifact token named Scrap. SVar:TrigToken:DB$ Token | TokenScript$ scrap A:AB$ Charm | Cost$ 1 R Sac<1/Artifact> | Choices$ DBCounter,DBGoad,DBLoot | CharmNum$ 1 -SVar:DBCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | SubAbility$ DBPump | SpellDescription$ Put a +1/+1 counter on NICKNAME. It gains haste until end of turn. -SVar:DBPump:DB$ Pump | Defined$ Self | KW$ Haste +SVar:DBCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | SubAbility$ DBPump | SpellDescription$ Put a +1/+1 counter on NICKNAME. It gains menace until end of turn. +SVar:DBPump:DB$ Pump | Defined$ Self | KW$ Menace SVar:DBGoad:DB$ Goad | ValidTgts$ Creature | SpellDescription$ Goad target creature. SVar:DBLoot:DB$ Discard | Mode$ TgtChoose | SubAbility$ DBDraw | SpellDescription$ Discard a card, then draw a card. SVar:DBDraw:DB$ Draw SVar:AIPreference:SacCost$Artifact.token DeckHints:Type$Artifact -DeckHas:Ability$Discard|Token|Counters & Type$Artifact & Keyword$Haste -Oracle:Whenever a nontoken artifact you control is put into a graveyard from the battlefield, create a colorless artifact token named Scrap.\n{1}{R}, Sacrifice an artifact: Choose one —\n• Put a +1/+1 counter on Farid. It gains haste until end of turn.\n• Goad target creature.\n• Discard a card, then draw a card. \ No newline at end of file +DeckHas:Ability$Discard|Token|Counters & Type$Artifact & Keyword$Menace +Oracle:Whenever a nontoken artifact you control is put into a graveyard from the battlefield, create a colorless artifact token named Scrap.\n{1}{R}, Sacrifice an artifact: Choose one —\n• Put a +1/+1 counter on Farid. It gains menace until end of turn.\n• Goad target creature.\n• Discard a card, then draw a card. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/g/gorilla_tactics.txt b/forge-gui/res/cardsfolder/g/gorilla_tactics.txt index 8f556c5c3b8..4338219b7ca 100644 --- a/forge-gui/res/cardsfolder/g/gorilla_tactics.txt +++ b/forge-gui/res/cardsfolder/g/gorilla_tactics.txt @@ -2,7 +2,7 @@ Name:Gorilla Tactics ManaCost:1 G Types:Instant A:SP$ Token | Cost$ 1 G | TokenScript$ g_2_2_gorilla | SpellDescription$ Create a 2/2 green Gorilla creature token. -T:Mode$ Discarded | ValidCard$ Card.Self | ValidCause$ Card.OppCtrl | Execute$ TrigDouble | TriggerDescription$ When a spell or ability an opponent controls causes you to discard Gorilla Tactics, create two 2/2 green Gorilla creature tokens. +T:Mode$ Discarded | ValidCard$ Card.Self | ValidCause$ Card.OppCtrl | Execute$ TrigDouble | TriggerDescription$ When a spell or ability an opponent controls causes you to discard CARDNAME, create two 2/2 green Gorilla creature tokens. SVar:TrigDouble:DB$ Token | TokenScript$ g_2_2_gorilla | TokenAmount$ 2 DeckHas:Ability$Token Oracle:Create a 2/2 green Gorilla creature token.\nWhen a spell or ability an opponent controls causes you to discard Gorilla Tactics, create two 2/2 green Gorilla creature tokens. diff --git a/forge-gui/res/cardsfolder/g/grafdiggers_cage.txt b/forge-gui/res/cardsfolder/g/grafdiggers_cage.txt index f77d045954e..72f0491a6f8 100644 --- a/forge-gui/res/cardsfolder/g/grafdiggers_cage.txt +++ b/forge-gui/res/cardsfolder/g/grafdiggers_cage.txt @@ -1,7 +1,7 @@ Name:Grafdigger's Cage ManaCost:1 Types:Artifact -R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard,Library | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Description$ Creature cards in graveyards and libraries can't enter the battlefield. +R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard,Library | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Layer$ CantHappen | Description$ Creature cards in graveyards and libraries can't enter the battlefield. S:Mode$ CantBeCast | Origin$ Graveyard,Library | Description$ Players can't cast spells from graveyards or libraries. SVar:NonStackingEffect:True AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/g/greasefang_okiba_boss.txt b/forge-gui/res/cardsfolder/g/greasefang_okiba_boss.txt index a5204791aeb..2a083be643c 100644 --- a/forge-gui/res/cardsfolder/g/greasefang_okiba_boss.txt +++ b/forge-gui/res/cardsfolder/g/greasefang_okiba_boss.txt @@ -3,8 +3,9 @@ ManaCost:1 W B Types:Legendary Creature Rat Pilot PT:4/3 T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigReturn | TriggerDescription$ At the beginning of combat on your turn, return target Vehicle card from your graveyard to the battlefield. It gains haste. Return it to its owner's hand at the beginning of your next end step. -SVar:TrigReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Select target Vehicle card in your graveyard | ValidTgts$ Vehicle.YouOwn | AnimateSubAbility$ Animate -SVar:Animate:DB$ Animate | Keywords$ Haste | Defined$ Remembered | Duration$ Permanent | AtEOT$ Hand +SVar:TrigReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Select target Vehicle card in your graveyard | ValidTgts$ Vehicle.YouOwn | SubAbility$ Animate | RememberChanged$ True | AtEOT$ Hand +SVar:Animate:DB$ Animate | Keywords$ Haste | Defined$ Remembered | Duration$ Permanent | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True DeckHas:Ability$Graveyard DeckNeeds:Type$Vehicle Oracle:At the beginning of combat on your turn, return target Vehicle card from your graveyard to the battlefield. It gains haste. Return it to its owner's hand at the beginning of your next end step. diff --git a/forge-gui/res/cardsfolder/g/gruesome_realization.txt b/forge-gui/res/cardsfolder/g/gruesome_realization.txt index 452d4dc887d..6d5fe617b66 100644 --- a/forge-gui/res/cardsfolder/g/gruesome_realization.txt +++ b/forge-gui/res/cardsfolder/g/gruesome_realization.txt @@ -3,6 +3,6 @@ ManaCost:1 B B Types:Sorcery A:SP$ Charm | Choices$ DBDraw,DBDebuff SVar:DBDraw:DB$ Draw | NumCards$ 2 | SubAbility$ DBLoseLife | SpellDescription$ You draw two cards and you lose 2 life. -SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1 +SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 2 SVar:DBDebuff:DB$ PumpAll | ValidCards$ Creature.OppCtrl | NumAtt$ -1 | NumDef$ -1 | IsCurse$ True | SpellDescription$ Creatures your opponents control get -1/-1 until end of turn. Oracle:Choose one —\n• You draw two cards and you lose 2 life.\n• Creatures your opponents control get -1/-1 until end of turn. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/g/gurgling_anointer.txt b/forge-gui/res/cardsfolder/g/gurgling_anointer.txt index 39f11fd0228..140f551a77b 100644 --- a/forge-gui/res/cardsfolder/g/gurgling_anointer.txt +++ b/forge-gui/res/cardsfolder/g/gurgling_anointer.txt @@ -7,7 +7,7 @@ SVar:SacMe:3 T:Mode$ Drawn | ValidCard$ Card.YouCtrl | Number$ 2 | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you draw your second card each turn, put a +1/+1 counter on CARDNAME. SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME dies, return another target creature card with mana value less than or equal to CARDNAME's power from your graveyard to the battlefield. -SVar:TrigReturn:DB$ ChangeZone | ValidTgt$ Creature.YouOwn+cmcLEX+Other | TgtPrompt$ Choose another target creature card with mana value less than or equal to CARDNAME's power | Origin$ Graveyard | Destination$ Battlefield +SVar:TrigReturn:DB$ ChangeZone | ValidTgts$ Creature.YouOwn+cmcLEX+Other | TgtPrompt$ Choose another target creature card with mana value less than or equal to CARDNAME's power | Origin$ Graveyard | Destination$ Battlefield SVar:X:TriggeredCard$CardPower DeckHas:Ability$Counters|Graveyard Oracle:Flying\nWhenever you draw your second card each turn, put a +1/+1 counter on Gurgling Anointer.\nWhen Gurgling Anointer dies, return another target creature card with mana value less than or equal to Gurgling Anointer's power from your graveyard to the battlefield. diff --git a/forge-gui/res/cardsfolder/i/illuna_apex_of_wishes.txt b/forge-gui/res/cardsfolder/i/illuna_apex_of_wishes.txt index 8006aae5385..c51ca78b14d 100644 --- a/forge-gui/res/cardsfolder/i/illuna_apex_of_wishes.txt +++ b/forge-gui/res/cardsfolder/i/illuna_apex_of_wishes.txt @@ -6,9 +6,7 @@ K:Mutate:3 RG U U K:Flying K:Trample T:Mode$ Mutates | ValidCard$ Card.Self | Execute$ TrigDigUntil | TriggerDescription$ Whenever this creature mutates, exile cards from the top of your library until you exile a nonland permanent card. Put that card onto the battlefield or into your hand. -SVar:TrigDigUntil:DB$ DigUntil | Valid$ Permanent.nonLand | ValidDescription$ nonland permanent | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | SubAbility$ DBChoose -SVar:DBChoose:DB$ GenericChoice | Choices$ Battlefield,Hand | Defined$ You -SVar:Battlefield:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup | SpellDescription$ Put the nonland permanent onto the battlefield -SVar:Hand:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Hand | SubAbility$ DBCleanup | SpellDescription$ Put the nonland permanent into your hand +SVar:TrigDigUntil:DB$ DigUntil | Valid$ Permanent.nonLand | ValidDescription$ nonland permanent | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | SubAbility$ DBChange +SVar:DBChange:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | DestinationAlternative$ Hand | AlternativeDestinationMessage$ Put that card onto the battlefield instead of putting it into your hand? | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Mutate {3}{R/G}{U}{U} (If you cast this spell for its mutate cost, put it over or under target non-Human creature you own. They mutate into the creature on top plus all abilities from under it.)\nFlying, trample\nWhenever this creature mutates, exile cards from the top of your library until you exile a nonland permanent card. Put that card onto the battlefield or into your hand. diff --git a/forge-gui/res/cardsfolder/i/intrepid_adversary.txt b/forge-gui/res/cardsfolder/i/intrepid_adversary.txt index f403d206104..efb36af41ee 100644 --- a/forge-gui/res/cardsfolder/i/intrepid_adversary.txt +++ b/forge-gui/res/cardsfolder/i/intrepid_adversary.txt @@ -4,9 +4,9 @@ Types:Creature Human Scout PT:3/1 K:Lifelink T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {1}{W} any number of times. When you pay this cost one or more times, put that many valor counters on CARDNAME. -SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 W\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many valor counters on CARDNAME. -SVar:TrigPutCounter:DB$ PutCounter | CounterType$ VALOR | CounterNum$ NumTimes -SVar:NumTimes:Number$0 +SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 W\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | RememberSVarAmount$ NumTimes | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many valor counters on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ VALOR | CounterNum$ X +SVar:X:Count$TriggerRememberAmount S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddPower$ Z | AddToughness$ Z | Description$ Creatures you control get +1/+1 for each valor counter on CARDNAME. SVar:Z:Count$CardCounters.VALOR DeckHas:Ability$LifeGain|Counters diff --git a/forge-gui/res/cardsfolder/k/kunoros_hound_of_athreos.txt b/forge-gui/res/cardsfolder/k/kunoros_hound_of_athreos.txt index d13c61e88b4..4924dd4516d 100644 --- a/forge-gui/res/cardsfolder/k/kunoros_hound_of_athreos.txt +++ b/forge-gui/res/cardsfolder/k/kunoros_hound_of_athreos.txt @@ -5,7 +5,7 @@ PT:3/3 K:Vigilance K:Menace K:Lifelink -R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Description$ Creature cards in graveyards can't enter the battlefield. +R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Layer$ CantHappen | Description$ Creature cards in graveyards can't enter the battlefield. S:Mode$ CantBeCast | Origin$ Graveyard | Description$ Players can't cast spells from graveyards. SVar:NonStackingEffect:True Oracle:Vigilance, menace, lifelink\nCreature cards in graveyards can't enter the battlefield.\nPlayers can't cast spells from graveyards. diff --git a/forge-gui/res/cardsfolder/l/legions_to_ashes.txt b/forge-gui/res/cardsfolder/l/legions_to_ashes.txt index a9f6c9ad77f..a5984327b70 100644 --- a/forge-gui/res/cardsfolder/l/legions_to_ashes.txt +++ b/forge-gui/res/cardsfolder/l/legions_to_ashes.txt @@ -1,6 +1,7 @@ Name:Legions to Ashes ManaCost:1 W B Types:Sorcery -A:SP$ ChangeZoneAll | TgtPrompt$ Select target nonland permanent an opponent controls | ValidTgts$ Permanent.nonLand+OppCtrl | ChangeType$ TargetedCard.Self,Creature.NotDefinedTargeted+token+sharesNameWith Targeted | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target nonland permanent an opponent controls and all tokens that player controls with the same name as that permanent. +A:SP$ Pump | ValidTgts$ Permanent.nonland | TgtPrompt$ Select target nonland permanent an opponent controls | SubAbility$ ExileAll | SpellDescription$ Exile target nonland permanent an opponent controls and all tokens that player controls with the same name as that permanent. +SVar:ExileAll:DB$ ChangeZoneAll | ChangeType$ TargetedCard.Self,Card.NotDefinedTargeted+token+sharesNameWith Targeted+ControlledBy TargetedController | Origin$ Battlefield | Destination$ Exile AI:RemoveDeck:Random Oracle:Exile target nonland permanent an opponent controls and all tokens that player controls with the same name as that permanent. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/l/livio_oathsworn_sentinel.txt b/forge-gui/res/cardsfolder/l/livio_oathsworn_sentinel.txt index 81b4729d172..40b0d7f42a1 100644 --- a/forge-gui/res/cardsfolder/l/livio_oathsworn_sentinel.txt +++ b/forge-gui/res/cardsfolder/l/livio_oathsworn_sentinel.txt @@ -3,7 +3,7 @@ ManaCost:1 W Types:Legendary Creature Human Knight PT:2/2 A:AB$ ChangeZone | Cost$ 1 W | ValidTgts$ Creature.Other | TgtPrompt$ Select another target creature | Optional$ True | DefinedPlayer$ TargetedController | Chooser$ TargetedController | Origin$ Battlefield | Destination$ Exile | WithCountersType$ AEGIS | StackDescription$ {p:You} chooses {c:Targeted}. {p:TargetedController} may exile it with an aegis counter on it. | SpellDescription$ Choose another target creature. Its controller may exile it with an aegis counter on it. -A:AB$ ChangeZoneAll | Cost$ 2 W T | ChangeType$ Creature.counters_GE1_AEGIS | Origin$ Exile | Destination$ Battlefield | SpellDescription$ Return all exiled cards with aegis counters on them to the battlefield under their owners' control. +A:AB$ ChangeZoneAll | Cost$ 2 W T | ChangeType$ Card.counters_GE1_AEGIS | Origin$ Exile | Destination$ Battlefield | SpellDescription$ Return all exiled cards with aegis counters on them to the battlefield under their owners' control. K:Partner DeckHas:Ability$Counters Oracle:{1}{W}: Choose another target creature. Its controller may exile it with an aegis counter on it.\n{2}{W}, {T}: Return all exiled cards with aegis counters on them to the battlefield under their owners' control.\nPartner (You can have two commanders if both have partner.) diff --git a/forge-gui/res/cardsfolder/l/lorcan_warlock_collector.txt b/forge-gui/res/cardsfolder/l/lorcan_warlock_collector.txt index 43c7f96894e..e15def1a18d 100644 --- a/forge-gui/res/cardsfolder/l/lorcan_warlock_collector.txt +++ b/forge-gui/res/cardsfolder/l/lorcan_warlock_collector.txt @@ -7,6 +7,6 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Graveyard | ValidCard$ Creature SVar:TrigReanimate:AB$ ChangeZone | Cost$ PayLife | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | ChangeNum$ 1 | AnimateSubAbility$ Animate SVar:Animate:DB$ Animate | Defined$ TriggeredCard | Types$ Warlock | Duration$ Permanent SVar:X:TriggeredCard$CardManaCost -R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | Destination$ Graveyard | ValidLKI$ Creature.Warlock+YouCtrl | ReplaceWith$ Exile | CheckSelfLKIZone$ True | Description$ If a Warlock you control would die, exile it instead. +R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | Destination$ Graveyard | ValidLKI$ Warlock.YouCtrl | ReplaceWith$ Exile | CheckSelfLKIZone$ True | Description$ If a Warlock you control would die, exile it instead. SVar:Exile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | Defined$ ReplacedCard Oracle:Flying\nWhenever a creature card is put into an opponent's graveyard from anywhere, you may pay life equal to its mana value. If you do, put it onto the battlefield under your control. It's a Warlock in addition to its other types.\nIf a Warlock you control would die, exile it instead. diff --git a/forge-gui/res/cardsfolder/m/mentor_of_the_meek.txt b/forge-gui/res/cardsfolder/m/mentor_of_the_meek.txt index f6bbf8e3f60..18f36c1f600 100644 --- a/forge-gui/res/cardsfolder/m/mentor_of_the_meek.txt +++ b/forge-gui/res/cardsfolder/m/mentor_of_the_meek.txt @@ -3,6 +3,6 @@ ManaCost:2 W Types:Creature Human Soldier PT:2/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.powerLE2+YouCtrl+Other | TriggerZones$ Battlefield | Execute$ TrigDraw | Optional$ True | TriggerDescription$ Whenever another creature with power 2 or less enters the battlefield under your control, you may pay {1}. If you do, draw a card. -SVar:TrigDraw:AB$Draw | Cost$ 1 | NumCards$ 1 | SpellDescription$ Draw a card. +SVar:TrigDraw:AB$ Draw | Cost$ 1 | NumCards$ 1 | SpellDescription$ Draw a card. SVar:PlayMain1:TRUE Oracle:Whenever another creature with power 2 or less enters the battlefield under your control, you may pay {1}. If you do, draw a card. diff --git a/forge-gui/res/cardsfolder/m/moonshae_pixie.txt b/forge-gui/res/cardsfolder/m/moonshae_pixie.txt index f2f45c2d8ca..48bccc005b6 100644 --- a/forge-gui/res/cardsfolder/m/moonshae_pixie.txt +++ b/forge-gui/res/cardsfolder/m/moonshae_pixie.txt @@ -3,7 +3,7 @@ ManaCost:3 U Types:Creature Faerie PT:2/2 K:Flying -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw cards equal to the number of opponents who were dealt combat damage this turn. +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw cards equal to the number of opponents who were dealt combat damage this turn. SVar:TrigDraw:DB$ Draw | NumCards$ X SVar:X:PlayerCountRegisteredOpponents$HasPropertywasDealtCombatDamageThisTurn AlternateMode:Adventure diff --git a/forge-gui/res/cardsfolder/m/myrkuls_edict.txt b/forge-gui/res/cardsfolder/m/myrkuls_edict.txt index 81fab7c208a..62037404491 100644 --- a/forge-gui/res/cardsfolder/m/myrkuls_edict.txt +++ b/forge-gui/res/cardsfolder/m/myrkuls_edict.txt @@ -3,7 +3,7 @@ ManaCost:1 B Types:Sorcery A:SP$ RollDice | Sides$ 20 | ResultSubAbilities$ 1-9:OneOppSac,10-19:EachOppSac,20:SacTopPower | SpellDescription$ Roll a d20. SVar:OneOppSac:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | SubAbility$ DBSac | StackDescription$ SpellDescription | SpellDescription$ 1—9 VERT Choose an opponent. That player sacrifices a creature. -SVar:DBSac:DB$ Sacrifice | Defined$ Chosen | SacValid$ Creature | SubAbility$ DBCleanupChosen +SVar:DBSac:DB$ Sacrifice | Defined$ ChosenPlayer | SacValid$ Creature | SubAbility$ DBCleanupChosen SVar:EachOppSac:DB$ Sacrifice | Defined$ Player.Opponent | SacValid$ Creature | StackDescription$ SpellDescription | SpellDescription$ 10—19 VERT Each opponent sacrifices a creature. SVar:SacTopPower:DB$ RepeatEach | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ DBChooseCard | SubAbility$ DBSacAll | StackDescription$ SpellDescription | SpellDescription$ 20 VERT Each opponent sacrifices a creature with the greatest power among creatures that player controls. SVar:DBChooseCard:DB$ ChooseCard | Defined$ Player.IsRemembered | Choices$ Creature.greatestPowerControlledByRemembered | ChoiceTitle$ Choose a creature you control with the greatest power | Mandatory$ True | RememberChosen$ True diff --git a/forge-gui/res/cardsfolder/n/natures_will.txt b/forge-gui/res/cardsfolder/n/natures_will.txt index f3988cc3a83..ea16cf4cace 100644 --- a/forge-gui/res/cardsfolder/n/natures_will.txt +++ b/forge-gui/res/cardsfolder/n/natures_will.txt @@ -1,10 +1,8 @@ Name:Nature's Will ManaCost:2 G G Types:Enchantment -T:Mode$ DamageDoneOnce | CombatDamage$ True | ValidSource$ Creature.YouCtrl | TriggerZones$ Battlefield | ValidTarget$ Player | Execute$ TrigRememberTarget | TriggerDescription$ Whenever one or more creatures you control deal combat damage to a player, tap all lands that player controls and untap all lands you control. -SVar:TrigRememberTarget:DB$ Pump | RememberObjects$ TriggeredTarget | SubAbility$ DBTapAll -SVar:DBTapAll:DB$ TapAll | ValidCards$ Land.RememberedPlayerCtrl | SubAbility$ DBUntapAll -SVar:DBUntapAll:DB$ UntapAll | ValidCards$ Land.YouCtrl | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +T:Mode$ DamageDoneOnce | CombatDamage$ True | ValidSource$ Creature.YouCtrl | TriggerZones$ Battlefield | ValidTarget$ Player | Execute$ DBTapAll | TriggerDescription$ Whenever one or more creatures you control deal combat damage to a player, tap all lands that player controls and untap all lands you control. +SVar:DBTapAll:DB$ TapAll | ValidCards$ Land.ControlledBy TriggeredTarget | SubAbility$ DBUntapAll +SVar:DBUntapAll:DB$ UntapAll | ValidCards$ Land.YouCtrl SVar:PlayMain1:TRUE Oracle:Whenever one or more creatures you control deal combat damage to a player, tap all lands that player controls and untap all lands you control. diff --git a/forge-gui/res/cardsfolder/n/necromancy.txt b/forge-gui/res/cardsfolder/n/necromancy.txt index c9b3f5cb1df..24538975dfc 100644 --- a/forge-gui/res/cardsfolder/n/necromancy.txt +++ b/forge-gui/res/cardsfolder/n/necromancy.txt @@ -2,13 +2,14 @@ Name:Necromancy ManaCost:2 B Types:Enchantment K:MayFlashSac -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ RaiseDead | TriggerDescription$ When CARDNAME enters the battlefield, if it's on the battlefield, it becomes an Aura with "enchant creature put onto the battlefield with CARDNAME." Put target creature card from a graveyard onto the battlefield under your control and attach CARDNAME to it +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ RaiseDead | TriggerDescription$ When CARDNAME enters the battlefield, if it's on the battlefield, it becomes an Aura with "enchant creature put onto the battlefield with CARDNAME." Put target creature card from a graveyard onto the battlefield under your control and attach CARDNAME to it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it. SVar:RaiseDead:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | RememberChanged$ True | TgtPrompt$ Select target creature card in a graveyard | ValidTgts$ Creature | ChangeNum$ 1 | SubAbility$ Aurify SVar:Aurify:DB$ Animate | IsPresent$ Card.Self | Types$ Aura | OverwriteSpells$ True | Abilities$ NewAttach | Keywords$ Enchant creature put onto the battlefield with CARDNAME | Duration$ Permanent | SubAbility$ NecromAttach -SVar:NewAttach:SP$ Attach | Cost$ 2 B | ValidTgts$ Creature.IsRemembered | AILogic$ Pump +SVar:NewAttach:SP$ Attach | Cost$ 2 B | ValidTgts$ Creature.IsRemembered | AILogic$ Pump | SubAbility$ DBDelay +SVar:DBDelay:DB$ DelayedTrigger | Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Execute$ TrigSacrifice | RememberObjects$ RememberedLKI | TriggerDescription$ When CARDNAME leaves the battlefield, that creature's controller sacrifices it. SVar:NecromAttach:DB$ Attach | Defined$ Remembered -T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigSac | TriggerDescription$ When CARDNAME leaves the battlefield, enchanted permanent's controller sacrifices it. -SVar:TrigSac:DB$ Destroy | Sacrifice$ True | Defined$ DirectRemembered | SubAbility$ DBCleanup +SVar:TrigSac:DB$ Destroy | Sacrifice$ True | Defined$ DirectRemembered +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:NeedsToPlayVar:Y GE1 SVar:Y:Count$TypeInYourYard.Creature diff --git a/forge-gui/res/cardsfolder/o/olivia_crimson_bride.txt b/forge-gui/res/cardsfolder/o/olivia_crimson_bride.txt index d3d21e4db38..6badaf7a978 100644 --- a/forge-gui/res/cardsfolder/o/olivia_crimson_bride.txt +++ b/forge-gui/res/cardsfolder/o/olivia_crimson_bride.txt @@ -5,8 +5,9 @@ PT:3/4 K:Flying K:Haste T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigChange | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME attacks, return target creature card from your graveyard to the battlefield tapped and attacking. It gains "When you don't control a legendary Vampire, exile this creature." -SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouOwn | Tapped$ True | Attacking$ True | AnimateSubAbility$ DBAnimate -SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Duration$ Permanent | Triggers$ TrigOlivia +SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouOwn | Tapped$ True | Attacking$ True | RememberChanged$ True | SubAbility$ DBAnimate +SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Duration$ Permanent | Triggers$ TrigOlivia | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:TrigOlivia:Mode$ Always | TriggerZones$ Battlefield | IsPresent$ Vampire.YouCtrl+Legendary | PresentCompare$ EQ0 | Execute$ TrigExile | TriggerDescription$ When you don't control a legendary Vampire, exile this creature. SVar:TrigExile:DB$ ChangeZone | Defined$ Self | Origin$ Battlefield | Destination$ Exile SVar:HasAttackEffect:TRUE diff --git a/forge-gui/res/cardsfolder/o/omen_machine.txt b/forge-gui/res/cardsfolder/o/omen_machine.txt index 08071df7c0b..51301808a75 100644 --- a/forge-gui/res/cardsfolder/o/omen_machine.txt +++ b/forge-gui/res/cardsfolder/o/omen_machine.txt @@ -5,7 +5,7 @@ S:Mode$ CantDraw | ValidPlayer$ Player | Description$ Players can't draw cards. T:Mode$ Phase | Phase$ Draw | ValidPlayer$ Player | Execute$ TrigOmenExileCard | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of each player's draw step, that player exiles the top card of their library. If it's a land card, the player puts it onto the battlefield. Otherwise, the player casts it without paying its mana cost if able. SVar:TrigOmenExileCard:DB$ Dig | DigNum$ 1 | ChangeNum$ All | Defined$ TriggeredPlayer | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBOmenLand SVar:DBOmenLand:DB$ ChangeZone | Origin$ Exile | Destination$ Battlefield | ChangeType$ Land.IsRemembered+ActivePlayerCtrl | ChangeNum$ 1 | DefinedPlayer$ TriggeredPlayer | Chooser$ TriggeredPlayer | Hidden$ True | Mandatory$ True | SubAbility$ DBOmenPlay -SVar:DBOmenPlay:DB$ Play | ValidZone$ Exile | Controller$ TriggeredPlayer | Valid$ Card.IsRemembered | WithoutManaCost$ True | SubAbility$ DBOmenCleanup +SVar:DBOmenPlay:DB$ Play | ValidZone$ Exile | Controller$ TriggeredPlayer | Valid$ Card.IsRemembered | ValidSA$ Spell | WithoutManaCost$ True | SubAbility$ DBOmenCleanup SVar:DBOmenCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:Random Oracle:Players can't draw cards.\nAt the beginning of each player's draw step, that player exiles the top card of their library. If it's a land card, the player puts it onto the battlefield. Otherwise, the player casts it without paying its mana cost if able. diff --git a/forge-gui/res/cardsfolder/p/personal_incarnation.txt b/forge-gui/res/cardsfolder/p/personal_incarnation.txt index a5609389dd0..401055f6c66 100644 --- a/forge-gui/res/cardsfolder/p/personal_incarnation.txt +++ b/forge-gui/res/cardsfolder/p/personal_incarnation.txt @@ -6,9 +6,7 @@ A:AB$ Effect | Cost$ 0 | Activator$ Player.Owner | Name$ Personal Incarnation Re SVar:RedirectDamage:Event$ DamageDone | ValidTarget$ Creature.EffectSource | ReplaceWith$ RedirectDmg | DamageTarget$ You | Description$ The next 1 damage that would be dealt to EFFECTSOURCE this turn is dealt to its owner instead. Only EFFECTSOURCE's owner may activate this ability. SVar:RedirectDmg:DB$ ReplaceSplitDamage | DamageTarget$ You T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigLoseLife | TriggerDescription$ When CARDNAME dies, its owner loses half their life, rounded up. -SVar:TrigLoseLife:DB$ Pump | RememberObjects$ TriggeredCardOwner | SubAbility$ DBLoseLife -SVar:DBLoseLife:DB$ LoseLife | Defined$ Remembered | LifeAmount$ HavocX | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:HavocX:PlayerCountRemembered$LifeTotal/HalfUp +SVar:TrigLoseLife:DB$ LoseLife | Defined$ TriggeredCardOwner | LifeAmount$ HavocX +SVar:HavocX:PlayerCountDefinedTriggeredCardOwner$LifeTotal/HalfUp AI:RemoveDeck:All Oracle:{0}: The next 1 damage that would be dealt to Personal Incarnation this turn is dealt to its owner instead. Only Personal Incarnation's owner may activate this ability.\nWhen Personal Incarnation dies, its owner loses half their life, rounded up. diff --git a/forge-gui/res/cardsfolder/p/pirated_copy.txt b/forge-gui/res/cardsfolder/p/pirated_copy.txt index ca1a3b435ee..64a2bb6b071 100644 --- a/forge-gui/res/cardsfolder/p/pirated_copy.txt +++ b/forge-gui/res/cardsfolder/p/pirated_copy.txt @@ -4,6 +4,6 @@ Types:Creature Shapeshifter Pirate PT:0/0 K:ETBReplacement:Copy:DBCopy:Optional SVar:DBCopy:DB$ Clone | Choices$ Creature.Other | AddTypes$ Pirate | AddTriggers$ DrawTrig | AddSVars$ TrigDraw | SpellDescription$ You may have CARDNAME enter the battlefield as a copy of any creature on the battlefield, except it's a Pirate in addition to its other types and it has "Whenever this creature or another creature with the same name deals combat damage to a player, you draw a card." -SVar:DrawTrig:Mode$ DamageDone | ValidSource$ Card.Self,Creature.sameName | TriggerZones$ Battlefield | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever this creature or another creature with the same name deals combat damage to a player, you draw a card. +SVar:DrawTrig:Mode$ DamageDone | ValidSource$ Card.Self,Creature.Other+sameName | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever this creature or another creature with the same name deals combat damage to a player, you draw a card. SVar:TrigDraw:DB$ Draw Oracle:You may have Pirated Copy enter the battlefield as a copy of any creature on the battlefield, except it's a Pirate in addition to its other types and it has "Whenever this creature or another creature with the same name deals combat damage to a player, you draw a card." diff --git a/forge-gui/res/cardsfolder/p/primal_adversary.txt b/forge-gui/res/cardsfolder/p/primal_adversary.txt index 99d1c966c3c..4e11f174f9d 100644 --- a/forge-gui/res/cardsfolder/p/primal_adversary.txt +++ b/forge-gui/res/cardsfolder/p/primal_adversary.txt @@ -4,9 +4,9 @@ Types:Creature Wolf PT:4/3 K:Trample T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {1}{G} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many target lands you control become 3/3 Wolf creatures with haste that are still lands. -SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 G\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many target lands you control become 3/3 Wolf creatures with haste that are still lands. -SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ NumTimes | SubAbility$ DBAnimate -SVar:DBAnimate:DB$ Animate | TargetMin$ 0 | TargetMax$ NumTimes | ValidTgts$ Land.YouCtrl | TgtPrompt$ Select up to that many target lands you control | Power$ 3 | Toughness$ 3 | Types$ Wolf,Creature | Keywords$ Haste | Duration$ Permanent -SVar:NumTimes:Number$0 +SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 G\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | RememberSVarAmount$ NumTimes | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many target lands you control become 3/3 Wolf creatures with haste that are still lands. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBAnimate +SVar:DBAnimate:DB$ Animate | TargetMin$ 0 | TargetMax$ X | ValidTgts$ Land.YouCtrl | TgtPrompt$ Select up to that many target lands you control | Power$ 3 | Toughness$ 3 | Types$ Wolf,Creature | Keywords$ Haste | Duration$ Permanent +SVar:X:Count$TriggerRememberAmount DeckHas:Ability$Counters Oracle:Trample\nWhen Primal Adversary enters the battlefield, you may pay {1}{G} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Primal Adversary, then up to that many target lands you control become 3/3 Wolf creatures with haste that are still lands. diff --git a/forge-gui/res/cardsfolder/p/primal_amulet_primal_wellspring.txt b/forge-gui/res/cardsfolder/p/primal_amulet_primal_wellspring.txt index c18c54ae554..91e49776fd1 100644 --- a/forge-gui/res/cardsfolder/p/primal_amulet_primal_wellspring.txt +++ b/forge-gui/res/cardsfolder/p/primal_amulet_primal_wellspring.txt @@ -3,15 +3,13 @@ ManaCost:4 Types:Artifact S:Mode$ ReduceCost | ValidCard$ Instant,Sorcery | Type$ Spell | Activator$ You | Amount$ 1 | Description$ Instant and sorcery spells you cast cost {1} less to cast. T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigCharge | TriggerDescription$ Whenever you cast an instant or sorcery spell, put a charge counter on CARDNAME. Then if there are four or more charge counters on it, you may remove those counters and transform it. -SVar:TrigCharge:DB$ PutCounter | Defined$ Self | CounterType$ CHARGE | CounterNum$ 1 | SubAbility$ DBStoreSVar -SVar:DBStoreSVar:DB$ StoreSVar | SVar$ FullyCharged | Type$ Number | Expression$ 1 | ConditionCheckSVar$ ChargeCounter | ConditionSVarCompare$ GE1 | SubAbility$ DBRemoveCtrs -SVar:DBRemoveCtrs:DB$ RemoveCounter | Defined$ Self | CounterType$ CHARGE | CounterNum$ All | Optional$ True | ConditionCheckSVar$ FullyCharged | ConditionSVarCompare$ GE1 | SubAbility$ DBTransform -SVar:DBTransform:DB$ SetState | Defined$ Self | Mode$ Transform | ConditionCheckSVar$ Discharged | ConditionSVarCompare$ EQ1 -SVar:ChargeCounter:Count$Valid Card.Self+counters_GE4_CHARGE -SVar:Discharged:Count$Valid Card.Self+counters_EQ0_CHARGE -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | Execute$ DBInitSVar | Static$ True -SVar:DBInitSVar:DB$ StoreSVar | SVar$ FullyCharged | Type$ Number | Expression$ 0 -SVar:FullyCharged:Number$0 +SVar:TrigCharge:DB$ PutCounter | Defined$ Self | CounterType$ CHARGE | CounterNum$ 1 | SubAbility$ DBBranch +SVar:DBBranch:DB$ Branch | BranchConditionSVar$ FullyCharged | TrueSubAbility$ DBRemoveCtrs +SVar:DBRemoveCtrs:DB$ RemoveCounter | Defined$ Self | CounterType$ CHARGE | CounterNum$ All | Optional$ True | RememberAmount$ True | SubAbility$ DBTransform +SVar:DBTransform:DB$ SetState | Defined$ Self | Mode$ Transform | ConditionCheckSVar$ Discharged | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:FullyCharged:Count$Valid Card.Self+counters_GE4_CHARGE +SVar:Discharged:Count$RememberedSize AlternateMode:DoubleFaced Oracle:Instant and sorcery spells you cast cost {1} less to cast.\nWhenever you cast an instant or sorcery spell, put a charge counter on Primal Amulet. Then if there are four or more charge counters on it, you may remove those counters and transform it. diff --git a/forge-gui/res/cardsfolder/p/pure_intentions.txt b/forge-gui/res/cardsfolder/p/pure_intentions.txt index 12731beaa57..2033b1c98aa 100644 --- a/forge-gui/res/cardsfolder/p/pure_intentions.txt +++ b/forge-gui/res/cardsfolder/p/pure_intentions.txt @@ -4,7 +4,8 @@ Types:Instant Arcane A:SP$ Effect | Cost$ W | Triggers$ PureDiscarded | SpellDescription$ Whenever a spell or ability an opponent controls causes you to discard cards this turn, return those cards from your graveyard to your hand. SVar:PureDiscarded:Mode$ Discarded | ValidCard$ Card.YouCtrl | ValidCause$ Card.OppCtrl | TriggerZones$ Command | Execute$ TrigPureChange | TriggerDescription$ Whenever a spell or ability an opponent controls causes you to discard cards this turn, return those cards from your graveyard to your hand. SVar:TrigPureChange:DB$ ChangeZone | Defined$ TriggeredCardLKICopy | Origin$ Graveyard | Destination$ Hand -T:Mode$ Discarded | ValidCard$ Card.Self | ValidCause$ Card.OppCtrl | Execute$ TrigPureReturn | TriggerDescription$ When a spell or ability an opponent controls causes you to discard CARDNAME, return it to your hand. +T:Mode$ Discarded | ValidCard$ Card.Self | ValidCause$ Card.OppCtrl | Execute$ TrigDelay | TriggerDescription$ When a spell or ability an opponent controls causes you to discard CARDNAME, return CARDNAME from your graveyard to your hand at the beginning of the next end step. +SVar:TrigDelay:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ TrigPureReturn | SpellDescription$ Return CARDNAME from your graveyard to your hand at the beginning of the next end step. SVar:TrigPureReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand SVar:DiscardMeByOpp:1 AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/r/riverfall_mimic.txt b/forge-gui/res/cardsfolder/r/riverfall_mimic.txt index d1e68c0a925..842b1a872cf 100644 --- a/forge-gui/res/cardsfolder/r/riverfall_mimic.txt +++ b/forge-gui/res/cardsfolder/r/riverfall_mimic.txt @@ -3,6 +3,8 @@ ManaCost:1 UR Types:Creature Shapeshifter PT:2/1 T:Mode$ SpellCast | ValidCard$ Card.Blue+Red | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigAnimate | TriggerDescription$ Whenever you cast a spell that's both blue and red, CARDNAME has base power and toughness 3/3 until end of turn and can't be blocked this turn. -SVar:TrigAnimate:DB$ Animate | Defined$ Self | Power$ 3 | Toughness$ 3 | HiddenKeywords$ Unblockable +SVar:TrigAnimate:DB$ Animate | Defined$ Self | Power$ 3 | Toughness$ 3 | SubAbility$ DBUnblockable +SVar:DBUnblockable:DB$ Effect | ExileOnMoved$ Battlefield | RememberObjects$ Self | StaticAbilities$ Unblockable +SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn. SVar:BuffedBy:Card.Blue+Red Oracle:Whenever you cast a spell that's both blue and red, Riverfall Mimic has base power and toughness 3/3 until end of turn and can't be blocked this turn. diff --git a/forge-gui/res/cardsfolder/s/sand_golem.txt b/forge-gui/res/cardsfolder/s/sand_golem.txt index 41905d9cee1..afe52163795 100644 --- a/forge-gui/res/cardsfolder/s/sand_golem.txt +++ b/forge-gui/res/cardsfolder/s/sand_golem.txt @@ -3,8 +3,7 @@ ManaCost:5 Types:Artifact Creature Golem PT:3/3 T:Mode$ Discarded | ValidCard$ Card.Self | ValidCause$ Card.OppCtrl | Execute$ DelTrig | TriggerDescription$ When a spell or ability an opponent controls causes you to discard CARDNAME, return CARDNAME from your graveyard to the battlefield with a +1/+1 counter on it at the beginning of the next end step. -SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ TrigReturn | TriggerDescription$ return CARDNAME from your graveyard to the battlefield with a +1/+1 counter on it at the beginning of the next end step. -SVar:TrigReturn:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Battlefield | SubAbility$ AddCounter -SVar:AddCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ TrigReturn | TriggerDescription$ Return CARDNAME from your graveyard to the battlefield with a +1/+1 counter on it at the beginning of the next end step. +SVar:TrigReturn:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Battlefield | WithCountersType$ P1P1 SVar:DiscardMeByOpp:3 Oracle:When a spell or ability an opponent controls causes you to discard Sand Golem, return Sand Golem from your graveyard to the battlefield with a +1/+1 counter on it at the beginning of the next end step. diff --git a/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt b/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt index 88cf440bd17..fe1bafcc93f 100644 --- a/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt +++ b/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt @@ -13,9 +13,7 @@ Name:Sasaya's Essence ManaCost:1 G G Colors:green Types:Legendary Enchantment -T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigMana | Static$ True | TriggerDescription$ Whenever a land you control is tapped for mana, add an additional one mana of any type that land produced for each other land you control with the same name as it. -SVar:TrigMana:DB$ Pump | RememberObjects$ TriggeredCard | SubAbility$ DBRepeat -SVar:DBRepeat:DB$ RepeatEach | UseImprinted$ True | RepeatCards$ Land.YouCtrl+IsNotRemembered+sharesNameWith Remembered | RepeatSubAbility$ DBManaReflect | SubAbility$ DBCleanup +T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | Execute$ DBRepeat | Static$ True | TriggerDescription$ Whenever a land you control is tapped for mana, add an additional one mana of any type that land produced for each other land you control with the same name as it. +SVar:DBRepeat:DB$ RepeatEach | RepeatCards$ Land.YouCtrl+NotTriggeredCard+sharesNameWith TriggeredCard | RepeatSubAbility$ DBManaReflect SVar:DBManaReflect:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Whenever a land you control is tapped for mana, add an additional one mana of any type that land produced for each other land you control with the same name as it. diff --git a/forge-gui/res/cardsfolder/s/sigil_of_valor.txt b/forge-gui/res/cardsfolder/s/sigil_of_valor.txt index b13987ae686..de392054a90 100644 --- a/forge-gui/res/cardsfolder/s/sigil_of_valor.txt +++ b/forge-gui/res/cardsfolder/s/sigil_of_valor.txt @@ -3,8 +3,6 @@ ManaCost:2 Types:Artifact Equipment K:Equip:1 T:Mode$ Attacks | ValidCard$ Card.EquippedBy | Alone$ True | Execute$ TrigPump | TriggerDescription$ Whenever equipped creature attacks alone, it gets +1/+1 until end of turn for each other creature you control. -SVar:TrigPump:DB$ Pump | RememberObjects$ TriggeredAttacker | SubAbility$ TrigPump2 -SVar:TrigPump2:DB$ Pump | Defined$ Remembered | NumAtt$ X | NumDef$ X | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Count$Valid Creature.YouCtrl+IsNotRemembered +SVar:TrigPump:DB$ Pump | Defined$ TriggeredAttackerLKICopy | NumAtt$ X | NumDef$ X +SVar:X:Count$Valid Creature.YouCtrl+NotTriggeredAttacker Oracle:Whenever equipped creature attacks alone, it gets +1/+1 until end of turn for each other creature you control.\nEquip {1} ({1}: Attach to target creature you control. Equip only as a sorcery.) diff --git a/forge-gui/res/cardsfolder/s/solitude.txt b/forge-gui/res/cardsfolder/s/solitude.txt index 3a44daf7058..a6250d46f39 100644 --- a/forge-gui/res/cardsfolder/s/solitude.txt +++ b/forge-gui/res/cardsfolder/s/solitude.txt @@ -7,7 +7,8 @@ K:Lifelink K:Evoke:ExileFromHand<1/Card.White+Other/white card> T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters the battlefield, exile up to one other target creature. That creature's controller gains life equal to its power. SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Creature.Other | RememberLKI$ True | TgtPrompt$ Select up to one other target creature | TargetMin$ 0 | TargetMax$ 1 | SubAbility$ DBGainLife -SVar:DBGainLife:DB$ GainLife | Defined$ RememberedController | LifeAmount$ X +SVar:DBGainLife:DB$ GainLife | Defined$ RememberedController | LifeAmount$ X | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:RememberedLKI$CardPower DeckHas:Ability$LifeGain Oracle:Flash\nLifelink\nWhen Solitude enters the battlefield, exile up to one other target creature. That creature's controller gains life equal to its power.\nEvoke—Exile a white card from your hand. diff --git a/forge-gui/res/cardsfolder/s/sowing_salt.txt b/forge-gui/res/cardsfolder/s/sowing_salt.txt index ab4db027d46..2219d459e84 100644 --- a/forge-gui/res/cardsfolder/s/sowing_salt.txt +++ b/forge-gui/res/cardsfolder/s/sowing_salt.txt @@ -5,7 +5,7 @@ A:SP$ ChangeZone | Cost$ 2 R R | Origin$ Battlefield | Destination$ Exile | Vali SVar:ExileYard:DB$ ChangeZoneAll | Origin$ Graveyard | Destination$ Exile | Defined$ RememberedController | ChangeType$ Remembered.sameName | SubAbility$ ExileHand | StackDescription$ None SVar:ExileHand:DB$ ChangeZone | Origin$ Hand | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInHand | Chooser$ You | SubAbility$ ExileLib | StackDescription$ None SVar:ExileLib:DB$ ChangeZone | Origin$ Library | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInLib | Chooser$ You | Search$ True | Shuffle$ True | SubAbility$ DBCleanup | StackDescription$ None -SVar:NumInHand:RememberedController$CardsInHand -SVar:NumInLib:RememberedController$CardsInLibrary +SVar:NumInHand:PlayerCountRememberedController$CardsInHand +SVar:NumInLib:PlayerCountRememberedController$CardsInLibrary SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Exile target nonbasic land. Search its controller's graveyard, hand, and library for all cards with the same name as that land and exile them. Then that player shuffles. diff --git a/forge-gui/res/cardsfolder/s/spectral_adversary.txt b/forge-gui/res/cardsfolder/s/spectral_adversary.txt index 063ddcf9bd9..ea66850993a 100644 --- a/forge-gui/res/cardsfolder/s/spectral_adversary.txt +++ b/forge-gui/res/cardsfolder/s/spectral_adversary.txt @@ -5,9 +5,9 @@ PT:2/1 K:Flash K:Flying T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {1}{U} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many other target artifacts, creatures, and/or enchantments phase out. -SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 U\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many other target artifacts, creatures, and/or enchantments phase out. -SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ NumTimes | SubAbility$ DBPhases -SVar:DBPhases:DB$ Phases | ValidTgts$ Creature.Other,Artifact.Other,Enchantment.Other | TgtPrompt$ Select up to that many other target artifacts, creatures, and/or enchantments | TargetMin$ 0 | TargetMax$ NumTimes -SVar:NumTimes:Number$0 +SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 U\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | RememberSVarAmount$ NumTimes | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many other target artifacts, creatures, and/or enchantments phase out. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBPhases +SVar:DBPhases:DB$ Phases | ValidTgts$ Creature.Other,Artifact.Other,Enchantment.Other | TgtPrompt$ Select up to that many other target artifacts, creatures, and/or enchantments | TargetMin$ 0 | TargetMax$ X +SVar:X:Count$TriggerRememberAmount DeckHas:Ability$Counters Oracle:Flash\nFlying\nWhen Spectral Adversary enters the battlefield, you may pay {1}{U} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Spectral Adversary, then up to that many other target artifacts, creatures, and/or enchantments phase out. diff --git a/forge-gui/res/cardsfolder/s/splinter.txt b/forge-gui/res/cardsfolder/s/splinter.txt index 29474054ed5..728c70a2b04 100644 --- a/forge-gui/res/cardsfolder/s/splinter.txt +++ b/forge-gui/res/cardsfolder/s/splinter.txt @@ -5,7 +5,7 @@ A:SP$ ChangeZone | Cost$ 2 G G | Origin$ Battlefield | Destination$ Exile | Vali SVar:ExileYard:DB$ ChangeZoneAll | Origin$ Graveyard | Destination$ Exile | Defined$ RememberedController | ChangeType$ Remembered.sameName | SubAbility$ ExileHand | StackDescription$ None SVar:ExileHand:DB$ ChangeZone | Origin$ Hand | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInHand | Chooser$ You | SubAbility$ ExileLib | StackDescription$ None SVar:ExileLib:DB$ ChangeZone | Origin$ Library | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInLib | Chooser$ You | Search$ True | Shuffle$ True | SubAbility$ DBCleanup | StackDescription$ None -SVar:NumInHand:RememberedController$CardsInHand -SVar:NumInLib:RememberedController$CardsInLibrary +SVar:NumInHand:PlayerCountRememberedController$CardsInHand +SVar:NumInLib:PlayerCountRememberedController$CardsInLibrary SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Exile target artifact. Search its controller's graveyard, hand, and library for all cards with the same name as that artifact and exile them. Then that player shuffles. diff --git a/forge-gui/res/cardsfolder/s/swords_to_plowshares.txt b/forge-gui/res/cardsfolder/s/swords_to_plowshares.txt index e75a18cde30..7ffd4a071eb 100644 --- a/forge-gui/res/cardsfolder/s/swords_to_plowshares.txt +++ b/forge-gui/res/cardsfolder/s/swords_to_plowshares.txt @@ -2,6 +2,7 @@ Name:Swords to Plowshares ManaCost:W Types:Instant A:SP$ ChangeZone | ValidTgts$ Creature | Origin$ Battlefield | Destination$ Exile | RememberLKI$ True | SubAbility$ DBGainLife | SpellDescription$ Exile target creature. -SVar:DBGainLife:DB$ GainLife | Defined$ RememberedController | LifeAmount$ X | SpellDescription$ Its controller gains life equal to its power. +SVar:DBGainLife:DB$ GainLife | Defined$ RememberedController | LifeAmount$ X | SubAbility$ DBCleanup | SpellDescription$ Its controller gains life equal to its power. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:RememberedLKI$CardPower Oracle:Exile target creature. Its controller gains life equal to its power. diff --git a/forge-gui/res/cardsfolder/t/tainted_adversary.txt b/forge-gui/res/cardsfolder/t/tainted_adversary.txt index f330d222aa4..3fc6893f907 100644 --- a/forge-gui/res/cardsfolder/t/tainted_adversary.txt +++ b/forge-gui/res/cardsfolder/t/tainted_adversary.txt @@ -4,10 +4,9 @@ Types:Creature Zombie PT:2/3 K:Deathtouch T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {2}{B} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then create twice that many black 2/2 Zombie creature tokens with decayed. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.) -SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<2 B\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then create twice that many black 2/2 Zombie creature tokens with decayed. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.) -SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ NumTimes | SubAbility$ DBToken -SVar:DBToken:DB$ Token | TokenScript$ b_2_2_zombie_decayed | TokenAmount$ TwiceNumTimes -SVar:NumTimes:Number$0 -SVar:TwiceNumTimes:SVar$NumTimes/Twice +SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<2 B\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | RememberSVarAmount$ NumTimes | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then create twice that many black 2/2 Zombie creature tokens with decayed. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.) +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBToken +SVar:DBToken:DB$ Token | TokenScript$ b_2_2_zombie_decayed | TokenAmount$ SVar$X/Twice +SVar:X:Count$TriggerRememberAmount DeckHas:Ability$Token|Counters Oracle:Deathtouch\nWhen Tainted Adversary enters the battlefield, you may pay {2}{B} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Tainted Adversary, then create twice that many black 2/2 Zombie creature tokens with decayed. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.) diff --git a/forge-gui/res/cardsfolder/t/the_ruinous_powers.txt b/forge-gui/res/cardsfolder/t/the_ruinous_powers.txt index 7f5bd2d4a95..28dbba4cc74 100644 --- a/forge-gui/res/cardsfolder/t/the_ruinous_powers.txt +++ b/forge-gui/res/cardsfolder/t/the_ruinous_powers.txt @@ -5,8 +5,8 @@ T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | E SVar:TrigChoose:DB$ ChoosePlayer | Defined$ You | Choices$ Opponent | Random$ True | SubAbility$ DBExile SVar:DBExile:DB$ Dig | Defined$ ChosenPlayer | DigNum$ 1 | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay | Triggers$ TriggerCastDoM | ExileOnMoved$ Exile | RememberObjects$ Remembered -SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayIgnoreColor$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand | AffectedZone$ Exile | Description$ Until end of turn, you may cast that card and you may spend mana as though it were mana of any color to cast it. -SVar:TriggerCastDoM:Mode$ SpellCast | ValidCard$ Card.IsRemembered | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigLoseLife | Secondary$ True | TriggerDescription$ At the beginning of your upkeep, choose an opponent at random. Exile the top card of that player's library. Until end of turn, you may play that card and you may spend mana as though it were mana of any color to cast it. When you cast a spell this way, its owner loses life equal to its mana value. +SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayIgnoreColor$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ Until end of turn, you may play that card and you may spend mana as though it were mana of any color to cast it. +SVar:TriggerCastDoM:Mode$ SpellCast | ValidCard$ Card.IsRemembered | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigLoseLife | Secondary$ True | TriggerDescription$ When you cast a spell this way, its owner loses life equal to its mana value. SVar:TrigLoseLife:DB$ LoseLife | Defined$ TriggeredCardOwner | LifeAmount$ X SVar:X:TriggeredStackInstance$CardManaCostLKI Oracle:At the beginning of your upkeep, choose an opponent at random. Exile the top card of that player's library. Until end of turn, you may play that card and you may spend mana as though it were mana of any color to cast it. When you cast a spell this way, its owner loses life equal to its mana value. diff --git a/forge-gui/res/cardsfolder/upcoming/aisha_of_sparks_and_smoke.txt b/forge-gui/res/cardsfolder/upcoming/aisha_of_sparks_and_smoke.txt new file mode 100644 index 00000000000..f5003cac6fa --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/aisha_of_sparks_and_smoke.txt @@ -0,0 +1,11 @@ +Name:Aisha of Sparks and Smoke +ManaCost:1 R R +Types:Legendary Creature Human Warrior +PT:4/2 +K:Prowess +A:AB$ Pump | Cost$ RW | Defined$ Self | KW$ First Strike | SpellDescription$ NICKNAME gains first strike until end of turn. +T:Mode$ DamageDealtOnce | CombatDamage$ True | ValidSource$ Card.Self | Execute$ TrigCast | OptionalDecider$ You | TriggerDescription$ Whenever NICKNAME deals combat damage, you may cast a sorcery spell from your hand with mana value less than or equal to that damage without paying its mana cost. +SVar:TrigCast:DB$ Play | Valid$ Sorcery.YouOwn+cmcLEX | ValidSA$ Spell | ValidZone$ Hand | WithoutManaCost$ True | Amount$ 1 | Optional$ True +SVar:X:TriggerCount$DamageAmount +DeckHints:Type$Sorcery +Oracle:Prowess (Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn.)\n{R/W}: Aisha gains first strike until end of turn.\nWhenever Aisha deals combat damage, you may cast a sorcery spell from your hand with mana value less than or equal to that damage without paying its mana cost. diff --git a/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt b/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt new file mode 100644 index 00000000000..d74028da427 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt @@ -0,0 +1,13 @@ +Name:Argentum Masticore +ManaCost:5 +Types:Artifact Creature Phyrexian Masticore +PT:5/5 +K:First strike +K:Protection:Card.MultiColor:Protection from multicolored +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigSacrifice | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you discard a card. When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. +SVar:TrigSacrifice:DB$ Sacrifice | UnlessCost$ Discard<1/Card> | UnlessPayer$ You | OrString$ Sacrifice it. | SubAbility$ TrigImmediateTrig +SVar:TrigImmediateTrig:DB$ ImmediateTrigger | ConditionDefined$ Discarded | ConditionPresent$ Card | ConditionCompare$ GE1 | RememberObjects$ Discarded | Execute$ TrigDestroy | TriggerDescription$ When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. +SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Permanent.nonLand+cmcLEX+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls with mana value less or equal to the discarded card +SVar:X:TriggerRemembered$CardManaCost +DeckHas:Ability$Discard|Sacrifice +Oracle:First strike, protection from multicolored\nAt the beginning of your upkeep, sacrifice Argentum Masticore unless you discard a card. When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. diff --git a/forge-gui/res/cardsfolder/upcoming/arteeoh_dread_scavenger.txt b/forge-gui/res/cardsfolder/upcoming/arteeoh_dread_scavenger.txt new file mode 100644 index 00000000000..3171f634429 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/arteeoh_dread_scavenger.txt @@ -0,0 +1,14 @@ +Name:Arteeoh, Dread Scavenger +ManaCost:1 B G U +Types:Legendary Artifact Creature Robot +PT:3/3 +K:Flying +K:Deathtouch +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigExchange | TriggerDescription$ Whenever NICKNAME deals combat damage to a player, you may exchange control of two other target artifacts. When you do, create a token that's a copy of target artifact you don't control, except it's a 1/1 green Squirrel creature token in addition to its other colors and types. +SVar:TrigExchange:DB$ ExchangeControl | RememberExchanged$ True | ValidTgts$ Artifact.Other | TargetMin$ 2 | TargetMax$ 2 | TgtPrompt$ Choose two other target artifacts | Optional$ True | AILogic$ TrigTwoTargets | SubAbility$ TrigImmediateTrig +SVar:TrigImmediateTrig:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE2 | SubAbility$ DBCleanup | Execute$ TrigToken | TriggerDescription$ When you do, create a token that's a copy of target artifact you don't control, except it's a 1/1 green Squirrel creature token in addition to its other colors and types. +SVar:TrigToken:DB$ CopyPermanent | ValidTgts$ Artifact.YouDontCtrl | TgtPrompt$ Select target artifact you don't control | SetPower$ 1 | SetToughness$ 1 | AddColors$ Green | AddTypes$ Creature & Squirrel +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +DeckHas:Ability$Token & Type$Squirrel +AI:RemoveDeck:Random +Oracle:Flying, deathtouch\nWhenever Arteeoh deals combat damage to a player, you may exchange control of two other target artifacts. When you do, create a token that's a copy of target artifact you don't control, except it's a 1/1 green Squirrel creature token in addition to its other colors and types. diff --git a/forge-gui/res/cardsfolder/upcoming/baldin_century_herdmaster.txt b/forge-gui/res/cardsfolder/upcoming/baldin_century_herdmaster.txt new file mode 100644 index 00000000000..824a84426b6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/baldin_century_herdmaster.txt @@ -0,0 +1,10 @@ +Name:Baldin, Century Herdmaster +ManaCost:4 W W +Types:Legendary Creature Human Warrior +PT:0/7 +S:Mode$ CombatDamageToughness | Condition$ PlayerTurn | ValidCard$ Creature | Description$ As long as it's your turn, each creature assigns combat damage equal to its toughness rather than its power. +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$ Whenever CARDNAME attacks, up to one hundred target creatures each get +0/+X until end of turn, where X is the number of cards in your hand. +SVar:TrigPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | TargetMin$ 0 | TargetMax$ 100 | NumDef$ +X | AILogic$ Pump +SVar:HasAttackEffect:TRUE +SVar:X:Count$CardsInYourHand +Oracle:As long as it's your turn, each creature assigns combat damage equal to its toughness rather than its power.\nWhenever Baldin, Century Herdmaster attacks, up to one hundred target creatures each get +0/+X until end of turn, where X is the number of cards in your hand. diff --git a/forge-gui/res/cardsfolder/upcoming/bonepicker_skirge.txt b/forge-gui/res/cardsfolder/upcoming/bonepicker_skirge.txt new file mode 100644 index 00000000000..b211059af4c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bonepicker_skirge.txt @@ -0,0 +1,11 @@ +Name:Bonepicker Skirge +ManaCost:2 B +Types:Creature Phyrexian Imp +PT:2/2 +K:Flying +S:Mode$ Continuous | Affected$ Card.Self | CheckSVar$ X | SVarCompare$ GE3 | AddKeyword$ Deathtouch & Lifelink | Description$ Corrupted – As long as an opponent has three or more poison counters, CARDNAME has deathtouch and lifelink. +SVar:X:PlayerCountOpponents$HighestPoisonCounters +DeckHas:Ability$LifeGain +AI:RemoveDeck:Random +DeckNeeds:Ability$Counters +Oracle:Flying\nCorrupted – As long as an opponent has three or more poison counters, Bonepicker Skirge has deathtouch and lifelink. diff --git a/forge-gui/res/cardsfolder/upcoming/by_elspeths_command.txt b/forge-gui/res/cardsfolder/upcoming/by_elspeths_command.txt new file mode 100644 index 00000000000..43350b9d5f8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/by_elspeths_command.txt @@ -0,0 +1,15 @@ +Name:By Elspeth's Command +ManaCost:2 W +Types:Enchantment +T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | Execute$ TrigCharm | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of combat on your turn, ABILITY +SVar:TrigCharm:DB$ Charm | Choices$ PumpField,PumpHand,Token | ChoiceRestriction$ YourLastCombat +SVar:PumpField:DB$ Pump | ValidTgts$ Soldier | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one target Soldier | SubAbility$ DBEffect | SpellDescription$ Up to one target Soldier perpetually gets +1/+1 and gains flying. +SVar:DBEffect:DB$ Effect | StaticAbilities$ PerpetualPump | RememberObjects$ Targeted | Name$ By Elspeth's Command's First Mode's Perpetual Effect | Duration$ Permanent | SubAbility$ DBCleanup +SVar:PerpetualPump:Mode$ Continuous | Affected$ Card.IsRemembered | AddPower$ 1 | AddToughness$ 1 | AddKeyword$ Flying | EffectZone$ Command | AffectedZone$ Battlefield,Hand,Graveyard,Exile,Stack,Library,Command | Description$ This Soldier perpetually gets +1/+1 and gains flying. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearChosenCard$ True +SVar:PumpHand:DB$ ChooseCard | ChoiceZone$ Hand | Choices$ Soldier.YouOwn | ChoiceTitle$ Choose a Soldier card in your hand | Amount$ 1 | SubAbility$ DBEffect2 | SpellDescription$ Choose a Soldier card in your hand. It perpetually gets +1/+1 and gains vigilance. +SVar:DBEffect2:DB$ Effect | StaticAbilities$ PerpetualPump2 | Name$ By Elspeth's Command's Second Mode's Perpetual Effect | Duration$ Permanent | SubAbility$ DBCleanup +SVar:PerpetualPump2:Mode$ Continuous | Affected$ Card.ChosenCard | AddPower$ 1 | AddToughness$ 1 | AddKeyword$ Vigilance | EffectZone$ Command | AffectedZone$ Battlefield,Hand,Graveyard,Exile,Stack,Library,Command | Description$ The chosen Soldier card perpetually gets +1/+1 and gains vigilance. +SVar:Token:DB$ Token | TokenScript$ c_1_1_a_soldier | SpellDescription$ Create a 1/1 colorless Soldier artifact creature token. +DeckHas:Ability$Token & Type$Artifact|Soldier +Oracle:At the beginning of combat on your turn, choose one that wasn't chosen during your last combat —\n• Up to one target Soldier perpetually gets +1/+1 and gains flying.\n• Choose a Soldier card in your hand. It perpetually gets +1/+1 and gains vigilance.\n• Create a 1/1 colorless Soldier artifact creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/furnace_punisher.txt b/forge-gui/res/cardsfolder/upcoming/furnace_punisher.txt new file mode 100644 index 00000000000..36f453c2ed5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/furnace_punisher.txt @@ -0,0 +1,11 @@ +Name:Furnace Punisher +ManaCost:2 R +Types:Creature Phyrexian Warrior +PT:3/3 +K:Menace +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | Execute$ TrigDealDamage | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of each player's upkeep, CARDNAME deals 2 damage to that player unless they control two or more basic lands. +SVar:TrigDealDamage:DB$ DealDamage | Defined$ TriggeredPlayer | NumDmg$ 2 | ConditionPresent$ Land.Basic+ControlledBy TriggeredPlayer | ConditionCompare$ LT2 +AI:RemoveDeck:Random +SVar:X:Count$Valid Land.Basic+YouCtrl +SVar:NeedsToPlayVar:X GE2 +Oracle:Menace\nAt the beginning of each player's upkeep, Furnace Punisher deals 2 damage to that player unless they control two or more basic lands. diff --git a/forge-gui/res/cardsfolder/upcoming/green_suns_twilight.txt b/forge-gui/res/cardsfolder/upcoming/green_suns_twilight.txt new file mode 100644 index 00000000000..0e7245ae2cd --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/green_suns_twilight.txt @@ -0,0 +1,11 @@ +Name:Green Sun's Twilight +ManaCost:X G +Types:Sorcery +A:SP$ DigMultiple | DigNum$ SVar$X/Plus.1 | Reveal$ True | ChangeValid$ Creature,Land | ChangeLater$ True | RememberChanged$ True | ImprintRest$ True | SubAbility$ DBBranch | SpellDescription$ Reveal the top X plus one cards of your library. Choose a creature card and/or a land card from among them. Put those cards into your hand and the rest on the bottom of your library in a random order. If X is 5 or more, instead put the chosen cards onto the battlefield or into your hand and the rest on the bottom of your library in a random order. +SVar:DBBranch:DB$ Branch | BranchConditionSVar$ X | BranchConditionSVarCompare$ GE5 | TrueSubAbility$ DBChangeZone | FalseSubAbility$ Hand | SubAbility$ RestBottom +SVar:DBChangeZone:DB$ ChangeZone | Defined$ Remembered | Origin$ Library | Destination$ Battlefield | DestinationAlternative$ Hand | AlternativeDestinationMessage$ Put the chosen card(s) onto the battlefield instead of into your hand? +SVar:Hand:DB$ ChangeZone | Defined$ Remembered | Origin$ Library | Destination$ Hand | NoShuffle$ True +SVar:RestBottom:DB$ ChangeZone | Defined$ Imprinted | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | NoShuffle$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True +SVar:X:Count$xPaid +Oracle:Reveal the top X plus one cards of your library. Choose a creature card and/or a land card from among them. Put those cards into your hand and the rest on the bottom of your library in a random order. If X is 5 or more, instead put the chosen cards onto the battlefield or into your hand and the rest on the bottom of your library in a random order. diff --git a/forge-gui/res/cardsfolder/upcoming/immard_the_stormcleaver.txt b/forge-gui/res/cardsfolder/upcoming/immard_the_stormcleaver.txt new file mode 100644 index 00000000000..84ad5401275 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/immard_the_stormcleaver.txt @@ -0,0 +1,14 @@ +Name:Immard, the Stormcleaver +ManaCost:1 U R W +Types:Legendary Creature Human Soldier +PT:4/4 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharge | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, put a charge counter on him or remove one from him. When you remove a counter this way, ABILITY +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigCharge | Secondary$ True | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, put a charge counter on him or remove one from him. When you remove a counter this way, ABILITY +SVar:TrigCharge:DB$ AddOrRemoveCounter | Defined$ Self | CounterType$ CHARGE | CounterNum$ 1 | RememberRemovedCards$ True | SubAbility$ DBImmediateTrigger +SVar:DBImmediateTrigger:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card.Self | ConditionCompare$ GE1 | Execute$ TrigCharm | SubAbility$ DBCleanup | TriggerDescription$ When you remove a counter this way, ABILITY +SVar:TrigCharm:DB$ Charm | Choices$ DBDamage,DBPump +SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 4 | SpellDescription$ CARDNAME deals 4 damage to any target. +SVar:DBPump:DB$ Pump | Defined$ Self | KW$ Lifelink & Indestructible | SpellDescription$ CARDNAME gains lifelink and indestructible until end of turn. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +DeckHas:Ability$Counters|LifeGain +Oracle:Whenever Immard, the Stormcleaver enters the battlefield or attacks, put a charge counter on him or remove one from him. When you remove a counter this way, choose one—\n• Immard, the Stormcleaver deals 4 damage to any target.\n• Immard, the Stormcleaver gains lifelink and indestructible until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/kaito_dancing_shadow.txt b/forge-gui/res/cardsfolder/upcoming/kaito_dancing_shadow.txt new file mode 100644 index 00000000000..d2be974c5e0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/kaito_dancing_shadow.txt @@ -0,0 +1,12 @@ +Name:Kaito, Dancing Shadow +ManaCost:2 U B +Types:Legendary Planeswalker Kaito +Loyalty:3 +T:Mode$ DamageDoneOnce | CombatDamage$ True | ValidSource$ Creature.YouCtrl | TriggerZones$ Battlefield | ValidTarget$ Player | Execute$ TrigLoyalty | TriggerDescription$ Whenever one or more creatures you control deal combat damage to a player, you may return one of them to its owner's hand. If you do, you may activate loyalty abilities of NICKNAME twice this turn rather than only once. +SVar:TrigLoyalty:AB$ Effect | Cost$ Return<1/Card.TriggeredSources> | StaticAbilities$ PWTwice +SVar:PWTwice:Mode$ NumLoyaltyAct | ValidCard$ Card.EffectSource | Twice$ True | Description$ You may activate the loyalty abilities of NICKNAME twice this turn rather than only once. +A:AB$ Pump | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature | TgtPrompt$ Select up to one target creature | KW$ HIDDEN CARDNAME can't attack or block. | IsCurse$ True | Duration$ UntilYourNextTurn | AILogic$ DetainNonLand | StackDescription$ {c:Targeted} can't attack or block until your next turn. | SpellDescription$ Up to one target creature can't attack or block until your next turn. +A:AB$ Draw | Cost$ AddCounter<0/LOYALTY> | NumCards$ 1 | Planeswalker$ True | SpellDescription$ Draw a card. +A:AB$ Token | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | TokenScript$ c_2_2_a_drone_deathtouch_leavedrain | SpellDescription$ Create a 2/2 colorless Drone artifact creature token with deathtouch and "When this creature leaves the battlefield, each opponent loses 2 life and you gain 2 life." +DeckHas:Ability$Token|LifeGain & Type$Artifact|Drone +Oracle:Whenever one or more creatures you control deal combat damage to a player, you may return one of them to its owner's hand. If you do, you may activate loyalty abilities of Kaito twice this turn rather than only once.\n[+1]: Up to one target creature can't attack or block until your next turn.\n[0]: Draw a card.\n[-2]: Create a 2/2 colorless Drone artifact creature token with deathtouch and "When this creature leaves the battlefield, each opponent loses 2 life and you gain 2 life." diff --git a/forge-gui/res/cardsfolder/upcoming/kethek_crucible_goliath.txt b/forge-gui/res/cardsfolder/upcoming/kethek_crucible_goliath.txt new file mode 100644 index 00000000000..ce0048c32a0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/kethek_crucible_goliath.txt @@ -0,0 +1,9 @@ +Name:Kethek, Crucible Goliath +ManaCost:2 R B +Types:Legendary Creature Phyrexian Beast +PT:4/4 +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | Execute$ TrigSac | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your end step, you may sacrifice another creature. If you do, reveal cards from the top of your library until you reveal a nonlegendary creature card with lesser mana value, put it onto the battlefield, then put the rest on the bottom of your library in a random order. +SVar:TrigSac:AB$ DigUntil | Cost$ Sac<1/Creature.Other/another creature> | Defined$ You | Valid$ Card.Creature+nonLegendary+cmcLTX | FoundDestination$ Battlefield | RevealedDestination$ Library | RestRandomOrder$ True +SVar:X:Sacrificed$CardManaCost +DeckHas:Ability$Sacrifice +Oracle:At the beginning of your end step, you may sacrifice another creature. If you do, reveal cards from the top of your library until you reveal a nonlegendary creature card with lesser mana value, put it onto the battlefield, then put the rest on the bottom of your library in a random order. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/maarika_brutal_gladiator.txt b/forge-gui/res/cardsfolder/upcoming/maarika_brutal_gladiator.txt new file mode 100644 index 00000000000..c858ccfc00e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/maarika_brutal_gladiator.txt @@ -0,0 +1,9 @@ +Name:Maarika, Brutal Gladiator +ManaCost:2 B R G +Types:Legendary Creature Human Warrior +PT:7/4 +K:CARDNAME must be blocked if able. +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Indestructible | Condition$ PlayerTurn | Description$ As long as it's your turn, NICKNAME has indestructible. +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Creature.wasDealtExcessDamageThisTurn | Execute$ TrigSac | TriggerDescription$ Whenever NICKNAME deals damage to a creature, if that creature was dealt excess damage this turn, that creature's controller sacrifices a noncreature, nonland permanent. +SVar:TrigSac:DB$ Sacrifice | SacValid$ Permanent.nonCreature+nonLand | SacMessage$ noncreature, nonland permanent | Defined$ TriggeredTargetController +Oracle:Maarika, Brutal Gladiator must be blocked if able.\nAs long as it's your turn, Maarika has indestructible.\nWhenever Maarika deals damage to a creature, if that creature was dealt excess damage this turn, that creature's controller sacrifices a noncreature, nonland permanent. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/migloz_maze_crusher.txt b/forge-gui/res/cardsfolder/upcoming/migloz_maze_crusher.txt new file mode 100644 index 00000000000..c0547163bfc --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/migloz_maze_crusher.txt @@ -0,0 +1,10 @@ +Name:Migloz, Maze Crusher +ManaCost:1 R G +Types:Legendary Creature Phyrexian Beast +PT:4/4 +K:etbCounter:OIL:5 +A:AB$ Pump | Cost$ 1 SubCounter<1/OIL/NICKNAME> | Defined$ Self | KW$ Vigilance & Menace | SpellDescription$ It gains vigilance and menace until end of turn. +A:AB$ Pump | Cost$ 2 SubCounter<2/OIL/NICKNAME> | Defined$ Self | NumAtt$ 2 | NumDef$ 2 | SpellDescription$ It gets +2/+2 until end of turn. +A:AB$ Destroy | Cost$ 3 SubCounter<3/OIL/NICKNAME> | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | SpellDescription$ Destroy target artifact or enchantment. +DeckHas:Ability$Counters +Oracle:Migloz, Maze Crusher enters the battlefield with five oil counters on it.\n{1}, Remove an oil counter from Migloz: It gains vigilance and menace until end of turn.\n{2}, Remove two oil counters from Migloz: It gets +2/+2 until end of turn.\n{3}, Remove three oil counters from Migloz: Destroy target artifact or enchantment. diff --git a/forge-gui/res/cardsfolder/upcoming/mindsplice_apparatus.txt b/forge-gui/res/cardsfolder/upcoming/mindsplice_apparatus.txt new file mode 100644 index 00000000000..a35114952b8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/mindsplice_apparatus.txt @@ -0,0 +1,11 @@ +Name:Mindsplice Apparatus +ManaCost:3 U +Types:Artifact +K:Flash +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigCounter | TriggerDescription$ At the beginning of your upkeep, put an oil counter on CARDNAME. +SVar:TrigCounter:DB$ PutCounter | Defined$ Self | CounterType$ OIL | CounterNum$ 1 +S:Mode$ ReduceCost | ValidCard$ Instant,Sorcery | Type$ Spell | Activator$ You | Amount$ X | Description$ Instant and sorcery spells you cast cost 1 less to cast for each oil counter on CARDNAME. +SVar:X:Count$CardCounters.OIL +DeckHas:Ability$Counters +DeckHints:Type$Instant|Sorcery +Oracle:Flash\nAt the beginning of your upkeep, put an oil counter on Mindsplice Apparatus.\nInstant and sorcery spells you cast cost 1 less to cast for each oil counter on Mindsplice Apparatus. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/quicksilver_fisher.txt b/forge-gui/res/cardsfolder/upcoming/quicksilver_fisher.txt new file mode 100644 index 00000000000..16b59503015 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/quicksilver_fisher.txt @@ -0,0 +1,10 @@ +Name:Quicksilver Fisher +ManaCost:3 U U +Types:Creature Phyrexian Drake +PT:4/3 +K:Flying +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw a card, then discard a card. +SVar:TrigDraw:DB$ Draw | SubAbility$ DBDiscard +SVar:DBDiscard:DB$ Discard | Mode$ TgtChoose +DeckHas:Ability$Discard +Oracle:Flying\nWhen Quicksilver Fisher enters the battlefield, draw a card, then discard a card. diff --git a/forge-gui/res/cardsfolder/upcoming/sawblade_scamp.txt b/forge-gui/res/cardsfolder/upcoming/sawblade_scamp.txt new file mode 100644 index 00000000000..2155df75bac --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sawblade_scamp.txt @@ -0,0 +1,11 @@ +Name:Sawblade Scamp +ManaCost:R +Types:Creature Phyrexian Beast +PT:1/1 +K:Haste +T:Mode$ SpellCast | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ You | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a noncreature spell, put an oil counter on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ OIL +A:AB$ DealDamage | Cost$ T SubCounter<1/OIL> | Defined$ Opponent | NumDmg$ 1 | SpellDescription$ It deals 1 damage to each opponent. +SVar:BuffedBy:Card.nonCreature +DeckHas:Ability$Counters +Oracle:Haste\nWhenever you cast a noncreature spell, put an oil counter on Sawblade Scamp.\n{T}, Remove an oil counter from Sawblade Scamp: It deals 1 damage to each opponent. diff --git a/forge-gui/res/cardsfolder/upcoming/sinew_dancer.txt b/forge-gui/res/cardsfolder/upcoming/sinew_dancer.txt new file mode 100644 index 00000000000..c266a042198 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sinew_dancer.txt @@ -0,0 +1,9 @@ +Name:Sinew Dancer +ManaCost:W +Types:Creature Phyrexian Soldier +PT:1/1 +A:AB$ Tap | Cost$ 3 W T | ValidTgts$ Creature | SpellDescription$ Tap target creature. +A:AB$ Tap | PrecostDesc$ Corrupted — | Cost$ W T | ValidTgts$ Creature | CheckSVar$ X | SVarCompare$ GE3 | SpellDescription$ Tap target creature. Activate only if an opponent has three or more poison counters. +SVar:X:PlayerCountOpponents$HighestPoisonCounters +DeckHints:Ability$Counters +Oracle:{3}{W}, {T}: Tap target creature.\nCorrupted - {W}, {T}: Tap target creature. Activate only if an opponent has three or more poison counters. diff --git a/forge-gui/res/cardsfolder/upcoming/tadeas_juniper_ascendant.txt b/forge-gui/res/cardsfolder/upcoming/tadeas_juniper_ascendant.txt new file mode 100644 index 00000000000..f64cb7651ec --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/tadeas_juniper_ascendant.txt @@ -0,0 +1,15 @@ +Name:Tadeas, Juniper Ascendant +ManaCost:2 G W +Types:Legendary Creature Human Monk +PT:1/3 +K:Reach +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Hexproof | IsPresent$ Card.Self+notattacking | Description$ Teleport — CARDNAME has hexproof unless he's attacking. +T:Mode$ Attacks | ValidCard$ Creature.YouCtrl+withReach | TriggerZones$ Battlefield | Execute$ TrigUntap | TriggerDescription$ Whenever a creature you control with reach attacks, untap it and it can't be blocked by creatures with greater power this combat. +SVar:TrigUntap:DB$ Untap | Defined$ TriggeredAttackerLKICopy | SubAbility$ DBEffect +SVar:DBEffect:DB$ Effect | RememberObjects$ TriggeredAttacker | StaticAbilities$ CantBeBlockedPow | ForgetOnMoved$ Battlefield | Duration$ UntilEndOfCombat +SVar:CantBeBlockedPow:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | ValidBlocker$ Creature.powerGTX | Description$ CARDNAME can't be blocked by creatures with greater power this combat. +SVar:X:Remembered$CardPower +T:Mode$ DamageDoneOnce | CombatDamage$ True | ValidSource$ Creature.YouCtrl | TriggerZones$ Battlefield | ValidTarget$ Player | Execute$ TrigDraw | TriggerDescription$ Whenever one or more creatures you control deal combat damage to a player, draw a card. +SVar:TrigDraw:DB$ Draw +DeckHints:Keyword$Reach +Oracle:Reach\nTadeas, Juniper Ascendant has hexproof unless he's attacking.\nWhenever a creature you control with reach attacks, untap it and it can't be blocked by creatures with greater power this combat.\nWhenever one or more creatures you control deal combat damage to a player, draw a card. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/the_howling_abomination.txt b/forge-gui/res/cardsfolder/upcoming/the_howling_abomination.txt new file mode 100644 index 00000000000..4c6510b0124 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_howling_abomination.txt @@ -0,0 +1,11 @@ +Name:The Howling Abomination +ManaCost:3 R G +Types:Legendary Creature Human Beast Warrior +PT:5/5 +K:Haste +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Trample | CheckSVar$ X | SVarCompare$ GE3 | Description$ CARDNAME has trample as long as you've cast three or more spells this turn. +T:Mode$ BecomesTarget | ValidTarget$ Card.Self | ValidSource$ Spell | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever NICKNAME becomes the target of a spell, he gets +2/+2 until end of turn and deals 2 damage to each opponent. +SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ +2 | NumDef$ +2 | SubAbility$ DBDmg +SVar:DBDmg:DB$ DealDamage | Defined$ Opponent | NumDmg$ 2 +SVar:X:Count$ThisTurnCast_Card.YouCtrl +Oracle:Haste\nThe Howling Abomination has trample as long as you've cast three or more spells this turn.\nWhenever The Howling Abomination becomes the target of a spell, he gets +2/+2 until end of turn and deals 2 damage to each opponent. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/vikya_scorching_stalwart.txt b/forge-gui/res/cardsfolder/upcoming/vikya_scorching_stalwart.txt new file mode 100644 index 00000000000..ff8e82c921e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/vikya_scorching_stalwart.txt @@ -0,0 +1,10 @@ +Name:Vikya, Scorching Stalwart +ManaCost:2 W +Types:Legendary Creature Human Warrior +PT:2/4 +K:Training +A:AB$ DealDamage | Cost$ 4 R Untap Discard<1/Card> | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | AILogic$ PowerDmg | ExcessSVar$ Excess | SubAbility$ DBDraw | SpellDescription$ CARDNAME deals damage equal to his power to any target. +SVar:DBDraw:DB$ Draw | ConditionCheckSVar$ Excess | ConditionSVarCompare$ GE1 | ConditionDefined$ Targeted | ConditionPresent$ Creature | IfDesc$ True | SpellDescription$ If excess damage was dealt to a creature this way, draw a card. ({Q} is the untap symbol.) +SVar:X:Count$CardPower +DeckHas:Ability$Counters|Discard +Oracle:Training (Whenever this creature attacks with another creature with greater power, put a +1/+1 counter on this creature.)\n{4}{R}, {Q}, Discard a card: Vikya, Scorching Stalwart deals damage equal to his power to any target. If excess damage was dealt to a creature this way, draw a card. ({Q} is the untap symbol.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/yotian_courier.txt b/forge-gui/res/cardsfolder/upcoming/yotian_courier.txt new file mode 100644 index 00000000000..43b466cfacb --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/yotian_courier.txt @@ -0,0 +1,14 @@ +Name:Yotian Courier +ManaCost:U R +Types:Creature Human Artificer +PT:2/2 +K:Flying +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ Whenever CARDNAME attacks, ABILITY +SVar:TrigCharm:DB$ Charm | Choices$ Powerstone,Seek | ChoiceRestriction$ YourLastCombat +SVar:Powerstone:DB$ Token | TokenTapped$ True | TokenScript$ c_a_powerstone | SpellDescription$ Create a tapped Powerstone token. +SVar:Seek:DB$ ChangeZone | Origin$ Library | Destination$ Hand | AtRandom$ True | NoShuffle$ True | NoLooking$ True | NoReveal$ True | ChangeNum$ 1 | ChangeType$ Artifact.cmcEQX | SpellDescription$ Seek an artifact card with mana value equal to the number of Powerstones you control. +SVar:X:Count$Valid Powerstone.YouCtrl +DeckNeeds:Type$Artifact +DeckHas:Ability$Token & Type$Artifact +SVar:HasAttackEffect:TRUE +Oracle:Flying\nWhenever Yotian Courier attacks, choose one that wasn't chosen during your last combat —\n• Create a tapped Powerstone token.\n• Seek an artifact card with mana value equal to the number of Powerstones you control. diff --git a/forge-gui/res/cardsfolder/upcoming/zenith_chronicler.txt b/forge-gui/res/cardsfolder/upcoming/zenith_chronicler.txt new file mode 100644 index 00000000000..1c71f96b130 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/zenith_chronicler.txt @@ -0,0 +1,7 @@ +Name:Zenith Chronicler +ManaCost:2 +Types:Artifact Creature Phyrexian Construct +PT:3/1 +T:Mode$ SpellCast | ValidCard$ Card.MultiColor | ValidActivatingPlayer$ Player | TriggerZones$ Battlefield | ActivatorThisTurnCast$ EQ1 | NoResolvingCheck$ True | Execute$ TrigDraw | TriggerDescription$ Whenever a player casts their first multicolored spell each turn, each other player draws a card. +SVar:TrigDraw:DB$ Draw | Defined$ NonTriggeredCardController +Oracle:Whenever a player casts their first multicolored spell each turn, each other player draws a card. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/zethi_arcane_blademaster.txt b/forge-gui/res/cardsfolder/upcoming/zethi_arcane_blademaster.txt new file mode 100644 index 00000000000..b62184e1ec8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/zethi_arcane_blademaster.txt @@ -0,0 +1,13 @@ +Name:Zethi, Arcane Blademaster +ManaCost:1 W U +Types:Legendary Creature Human Soldier +PT:3/3 +K:Multikicker:WU +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When NICKNAME enters the battlefield, exile up to X target instant cards from your graveyard, where X is the number of times NICKNAME was kicked. Put a kick counter on each of them. +SVar:TrigExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | WithCountersType$ KICK | ValidTgts$ Instant.YouOwn | TargetMin$ 0 | TargetMax$ XKicked | TgtPrompt$ Select up to X target instant cards +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPlay | TriggerZones$ Battlefield | TriggerDescription$ Whenever NICKNAME attacks, copy each exiled card you own with a kick counter on it. You may cast the copies. +SVar:TrigPlay:DB$ Play | Valid$ Card.counters_GE1_KICK | ValidSA$ Spell | ValidZone$ Exile | Amount$ All | CopyCard$ True | Optional$ True +SVar:XKicked:Count$TimesKicked +DeckHints:Type$Instant & Ability$Discard +DeckHas:Ability$Graveyard +Oracle:Multikicker {W/U}\nWhen Zethi enters the battlefield, exile up to X target instant cards from your graveyard, where X is the number of times Zethi was kicked. Put a kick counter on each of them.\nWhenever Zethi attacks, copy each exiled card you own with a kick counter on it. You may cast the copies. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/v/volcanic_offering.txt b/forge-gui/res/cardsfolder/v/volcanic_offering.txt index 7e172d0382c..ca101db90fa 100644 --- a/forge-gui/res/cardsfolder/v/volcanic_offering.txt +++ b/forge-gui/res/cardsfolder/v/volcanic_offering.txt @@ -3,7 +3,7 @@ ManaCost:4 R Types:Instant A:SP$ Pump | Cost$ 4 R | ValidTgts$ Land.nonBasic+YouDontCtrl | TgtPrompt$ Select target nonbasic land you don't control | AILogic$ Destroy | IsCurse$ True | RememberTargets$ True | SubAbility$ DBDestroyLand | SpellDescription$ Destroy target nonbasic land you don't control and target nonbasic land of an opponent's choice you don't control. SVar:DBDestroyLand:DB$ Pump | TargetingPlayer$ Player.Opponent | ValidTgts$ Land.nonBasic+YouDontCtrl | TgtPrompt$ Select target nonbasic land the caster of this spell don't control | AILogic$ Destroy | IsCurse$ True | RememberTargets$ True | SubAbility$ DBDestroy -SVar:DBDestroy:DB$ Destroy | Defined$ Remembered | SubAbility$ DBDamage +SVar:DBDestroy:DB$ Destroy | Defined$ Remembered | AILogic$ Always | SubAbility$ DBDamage SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature you don't control | NumDmg$ 7 | DamageMap$ True | SubAbility$ DBDamage2 | SpellDescription$ CARDNAME deals 7 damage to target creature you don't control and 7 damage to target creature of an opponent's choice you don't control. SVar:DBDamage2:DB$ DealDamage | TargetingPlayer$ Player.Opponent | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature the caster of this spell don't control | NumDmg$ 7 | SubAbility$ DBDamageResolve SVar:DBDamageResolve:DB$ DamageResolve | SubAbility$ DBCleanup diff --git a/forge-gui/res/cardsfolder/w/wand_of_denial.txt b/forge-gui/res/cardsfolder/w/wand_of_denial.txt index c8eedd4405f..fddb44c5951 100644 --- a/forge-gui/res/cardsfolder/w/wand_of_denial.txt +++ b/forge-gui/res/cardsfolder/w/wand_of_denial.txt @@ -2,7 +2,7 @@ Name:Wand of Denial ManaCost:2 Types:Artifact A:AB$ PeekAndReveal | Cost$ T | ValidTgts$ Player | NoReveal$ True | RememberPeeked$ True | SubAbility$ DBChangeZone | StackDescription$ SpellDescription | SpellDescription$ Look at the top card of target player's library. If it's a nonland card, you may pay 2 life. If you do, put it into that player's graveyard. -SVar:DBChangeZone:DB$ Dig | Defined$ Targeted | DestinationZone$ Graveyard | ConditionDefined$ Remembered | ConditionPresent$ Card.nonLand | ConditionCompare$ GE1 | UnlessPayer$ You | UnlessCost$ PayLife<2> | UnlessSwitched$ True | StackDescription$ None | SubAbility$ DBCleanup +SVar:DBChangeZone:DB$ Dig | Defined$ Targeted | DestinationZone$ Graveyard | DigNum$ 1 | ChangeNum$ All | ConditionDefined$ Remembered | ConditionPresent$ Card.nonLand | ConditionCompare$ GE1 | UnlessPayer$ You | UnlessCost$ PayLife<2> | UnlessSwitched$ True | StackDescription$ None | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:All Oracle:{T}: Look at the top card of target player's library. If it's a nonland card, you may pay 2 life. If you do, put it into that player's graveyard. diff --git a/forge-gui/res/cardsfolder/w/weathered_runestone.txt b/forge-gui/res/cardsfolder/w/weathered_runestone.txt index 824a1b4f92e..68f39c73052 100644 --- a/forge-gui/res/cardsfolder/w/weathered_runestone.txt +++ b/forge-gui/res/cardsfolder/w/weathered_runestone.txt @@ -1,7 +1,7 @@ Name:Weathered Runestone ManaCost:2 Types:Artifact -R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard,Library | Destination$ Battlefield | ValidLKI$ Permanent.nonland | Prevent$ True | Description$ Nonland permanent cards in graveyards and libraries can't enter the battlefield. +R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard,Library | Destination$ Battlefield | ValidLKI$ Permanent.nonland | Prevent$ True | Layer$ CantHappen | Description$ Nonland permanent cards in graveyards and libraries can't enter the battlefield. S:Mode$ CantBeCast | Origin$ Graveyard,Library | Description$ Players can't cast spells from graveyards or libraries. SVar:NonStackingEffect:True AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/w/wicked_guardian.txt b/forge-gui/res/cardsfolder/w/wicked_guardian.txt index e8655d8b8d2..eb81b02d470 100644 --- a/forge-gui/res/cardsfolder/w/wicked_guardian.txt +++ b/forge-gui/res/cardsfolder/w/wicked_guardian.txt @@ -2,7 +2,9 @@ Name:Wicked Guardian ManaCost:3 B Types:Creature Human Noble PT:4/2 -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDamage | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may have it deal 2 damage to another creature you control. If you do, draw a card. -SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature.Other+YouCtrl | TgtPrompt$ Select another creature you control | NumDmg$ 2 | SubAbility$ DBDraw -SVar:DBDraw:DB$ Draw | NumCards$ 1 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChoose | TriggerDescription$ When CARDNAME enters the battlefield, you may have it deal 2 damage to another creature you control. If you do, draw a card. +SVar:TrigChoose:DB$ ChooseCard | Choices$ Creature.YouCtrl+Other | ChoiceZone$ Battlefield | ChoiceTitle$ Select another creature you control | SubAbility$ DBDamageChosen +SVar:DBDamageChosen:DB$ DealDamage | Defined$ ChosenCard | NumDmg$ 2 | SubAbility$ DBDraw +SVar:DBDraw:DB$ Draw | NumCards$ 1 | ConditionDefined$ ChosenCard | ConditionPresent$ Card | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True Oracle:When Wicked Guardian enters the battlefield, you may have it deal 2 damage to another creature you control. If you do, draw a card. diff --git a/forge-gui/res/cardsfolder/w/worms_of_the_earth.txt b/forge-gui/res/cardsfolder/w/worms_of_the_earth.txt index b758fbfa3d0..b1432871eaa 100644 --- a/forge-gui/res/cardsfolder/w/worms_of_the_earth.txt +++ b/forge-gui/res/cardsfolder/w/worms_of_the_earth.txt @@ -2,7 +2,7 @@ Name:Worms of the Earth ManaCost:2 B B B Types:Enchantment S:Mode$ CantPlayLand | Description$ Players can't play lands. -R:Event$ Moved | ActiveZones$ Battlefield | Destination$ Battlefield | ValidCard$ Land | Prevent$ True | Description$ Lands can't enter the battlefield. +R:Event$ Moved | ActiveZones$ Battlefield | Destination$ Battlefield | ValidCard$ Land | Prevent$ True | Layer$ CantHappen | Description$ Lands can't enter the battlefield. T:Mode$ Phase | Phase$ Upkeep | TriggerZones$ Battlefield | Execute$ RepeatAbility | TriggerDescription$ At the beginning of each upkeep, any player may sacrifice two lands or have CARDNAME deal 5 damage to that player. If a player does either, destroy CARDNAME. SVar:RepeatAbility:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBChoose SVar:DBChoose:DB$ GenericChoice | Defined$ Player.IsRemembered | Choices$ SacTwoLands,DealDmg | AILogic$ PayUnlessCost diff --git a/forge-gui/res/cardsfolder/z/zurs_weirding.txt b/forge-gui/res/cardsfolder/z/zurs_weirding.txt index 14018603d6d..9b13cca8e4f 100644 --- a/forge-gui/res/cardsfolder/z/zurs_weirding.txt +++ b/forge-gui/res/cardsfolder/z/zurs_weirding.txt @@ -4,7 +4,7 @@ Types:Enchantment S:Mode$ Continuous | AffectedZone$ Hand | MayLookAt$ Player | Description$ Players play with their hands revealed. R:Event$ Draw | ActiveZones$ Battlefield | ValidPlayer$ Player | ReplaceWith$ RevealTop | Description$ If a player would draw a card, they reveal it instead. Then any other player may pay 2 life. If a player does, put that card into its owner's graveyard. Otherwise, that player draws a card. SVar:RevealTop:DB$ PeekAndReveal | Defined$ ReplacedPlayer | NoPeek$ True | SubAbility$ DBMill -SVar:DBMill:DB$ Dig | Defined$ ReplacedPlayer | DestinationZone$ Graveyard | SubAbility$ DBDraw | UnlessPayer$ NonReplacedPlayer | UnlessCost$ PayLife<2> | UnlessSwitched$ True | UnlessResolveSubs$ WhenNotPaid | StackDescription$ None +SVar:DBMill:DB$ Dig | Defined$ ReplacedPlayer | DestinationZone$ Graveyard | DigNum$ 1 | ChangeNum$ All | SubAbility$ DBDraw | UnlessPayer$ NonReplacedPlayer | UnlessCost$ PayLife<2> | UnlessSwitched$ True | UnlessResolveSubs$ WhenNotPaid | StackDescription$ None SVar:DBDraw:DB$ Draw | Defined$ ReplacedPlayer | NumCards$ 1 | StackDescription$ that player draws a card SVar:NonStackingEffect:True AI:RemoveDeck:All diff --git a/forge-gui/res/editions/2021 Heroes of the Realm.txt b/forge-gui/res/editions/2021 Heroes of the Realm.txt index 95f6cfe360d..a3ed44fe505 100644 --- a/forge-gui/res/editions/2021 Heroes of the Realm.txt +++ b/forge-gui/res/editions/2021 Heroes of the Realm.txt @@ -7,5 +7,6 @@ ScryfallCode=PH21 [cards] 1 M Andrios, Roaming Explorer @Borja Pindado +2 M Arteeoh, Dread Scavenger @Zoltan Boros 3 M Byode, Inverse Sun @Dominik Mayer 4 M Ersta, Friend to All @Winona Nelson diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 2498c683a38..2df3a6a9062 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -585,12 +585,15 @@ ScryfallCode=SLD 608 R Darksteel Citadel @DXTR 609 R Hawkins National Laboratory @Ravenna Tran 610 R Bonescythe Sliver @Trevor Claxton +611 R Constricting Sliver @Karl Kopinski 612 R Essence Sliver @Glen Angus 613 R Pulmonic Sliver @Jeff Easley +615 R Sidewinder Sliver @Ron Spencer 617 R Ward Sliver @Pete Venters 618 R Diffusion Sliver @Trevor Claxton 619 R Galerider Sliver @James Zapata 620 R Mesmeric Sliver @Michael Bruinsma +623 R Scuttling Sliver @Mike Bierek 624 R Shadow Sliver @Warren Mahy 626 R Synapse Sliver @Thomas M. Baxa 627 R Telekinetic Sliver @Randy Elliott @@ -602,6 +605,8 @@ ScryfallCode=SLD 634 R Syphon Sliver @Tyler Jacobson 635 R Toxin Sliver @Lars Grant-West 636 R Belligerent Sliver @Raymond Swanland +637 R Blur Sliver @Daarken +638 R Fury Sliver @Paolo Parente 640 R Homing Sliver @Trevor Hairsine 641 R Magma Sliver @Wayne England 643 R Spiteful Sliver @Johann Bodin @@ -612,10 +617,12 @@ ScryfallCode=SLD 648 R Gemhide Sliver @John Matson 649 R Horned Sliver @Allen Williams 650 R Manaweft Sliver @Trevor Claxton +651 R Megantic Sliver @Ryan Barger 652 R Might Sliver @Jeff Miracola 653 R Muscle Sliver @Richard Kane Ferguson 654 R Predatory Sliver @Mathias Kollros 655 R Quick Sliver @John Avon +656 R Root Sliver @Matt Thompson 657 R Tempered Sliver @Mitchell Malloy 659 R Virulent Sliver @Franz Vohwinkel 660 R Cloudshredder Sliver @Filip Burburan @@ -641,6 +648,7 @@ ScryfallCode=SLD 682 R Shadowborn Apostle @Diego Andrade 683 R Shadowborn Apostle @Jakub Rebelka 684 R Shadowborn Apostle @Laynes +687 R Shadowborn Apostle @Roman Klonek 690 R Forest @Magali Villeneuve 691 R Hedron Archive @Cosmin Podar 692 R Pilgrim's Eye @Cosmin Podar @@ -659,10 +667,13 @@ ScryfallCode=SLD 707 R Knight Exemplar @Victor Adame Minguez 708 R Fellwar Stone @Dan Frazier 709 R Dragon's Hoard @Pedro Potier +710 R Command Tower @Evan Shipard 711 R Tireless Tracker @Nils Hamm 718 R Maro @Jesper Ejsing 719 R Maro @Mark Rosewater +720 R Thought-Knot Seer @Aleksi Briclot 721 R Diabolic Tutor @Brain Dead +724 R Lightning Strike @Frank Frazetta 726 R Zur the Enchanter @Chase Stone 727 R Fabled Passage @Warren Mahy 900 R The Scarab God @Barely Human diff --git a/forge-gui/res/formats/Archived/Legacy/2023-01-13.txt b/forge-gui/res/formats/Archived/Legacy/2023-01-13.txt new file mode 100644 index 00000000000..333b95ab4fc --- /dev/null +++ b/forge-gui/res/formats/Archived/Legacy/2023-01-13.txt @@ -0,0 +1,8 @@ +[format] +Name:Legacy (DMR) +Type:Archived +Subtype:Legacy +Effective:2023-01-13 +Sets:LEA, LEB, 2ED, ARN, ATQ, 3ED, LEG, DRC94, DRK, PHPR, FEM, 4ED, ICE, CHR, HML, ALL, MIR, VIS, 5ED, POR, WTH, TMP, STH, EXO, PO2, USG, ATH, ULG, 6ED, UDS, S99, PTK, MMQ, BRB, NMS, S00, PCY, BTD, INV, PLS, 7ED, APC, ODY, DKM, TOR, JUD, ONS, LGN, SCG, 8ED, MRD, DST, 5DN, CHK, BOK, SOK, 9ED, RAV, GPT, DIS, CSP, TSP, TSB, PLC, FUT, 10E, MED, LRW, DD1, MOR, SHM, EVE, DRB, ME2, ALA, DD2, CFX, DDC, ARB, M10, TD0, FVE, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, M13, V12, DDJ, RTR, CM1, GTC, DDK, DGM, MMA, M14, V13, DDL, THS, C13, BNG, DDM, JOU, MD1, CNS, VMA, M15, V14, DDN, KTK, C14, DVD, EVG, GVL, JVC, FRF, UGF, DDO, DTK, TPR, MM2, ORI, V15, DDP, BFZ, EXP, C15, PZ1, OGW, DDQ, SOI, W16, EMA, EMN, V16, CN2, DDR, KLD, MPS_KLD, C16, PZ2, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, HOU, C17, XLN, DDT, IMA, V17, RIX, A25, DDU, DOM, CM2, BBD, SS1, GS1, M19, C18, GRN, MPS_GRN, GK1, G18, GNT, UMA, RNA, MPS_RNA, GK2, WAR, MPS_WAR, MH1, SS2, M20, C19, ELD, MB1, GN2, THB, IKO, C20, SS3, M21, JMP, 2XM, ZNR, ZNE, ZNC, CMR, CC1, KHM, KHC, TSR, STX, STA, C21, MH2, H1R, AFR, AFC, MID, MIC, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR +Banned:Advantageous Proclamation; Adriana's Valor; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Backup Plan; Balance; Bazaar of Baghdad; Black Lotus; Brago's Favor; Bronze Tablet; Channel; Chaos Orb; Cleanse; Contract from Below; Crusade; Darkpact; Deathrite Shaman; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Double Stroke; Dreadhorde Arcanist; Earthcraft; Echoing Boon; Emissary's Ploy; Falling Star; Fastbond; Flash; Frantic Search; Gitaxian Probe; Goblin Recruiter; Gush; Hermit Druid; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imperial Seal; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Library of Alexandria; Lurrus of the Dream-Den; Mana Crypt; Mana Drain; Mana Vault; Memory Jar; Mental Misstep; Mind Twist; Mind's Desire; Mishra's Workshop; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Mystical Tutor; Natural Unity; Necropotence; Oath of Druids; Oko, Thief of Crowns; Power Play; Pradesh Gypsies; Ragavan, Nimble Pilferer; Rebirth; Secret Summoning; Secrets of Paradise; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Sovereign's Realm; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Tempest Efreet; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Underworld Breach; Unexpected Potential; Vampiric Tutor; Weight Advantage; Wheel of Fortune; Windfall; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker +Additional:Arvinox, the Mind Flail; Blanka, Ferocious Friend; Bjorna, Nightfall Alchemist; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Dhalsim, Pliable Pacifist; Dustin, Gadget Genius; E. Honda, Sumo Champion; Eleven, the Mage; Elmar, Ulvenwald Informant; Glenn, the Voice of Calm; Guile, Sonic Soldier; Hargilde, Kindly Runechanter; Havengul Laboratory; Ken, Burning Brawler; Lucas, the Sharpshooter; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Mind Flayer, the Shadow; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rick, Steadfast Leader; Ryu, World Warrior; Sophina, Spearsage Deserter; Wernog, Rider's Chaplain; Will the Wise; Zangief, the Red Cyclone diff --git a/forge-gui/res/formats/Archived/Vintage/2023-01-13.txt b/forge-gui/res/formats/Archived/Vintage/2023-01-13.txt new file mode 100644 index 00000000000..343a0caeff6 --- /dev/null +++ b/forge-gui/res/formats/Archived/Vintage/2023-01-13.txt @@ -0,0 +1,9 @@ +[format] +Name:Vintage (DMR) +Type:Archived +Subtype:Vintage +Effective:2023-01-13 +Sets:LEA, LEB, 2ED, ARN, ATQ, 3ED, LEG, DRC94, DRK, PHPR, FEM, 4ED, ICE, CHR, HML, ALL, MIR, VIS, 5ED, POR, WTH, TMP, STH, EXO, PO2, USG, ATH, ULG, 6ED, UDS, S99, PTK, MMQ, BRB, NMS, S00, PCY, BTD, INV, PLS, 7ED, APC, ODY, DKM, TOR, JUD, ONS, LGN, SCG, 8ED, MRD, DST, 5DN, CHK, BOK, SOK, 9ED, RAV, GPT, DIS, CSP, TSP, TSB, PLC, FUT, 10E, MED, LRW, DD1, MOR, SHM, EVE, DRB, ME2, ALA, DD2, CFX, DDC, ARB, M10, TD0, FVE, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, M13, V12, DDJ, RTR, CM1, GTC, DDK, DGM, MMA, M14, V13, DDL, THS, C13, BNG, DDM, JOU, MD1, CNS, VMA, M15, V14, DDN, KTK, C14, DVD, EVG, GVL, JVC, FRF, UGF, DDO, DTK, TPR, MM2, ORI, V15, DDP, BFZ, EXP, C15, PZ1, OGW, DDQ, SOI, W16, EMA, EMN, V16, CN2, DDR, KLD, MPS_KLD, C16, PZ2, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, HOU, C17, XLN, DDT, IMA, V17, RIX, A25, DDU, DOM, CM2, BBD, SS1, GS1, M19, C18, GRN, MPS_GRN, GK1, G18, GNT, UMA, RNA, MPS_RNA, GK2, WAR, MPS_WAR, MH1, SS2, M20, C19, ELD, MB1, GN2, THB, IKO, C20, SS3, M21, JMP, 2XM, ZNR, ZNE, ZNC, CMR, CC1, KHM, KHC, TSR, STX, STA, C21, MH2, H1R, AFR, AFC, MID, MIC, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR +Restricted:Ancestral Recall; Balance; Black Lotus; Brainstorm; Chalice of the Void; Channel; Demonic Consultation; Demonic Tutor; Dig Through Time; Flash; Gitaxian Probe; Golgari Grave-Troll; Gush; Imperial Seal; Karn, the Great Creator; Library of Alexandria; Lion's Eye Diamond; Lodestone Golem; Lotus Petal; Mana Crypt; Mana Vault; Memory Jar; Mental Misstep; Merchant Scroll; Mind's Desire; Monastery Mentor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Mystic Forge; Mystical Tutor; Narset, Parter of Veils; Necropotence; Ponder; Sol Ring; Strip Mine; Thorn of Amethyst; Time Vault; Time Walk; Timetwister; Tinker; Tolarian Academy; Treasure Cruise; Trinisphere; Vampiric Tutor; Wheel of Fortune; Windfall; Yawgmoth's Will +Banned:Adriana's Valor; Advantageous Proclamation; Amulet of Quoz; Assemble the Rank and Vile; Backup Plan; Brago's Favor; Bronze Tablet; Chaos Orb; Cleanse; Contract from Below; Crusade; Darkpact; Demonic Attorney; Double Stroke; Echoing Boon; Emissary's Ploy; Falling Star; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Muzzio's Preparations; Natural Unity; Power Play; Pradesh Gypsies; Rebirth; Secret Summoning; Secrets of Paradise; Sentinel Dispatch; Shahrazad; Sovereign's Realm; Stone-Throwing Devils; Summoner's Bond; Tempest Efreet; Timmerian Fiends; Unexpected Potential; Weight Advantage; Worldknit +Additional:Arvinox, the Mind Flail; Blanka, Ferocious Friend; Bjorna, Nightfall Alchemist; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Dhalsim, Pliable Pacifist; Dustin, Gadget Genius; E. Honda, Sumo Champion; Eleven, the Mage; Elmar, Ulvenwald Informant; Glenn, the Voice of Calm; Guile, Sonic Soldier; Hargilde, Kindly Runechanter; Havengul Laboratory; Ken, Burning Brawler; Lucas, the Sharpshooter; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Mind Flayer, the Shadow; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rick, Steadfast Leader; Ryu, World Warrior; Sophina, Spearsage Deserter; Wernog, Rider's Chaplain; Will the Wise; Zangief, the Red Cyclone diff --git a/forge-gui/res/formats/Sanctioned/Legacy.txt b/forge-gui/res/formats/Sanctioned/Legacy.txt index d4417bd86d0..d4bb3870224 100644 --- a/forge-gui/res/formats/Sanctioned/Legacy.txt +++ b/forge-gui/res/formats/Sanctioned/Legacy.txt @@ -3,5 +3,5 @@ Name:Legacy Order:105 Subtype:Legacy Type:Sanctioned -Sets:LEA, LEB, 2ED, ARN, ATQ, 3ED, LEG, DRC94, DRK, PHPR, FEM, 4ED, ICE, CHR, HML, ALL, MIR, VIS, 5ED, POR, WTH, TMP, STH, EXO, PO2, USG, ATH, ULG, 6ED, UDS, S99, PTK, MMQ, BRB, NMS, S00, PCY, BTD, INV, PLS, 7ED, APC, ODY, DKM, TOR, JUD, ONS, LGN, SCG, 8ED, MRD, DST, 5DN, CHK, BOK, SOK, 9ED, RAV, GPT, DIS, CSP, TSP, TSB, PLC, FUT, 10E, MED, LRW, DD1, MOR, SHM, EVE, DRB, ME2, ALA, DD2, CFX, DDC, ARB, M10, TD0, FVE, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, M13, V12, DDJ, RTR, CM1, GTC, DDK, DGM, MMA, M14, V13, DDL, THS, C13, BNG, DDM, JOU, MD1, CNS, VMA, M15, V14, DDN, KTK, C14, DVD, EVG, GVL, JVC, FRF, UGF, DDO, DTK, TPR, MM2, ORI, V15, DDP, BFZ, EXP, C15, PZ1, OGW, DDQ, SOI, W16, EMA, EMN, V16, CN2, DDR, KLD, MPS_KLD, C16, PZ2, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, HOU, C17, XLN, DDT, IMA, V17, RIX, A25, DDU, DOM, CM2, BBD, SS1, GS1, M19, C18, GRN, MPS_GRN, GK1, G18, GNT, UMA, RNA, MPS_RNA, GK2, WAR, MPS_WAR, MH1, SS2, M20, C19, ELD, MB1, GN2, SLD, THB, IKO, C20, SS3, M21, JMP, 2XM, ZNR, ZNE, ZNC, CMR, CC1, KHM, KHC, TSR, STX, STA, C21, MH2, H1R, AFR, AFC, MID, MIC, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, SLX, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD +Sets:LEA, LEB, 2ED, ARN, ATQ, 3ED, LEG, DRC94, DRK, PHPR, FEM, 4ED, ICE, CHR, HML, ALL, MIR, VIS, 5ED, POR, WTH, TMP, STH, EXO, PO2, USG, ATH, ULG, 6ED, UDS, S99, PTK, MMQ, BRB, NMS, S00, PCY, BTD, INV, PLS, 7ED, APC, ODY, DKM, TOR, JUD, ONS, LGN, SCG, 8ED, MRD, DST, 5DN, CHK, BOK, SOK, 9ED, RAV, GPT, DIS, CSP, TSP, TSB, PLC, FUT, 10E, MED, LRW, DD1, MOR, SHM, EVE, DRB, ME2, ALA, DD2, CFX, DDC, ARB, M10, TD0, FVE, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, M13, V12, DDJ, RTR, CM1, GTC, DDK, DGM, MMA, M14, V13, DDL, THS, C13, BNG, DDM, JOU, MD1, CNS, VMA, M15, V14, DDN, KTK, C14, DVD, EVG, GVL, JVC, FRF, UGF, DDO, DTK, TPR, MM2, ORI, V15, DDP, BFZ, EXP, C15, PZ1, OGW, DDQ, SOI, W16, EMA, EMN, V16, CN2, DDR, KLD, MPS_KLD, C16, PZ2, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, HOU, C17, XLN, DDT, IMA, V17, RIX, A25, DDU, DOM, CM2, BBD, SS1, GS1, M19, C18, GRN, MPS_GRN, GK1, G18, GNT, UMA, RNA, MPS_RNA, GK2, WAR, MPS_WAR, MH1, SS2, M20, C19, ELD, MB1, GN2, SLD, THB, IKO, C20, SS3, M21, JMP, 2XM, ZNR, ZNE, ZNC, CMR, CC1, KHM, KHC, TSR, STX, STA, C21, MH2, H1R, AFR, AFC, MID, MIC, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, SLX, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR Banned:Advantageous Proclamation; Adriana's Valor; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Backup Plan; Balance; Bazaar of Baghdad; Black Lotus; Brago's Favor; Bronze Tablet; Channel; Chaos Orb; Cleanse; Contract from Below; Crusade; Darkpact; Deathrite Shaman; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Double Stroke; Dreadhorde Arcanist; Earthcraft; Echoing Boon; Emissary's Ploy; Falling Star; Fastbond; Flash; Frantic Search; Gitaxian Probe; Goblin Recruiter; Gush; Hermit Druid; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imperial Seal; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Library of Alexandria; Lurrus of the Dream-Den; Mana Crypt; Mana Drain; Mana Vault; Memory Jar; Mental Misstep; Mind Twist; Mind's Desire; Mishra's Workshop; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Mystical Tutor; Natural Unity; Necropotence; Oath of Druids; Oko, Thief of Crowns; Power Play; Pradesh Gypsies; Ragavan, Nimble Pilferer; Rebirth; Secret Summoning; Secrets of Paradise; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Sovereign's Realm; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Tempest Efreet; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Underworld Breach; Unexpected Potential; Vampiric Tutor; Weight Advantage; Wheel of Fortune; Windfall; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker diff --git a/forge-gui/res/formats/Sanctioned/Vintage.txt b/forge-gui/res/formats/Sanctioned/Vintage.txt index 2c9ffdb5309..718514b02d3 100644 --- a/forge-gui/res/formats/Sanctioned/Vintage.txt +++ b/forge-gui/res/formats/Sanctioned/Vintage.txt @@ -3,6 +3,6 @@ Name:Vintage Order:104 Subtype:Vintage Type:Sanctioned -Sets:LEA, LEB, 2ED, ARN, ATQ, 3ED, LEG, DRC94, DRK, PHPR, FEM, 4ED, ICE, CHR, HML, ALL, MIR, VIS, 5ED, POR, WTH, TMP, STH, EXO, PO2, USG, ATH, ULG, 6ED, UDS, S99, PTK, MMQ, BRB, NMS, S00, PCY, BTD, INV, PLS, 7ED, APC, ODY, DKM, TOR, JUD, ONS, LGN, SCG, 8ED, MRD, DST, 5DN, CHK, BOK, SOK, 9ED, RAV, GPT, DIS, CSP, TSP, TSB, PLC, FUT, 10E, MED, LRW, DD1, MOR, SHM, EVE, DRB, ME2, ALA, DD2, CFX, DDC, ARB, M10, TD0, FVE, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, M13, V12, DDJ, RTR, CM1, GTC, DDK, DGM, MMA, M14, V13, DDL, THS, C13, BNG, DDM, JOU, MD1, CNS, VMA, M15, V14, DDN, KTK, C14, DVD, EVG, GVL, JVC, FRF, UGF, DDO, DTK, TPR, MM2, ORI, V15, DDP, BFZ, EXP, C15, PZ1, OGW, DDQ, SOI, W16, EMA, EMN, V16, CN2, DDR, KLD, MPS_KLD, C16, PZ2, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, HOU, C17, XLN, DDT, IMA, V17, RIX, A25, DDU, DOM, CM2, BBD, SS1, GS1, M19, C18, GRN, MPS_GRN, GK1, G18, GNT, UMA, RNA, MPS_RNA, GK2, WAR, MPS_WAR, MH1, SS2, M20, C19, ELD, MB1, GN2, SLD, THB, IKO, C20, SS3, M21, JMP, 2XM, ZNR, ZNE, ZNC, CMR, CC1, KHM, KHC, TSR, STX, STA, C21, MH2, H1R, AFR, AFC, MID, MIC, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, SLX, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD +Sets:LEA, LEB, 2ED, ARN, ATQ, 3ED, LEG, DRC94, DRK, PHPR, FEM, 4ED, ICE, CHR, HML, ALL, MIR, VIS, 5ED, POR, WTH, TMP, STH, EXO, PO2, USG, ATH, ULG, 6ED, UDS, S99, PTK, MMQ, BRB, NMS, S00, PCY, BTD, INV, PLS, 7ED, APC, ODY, DKM, TOR, JUD, ONS, LGN, SCG, 8ED, MRD, DST, 5DN, CHK, BOK, SOK, 9ED, RAV, GPT, DIS, CSP, TSP, TSB, PLC, FUT, 10E, MED, LRW, DD1, MOR, SHM, EVE, DRB, ME2, ALA, DD2, CFX, DDC, ARB, M10, TD0, FVE, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, M13, V12, DDJ, RTR, CM1, GTC, DDK, DGM, MMA, M14, V13, DDL, THS, C13, BNG, DDM, JOU, MD1, CNS, VMA, M15, V14, DDN, KTK, C14, DVD, EVG, GVL, JVC, FRF, UGF, DDO, DTK, TPR, MM2, ORI, V15, DDP, BFZ, EXP, C15, PZ1, OGW, DDQ, SOI, W16, EMA, EMN, V16, CN2, DDR, KLD, MPS_KLD, C16, PZ2, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, HOU, C17, XLN, DDT, IMA, V17, RIX, A25, DDU, DOM, CM2, BBD, SS1, GS1, M19, C18, GRN, MPS_GRN, GK1, G18, GNT, UMA, RNA, MPS_RNA, GK2, WAR, MPS_WAR, MH1, SS2, M20, C19, ELD, MB1, GN2, SLD, THB, IKO, C20, SS3, M21, JMP, 2XM, ZNR, ZNE, ZNC, CMR, CC1, KHM, KHC, TSR, STX, STA, C21, MH2, H1R, AFR, AFC, MID, MIC, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, SLX, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR Restricted:Ancestral Recall; Balance; Black Lotus; Brainstorm; Chalice of the Void; Channel; Demonic Consultation; Demonic Tutor; Dig Through Time; Flash; Gitaxian Probe; Golgari Grave-Troll; Gush; Imperial Seal; Karn, the Great Creator; Library of Alexandria; Lion's Eye Diamond; Lodestone Golem; Lotus Petal; Mana Crypt; Mana Vault; Memory Jar; Mental Misstep; Merchant Scroll; Mind's Desire; Monastery Mentor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Mystic Forge; Mystical Tutor; Narset, Parter of Veils; Necropotence; Ponder; Sol Ring; Strip Mine; Thorn of Amethyst; Time Vault; Time Walk; Timetwister; Tinker; Tolarian Academy; Treasure Cruise; Trinisphere; Vampiric Tutor; Wheel of Fortune; Windfall; Yawgmoth's Will Banned:Adriana's Valor; Advantageous Proclamation; Amulet of Quoz; Assemble the Rank and Vile; Backup Plan; Brago's Favor; Bronze Tablet; Chaos Orb; Cleanse; Contract from Below; Crusade; Darkpact; Demonic Attorney; Double Stroke; Echoing Boon; Emissary's Ploy; Falling Star; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Muzzio's Preparations; Natural Unity; Power Play; Pradesh Gypsies; Rebirth; Secret Summoning; Secrets of Paradise; Sentinel Dispatch; Shahrazad; Sovereign's Realm; Stone-Throwing Devils; Summoner's Bond; Tempest Efreet; Timmerian Fiends; Unexpected Potential; Weight Advantage; Worldknit diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 9593303d302..6564af2414e 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -1967,6 +1967,8 @@ lblNoValidCards=Keine gültigen Karten #DigUntilEffect.java lblDoYouWantDigYourLibrary=Möchtest dein Bibliothek durchsuchen? lblDoYouWantPutCardToZone=Möchtest du diese Karte nach {0} legen? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=Möchtest du {0} zufällige Karte(n) abwerfen? lblPlayerHasChosenCardsFrom={0} hat Karte(n) gewählt von diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 64b183ca866..5467530adcb 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -1969,6 +1969,8 @@ lblNoValidCards=No valid cards #DigUntilEffect.java lblDoYouWantDigYourLibrary=Do you want to dig your library? lblDoYouWantPutCardToZone=Do you want to put that card to {0}? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=Would you like to discard {0} random card(s)? lblPlayerHasChosenCardsFrom={0} has chosen card(s) from @@ -2101,6 +2103,7 @@ lblRemoveFromGame=Remove Card from Game lblRiggedRoll=Rigged Planar Roll lblWalkTo=Planeswalk to lblAskAI=Ask AI for suggestion +lblAskSimulationAI=Ask Simulation AI for suggestion #PhaseType.java lblUntapStep=Untap step lblUpkeepStep=Upkeep step diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index f5f1b1b8c2b..e04061ee006 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -1966,6 +1966,8 @@ lblNoValidCards=No hay cartas válidas #DigUntilEffect.java lblDoYouWantDigYourLibrary=¿Quieres escarbar en tu biblioteca? lblDoYouWantPutCardToZone=¿Quieres poner esa carta a {0}? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=¿Te gustaría descartar {0} carta(s) aleatoria(s)? lblPlayerHasChosenCardsFrom={0} ha elegido una o varias cartas de diff --git a/forge-gui/res/languages/fr-FR.properties b/forge-gui/res/languages/fr-FR.properties index f83b7978584..f2ebf18b233 100644 --- a/forge-gui/res/languages/fr-FR.properties +++ b/forge-gui/res/languages/fr-FR.properties @@ -1967,6 +1967,8 @@ lblNoValidCards=Aucune carte valide #DigUntilEffect.java lblDoYouWantDigYourLibrary=Voulez-vous fouiller votre bibliothèque ? lblDoYouWantPutCardToZone=Voulez-vous mettre cette carte dans {0} ? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=Voulez-vous défausser {0} carte(s) aléatoire(s) ? lblPlayerHasChosenCardsFrom={0} a choisi une ou plusieurs cartes parmi diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index 236f329bf35..0567a17ede2 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -1965,6 +1965,8 @@ lblNoValidCards=Nessuna carta valida #DigUntilEffect.java lblDoYouWantDigYourLibrary=Vuoi passare in rassegna il tuo grimorio? lblDoYouWantPutCardToZone=Vuoi mettere la carta in {0}? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=Vuoi scartare {0} carta/e a caso? lblPlayerHasChosenCardsFrom={0}ha scelto delle carte da diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index 6ee053f47f3..54b4c977740 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -1965,6 +1965,8 @@ lblNoValidCards=符合するカードがありません #DigUntilEffect.java lblDoYouWantDigYourLibrary=ライブラリーを探しますか? lblDoYouWantPutCardToZone=そのカードを {0}に置きますか? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=不作為に {0}枚のカードを捨てますか? lblPlayerHasChosenCardsFrom={0}がカードを選択した: diff --git a/forge-gui/res/languages/pt-BR.properties b/forge-gui/res/languages/pt-BR.properties index 0e5a8a40762..ff09833a288 100644 --- a/forge-gui/res/languages/pt-BR.properties +++ b/forge-gui/res/languages/pt-BR.properties @@ -2027,6 +2027,8 @@ lblNoValidCards=Sem cartas válidas #DigUntilEffect.java lblDoYouWantDigYourLibrary=Deseja vasculhar seu grimório? lblDoYouWantPutCardToZone=Deseja colocar essa carta em {0}? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=Descartar {0} carta(s) aleatoriamente? lblPlayerHasChosenCardsFrom={0} escolheu carta(s) de diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 3e9d94b932b..67be8ed3fb4 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -1969,6 +1969,8 @@ lblNoValidCards=没有有效的牌 #DigUntilEffect.java lblDoYouWantDigYourLibrary=你想要挖掘你的牌库吗? lblDoYouWantPutCardToZone=你想把这张牌放到{0}吗? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=你想随机弃掉%d张牌吗? lblPlayerHasChosenCardsFrom={0}选择了牌自 diff --git a/forge-gui/res/quest/commanderprecons/Chaos Incarnate [SCD] [2022].dck b/forge-gui/res/quest/commanderprecons/Chaos Incarnate [SCD] [2022].dck new file mode 100644 index 00000000000..002e211ad06 --- /dev/null +++ b/forge-gui/res/quest/commanderprecons/Chaos Incarnate [SCD] [2022].dck @@ -0,0 +1,78 @@ +[metadata] +name=Chaos Incarnate [SCD] [2022] +[Commander] +1 Kardur, Doomscourge|SCD|1 +[Main] +1 Abrade|SCD|1 +1 Akoum Refuge|SCD|1 +1 Ambition's Cost|SCD|1 +1 Arcane Signet|SCD|1 +1 Archfiend of Depravity|SCD|1 +1 Blasphemous Act|SCD|1 +1 Bloodfell Caves|SCD|1 +1 Bloodgift Demon|SCD|1 +1 Brash Taunter|SCD|1 +1 Breath of Malfegor|SCD|1 +1 Burnished Hart|SCD|1 +1 Chaos Warp|SCD|1 +1 Cinder Barrens|SCD|1 +1 Combustible Gearhulk|SCD|1 +1 Command Tower|SCD|1 +1 Commander's Sphere|SCD|1 +1 Coveted Jewel|SCD|1 +1 Deadly Tempest|SCD|1 +1 Dictate of the Twin Gods|SCD|1 +1 Dredge the Mire|SCD|1 +1 Explosion of Riches|SCD|1 +1 Feed the Swarm|SCD|1 +1 Fiery Confluence|SCD|1 +1 Foreboding Ruins|SCD|1 +1 Geode Rager|SCD|1 +1 Guttersnipe|SCD|1 +1 Hate Mirage|SCD|1 +1 Indulgent Tormentor|SCD|1 +1 Kaervek the Merciless|SCD|1 +1 Kazuul, Tyrant of the Cliffs|SCD|1 +1 Lightning Greaves|SCD|1 +1 Magmatic Force|SCD|1 +1 Mana Geyser|SCD|1 +1 Molten Slagheap|SCD|1 +14 Mountain|SCD|1 +1 Myriad Landscape|SCD|1 +1 Nihil Spellbomb|SCD|1 +1 Ob Nixilis Reignited|SCD|1 +1 Profane Command|SCD|1 +1 Rakdos Charm|SCD|1 +1 Rakdos Signet|SCD|1 +1 Rakshasa Debaser|SCD|1 +1 Read the Bones|SCD|1 +1 Reign of the Pit|SCD|1 +1 Sangromancer|SCD|1 +1 Scythe Specter|SCD|1 +1 Sepulchral Primordial|SCD|1 +1 Sign in Blood|SCD|1 +1 Smoldering Marsh|SCD|1 +1 Sol Ring|SCD|1 +1 Solemn Simulacrum|SCD|1 +1 Soul Shatter|SCD|1 +1 Spiteful Visions|SCD|1 +1 Stensia Bloodhall|SCD|1 +1 Stormfist Crusader|SCD|1 +1 Sunbird's Invocation|SCD|1 +15 Swamp|SCD|1 +1 Syphon Mind|SCD|1 +1 Talisman of Indulgence|SCD|1 +1 Tectonic Giant|SCD|1 +1 Temple of Malice|SCD|1 +1 Terminate|SCD|1 +1 Theater of Horrors|SCD|1 +1 Thermo-Alchemist|SCD|1 +1 Titan Hunter|SCD|1 +1 Unlicensed Disintegration|SCD|1 +1 Urborg Volcano|SCD|1 +1 Vampire Nighthawk|SCD|1 +1 Wayfarer's Bauble|SCD|1 +1 Wild Ricochet|SCD|1 +1 Wildfire Devils|SCD|1 +1 Worn Powerstone|SCD|1 +[Sideboard] diff --git a/forge-gui/res/quest/commanderprecons/Draconic Destruction [SCD] [2022].dck b/forge-gui/res/quest/commanderprecons/Draconic Destruction [SCD] [2022].dck new file mode 100644 index 00000000000..b74f90379c1 --- /dev/null +++ b/forge-gui/res/quest/commanderprecons/Draconic Destruction [SCD] [2022].dck @@ -0,0 +1,77 @@ +[metadata] +name=Draconic Destruction [SCD] [2022] +[Commander] +1 Atarka, World Render|SCD|1 +[Main] +1 Akoum Hellkite|SCD|1 +1 Arcane Signet|SCD|1 +1 Atarka Monument|SCD|1 +1 Beast Within|SCD|1 +1 Blossoming Defense|SCD|1 +1 Chain Reaction|SCD|1 +1 Cinder Glade|SCD|1 +1 Clan Defiance|SCD|1 +1 Command Tower|SCD|1 +1 Commander's Sphere|SCD|1 +1 Crucible of Fire|SCD|1 +1 Cultivate|SCD|1 +1 Demanding Dragon|SCD|1 +1 Draconic Disciple|SCD|1 +1 Dragon Mage|SCD|1 +1 Dragon Tempest|SCD|1 +1 Dragon's Hoard|SCD|1 +1 Dragonkin Berserker|SCD|1 +1 Dragonlord's Servant|SCD|1 +1 Dragonmaster Outcast|SCD|1 +1 Dragonspeaker Shaman|SCD|1 +1 Drakuseth, Maw of Flames|SCD|1 +1 Dream Pillager|SCD|1 +1 Drumhunter|SCD|1 +1 Elemental Bond|SCD|1 +1 Fires of Yavimaya|SCD|1 +1 Flameblast Dragon|SCD|1 +1 Foe-Razer Regent|SCD|1 +12 Forest|SCD|1 +1 Frontier Siege|SCD|1 +1 Furnace Whelp|SCD|1 +1 Game Trail|SCD|1 +1 Garruk's Uprising|SCD|1 +1 Harbinger of the Hunt|SCD|1 +1 Harmonize|SCD|1 +1 Haven of the Spirit Dragon|SCD|1 +1 Hoard-Smelter Dragon|SCD|1 +1 Hunter's Insight|SCD|1 +1 Hunter's Prowess|SCD|1 +1 Kazandu Refuge|SCD|1 +1 Loaming Shaman|SCD|1 +1 Magmaquake|SCD|1 +1 Mordant Dragon|SCD|1 +18 Mountain|SCD|1 +1 Path of Ancestry|SCD|1 +1 Primal Might|SCD|1 +1 Provoke the Trolls|SCD|1 +1 Rapacious Dragon|SCD|1 +1 Return to Nature|SCD|1 +1 Rugged Highlands|SCD|1 +1 Runehorn Hellkite|SCD|1 +1 Sakura-Tribe Elder|SCD|1 +1 Sarkhan, the Dragonspeaker|SCD|1 +1 Savage Ventmaw|SCD|1 +1 Scourge of Valkas|SCD|1 +1 Shamanic Revelation|SCD|1 +1 Shivan Oasis|SCD|1 +1 Sol Ring|SCD|1 +1 Spit Flame|SCD|1 +1 Steel Hellkite|SCD|1 +1 Sweltering Suns|SCD|1 +1 Swiftfoot Boots|SCD|1 +1 Talisman of Impulse|SCD|1 +1 Temple of Abandon|SCD|1 +1 Thunderbreak Regent|SCD|1 +1 Thundermaw Hellkite|SCD|1 +1 Timber Gorge|SCD|1 +1 Tyrant's Familiar|SCD|1 +1 Unleash Fury|SCD|1 +1 Vandalblast|SCD|1 +1 Verix Bladewing|SCD|1 +[Sideboard] diff --git a/forge-gui/res/quest/commanderprecons/First Flight [SCD] [2022].dck b/forge-gui/res/quest/commanderprecons/First Flight [SCD] [2022].dck new file mode 100644 index 00000000000..04aae9fa3b6 --- /dev/null +++ b/forge-gui/res/quest/commanderprecons/First Flight [SCD] [2022].dck @@ -0,0 +1,77 @@ +[metadata] +name=First Flight [SCD] [2022] +[Commander] +1 Isperia, Supreme Judge|SCD|1 +[Main] +1 Absorb|SCD|1 +1 Aetherize|SCD|1 +1 Angler Turtle|SCD|1 +1 Arcane Signet|SCD|1 +1 Archon of Redemption|SCD|1 +1 Aven Gagglemaster|SCD|1 +1 Azorius Signet|SCD|1 +1 Banishing Light|SCD|1 +1 Bident of Thassa|SCD|1 +1 Cartographer's Hawk|SCD|1 +1 Cleansing Nova|SCD|1 +1 Cloudblazer|SCD|1 +1 Coastal Tower|SCD|1 +1 Command Tower|SCD|1 +1 Commander's Sphere|SCD|1 +1 Condemn|SCD|1 +1 Counterspell|SCD|1 +1 Crush Contraband|SCD|1 +1 Diluvian Primordial|SCD|1 +1 Disenchant|SCD|1 +1 Emeria Angel|SCD|1 +1 Empyrean Eagle|SCD|1 +1 Ever-Watching Threshold|SCD|1 +1 Faerie Formation|SCD|1 +1 Favorable Winds|SCD|1 +1 Generous Gift|SCD|1 +1 Gideon Jura|SCD|1 +1 Gravitational Shift|SCD|1 +1 Hanged Executioner|SCD|1 +1 Hedron Archive|SCD|1 +1 Inspired Sphinx|SCD|1 +15 Island|SCD|1 +1 Jubilant Skybonder|SCD|1 +1 Kangee's Lieutenant|SCD|1 +1 Kangee, Sky Warden|SCD|1 +1 Meandering River|SCD|1 +1 Migratory Route|SCD|1 +1 Moorland Haunt|SCD|1 +1 Negate|SCD|1 +1 Pilgrim's Eye|SCD|1 +15 Plains|SCD|1 +1 Port Town|SCD|1 +1 Prairie Stream|SCD|1 +1 Rally of Wings|SCD|1 +1 Remorseful Cleric|SCD|1 +1 Sejiri Refuge|SCD|1 +1 Sephara, Sky's Blade|SCD|1 +1 Sharding Sphinx|SCD|1 +1 Sky Diamond|SCD|1 +1 Skycat Sovereign|SCD|1 +1 Skyscanner|SCD|1 +1 Sol Ring|SCD|1 +1 Soul Snare|SCD|1 +1 Sphinx of Enlightenment|SCD|1 +1 Sphinx's Revelation|SCD|1 +1 Staggering Insight|SCD|1 +1 Steel-Plume Marshal|SCD|1 +1 Storm Herd|SCD|1 +1 Swords to Plowshares|SCD|1 +1 Talisman of Progress|SCD|1 +1 Temple of Enlightenment|SCD|1 +1 Thought Vessel +1 Thunderclap Wyvern|SCD|1 +1 Tide Skimmer|SCD|1 +1 Time Wipe|SCD|1 +1 Tranquil Cove|SCD|1 +1 True Conviction|SCD|1 +1 Vow of Duty|SCD|1 +1 Warden of Evos Isle|SCD|1 +1 Windreader Sphinx|SCD|1 +1 Winged Words|SCD|1 +[Sideboard] diff --git a/forge-gui/res/quest/commanderprecons/Grave Danger [SCD] [2022].dck b/forge-gui/res/quest/commanderprecons/Grave Danger [SCD] [2022].dck new file mode 100644 index 00000000000..2651ec5b0d4 --- /dev/null +++ b/forge-gui/res/quest/commanderprecons/Grave Danger [SCD] [2022].dck @@ -0,0 +1,76 @@ +[metadata] +name=Grave Danger [SCD] [2022] +[Commander] +1 Gisa and Geralf|SCD|1 +[Main] +1 Arcane Signet|SCD|1 +1 Army of the Damned|SCD|1 +1 Cemetery Reaper|SCD|1 +1 Champion of the Perished|SCD|1 +1 Choked Estuary|SCD|1 +1 Command Tower|SCD|1 +1 Commander's Sphere|SCD|1 +1 Crippling Fear|SCD|1 +1 Cruel Revival|SCD|1 +1 Curse of Disturbance|SCD|1 +1 Deep Analysis|SCD|1 +1 Dimir Signet|SCD|1 +1 Diregraf Captain|SCD|1 +1 Dismal Backwater|SCD|1 +1 Distant Melody|SCD|1 +1 Enter the God-Eternals|SCD|1 +1 Eternal Skylord|SCD|1 +1 Feed the Swarm|SCD|1 +1 Fleshbag Marauder|SCD|1 +1 Geralf's Mindcrusher|SCD|1 +1 Gleaming Overseer|SCD|1 +1 Gravespawn Sovereign|SCD|1 +1 Gray Merchant of Asphodel|SCD|1 +1 Grimoire of the Dead|SCD|1 +1 Havengul Lich|SCD|1 +1 Heraldic Banner|SCD|1 +13 Island|SCD|1 +1 Josu Vess, Lich Knight|SCD|1 +1 Jwar Isle Refuge|SCD|1 +1 Laboratory Drudge|SCD|1 +1 Lazotep Plating|SCD|1 +1 Lazotep Reaver|SCD|1 +1 Liliana's Devotee|SCD|1 +1 Liliana's Mastery|SCD|1 +1 Liliana's Standard Bearer|SCD|1 +1 Liliana, Untouched by Death|SCD|1 +1 Lord of the Accursed|SCD|1 +1 Lotleth Giant|SCD|1 +1 Loyal Subordinate|SCD|1 +1 Midnight Reaper|SCD|1 +1 Mire Triton|SCD|1 +1 Murder|SCD|1 +1 Necromantic Selection|SCD|1 +1 Necrotic Hex|SCD|1 +1 Open the Graves|SCD|1 +1 Overseer of the Damned|SCD|1 +1 Pilfered Plans|SCD|1 +1 Salt Marsh|SCD|1 +1 Scourge of Nel Toth|SCD|1 +1 Sinister Sabotage|SCD|1 +1 Sol Ring|SCD|1 +1 Spark Reaper|SCD|1 +1 Submerged Boneyard|SCD|1 +1 Sunken Hollow|SCD|1 +18 Swamp|SCD|1 +1 Syphon Flesh|SCD|1 +1 Talisman of Dominance|SCD|1 +1 Temple of Deceit|SCD|1 +1 Unbreathing Horde|SCD|1 +1 Undead Augur|SCD|1 +1 Undermine|SCD|1 +1 Unstable Obelisk|SCD|1 +1 Vampiric Rites|SCD|1 +1 Vela the Night-Clad|SCD|1 +1 Vengeful Dead|SCD|1 +1 Victimize|SCD|1 +1 Vizier of the Scorpion|SCD|1 +1 Wayfarer's Bauble|SCD|1 +1 Withered Wretch|SCD|1 +1 Zombie Apocalypse|SCD|1 +[Sideboard] diff --git a/forge-gui/res/quest/commanderprecons/Token Triumph [SCD] [2022].dck b/forge-gui/res/quest/commanderprecons/Token Triumph [SCD] [2022].dck new file mode 100644 index 00000000000..660ec3d15ca --- /dev/null +++ b/forge-gui/res/quest/commanderprecons/Token Triumph [SCD] [2022].dck @@ -0,0 +1,78 @@ +[metadata] +name=Token Triumph [SCD] [2022] +[Commander] +1 Emmara, Soul of the Accord|SCD|1 +[Main] +1 Ajani, Caller of the Pride|SCD|1 +1 Arcane Signet|SCD|1 +1 Aura Mutation|SCD|1 +1 Avacyn's Pilgrim|SCD|1 +1 Blossoming Sands|SCD|1 +1 Camaraderie|SCD|1 +1 Canopy Vista|SCD|1 +1 Champion of Lambholt|SCD|1 +1 Citanul Hierophants|SCD|1 +1 Citywide Bust|SCD|1 +1 Collective Blessing|SCD|1 +1 Collective Unconscious|SCD|1 +1 Command Tower|SCD|1 +1 Commander's Insignia|SCD|1 +1 Commander's Sphere|SCD|1 +1 Conclave Tribunal|SCD|1 +1 Curse of Bounty|SCD|1 +1 Dauntless Escort|SCD|1 +1 Dawn of Hope|SCD|1 +1 Devouring Light|SCD|1 +1 Dictate of Heliod|SCD|1 +1 Elfhame Palace|SCD|1 +1 Eternal Witness|SCD|1 +1 Farhaven Elf|SCD|1 +1 Felidar Retreat|SCD|1 +15 Forest|SCD|1 +1 Fortified Village|SCD|1 +1 Graypelt Refuge|SCD|1 +1 Great Oak Guardian|SCD|1 +1 Harmonize|SCD|1 +1 Harvest Season|SCD|1 +1 Holdout Settlement|SCD|1 +1 Hornet Nest|SCD|1 +1 Hornet Queen|SCD|1 +1 Hour of Reckoning|SCD|1 +1 Idol of Oblivion|SCD|1 +1 Jade Mage|SCD|1 +1 Jaspera Sentinel|SCD|1 +1 Karametra's Favor|SCD|1 +1 Leafkin Druid|SCD|1 +1 Loyal Guardian|SCD|1 +1 Maja, Bretagard Protector|SCD|1 +1 March of the Multitudes|SCD|1 +1 Mentor of the Meek|SCD|1 +1 Nissa's Expedition|SCD|1 +1 Nullmage Shepherd|SCD|1 +1 Overrun|SCD|1 +1 Overwhelming Instinct|SCD|1 +1 Path to Exile|SCD|1 +14 Plains|SCD|1 +1 Presence of Gond|SCD|1 +1 Reclamation Sage|SCD|1 +1 Rishkar, Peema Renegade|SCD|1 +1 Rootborn Defenses|SCD|1 +1 Scatter the Seeds|SCD|1 +1 Scavenging Ooze|SCD|1 +1 Selesnya Evangel|SCD|1 +1 Selesnya Guildmage|SCD|1 +1 Slate of Ancestry|SCD|1 +1 Sol Ring|SCD|1 +1 Sporemound|SCD|1 +1 Sylvan Reclamation|SCD|1 +1 Talisman of Unity|SCD|1 +1 Temple of Plenty|SCD|1 +1 Thunderfoot Baloth|SCD|1 +1 Tranquil Expanse|SCD|1 +1 Trostani Discordant|SCD|1 +1 Valor in Akros|SCD|1 +1 Verdant Force|SCD|1 +1 Vitu-Ghazi, the City-Tree|SCD|1 +1 Voice of Many|SCD|1 +1 White Sun's Zenith|SCD|1 +[Sideboard] diff --git a/forge-gui/res/tokenscripts/c_2_2_a_drone_deathtouch_leavedrain.txt b/forge-gui/res/tokenscripts/c_2_2_a_drone_deathtouch_leavedrain.txt new file mode 100644 index 00000000000..8aa7f698360 --- /dev/null +++ b/forge-gui/res/tokenscripts/c_2_2_a_drone_deathtouch_leavedrain.txt @@ -0,0 +1,9 @@ +Name:Drone Token +ManaCost:no cost +Types:Artifact Creature Drone +PT:2/2 +K:Deathtouch +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Execute$ TrigDrain | TriggerDescription$ When this creature leaves the battlefield, each opponent loses 2 life and you gain 2 life. +SVar:TrigDrain:DB$ LoseLife | Defined$ Opponent | LifeAmount$ 2 | SubAbility$ DBGainLife +SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 2 +Oracle:Deathtouch\nWhen this creature leaves the battlefield, each opponent loses 2 life and you gain 2 life. diff --git a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java index 9aade51b490..effcbc211a9 100644 --- a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java +++ b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java @@ -171,8 +171,9 @@ public class CardDetailUtil { } public static String formatCardType(final CardStateView card, final boolean canShow) { + boolean isInPlay = card.getCard() != null && ZoneType.Battlefield.equals(card.getCard().getZone()); String translatedtype = CardTranslation.getTranslatedType(card.getName(), card.getType().toString()); - return canShow ? translatedtype : (card.getState() == CardStateName.FaceDown ? "Creature" : "---"); + return canShow ? translatedtype : (card.getState() == CardStateName.FaceDown && isInPlay ? "Creature" : ""); } public static String formatPowerToughness(final CardStateView card, final boolean canShow) { diff --git a/forge-gui/src/main/java/forge/interfaces/IDevModeCheats.java b/forge-gui/src/main/java/forge/interfaces/IDevModeCheats.java index 4e3bfbae68e..886261b5a7f 100644 --- a/forge-gui/src/main/java/forge/interfaces/IDevModeCheats.java +++ b/forge-gui/src/main/java/forge/interfaces/IDevModeCheats.java @@ -59,7 +59,7 @@ public interface IDevModeCheats { void planeswalkTo(); - void askAI(); + void askAI(boolean useSimulation); /** * Implementation of {@link IDevModeCheats} that disallows cheating by @@ -144,7 +144,7 @@ public interface IDevModeCheats { public void removeCardsFromGame() { } @Override - public void askAI() { + public void askAI(boolean useSimulation) { } }; diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index 9b5cc2c5dc0..07cd9c0b126 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -114,9 +114,11 @@ public class HumanPlay { final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa); if (!req.playAbility(true, false, false)) { - if (flippedToCast && !castFaceDown) { + Card rollback = p.getGame().getCardState(sa.getHostCard()); + if (castFaceDown) { + rollback.setFaceDown(false); + } else if (flippedToCast) { // need to get the changed card if able - Card rollback = p.getGame().getCardState(sa.getHostCard()); rollback.turnFaceDown(true); //need to set correct imagekey when forcing facedown rollback.setImageKey(ImageKeys.getTokenKey(isforetold ? ImageKeys.FORETELL_IMAGE : ImageKeys.HIDDEN_CARD)); diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index 43994257c45..f5010b2f039 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -173,7 +173,7 @@ public class HumanPlaySpellAbility { if (ability.getHostCard().isMadness()) { // if a player failed to play madness cost, move the card to graveyard Card newCard = game.getAction().moveToGraveyard(c, null); - newCard.setMadnessWithoutCast(true); + newCard.setDiscarded(true); } } else { payment.refundPayment(); diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 4150cb01d27..8918a30f1af 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -3042,8 +3042,9 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont }); } - public void askAI() { + public void askAI(boolean useSimulation) { PlayerControllerAi ai = new PlayerControllerAi(player.getGame(), player, player.getOriginalLobbyPlayer()); + ai.setUseSimulation(useSimulation); player.runWithController(() -> { List sas = ai.chooseSpellAbilityToPlay(); SpellAbility chosen = sas == null ? null : sas.get(0); @@ -3334,7 +3335,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont public CardCollection chooseCardsForEffectMultiple(Map validMap, SpellAbility sa, String title, boolean isOptional) { CardCollection result = new CardCollection(); for (Map.Entry e : validMap.entrySet()) { - result.addAll(chooseCardsForEffect(e.getValue(), sa, title + " " + e.getKey(), 0, 1, isOptional, null)); + result.addAll(chooseCardsForEffect(e.getValue(), sa, title + " (" + e.getKey() + ")", 0, 1, isOptional, null)); } return result; }